From ec6583cac340400e17580252643963ed6b4db3df Mon Sep 17 00:00:00 2001 From: brianbrix Date: Wed, 25 Mar 2026 14:00:22 +0300 Subject: [PATCH 01/18] Add import for programs --- .../aim/action/dataimporter/DataImporter.java | 28 ++++- .../action/dataimporter/ExcelImporter.java | 35 ++++-- .../action/dataimporter/TxtDataImporter.java | 26 +++- .../dataimporter/util/ImporterConstants.java | 1 + .../dataimporter/util/ImporterUtil.java | 118 ++++++++++++++++++ .../module/aim/form/DataImporterForm.java | 18 +++ .../WEB-INF/jsp/aim/view/dataImporter.jsp | 55 ++++++++ 7 files changed, 267 insertions(+), 14 deletions(-) diff --git a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/DataImporter.java b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/DataImporter.java index 28625f69a70..6cc8a0f6622 100644 --- a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/DataImporter.java +++ b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/DataImporter.java @@ -55,10 +55,12 @@ import org.digijava.module.aim.action.dataimporter.util.ImporterConstants; import org.digijava.module.aim.dbentity.AmpOrgGroup; +import org.digijava.module.aim.dbentity.AmpActivityProgramSettings; import org.digijava.module.aim.form.DataImporterForm; import org.digijava.module.aim.util.DbUtil; import org.digijava.module.aim.util.DynLocationManagerUtil; import org.digijava.module.aim.util.LocationUtil; +import org.digijava.module.aim.util.ProgramUtil; import org.digijava.module.categorymanager.dbentity.AmpCategoryValue; import org.digijava.module.aim.dbentity.AmpCategoryValueLocations; import org.digijava.module.categorymanager.util.CategoryConstants; @@ -90,6 +92,7 @@ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServlet List orgGroups = DbUtil.getAllOrgGroups(); request.setAttribute("orgGroups", orgGroups); request.setAttribute("activityStatuses", getActivityStatuses()); + request.setAttribute("programClassifications", getProgramClassificationNames()); List availableLocations = getAvailableLocations(); request.setAttribute("availableLocations", availableLocations); AmpCategoryValueLocations defaultLocation = DynLocationManagerUtil.getDefaultCountry(); @@ -372,9 +375,11 @@ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServlet boolean createMissingOrgs = dataImporterForm.isCreateMissingOrgs(); boolean createMissingSectors = dataImporterForm.isCreateMissingSectors(); boolean createMissingOrgGroups = dataImporterForm.isCreateMissingOrgGroups(); + boolean createMissingPrograms = dataImporterForm.isCreateMissingPrograms(); Long orgGroupId = dataImporterForm.getOrgGroupId(); Long defaultActivityStatusId = dataImporterForm.getDefaultActivityStatusId(); Long defaultLocationId = dataImporterForm.getDefaultLocationId(); + String defaultProgramClassification = dataImporterForm.getDefaultProgramClassification(); logger.info("Internal: "+ isInternal); logger.info("Skip existing: "+ skipExisting); logger.info("Validate activities: "+ validateActivities); @@ -383,9 +388,11 @@ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServlet logger.info("Create missing orgs: "+ createMissingOrgs); logger.info("Create missing sectors: {}", createMissingSectors); logger.info("Create missing org groups: " + createMissingOrgGroups); + logger.info("Create missing programs: " + createMissingPrograms); logger.info("Org group id: "+ orgGroupId); logger.info("Default activity status id: {}", defaultActivityStatusId); logger.info("Default location id: {}", defaultLocationId); + logger.info("Default program classification: {}", defaultProgramClassification); if (createMissingOrgs && orgGroupId == null && !createMissingOrgGroups && !columnPairsToUse.containsValue(ImporterConstants.ORG_GROUP)) { response.setHeader("errorMessage", @@ -403,15 +410,22 @@ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServlet response.setStatus(400); return mapping.findForward("importData"); } + if (columnPairsToUse.containsValue(ImporterConstants.PROGRAM_NAME) + && !columnPairsToUse.containsValue(ImporterConstants.PROGRAM_CLASSIFICATION) + && (defaultProgramClassification == null || defaultProgramClassification.trim().isEmpty())) { + response.setHeader("errorMessage", "Please select a default program classification when no 'Program Classification' column is mapped."); + response.setStatus(400); + return mapping.findForward("importData"); + } logger.info("Configuration: {}", columnPairsToUse); try { if ((Objects.equals(request.getParameter("fileType"), "excel") || Objects.equals(request.getParameter("fileType"), "csv"))) { String dataSheetChoice = request.getParameter("dataSheetChoice"); String dataSheetName = request.getParameter("dataSheetName"); boolean useSpecificSheet = "sheet".equals(dataSheetChoice) && dataSheetName != null && !dataSheetName.trim().isEmpty(); - res = processExcelFileInBatches(importedFilesRecord, tempFile, request, columnPairsToUse, isInternal, skipExisting, useSpecificSheet ? dataSheetName : null, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId); + res = processExcelFileInBatches(importedFilesRecord, tempFile, request, columnPairsToUse, isInternal, skipExisting, useSpecificSheet ? dataSheetName : null, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId, defaultProgramClassification, createMissingPrograms); } else if ( Objects.equals(request.getParameter("fileType"), "text")) { - res = TxtDataImporter.processTxtFileInBatches(importedFilesRecord, tempFile, request, columnPairsToUse, isInternal, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId); + res = TxtDataImporter.processTxtFileInBatches(importedFilesRecord, tempFile, request, columnPairsToUse, isInternal, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId, defaultProgramClassification, createMissingPrograms); } } catch (Exception e) { ImportedFileUtil.updateFileStatus(importedFilesRecord, ImportStatus.FAILED); @@ -641,6 +655,7 @@ private Map getEntityFieldsInfo() { // Indicator columns for M&E import fieldsInfos.add(ImporterConstants.INDICATOR_NAME); fieldsInfos.add(ImporterConstants.PROGRAM_NAME); + fieldsInfos.add(ImporterConstants.PROGRAM_CLASSIFICATION); fieldsInfos.add(ImporterConstants.INDICATOR_LOCATION); fieldsInfos.add(ImporterConstants.ORIGINAL_BASE_VALUE); fieldsInfos.add(ImporterConstants.ORIGINAL_BASE_VALUE_DATE); @@ -680,4 +695,13 @@ private List getActivityStatuses() { return activityStatuses; } + private List getProgramClassificationNames() { + return ProgramUtil.getEnabledProgramSettings().stream() + .filter(Objects::nonNull) + .map(AmpActivityProgramSettings::getName) + .filter(Objects::nonNull) + .sorted(String.CASE_INSENSITIVE_ORDER) + .collect(Collectors.toList()); + } + } diff --git a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/ExcelImporter.java b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/ExcelImporter.java index 3a7b8797d4e..8c810350996 100644 --- a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/ExcelImporter.java +++ b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/ExcelImporter.java @@ -46,10 +46,10 @@ public class ExcelImporter { private static final int BATCH_SIZE = 1000; public static int processExcelFileInBatches(ImportedFilesRecord importedFilesRecord, File file, HttpServletRequest request, Map config, boolean isInternal) { - return processExcelFileInBatches(importedFilesRecord, file, request, config, isInternal, false, null, false, false, null, false, false, false, false, null, null); + return processExcelFileInBatches(importedFilesRecord, file, request, config, isInternal, false, null, false, false, null, false, false, false, false, null, null, null, false); } - public static int processExcelFileInBatches(ImportedFilesRecord importedFilesRecord, File file, HttpServletRequest request, Map config, boolean isInternal, boolean skipExisting, String sheetNameToProcess, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId) { + public static int processExcelFileInBatches(ImportedFilesRecord importedFilesRecord, File file, HttpServletRequest request, Map config, boolean isInternal, boolean skipExisting, String sheetNameToProcess, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId, String defaultProgramClassification, boolean createMissingPrograms) { int res = 0; ImportedFileUtil.updateFileStatus(importedFilesRecord, ImportStatus.IN_PROGRESS); try (Workbook workbook = new XSSFWorkbook(file)) { @@ -66,7 +66,7 @@ public static int processExcelFileInBatches(ImportedFilesRecord importedFilesRec if (isInternal) { addDonorAgencyColumn(sheet, FeaturesUtil.getGlobalSettingValue("Internal Ecowas Donor")); } - processSheetInBatches(sheet, request, config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId); + processSheetInBatches(sheet, request, config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId, defaultProgramClassification, createMissingPrograms); } else { // Process each sheet in the workbook for (int i = 0; i < numberOfSheets; i++) { @@ -75,7 +75,7 @@ public static int processExcelFileInBatches(ImportedFilesRecord importedFilesRec if (isInternal) { addDonorAgencyColumn(sheet, FeaturesUtil.getGlobalSettingValue("Internal Ecowas Donor")); } - processSheetInBatches(sheet, request, config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId); + processSheetInBatches(sheet, request, config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId, defaultProgramClassification, createMissingPrograms); } } @@ -119,7 +119,7 @@ private static void addDonorAgencyColumn(Sheet sheet, String donorAgencyValue) { } - public static void processSheetInBatches(Sheet sheet, HttpServletRequest request,Map config, ImportedFilesRecord importedFilesRecord, boolean skipExisting, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId) throws JsonProcessingException { + public static void processSheetInBatches(Sheet sheet, HttpServletRequest request,Map config, ImportedFilesRecord importedFilesRecord, boolean skipExisting, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId, String defaultProgramClassification, boolean createMissingPrograms) throws JsonProcessingException { // Get the number of rows in the sheet int rowCount = sheet.getPhysicalNumberOfRows(); logger.info("There are {} rows in sheet {} " , rowCount, sheet.getSheetName()); @@ -141,12 +141,12 @@ public static void processSheetInBatches(Sheet sheet, HttpServletRequest request } // Process the batch - processBatch(batch, sheet, request,config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId); + processBatch(batch, sheet, request,config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId, defaultProgramClassification, createMissingPrograms); } } - public static void processBatch(List batch,Sheet sheet, HttpServletRequest request, Map config, ImportedFilesRecord importedFilesRecord, boolean skipExisting, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId) throws JsonProcessingException { + public static void processBatch(List batch,Sheet sheet, HttpServletRequest request, Map config, ImportedFilesRecord importedFilesRecord, boolean skipExisting, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId, String defaultProgramClassification, boolean createMissingPrograms) throws JsonProcessingException { // Process the batch of rows SessionUtil.extendSessionIfNeeded(request); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); @@ -213,6 +213,8 @@ public static void processBatch(List batch,Sheet sheet, HttpServletRequest // Use holder arrays to capture values from lambda (for effectively final requirement) final Long[] existingActivityIdHolder = new Long[1]; // Store only the ID, not the entity final Long[] responsibleOrgIdHolder = new Long[1]; + final String[] programNamesHolder = new String[1]; + final String[] programClassificationHolder = new String[1]; // Phase 1: Data preparation - use transaction for reading/preparing data ONLY try { @@ -329,6 +331,12 @@ public static void processBatch(List batch,Sheet sheet, HttpServletRequest break; case ImporterConstants.PROCUREMENT_SYSTEM: break; + case ImporterConstants.PROGRAM_NAME: + programNamesHolder[0] = getStringValueFromCell(cell, false); + break; + case ImporterConstants.PROGRAM_CLASSIFICATION: + programClassificationHolder[0] = getStringValueFromCell(cell, false); + break; case ImporterConstants.REPORTING_DATE: default: logger.error("Unexpected value: " + entry.getValue()); @@ -396,6 +404,19 @@ public static void processBatch(List batch,Sheet sheet, HttpServletRequest logger.error("Failed to add indicator data for activity " + activityId, e); } } + + if (activityId != null && (programNamesHolder[0] != null && !programNamesHolder[0].trim().isEmpty())) { + try { + final Long activityIdFinal = activityId; + PersistenceManager.inTransaction(() -> { + Session s = PersistenceManager.getRequestDBSession(); + addProgramsToActivity(activityIdFinal, programNamesHolder[0], programClassificationHolder[0], + defaultProgramClassification, createMissingPrograms, s); + }); + } catch (Exception e) { + logger.error("Failed to add programs for activity " + activityId, e); + } + } } } } diff --git a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/TxtDataImporter.java b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/TxtDataImporter.java index 18f2b32aee3..bfc06f9515b 100644 --- a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/TxtDataImporter.java +++ b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/TxtDataImporter.java @@ -40,7 +40,7 @@ public class TxtDataImporter { private static final Logger logger = LoggerFactory.getLogger(TxtDataImporter.class); - public static int processTxtFileInBatches(ImportedFilesRecord importedFilesRecord, File file, HttpServletRequest request, Map config, boolean isInternal, boolean skipExisting, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId) + public static int processTxtFileInBatches(ImportedFilesRecord importedFilesRecord, File file, HttpServletRequest request, Map config, boolean isInternal, boolean skipExisting, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId, String defaultProgramClassification, boolean createMissingPrograms) { logger.info("Processing txt file: " + file.getName()); CSVParser parser = new CSVParserBuilder().withSeparator(request.getParameter("dataSeparator").charAt(0)).build(); @@ -59,7 +59,7 @@ public static int processTxtFileInBatches(ImportedFilesRecord importedFilesRecor logger.info("Batch number here: {}",batchNumber); // Process the batch - processBatch(batch, request, config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId); + processBatch(batch, request, config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId, defaultProgramClassification, createMissingPrograms); // Clear the batch for the next set of rows batch.clear(); batchNumber+=1; @@ -69,7 +69,7 @@ public static int processTxtFileInBatches(ImportedFilesRecord importedFilesRecor // Process any remaining rows in the batch if (!batch.isEmpty()) { logger.info("Processing last batch of size {}", batch.size()); - processBatch(batch, request, config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId); + processBatch(batch, request, config, importedFilesRecord, skipExisting, createMissingOrgs, createMissingSectors, orgGroupId, createMissingOrgGroups, skipRecordsWithoutTransactions, validateActivities, addDisbursementForCommitment, defaultActivityStatusId, defaultLocationId, defaultProgramClassification, createMissingPrograms); } } catch (IOException | CsvValidationException e) { logger.error("Error processing txt file "+e.getMessage(),e); @@ -79,7 +79,7 @@ public static int processTxtFileInBatches(ImportedFilesRecord importedFilesRecor } - private static void processBatch(List> batch, HttpServletRequest request, Map config, ImportedFilesRecord importedFilesRecord, boolean skipExisting, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId) throws JsonProcessingException { + private static void processBatch(List> batch, HttpServletRequest request, Map config, ImportedFilesRecord importedFilesRecord, boolean skipExisting, boolean createMissingOrgs, boolean createMissingSectors, Long orgGroupId, boolean createMissingOrgGroups, boolean skipRecordsWithoutTransactions, boolean validateActivities, boolean addDisbursementForCommitment, Long defaultActivityStatusId, Long defaultLocationId, String defaultProgramClassification, boolean createMissingPrograms) throws JsonProcessingException { logger.info("Processing txt batch"); SessionUtil.extendSessionIfNeeded(request); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); @@ -120,6 +120,8 @@ private static void processBatch(List> batch, HttpServletReq // Use holder arrays to capture values from lambda (for effectively final requirement) final Long[] existingActivityIdHolder = new Long[1]; // Store only the ID, not the entity final Long[] responsibleOrgIdHolder = new Long[1]; + final String[] programNamesHolder = new String[1]; + final String[] programClassificationHolder = new String[1]; // Phase 1: Data preparation - use transaction for reading/preparing data ONLY try { @@ -224,6 +226,12 @@ private static void processBatch(List> batch, HttpServletReq break; case ImporterConstants.PROJECT_STATUS: break; + case ImporterConstants.PROGRAM_NAME: + programNamesHolder[0] = rowRef.get(entry.getKey().trim()); + break; + case ImporterConstants.PROGRAM_CLASSIFICATION: + programClassificationHolder[0] = rowRef.get(entry.getKey().trim()); + break; default: logger.error("Unexpected value: " + entry.getValue()); break; @@ -268,7 +276,15 @@ private static void processBatch(List> batch, HttpServletReq // This avoids nested transaction issues when ActivityGatekeeper.doWithLock creates its own transaction try { // Pass only the ID, not the entity - importTheData will re-fetch in its own transaction context - importTheData(importDataModel, null, importedProject, componentName, componentCode, responsibleOrgIdHolder[0], fundings, existingActivityIdHolder[0], validateActivities); + Long activityId = importTheData(importDataModel, null, importedProject, componentName, componentCode, responsibleOrgIdHolder[0], fundings, existingActivityIdHolder[0], validateActivities); + if (activityId != null && programNamesHolder[0] != null && !programNamesHolder[0].trim().isEmpty()) { + final Long activityIdFinal = activityId; + PersistenceManager.inTransaction(() -> { + Session s = PersistenceManager.getRequestDBSession(); + addProgramsToActivity(activityIdFinal, programNamesHolder[0], programClassificationHolder[0], + defaultProgramClassification, createMissingPrograms, s); + }); + } } catch (JsonProcessingException e) { throw e; } diff --git a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/util/ImporterConstants.java b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/util/ImporterConstants.java index 8637a6fbcf5..64a3bb6cf2a 100644 --- a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/util/ImporterConstants.java +++ b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/util/ImporterConstants.java @@ -66,6 +66,7 @@ private ImporterConstants() { // ----- Indicator (M&E) columns ----- public static final String INDICATOR_NAME = "Indicator Name"; public static final String PROGRAM_NAME = "Program Name"; + public static final String PROGRAM_CLASSIFICATION = "Program Classification"; /** Used for project-level location (e.g. Project Location). */ public static final String LOCATION = "Location"; /** Used for matching indicator value to activity location; distinct from project Location. */ diff --git a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/util/ImporterUtil.java b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/util/ImporterUtil.java index f96aebe3ec3..45da4fb6c3b 100644 --- a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/util/ImporterUtil.java +++ b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/util/ImporterUtil.java @@ -2701,6 +2701,11 @@ private static List getValuesByType(Set va * divided evenly among all activity programs (including the one just added). */ public static void addProgramToActivityIfMissing(AmpActivityVersion activity, AmpTheme program, Session session) { + addProgramToActivityIfMissing(activity, program, session, null); + } + + public static void addProgramToActivityIfMissing(AmpActivityVersion activity, AmpTheme program, Session session, + AmpActivityProgramSettings programSetting) { if (activity == null || program == null) return; Set actPrograms = activity.getActPrograms(); if (actPrograms == null) { @@ -2710,12 +2715,17 @@ public static void addProgramToActivityIfMissing(AmpActivityVersion activity, Am for (AmpActivityProgram ap : actPrograms) { if (ap.getProgram() != null && program.getAmpThemeId() != null && program.getAmpThemeId().equals(ap.getProgram().getAmpThemeId())) { + if (ap.getProgramSetting() == null && programSetting != null) { + ap.setProgramSetting(programSetting); + session.saveOrUpdate(ap); + } return; } } AmpActivityProgram activityProgram = new AmpActivityProgram(); activityProgram.setActivity(activity); activityProgram.setProgram(program); + activityProgram.setProgramSetting(programSetting); activityProgram.setProgramPercentage(100f); actPrograms.add(activityProgram); session.save(activityProgram); @@ -2738,6 +2748,114 @@ public static void addProgramToActivityIfMissing(AmpActivityVersion activity, Am } } + public static void addProgramsToActivity(Long activityId, String rawProgramNames, String rowProgramClassification, + String fallbackProgramClassification, boolean createMissingPrograms, + Session session) { + if (activityId == null || rawProgramNames == null || rawProgramNames.trim().isEmpty()) { + return; + } + String resolvedClassification = (rowProgramClassification != null && !rowProgramClassification.trim().isEmpty()) + ? rowProgramClassification.trim() + : (fallbackProgramClassification != null ? fallbackProgramClassification.trim() : null); + if (resolvedClassification == null || resolvedClassification.isEmpty()) { + logger.info("Skipping programs because program classification could not be resolved"); + return; + } + AmpActivityVersion activity = session.get(AmpActivityVersion.class, activityId); + if (activity == null) { + logger.info("Skipping programs because activity {} was not found", activityId); + return; + } + + AmpActivityProgramSettings programSetting = resolveProgramSettingByName(resolvedClassification); + if (programSetting == null) { + logger.warn("Program classification '{}' not found; skipping programs for activity {}", + resolvedClassification, activityId); + return; + } + + for (String programName : splitMultiValues(rawProgramNames)) { + AmpTheme program = createMissingPrograms + ? getOrCreateProgramByName(programName, resolvedClassification, session) + : getProgramByNameAndClassification(programName, resolvedClassification, session); + if (program == null) { + logger.info("Program '{}' not found and createMissingPrograms is disabled; skipping", programName); + continue; + } + addProgramToActivityIfMissing(activity, program, session, programSetting); + } + session.flush(); + } + + private static AmpTheme getProgramByNameAndClassification(String programName, String classification, + Session session) { + if (programName == null || programName.trim().isEmpty()) { + return null; + } + AmpActivityProgramSettings setting = resolveProgramSettingByName(classification); + if (setting == null) { + return ProgramUtil.getTheme(programName.trim()); + } + String hql = "SELECT t FROM " + AmpTheme.class.getName() + " t WHERE LOWER(t.name) = LOWER(:name)"; + Query query; + if (setting.getDefaultHierarchy() != null && setting.getDefaultHierarchy().getAmpThemeId() != null) { + hql += " AND t.parentThemeId.ampThemeId = :parentId"; + query = session.createQuery(hql); + query.setParameter("parentId", setting.getDefaultHierarchy().getAmpThemeId()); + } else { + query = session.createQuery(hql); + } + query.setParameter("name", programName.trim(), StringType.INSTANCE); + query.setMaxResults(1); + AmpTheme theme = (AmpTheme) query.uniqueResult(); + return theme != null ? theme : ProgramUtil.getTheme(programName.trim()); + } + + public static AmpTheme getOrCreateProgramByName(String programName, String classification, Session session) { + AmpTheme existing = getProgramByNameAndClassification(programName, classification, session); + if (existing != null) { + return existing; + } + if (programName == null || programName.trim().isEmpty()) { + return null; + } + try { + AmpTheme newTheme = new AmpTheme(); + String trimmedName = programName.trim(); + newTheme.setName(trimmedName); + String code = trimmedName.replaceAll("[^a-zA-Z0-9_-]", "_").replaceAll("_+", "_").trim(); + if (code.length() > 45) code = code.substring(0, 45); + newTheme.setThemeCode("IMP_" + code + "_" + System.currentTimeMillis()); + newTheme.setIndlevel(0); + AmpActivityProgramSettings setting = resolveProgramSettingByName(classification); + if (setting != null && setting.getDefaultHierarchy() != null) { + newTheme.setParentThemeId(setting.getDefaultHierarchy()); + Integer rootLevel = setting.getDefaultHierarchy().getIndlevel(); + newTheme.setIndlevel(rootLevel != null ? rootLevel + 1 : 1); + } else { + newTheme.setParentThemeId(null); + } + session.save(newTheme); + session.flush(); + return newTheme; + } catch (Exception e) { + logger.warn("Failed to create program '{}' for classification '{}'", programName, classification, e); + return null; + } + } + + private static AmpActivityProgramSettings resolveProgramSettingByName(String classification) { + if (classification == null || classification.trim().isEmpty()) { + return null; + } + try { + return ProgramUtil.getAmpActivityProgramSettings(classification.trim()); + } catch (Exception e) { + logger.warn("Could not resolve program setting '{}'", classification, e); + return null; + } + } + /** * Returns the program (theme) by name, or creates a new root-level program if it does not exist. * @param programName program name (must be non-empty) diff --git a/amp/src/main/java/org/digijava/module/aim/form/DataImporterForm.java b/amp/src/main/java/org/digijava/module/aim/form/DataImporterForm.java index c1b46a0869f..af53ed22ba0 100644 --- a/amp/src/main/java/org/digijava/module/aim/form/DataImporterForm.java +++ b/amp/src/main/java/org/digijava/module/aim/form/DataImporterForm.java @@ -17,9 +17,11 @@ public class DataImporterForm extends ActionForm { private boolean createMissingOrgs; private boolean createMissingSectors; private boolean createMissingOrgGroups; + private boolean createMissingPrograms; private Long orgGroupId; private Long defaultActivityStatusId; private Long defaultLocationId; + private String defaultProgramClassification; private boolean validateActivities; private boolean addDisbursementForCommitment; @@ -79,6 +81,14 @@ public void setCreateMissingOrgGroups(boolean createMissingOrgGroups) { this.createMissingOrgGroups = createMissingOrgGroups; } + public boolean isCreateMissingPrograms() { + return createMissingPrograms; + } + + public void setCreateMissingPrograms(boolean createMissingPrograms) { + this.createMissingPrograms = createMissingPrograms; + } + public Long getOrgGroupId() { return orgGroupId; } @@ -103,6 +113,14 @@ public void setDefaultLocationId(Long defaultLocationId) { this.defaultLocationId = defaultLocationId; } + public String getDefaultProgramClassification() { + return defaultProgramClassification; + } + + public void setDefaultProgramClassification(String defaultProgramClassification) { + this.defaultProgramClassification = defaultProgramClassification; + } + public Set getFileHeaders() { return fileHeaders; } diff --git a/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp b/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp index 0e5d1be6acb..67cfb41853b 100644 --- a/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp +++ b/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp @@ -77,6 +77,13 @@ }); } + if ($('#defaultProgramClassification').length) { + applySelect2('#defaultProgramClassification', { + width: '100%', + placeholder: 'Select fallback program classification' + }); + } + if ($('#data-sheet').length) { applySelect2('#data-sheet', { width: '100%', @@ -122,6 +129,28 @@ $('#defaultLocationId').val(defaultLocationId).trigger('change.select2'); } } + toggleProgramClassificationFallback(); + } + + function hasMappedProgramField() { + return $('#selected-pairs-table-body tr').filter(function() { + return $(this).attr('data-selected-field') === 'Program Name'; + }).length > 0; + } + + function hasMappedProgramClassificationField() { + return $('#selected-pairs-table-body tr').filter(function() { + return $(this).attr('data-selected-field') === 'Program Classification'; + }).length > 0; + } + + function toggleProgramClassificationFallback() { + var hasMappings = $('#selected-pairs-table-body tr').length > 0; + var showFallback = hasMappings && hasMappedProgramField() && !hasMappedProgramClassificationField(); + $('#program-classification-fallback').toggle(showFallback); + if (!showFallback) { + $('#defaultProgramClassification').val('').trigger('change.select2'); + } } function repopulateSelectedPairs(updatedMap) { @@ -475,11 +504,15 @@ var createMissingOrgs = $('#createMissingOrgs').prop('checked'); var createMissingSectors = $('#createMissingSectors').prop('checked'); var createMissingOrgGroups = $('#createMissingOrgGroups').prop('checked'); + var createMissingPrograms = $('#createMissingPrograms').prop('checked'); var orgGroupId = $('#orgGroupId').val(); var defaultActivityStatusId = $('#defaultActivityStatusId').val(); var defaultLocationId = $('#defaultLocationId').val() || $('#defaultLocationId').attr('data-default-location-id') || ''; + var defaultProgramClassification = $('#defaultProgramClassification').val(); var hasOrgGroupMapping = hasMappedOrgGroupField(); var hasProjectLocationMapping = hasMappedProjectLocationField(); + var hasProgramMapping = hasMappedProgramField(); + var hasProgramClassificationMapping = hasMappedProgramClassificationField(); console.log("Internal", internal); console.log("Skip existing", skipExisting); console.log("Skip records without transactions", skipRecordsWithoutTransactions); @@ -488,9 +521,11 @@ console.log("Create missing orgs", createMissingOrgs); console.log("Create missing sectors", createMissingSectors); console.log("Create missing org groups", createMissingOrgGroups); + console.log("Create missing programs", createMissingPrograms); console.log("Org group id", orgGroupId); console.log("Default activity status id", defaultActivityStatusId); console.log("Default location id", defaultLocationId); + console.log("Default program classification", defaultProgramClassification); if (createMissingOrgs && !orgGroupId && !hasOrgGroupMapping && !createMissingOrgGroups) { alert("Please select an Organization Group, map the Organization Group column, or enable organization group creation for newly created organizations."); return; @@ -499,6 +534,10 @@ alert("Please select a fallback location when no Project Location column is mapped."); return; } + if (hasProgramMapping && !hasProgramClassificationMapping && !defaultProgramClassification) { + alert("Please select a default program classification when no Program Classification column is mapped."); + return; + } var dataSeparator = $('#data-separator').val(); var currentConfigName = $('#current-config-name').val(); var existingConfig = (currentConfigName && currentConfigName.trim() !== '') ? currentConfigName.trim() : $('#existing-config').val(); @@ -524,6 +563,7 @@ formData.append('createMissingOrgs', createMissingOrgs); formData.append('createMissingSectors', createMissingSectors); formData.append('createMissingOrgGroups', createMissingOrgGroups); + formData.append('createMissingPrograms', createMissingPrograms); if (createMissingOrgs && orgGroupId) { formData.append('orgGroupId', orgGroupId); } @@ -533,6 +573,9 @@ if (defaultLocationId) { formData.append('defaultLocationId', defaultLocationId); } + if (defaultProgramClassification) { + formData.append('defaultProgramClassification', defaultProgramClassification); + } formData.append('action',"uploadDataFile"); formData.append('fileType', fileType); formData.append('dataSeparator', dataSeparator); @@ -1014,6 +1057,7 @@ + + +
From d3291ae4a277a9c5dbefd2345788d7d1ed81f667 Mon Sep 17 00:00:00 2001 From: brianbrix Date: Wed, 25 Mar 2026 14:58:00 +0300 Subject: [PATCH 02/18] Add import for programs --- .../WEB-INF/jsp/aim/view/dataImporter.jsp | 101 +++++++++--------- .../jsp/aim/view/viewImportProgress.jsp | 100 ++++++++--------- 2 files changed, 102 insertions(+), 99 deletions(-) diff --git a/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp b/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp index 67cfb41853b..2876fe2ec5b 100644 --- a/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp +++ b/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp @@ -631,10 +631,10 @@ --accent-warm: #8a6f56; --surface-muted: #f0f2f4; --row-alt: #f8f9fa; - --shadow: 0 8px 20px rgba(25, 39, 52, 0.06); - --radius-lg: 20px; - --radius-md: 14px; - --radius-sm: 10px; + --shadow: 0 4px 12px rgba(25, 39, 52, 0.05); + --radius-lg: 14px; + --radius-md: 10px; + --radius-sm: 6px; } html { @@ -644,8 +644,8 @@ body { margin: 0; font-family: Arial, Helvetica, sans-serif; - font-size: 14px; - line-height: 1.5; + font-size: 12px; + line-height: 1.4; color: var(--text-strong); background: var(--page-bg); } @@ -660,7 +660,7 @@ .importer-page { max-width: 1180px; margin: 0 auto; - padding: 40px 20px 56px; + padding: 22px 16px 34px; } .hero-card, @@ -673,52 +673,53 @@ } .hero-card { - border-radius: 24px; - padding: 36px; - margin-bottom: 24px; + border-radius: 16px; + padding: 20px; + margin-bottom: 14px; background: var(--panel-bg); } .hero-card h1, .workspace-card h2, .upload-stage h3 { - margin: 0 0 10px; - font-size: 1.5rem; + margin: 0 0 8px; + font-size: 18px; font-weight: 700; - letter-spacing: 0.02em; + letter-spacing: 0; } .hero-card p, .section-copy, .helper-note { color: var(--text-soft); - line-height: 1.6; + line-height: 1.45; margin: 0; + font-size: 12px; } .panel-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 20px; - margin-bottom: 24px; + gap: 14px; + margin-bottom: 14px; } .panel-card, .workspace-card, .upload-stage { border-radius: var(--radius-lg); - padding: 24px; + padding: 16px; } .workspace-card { - margin-top: 24px; + margin-top: 14px; } .section-label { display: inline-block; - margin-bottom: 8px; - font-size: 13px; - letter-spacing: 0.18em; + margin-bottom: 6px; + font-size: 11px; + letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); font-weight: 700; @@ -726,8 +727,8 @@ label { display: inline-block; - margin-bottom: 6px; - font-size: 14px; + margin-bottom: 4px; + font-size: 12px; font-weight: 700; color: var(--text-strong); } @@ -738,16 +739,16 @@ width: 100%; max-width: 100%; box-sizing: border-box; - padding: 12px 14px; + padding: 8px 10px; border-radius: var(--radius-sm); border: 1px solid rgba(22, 53, 67, 0.18); background: #fff; color: var(--text-strong); - font-size: 14px; + font-size: 12px; } input[type="file"] { - padding: 10px 12px; + padding: 7px 9px; background: var(--surface-muted); } @@ -755,12 +756,12 @@ button { border: 1px solid #506673; border-radius: 999px; - padding: 12px 18px; + padding: 8px 14px; font-weight: 700; cursor: pointer; color: #fff; background: var(--accent); - font-size: 14px; + font-size: 12px; box-shadow: none; transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease; } @@ -779,14 +780,14 @@ .inline-field, .toggle-grid, .sheet-choice-card { - margin-top: 16px; + margin-top: 12px; } .toggle-grid { display: grid; - gap: 10px; - margin-top: 18px; - padding: 18px; + gap: 8px; + margin-top: 14px; + padding: 12px; border-radius: var(--radius-md); background: var(--surface-muted); border: 1px solid var(--panel-border); @@ -819,16 +820,16 @@ table th, table td { text-align: left; - padding: 14px 16px; - font-size: 14px; + padding: 10px 12px; + font-size: 12px; border-bottom: 1px solid rgba(22, 53, 67, 0.08); } table th { background: #eef1f3; color: var(--text-strong); - font-size: 13px; - letter-spacing: 0.12em; + font-size: 11px; + letter-spacing: 0.08em; text-transform: uppercase; } @@ -843,25 +844,25 @@ .mapping-toolbar { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - gap: 16px; + gap: 12px; align-items: end; - margin-bottom: 18px; + margin-bottom: 12px; } .mapping-actions { display: flex; - gap: 12px; + gap: 8px; flex-wrap: wrap; - margin-top: 16px; + margin-top: 10px; } .upload-stage { - margin-top: 22px; + margin-top: 14px; } #config-empty-note { - margin-top: 18px; - padding: 16px 18px; + margin-top: 12px; + padding: 10px 12px; border-radius: var(--radius-md); background: #f3efe9; color: #6a5a48; @@ -869,10 +870,10 @@ } .select2-container--default .select2-selection--single { - height: 46px; + height: 34px; border-radius: var(--radius-sm); border: 1px solid rgba(22, 53, 67, 0.18); - padding: 8px 12px; + padding: 3px 8px; display: flex; align-items: center; background: #fff; @@ -880,20 +881,20 @@ .select2-container--default .select2-selection--single .select2-selection__rendered { color: var(--text-strong); - font-size: 14px; - line-height: 28px; + font-size: 12px; + line-height: 24px; padding-left: 0; } .select2-dropdown { - border-radius: 14px; + border-radius: 8px; border-color: rgba(22, 53, 67, 0.18); overflow: hidden; } .select2-search__field { - border-radius: 10px; - padding: 8px 10px; + border-radius: 6px; + padding: 6px 8px; } @media (max-width: 768px) { diff --git a/amp/src/main/webapp/WEB-INF/jsp/aim/view/viewImportProgress.jsp b/amp/src/main/webapp/WEB-INF/jsp/aim/view/viewImportProgress.jsp index 128fce476c8..742366225a2 100644 --- a/amp/src/main/webapp/WEB-INF/jsp/aim/view/viewImportProgress.jsp +++ b/amp/src/main/webapp/WEB-INF/jsp/aim/view/viewImportProgress.jsp @@ -19,7 +19,7 @@ --danger: #7a5555; --warning: #7d6a53; --row-alt: #f8f9fa; - --shadow: 0 8px 20px rgba(25, 39, 52, 0.06); + --shadow: 0 4px 12px rgba(25, 39, 52, 0.05); } html { @@ -29,8 +29,8 @@ body { margin: 0; font-family: Arial, Helvetica, sans-serif; - font-size: 14px; - line-height: 1.5; + font-size: 12px; + line-height: 1.4; color: var(--text-strong); background: var(--page-bg); } @@ -45,7 +45,7 @@ .progress-page { max-width: 1220px; margin: 0 auto; - padding: 40px 20px 56px; + padding: 22px 16px 34px; } .hero-card, @@ -54,27 +54,27 @@ background: var(--panel-bg); border: 1px solid var(--panel-border); box-shadow: var(--shadow); - border-radius: 28px; + border-radius: 14px; } .hero-card { - padding: 34px; - margin-bottom: 22px; + padding: 20px; + margin-bottom: 14px; background: var(--panel-bg); } .hero-card h1, .panel-card h2, .records-card h2 { - margin: 0 0 10px; - font-size: 1.5rem; + margin: 0 0 8px; + font-size: 18px; } .section-label { display: inline-block; - margin-bottom: 8px; - font-size: 13px; - letter-spacing: 0.18em; + margin-bottom: 6px; + font-size: 11px; + letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent); font-weight: 700; @@ -83,13 +83,14 @@ .section-copy { margin: 0; color: var(--text-soft); - line-height: 1.6; + line-height: 1.45; + font-size: 12px; } .panel-card, .records-card { - padding: 24px; - margin-bottom: 22px; + padding: 16px; + margin-bottom: 14px; } table { @@ -104,15 +105,15 @@ td, th { text-align: left; - padding: 14px 16px; - font-size: 14px; + padding: 10px 12px; + font-size: 12px; border-bottom: 1px solid rgba(22, 53, 67, 0.08); } th { background: #eef1f3; - font-size: 13px; - letter-spacing: 0.12em; + font-size: 11px; + letter-spacing: 0.08em; text-transform: uppercase; } @@ -129,8 +130,8 @@ .nav-action-btn { border: 1px solid #506673; border-radius: 999px; - padding: 10px 16px; - font-size: 14px; + padding: 8px 14px; + font-size: 12px; font-weight: 700; cursor: pointer; color: #fff; @@ -139,13 +140,13 @@ } .view-more-btn { - margin-top: 8px; - padding: 8px 14px; - font-size: 13px; + margin-top: 6px; + padding: 6px 12px; + font-size: 11px; } .hero-actions { - margin-top: 18px; + margin-top: 12px; display: flex; justify-content: flex-end; } @@ -153,16 +154,17 @@ .status-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 14px; - margin: 18px 0; + gap: 10px; + margin: 12px 0; } .status-pill { - padding: 14px 16px; - border-radius: 18px; + padding: 10px 12px; + border-radius: 10px; font-weight: 700; background: #f1f3f5; border: 1px solid var(--panel-border); + font-size: 12px; } .status-pill.all { @@ -184,17 +186,17 @@ .filter-div { display: flex; flex-wrap: wrap; - gap: 12px; + gap: 8px; align-items: center; - margin-bottom: 18px; - padding: 14px 16px; - border-radius: 18px; + margin-bottom: 12px; + padding: 10px 12px; + border-radius: 10px; background: #f1f3f5; border: 1px solid var(--panel-border); } .filter-div label { - font-size: 14px; + font-size: 12px; font-weight: 700; } @@ -209,20 +211,20 @@ .dataTables_wrapper .dataTables_filter input, .dataTables_wrapper .dataTables_length select { border: 1px solid rgba(22, 53, 67, 0.18); - border-radius: 10px; - padding: 6px 10px; + border-radius: 6px; + padding: 5px 8px; background: #fff; - font-size: 14px; + font-size: 12px; } .uploads-filter-bar { display: flex; flex-wrap: wrap; - gap: 12px; + gap: 8px; align-items: end; - margin: 16px 0 18px; - padding: 14px 16px; - border-radius: 18px; + margin: 10px 0 12px; + padding: 10px 12px; + border-radius: 10px; background: #f1f3f5; border: 1px solid var(--panel-border); } @@ -230,21 +232,21 @@ .uploads-filter-field { display: flex; flex-direction: column; - gap: 6px; - min-width: 180px; + gap: 4px; + min-width: 160px; } .uploads-filter-field label { - font-size: 14px; + font-size: 12px; font-weight: 700; } .uploads-filter-field input { border: 1px solid rgba(22, 53, 67, 0.18); - border-radius: 10px; - padding: 8px 10px; + border-radius: 6px; + padding: 6px 8px; background: #fff; - font-size: 14px; + font-size: 12px; } .truncated-response { @@ -263,8 +265,8 @@ .hero-card, .panel-card, .records-card { - padding: 18px; - border-radius: 20px; + padding: 14px; + border-radius: 12px; } .filter-div { From 62b2e99930ed30be7492778087938eaced61968a Mon Sep 17 00:00:00 2001 From: brianbrix Date: Wed, 25 Mar 2026 15:24:16 +0300 Subject: [PATCH 03/18] Add import for programs --- .../aim/action/dataimporter/DataImporter.java | 6 ++++- .../WEB-INF/jsp/aim/view/dataImporter.jsp | 22 +++++++++---------- .../jsp/aim/view/viewImportProgress.jsp | 19 +++++++++++++++- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/DataImporter.java b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/DataImporter.java index 6cc8a0f6622..ab72f869307 100644 --- a/amp/src/main/java/org/digijava/module/aim/action/dataimporter/DataImporter.java +++ b/amp/src/main/java/org/digijava/module/aim/action/dataimporter/DataImporter.java @@ -696,7 +696,11 @@ private List getActivityStatuses() { } private List getProgramClassificationNames() { - return ProgramUtil.getEnabledProgramSettings().stream() + List settings = ProgramUtil.getEnabledProgramSettings(); + if (settings == null || settings.isEmpty()) { + settings = ProgramUtil.getAmpActivityProgramSettingsList(true); + } + return settings.stream() .filter(Objects::nonNull) .map(AmpActivityProgramSettings::getName) .filter(Objects::nonNull) diff --git a/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp b/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp index 2876fe2ec5b..d63ee6be82a 100644 --- a/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp +++ b/amp/src/main/webapp/WEB-INF/jsp/aim/view/dataImporter.jsp @@ -228,20 +228,20 @@ fileType = 'excel'; } if (fileType === "csv") { - $('#select-file-label').html("Select csv file"); + $('#select-file-label').html("Select CSV File"); $('#data-file').attr("accept", ".csv"); $('#template-file').attr("accept", ".csv"); $('#separator-div').hide(); $('#data-sheet-choice-div').hide(); } else if(fileType==="text") { - $('#select-file-label').html("Select text file"); + $('#select-file-label').html("Select Text File"); $('#data-file').attr("accept", ".txt"); $('#template-file').attr("accept", ".txt"); $('#separator-div').show(); $('#data-sheet-choice-div').hide(); } else if(fileType==="excel") { - $('#select-file-label').html("Select excel file"); + $('#select-file-label').html("Select Excel File"); $('#data-file').attr("accept", ".xls,.xlsx"); $('#template-file').attr("accept", ".xls,.xlsx"); $('#separator-div').hide(); @@ -269,11 +269,11 @@ formData.append('action', 'getDataFileSheets'); formData.append('fileType', $('#file-type').val()); var $select = $('#data-sheet'); - $select.prop('disabled', true).empty().append(''); + $select.prop('disabled', true).empty().append(''); fetch("${pageContext.request.contextPath}/aim/dataImporter.do", { method: "POST", body: formData }) .then(function(r) { return r.json(); }) .then(function(names) { - $select.empty().append(''); + $select.empty().append(''); if (Array.isArray(names)) { names.forEach(function(name) { $select.append($('').attr('value', name).text(name)); @@ -282,7 +282,7 @@ } }) .catch(function() { - $select.empty().append('').prop('disabled', false); + $select.empty().append('').prop('disabled', false); alert("Could not load sheets from file."); }); }); @@ -919,14 +919,14 @@
- +

Import Data

Prepare a template, map source columns to AMP entity fields, and upload your data only after the configuration table is ready.

- +

Choose File Settings

Pick the source format and optionally load an existing configuration before uploading a template.

@@ -953,7 +953,7 @@
- +

Load Configuration

Resume from a saved mapping or start with a fresh template upload.

@@ -983,7 +983,7 @@ - +

Build Your Mapping

Search entity fields, add mappings, and review the configuration table before uploading the actual data file.

@@ -1028,7 +1028,7 @@