====== ImportTechnicalAccountsFromCSVExecutor ====== Long Running Task (LRT) that bulk-creates or updates **Technical Accounts** in CzechIdM by reading a semicolon-separated CSV file. Each CSV row represents one technical account. The task maps configurable column names to entity fields, supports dry-run mode, and never triggers unintended provisioning operations on existing accounts. ===== How It Works ===== - The task reads the attached CSV file line by line (first line = header). - For each data row it looks up an existing Technical Account by the value in the **identifier column** (matched against ''code'') **scoped to the system named in ''systemcolumn''**. The same ''code'' may legally exist on different systems — the lookup returns only the TU linked to the row's system, if any. - Depending on whether the account exists and which behaviour flags are set, the account is **created**, **updated**, or **skipped**. - Optional columns assign identity/role guarantors, link the account to a provisioning system, and associate it with a Technical Asset. - All item-level results are written to the LRT task log. ===== CSV Format ===== ^ Property ^ Value ^ | Encoding | UTF-8 | | Column separator | '';'' (configurable) | | First row | Header with column names | | Multi-values | ''|'' by default (configurable)| | Date format | ''yyyy-MM-dd'' (ISO 8601) | Only the **identifier column** is mandatory. All other columns are optional — the task only reads a column when the corresponding parameter is configured **and** the column exists in the CSV header. Configuring a column name that is absent from the CSV header causes the task to fail immediately with a descriptive error listing the missing columns. ===== Task Parameters ===== ==== File & Format ==== ^ Parameter ^ Type ^ Required ^ Default ^ Description ^ | ''importFile'' | Attachment | Yes | — | CSV file to import | | ''separator'' | String | No | '';'' | Column separator character | | ''encoding'' | String | No | ''utf-8'' | File encoding | ==== Column Mapping ==== Each parameter value is the **exact name of a column in the CSV header** (case-sensitive). ^ Parameter ^ Description ^ | ''identifiercolumn'' | **Required.** Unique identifier for the account; stored as ''code''. Used to look up existing accounts. | | ''namecolumn'' | Technical account name (maps to ''externalCode'' — the Name field in the UI). | | ''descriptioncolumn'' | Free-text description. | | ''validfromcolumn'' | Validity start date (''yyyy-MM-dd''). Blank cell leaves the field unchanged. | | ''validtillcolumn'' | Validity end date (''yyyy-MM-dd''). Blank cell leaves the field unchanged. | | ''systemcolumn'' | Name (codeable identifier) of the provisioning system. Required when creating new accounts. | | ''mappingcolumn'' | Provisioning mapping name. Must be a TECHNICAL_ACCOUNT PROVISIONING mapping on the system. | | ''directidentitycolumn'' | Identity guarantors — pipe-separated list of **usernames**. | | ''guaranteerolecolumn'' | Role guarantors — pipe-separated list of **role codes**. | | ''technicalassetcolumn'' | Code of the Technical Asset to associate with the account. | | ''tiercolumn'' | Tier value written to the linked ''AccAccount'' when a new account is created. Blank cell leaves the field unset. | | ''zonecolumn'' | Zone value written to the linked ''AccAccount'' when a new account is created. Blank cell leaves the field unset. | | ''multivalueseparator'' | Character used to separate multiple values in multi-value columns. Default: ''|''. | ==== Behaviour Flags ==== ^ Parameter ^ Type ^ Default ^ Description ^ | ''cancreate'' | Boolean | ''false'' | Allow creating new Technical Accounts. When ''false'', rows whose identifier is not found are logged as skipped. | | ''canoverwrite'' | Boolean | ''false'' | Allow overwriting fields (''externalCode'', ''description'', ''validFrom'', ''validTill'') of existing accounts. When ''false'', existing accounts are logged as skipped. | | ''canchangeta'' | Boolean | ''false'' | Allow assigning or changing the Technical Asset (''technicalassetcolumn'') on an account. When ''false'', the asset column is ignored even if configured. | ===== Processing Logic per Row ===== read identifier from identifiercolumn read system name from systemcolumn (if configured) └─ find existing TechnicalAccount by code = identifier (scoped to the system from systemcolumn — see Code & Name Uniqueness Rules) ├─ NOT FOUND + cancreate = false → log CSV_IMPORT_ACCOUNT_SKIPPED (EXCEPTION), stop ├─ FOUND + canoverwrite = false → log CSV_IMPORT_ACCOUNT_SKIPPED (EXCEPTION), stop ├─ NOT FOUND + cancreate = true → resolve system + mapping (mandatory), create new account └─ FOUND + canoverwrite = true → update account fields (for new account: system and mapping must be resolved before save — see System Linking Rules) (after save) ├─ assign identity guarantors (directidentitycolumn) ├─ assign role guarantors (guaranteerolecolumn) ├─ assign technical asset (technicalassetcolumn, only when canchangeta = true) └─ for existing accounts: optionally validate AccAccount consistency (systemcolumn + mappingcolumn) **Unknown references** (identity username not found, role code not found, Technical Asset name not found) are logged as warnings and silently skipped — the account is still saved. ===== Multi-Value Columns ===== ''directidentitycolumn'' and ''guaranteerolecolumn'' can contain multiple values separated by the multi-value separator (default ''|''). Whitespace around each value is trimmed. Example cell value: jnovak|pdvorak|mmaly Each value is resolved independently. Values that cannot be resolved are skipped with a warning to console. The task only **adds** new guarantors — it never removes existing ones. Guarantors that were assigned previously (manually or by an earlier import) are left untouched regardless of what the current CSV contains. ===== System Linking Rules ===== Every Technical Account must have exactly one AccAccount. The link is maintained via the ''TechnicalAccountAccount'' join entity. ==== Creating a New Account ==== When ''cancreate = true'' and the identifier is not found: - **''systemcolumn'' is mandatory.** If not configured or the system is not found in CzechIdM, the row fails with ''CSV_IMPORT_ACCOUNT_ERROR''. - If ''mappingcolumn'' is configured: the named mapping must exist on the system **and** must be of type TECHNICAL_ACCOUNT PROVISIONING. Otherwise the row fails. - If ''mappingcolumn'' is not configured: the system must have **exactly one** TECHNICAL_ACCOUNT PROVISIONING mapping. Zero or more than one also causes the row to fail. - Once system and mapping are validated, a new ''AccAccount'' is created (uid = identifier) and linked via ''TechnicalAccountAccount''. ^ Scenario ^ Behaviour ^ | ''systemcolumn'' not configured | Error — row fails with ''CSV_IMPORT_ACCOUNT_ERROR''. | | System name not found in CzechIdM | Error — row fails with ''CSV_IMPORT_ACCOUNT_ERROR''. | | Mapping specified, not found on system | Error — row fails with ''CSV_IMPORT_ACCOUNT_ERROR''. | | Mapping specified, found but not TECHNICAL_ACCOUNT type | Error — row fails with ''CSV_IMPORT_ACCOUNT_ERROR''. | | Mapping not specified, no TECHNICAL_ACCOUNT mapping on system | Error — row fails with ''CSV_IMPORT_ACCOUNT_ERROR''. | | Mapping not specified, multiple TECHNICAL_ACCOUNT mappings on system | Error — specify mapping explicitly; row fails with ''CSV_IMPORT_ACCOUNT_ERROR''. | | System and mapping resolved successfully | New ''AccAccount'' (uid = identifier) created; ''TechnicalAccountAccount'' link saved. | ==== Updating an Existing Account ==== When the account already exists, its AccAccount is **never changed**. The system and mapping columns are only used for optional consistency validation — mismatches are logged as warnings, not errors. ^ Scenario ^ Behaviour ^ | ''systemcolumn'' not configured | System validation skipped. | | System name not found in CzechIdM | Warning logged, validation skipped. | | AccAccount is on a different system than specified | Warning logged; AccAccount is not changed. | | ''mappingcolumn'' not configured | Mapping validation skipped. | | AccAccount uses a different mapping than specified | Warning logged; AccAccount is not changed. | **Why is the AccAccount never changed for existing accounts?**\\ The import task assumes that if a Technical Account already exists in CzechIdM, its provisioning links were set up previously (either manually or by an earlier import run). Changing the AccAccount could trigger CzechIdM's provisioning engine to act on a live system. ==== Code (''identifiercolumn'' → ''code'') ==== * ''code'' is unique **per system**. The same ''code'' may exist on different systems — each combination ''(code, system)'' yields its own Technical Account. * The importer's lookup combines ''code'' with the system named in ''systemcolumn''. A TU linked to a **different** system is invisible to the lookup, so a new TU is created for the row's system. * If ''systemcolumn'' is not configured or the cell is blank, the lookup falls back to code-only. Uniqueness validation is also **not enforced** in this case — there is no system context to validate against. As a result, "orphan" Technical Accounts (those without an AccAccount link) are not constrained on ''code'' at all and multiple orphans with the same ''code'' may coexist. * Enforcement runs on the application side in two pre-save processors (''TechnicalAccountCodeCheckProcessor'' on the Technical Account, ''TechnicalAccountAccountCodeCheckProcessor'' on the link). On conflict the row fails with ''TECHNICAL_ACCOUNT_ALREADY_EXISTS_ON_SYSTEM''. ==== Name (''namecolumn'' → ''externalCode'') ==== * ''externalCode'' (FE label "Name") must be **unique across all Technical Accounts in CzechIdM**, regardless of which system the account is linked to. * A blank ''namecolumn'' cell (resulting in ''null'' / empty ''externalCode'') skips this validation. * Enforced by the framework in ''AbstractReadWriteDtoService.validateEntity''. On conflict the row fails with ''DuplicateExternalCodeException''. ^ Scenario ^ Outcome ^ | Same ''code'' on two different systems in the CSV | OK — two separate Technical Accounts are created (one per system). | | Same ''code'' on the same system in the CSV, ''canoverwrite=false'' | Second row is skipped (row 1's TU is found, not overwritten). | | Same ''code'' on the same system in the CSV, ''canoverwrite=true'' | Second row updates row 1's TU. | | Same ''name'' on two rows (any systems) | Second row fails — ''externalCode'' is globally unique. | | ''systemcolumn'' blank, identifier matches an orphan TU | Lookup finds the orphan and proceeds with update (per the standard flow). | **Practical impact for CSV imports:** * The same ''code'' may appear on multiple rows targeting different systems — each row creates its own TU. * Each row must use a **globally unique** ''name'' (or leave ''namecolumn'' blank). Re-running the import with the same ''name''s already present in the database also fails on ''DuplicateExternalCodeException''. ===== Technical Asset Assignment ===== The ''technicalassetcolumn'' column is only processed when **''canchangeta = true''**. When ''canchangeta = false'' the column is ignored even if it is present in the CSV and a matching Technical Asset exists in the system. Technical assets are looked up by **name** (the Name field shown in the UI). ^ Scenario ^ Behaviour ^ | ''canchangeta = false'' | Technical Asset column is ignored entirely. | | ''canchangeta = true'', asset found by name | Account's ''technicalAsset'' reference is set. | | ''canchangeta = true'', asset name not found | Warning logged, ''technicalAsset'' left unchanged. | ===== Dry-Run Mode ===== The task supports CzechIdM's built-in dry-run flag. When dry-run is enabled: * No Technical Accounts are created or updated. * No guarantors are assigned. * No AccAccounts or TechnicalAccountAccount links are created. * All rows (including new accounts) produce item-level log entries with state ''NOT_EXECUTED''. * Would-be operations are written to the application log at INFO level (prefix ''[DRY-RUN]''). * The task counter still increments so you can verify the row count. To execute a dry run, enable the **Dry run** toggle when scheduling the task in the UI, or set ''dryRun = true'' on the ''IdmLongRunningTaskDto'' before calling ''LongRunningTaskManager.executeSync()'' in code. ===== Result Codes ===== ^ Code ^ State ^ Counted as ^ Description ^ | ''CSV_IMPORT_ACCOUNT_CREATED'' | ''EXECUTED'' | successItemCount | Technical account was created. | | ''CSV_IMPORT_ACCOUNT_UPDATED'' | ''EXECUTED'' | successItemCount | Technical account was updated. | | ''CSV_IMPORT_ACCOUNT_SKIPPED'' | ''EXCEPTION'' | failedItemCount | Row skipped (cancreate=false or canoverwrite=false). | | ''CSV_IMPORT_ACCOUNT_ERROR'' | ''EXCEPTION'' | failedItemCount | Row failed (system/mapping could not be resolved). | ===== Examples ===== ==== Minimal CSV Example ==== Only identifier and name columns — no system, guarantors, or asset. identifiercolumn;namecolumn svc-api-gateway;API Gateway svc-db-reader;DB Reader svc-batch-runner;Batch Runner Task configuration: ^ Parameter ^ Value ^ | ''identifiercolumn'' | ''identifiercolumn'' | | ''namecolumn'' | ''namecolumn'' | | ''cancreate'' | ''true'' | | ''canoverwrite'' | ''false'' | ==== Full CSV Example ==== identifiercolumn;namecolumn;descriptioncolumn;validfromcolumn;validtillcolumn;systemcolumn;mappingcolumn;directidentitycolumn;guaranteerolecolumn;technicalassetcolumn svc-ldap-reader;LDAP Reader;LDAP read-only service account;2024-01-01;;CMS AD - Users;AD users provisioning mapping tech.;jnovak|pdvorak;role-ldap-admin|role-infra-team;Core Infrastructure svc-db-backup;DB Backup;Database backup service account;2024-01-01;2026-12-31;CMS AD - Users;AD users provisioning mapping tech.;mpolak;role-dba;Databases Task configuration: ^ Parameter ^ Value ^ | ''identifiercolumn'' | ''identifiercolumn'' | | ''namecolumn'' | ''namecolumn'' | | ''descriptioncolumn'' | ''descriptioncolumn'' | | ''validfromcolumn'' | ''validfromcolumn'' | | ''validtillcolumn'' | ''validtillcolumn'' | | ''systemcolumn'' | ''systemcolumn'' | | ''mappingcolumn'' | ''mappingcolumn'' | | ''directidentitycolumn'' | ''directidentitycolumn'' | | ''guaranteerolecolumn'' | ''guaranteerolecolumn'' | | ''technicalassetcolumn'' | ''technicalassetcolumn'' | | ''multivalueseparator'' | ''|'' | | ''cancreate'' | ''true'' | | ''canoverwrite'' | ''true'' | | ''canchangeta'' | ''true'' | A blank ''validtillcolumn'' cell (like the first row above) leaves the field unchanged — it does not clear an existing ''validTill'' date.