Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/abap/extension-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ ENDMETHOD.
turns a thrown exception into an `E` message, so the never‑raises contract still holds.
- A **wrong‑typed** `hooks` object (one that does not implement `ZIF_SSI_HOOKS`) **fails the import
closed** — configured hooks are never silently skipped.
- **Coverage:** all five hooks run on the **generic engine** (flat active create) and the facade. A
generated **deep/draft/upsert adapter** runs `coerce_field` / `on_before_persist` only when
regenerated with a hook‑aware `ZCL_SSI_ADAPTER_GEN` (`on_rows_parsed` / `on_message` / `on_completed`
always run — they sit in the facade).
- **Coverage:** the **generic engine** runs `coerce_field` / `on_before_persist` only; `on_rows_parsed`
/ `on_message` / `on_completed` run in the **facade** (`zcl_ssi_import`) — so a direct
`get_importer->import` call gets only the two engine hooks. **No** generated adapter — flat, deep, or
upsert — runs `coerce_field` / `on_before_persist` today; that would require regenerating with a
hook‑aware `ZCL_SSI_ADAPTER_GEN`.

Full reference + a runnable example: the
[public API contract](public-api-contract.md#extension-hooks-zif_ssi_hooks) and the
Expand Down
103 changes: 69 additions & 34 deletions docs/abap/fiori-elements.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,58 +59,93 @@ call).
largeObject); the user picks the file and the rows import. The action is surfaced on the list
report / object page through a **metadata extension** (see below).

!!! note "Frontend requirement: SAPUI5 ≥ 1.135"
File upload as an action parameter is a Fiori Elements (OData V4) feature **added in
SAPUI5 1.135** (April 2025). Older UI5 versions render the `FileContent` parameter as a
plain field. Backend requirement: see the release matrix below.

**Option fields** (on both parameters): `IsCsv` force CSV (else magic‑byte auto‑detect) · `IsDraft`
create drafts · `ChunkSize` rows/LUW (0 = default 500) · `DecimalSeparator` `'.'`/`','` · `HeaderRow`
1‑based header row. See [Options & data types](options.md).

## Surfacing the action in Fiori Elements (metadata extension)

In a Fiori Elements app the import action becomes a **button in the table toolbar** (and the upload
parameter renders a file picker) via a CDS **metadata extension** (DDLX) on your projection view — no
custom UI code. Conceptually:
In a Fiori Elements app the import action becomes a **button in the table toolbar** via a CDS
**metadata extension** (DDLX) on your projection view — no custom UI code. This is the (verified,
runnable) annotation from the samples' demo BO:

```abap
@Metadata.layer: #CORE
annotate entity ZC_YourProjection with
@UI: {
headerInfo: { typeName: 'Demo Order', typeNamePlural: 'Demo Orders',
title: { type: #STANDARD, value: 'OrderId' } }
}
annotate view ZSSI_C_S_ORD with
{
@UI.lineItem: [ { position: 10 } ] // a column …
YourField;
// the #FOR_ACTION line items put the import buttons in the table toolbar
@UI: { lineItem: [ { position: 10, importance: #HIGH },
{ type: #FOR_ACTION, dataAction: 'importUpload', label: 'Import Spreadsheet (File Upload)' },
{ type: #FOR_ACTION, dataAction: 'importExcel', label: 'Import Spreadsheet (Base64)' } ],
identification: [ { position: 10 },
{ type: #FOR_ACTION, dataAction: 'importUpload', label: 'Import Spreadsheet (File Upload)' } ],
selectionField: [ { position: 10 } ] }
OrderId;

@UI: { lineItem: [ { position: 20 } ], identification: [ { position: 20 } ] }
Customer;
}

// the action on the entity's toolbar:
@UI: { lineItem: [ { type: #FOR_ACTION, dataAction: 'importUpload', label: 'Import Excel' } ],
identification: [ { type: #FOR_ACTION, dataAction: 'importUpload', label: 'Import Excel' } ] }
```

!!! info "A complete, runnable example is in the samples"
The [samples repo](https://github.com/spreadsheetimporter/abap-spreadsheetimporter-samples) wires the
demo BO end‑to‑end for Fiori Elements — projection view, behaviour projection, service definition,
OData V4 UI service binding, and the metadata extension that puts the import button on the list
report — so you can build a Fiori Elements app on top and upload Excel files. The exact objects +
DDLX are shown here once published.

## The 758 on‑prem Fiori upload caveat
The full stack around it: projection view (`@Metadata.allowExtensions: true`, provider contract
`transactional_query`) → behavior projection (`use action importExcel; use action importUpload;
use function getCreateTemplate;`) → service definition → OData V4 UI service binding.

On **S/4HANA 2023 on‑prem**, the base64 **API channel is the universal, fully‑wired path**. The Fiori
`importUpload` control still renders a working `Edm.Stream` upload, but the mime‑type filter and file
name are not auto‑bound in the dialog — a confirmed **gateway framework‑version gap, not a modelling
bug**. The importer auto‑detects the file type from magic bytes regardless, so the import works either
way.
!!! info "A complete, runnable example is in the samples"
The [samples repo](https://github.com/spreadsheetimporter/abap-spreadsheetimporter-samples) ships the
whole thing — the wired demo BO (`ZSSI_R_S_ORD` + one‑line handlers), the projection stack with the
DDLX above, the published OData V4 service, **and a runnable Fiori Elements app**
(`app/demo-orders`) on top of it, including a custom "API channel" action
(`webapp/ext/ImportSpreadsheet.js`: file picker → base64 → `importExcel`) that works on every
supported release.

## Which channel works where (verified)

| | S/4HANA 2023 on‑prem (758) | BTP ABAP / S/4HANA Cloud (newer gateway) |
|---|---|---|
| **Native `importUpload` dialog** (needs SAPUI5 ≥ 1.135) | dialog **renders** — file‑upload control + the shipped field labels — but **submit fails**: the gateway cannot deserialize an inline `Edm.Stream` action parameter (`Parser error … while parsing an XML stream`) | ✅ works zero‑code (gateway emits the `Core.*` terms and accepts the stream payload) |
| **Base64 `importExcel`** — via API or an FE **custom action** | ✅ **works end‑to‑end** — verified live: per‑row BO messages (e.g. *"key value already in use"*) surface in the FE result dialog | ✅ works |

So on 758 build the upload button as a small FE **custom action** that base64‑encodes the picked file
and calls `importExcel` (the sample's
[`ImportSpreadsheet.js`](https://github.com/spreadsheetimporter/abap-spreadsheetimporter-samples/blob/main/app/demo-orders/webapp/ext/ImportSpreadsheet.js)
is exactly that, ~60 lines). Keep `importUpload` modeled — it lights up with zero change once the
backend moves to a gateway that supports it.

## The 758 on‑prem gateway gap (detail)

On **S/4HANA 2023 on‑prem**, the base64 **API channel is the universal, fully‑wired path**. The native
Fiori upload is blocked by the gateway in two ways — both **framework‑version gaps, not modelling
bugs** (verified empirically against a live 758 system, from curl *and* from a real FE 1.136 app):

1. `$metadata` carries **none** of the `Core.MediaType` / `Core.ContentDisposition` /
`Core.AcceptableMediaTypes` terms Fiori Elements uses to wire the mime filter and file name
(the `@EndUserText.label`s *do* arrive — the dialog fields are nicely labeled).
2. The action `POST` itself is rejected: the 758 gateway cannot deserialize an inline stream value in
the JSON action payload, in any representation (with/without `@odata.mediaContentType`, base64 or
base64url, OData 4.0 or 4.01 headers).

??? note "Why (the gateway detail)"
`ZSSI_A_FILE` is modelled *verbatim* per SAP's documented "File Upload as an Action Parameter" recipe
(`@Semantics.largeObject.mimeType/.fileName/.contentDispositionPreference:#INLINE` on `abap.rawstring`,
`@UI.hidden` on mime/filename). The live `$metadata` on 758 **does** render `FileContent` as
`Edm.Stream`, **but** the RAP→OData V4 gateway does **not** translate `@Semantics.largeObject` into the
`Core.MediaType` / `Core.ContentDisposition` / `Core.IsMediaType` / `Core.AcceptableMediaTypes` terms
that Fiori Elements needs to wire the mime filter and file name. The 758 `$metadata` carries **zero** of
those `Core.*` terms on the action‑parameter ComplexType.

This translation is done by the gateway metadata generator on SAP BTP ABAP / S/4HANA Cloud (and newer
on‑prem gateway SPs); it is not expressible via DDLX, a raw `@Core.*` passthrough, or a SRVD
annotation — the only on‑prem path is a gateway SP/note upgrade. Because the modelling already matches
SAP's recipe byte‑for‑byte, the dialog **auto‑wires on Cloud / a newer on‑prem gateway with zero change**
to this component.
`@UI.hidden` on mime/filename, `acceptableMimeTypes`). The live `$metadata` on 758 **does** render
`FileContent` as `Edm.Stream` (which is why the 1.135+ dialog shows the upload control), **but** the
RAP→OData V4 gateway does **not** translate `@Semantics.largeObject` into the `Core.*` terms, and its
JSON deserializer predates inline stream values in action payloads.

Neither half is expressible via DDLX, a raw `@Core.*` passthrough, or a SRVD annotation — the only
on‑prem path is a gateway SP/note upgrade. Because the modelling already matches SAP's recipe
byte‑for‑byte, the dialog **auto‑wires on Cloud / a newer on‑prem gateway with zero change** to this
component.

??? note "C1 API release (only for cross‑software‑component consumption on Cloud)"
The "use of non‑released API" ATC finding only fires when a *restricted* (ABAP for Cloud Development)
Expand Down
4 changes: 3 additions & 1 deletion docs/abap/integration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ ENDINTERFACE.
iv_assoc = '_Items' iv_child_alias = 'Item' iv_parent_key = 'ROOTKEY' ).
```
Create that class in your package (~50 lines of typed EML that fills the create lines via
`ZCL_SSI_UTIL=>fill_line`, sets `%is_draft`, and returns the result).
`ZCL_SSI_UTIL=>fill_line` and returns the result). The **flat** and **upsert** generators set
`%is_draft` when `is_options-is_draft = abap_true`; `generate_deep` emits **ACTIVE‑CREATE only**
(draft + deep/composition is out of scope), so `is_draft` is ignored on the deep path.

2. **Register** it once at startup so the factory routes your BO to it:
```abap
Expand Down
6 changes: 3 additions & 3 deletions docs/abap/public-api-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Every public API below has a **runnable, copy‑pasteable example**, each proven

| Object | Role | How a consumer uses it |
|---|---|---|
| **`ZIF_SSI_TYPES`** | Shared types: `ts_options` (incl. `mode`, `hooks`), `ts_row`/`ts_cell`, `ts_result`/`ts_message`/`ts_key`, `tt_map`, the `gc_mode`/`gc_comp` enums | Reference the types in your own code |
| **`ZIF_SSI_TYPES`** | Shared types: `ts_options` (incl. `mode`, `hooks`), `ts_row`/`ts_cell`, `ts_result`/`ts_message`/`ts_key`, `tt_map`, the `gc_mode` enum | Reference the types in your own code |
| **`ZIF_SSI_IMPORTER`** | The `import( it_rows is_options ) RETURNING rs_result` contract | The seam to **mock** the library (see [`test doubles`](#testing-against-the-library)) |
| **`ZIF_SSI_HOOKS`** | Optional developer **extension hooks** — `on_rows_parsed` / `coerce_field` / `on_before_persist` / `on_message` / `on_completed` (all `DEFAULT IGNORE`) | Implement the ones you need; inject via `ts_options-hooks` (see [Extension hooks](#extension-hooks-zif_ssi_hooks)) |
| **`ZCL_SSI_IMPORT`** | Facade — `import_file` (auto-detect xlsx/CSV) / `import_rows`. **Never raises** (errors → `ts_message`) | The simplest entry point |
Expand All @@ -46,7 +46,7 @@ Every public API below has a **runnable, copy‑pasteable example**, each proven
| **`ZCL_SSI_TEMPLATE`** | `build_create_template( iv_entity, iv_sample_rows, it_mapping )` | Generate an `.xlsx` CREATE template |
| **`ZCL_SSI_ADAPTER_GEN`** | Code generators: `generate_flat` / `generate_deep` / `generate_upsert` / `generate_action` / `generate_template_function` | You **run** these to emit a typed adapter for your BO |
| **`ZCL_SSI_FACTORY`** | `register( iv_entity, iv_class )` — register your adapter | Wire a generated adapter for an entity |
| **`ZCL_SSI_PARSER`** | `parse_xlsx` / `parse_csv` / `coerce` / `resolve_field` — stateless parsing utilities (`parse_xlsx` raises `zcx_ssi_parse`, see [Exceptions](#exceptions)) | Advanced/standalone parsing |
| **`ZCL_SSI_PARSER`** | `parse_xlsx` / `parse_csv` / `coerce` / `convexit_of` — stateless parsing utilities (`parse_xlsx` raises `zcx_ssi_parse`, see [Exceptions](#exceptions)) | Advanced/standalone parsing |
| **Abstract & custom entities** — `ZSSI_A_IMPORT` · `ZSSI_A_IMPORT_FILE` · `ZSSI_A_FILE` · `ZSSI_A_RESULT` · `ZSSI_A_TEMPLATE` · `ZSSI_A_TFILE` · `ZSSI_C_TEMPLATE` | The action/function parameter & result shapes | Reference them in your BDEF `action`/`function` signatures |
| **`ZSSI_IMPORTER`** (message class) | The translatable messages | Surfaced in `ts_message`; reference numbers if you map them |

Expand Down Expand Up @@ -90,7 +90,7 @@ one‑line RAP delegate, inject hooks via the optional `is_options`:
`%param` format fields override the base where supplied. Calling the importer **directly**
(`zcl_ssi_factory=>get_importer` → `import`) bypasses the facade, so only the engine‑level hooks run
there. `coerce_field` and `on_before_persist` are live on the **generic engine** (flat active
create); a generated **deep/draft/upsert adapter** gets them once regenerated with a `ZCL_SSI_ADAPTER_GEN`
create); no generated adapter (**flat, deep or upsert**) runs them today — an adapter gets them once regenerated with a `ZCL_SSI_ADAPTER_GEN`
version that emits the calls (follow‑on). On the engine path `on_before_persist`'s instances table is
anonymous (RTTC‑built) — navigate it with `ASSIGN COMPONENT` (incl. the `%CONTROL` flags) and mutate
**in place**; a typed adapter passes its typed table. Worked examples: `ZCL_SSI_UNIT_HOOKS` (the library's own hook tests) and
Expand Down
3 changes: 2 additions & 1 deletion docs/abap/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
| Opaque short dump on import | Lower‑case `entity_name`/`sub_name` in the dynamic EML | The component upper‑cases them; if you pass a BO/association name, give it uppercase |
| Your validations don't run | `IN LOCAL MODE` was used (bypasses validations/auth/prechecks) | The component calls EML **non‑local** against your BO; don't enable a "local/privileged" mode unless you mean to |
| Determination loops / times out | Your determination issues `MODIFY` that re‑triggers itself | Guard your determination (don't re‑modify the trigger field set); not a component issue |
| Dates all blank / wrong | Native Excel date cells or wrong format | Use **text** dates in the template (see [Options & data types](options.md)) |
| Dates all blank / wrong | Dates must be **text** in `YYYYMMDD` or `YYYY‑MM‑DD` form (an optional `Txx` time suffix is stripped); any other order (e.g. `31.12.2024`) or trailing junk is rejected | Fails **closed** with a per‑row conversion error (msg `002`, severity `E`) and the row is dropped — the value is never silently stored corrupt. Only calendar‑**range** checks apply (month `01`‑`12`, day `01`‑`31`), so an impossible date like Feb‑30 still passes and stores as‑is. See [Options & data types](options.md) |
| Number‑range error `BEHAVIOR_ILLEGAL_STATEMENT` | Your BO's custom number range does its own `COMMIT WORK` | Set the number‑range object to **main‑memory buffering** |
| Drafts created as active rows | No draft adapter registered for the BO | Generate + register a draft adapter — the generic engine is active‑only |
| Big file slow / memory | XCO holds the workbook in memory; one huge commit | Lower `rowThreshold`, tune `chunk_size`; consider splitting the file |
| `COMMIT ENTITIES is not allowed with this status` dump | You called the engine from inside a RAP action/handler | The engine owns the commit — call it from your own job/controller (or set `defer_commit` so the framework commits at request end); the upload action must only *store* the file |
| Fiori `importUpload` dialog: `Parser error … while parsing an XML stream` on submit | S/4HANA 2023 (758) gateway can't deserialize an inline `Edm.Stream` action parameter | Use the base64 `importExcel` channel (e.g. an FE custom action) on 758; the native dialog works on BTP ABAP / S/4HANA Cloud — see [Fiori Elements & file upload](fiori-elements.md#which-channel-works-where-verified) |
| Upload "succeeds", row has empty fields | A typed `CREATE FROM` without `%control` | Use `CREATE FIELDS ( … ) WITH` (auto‑sets control); the shipped handler / `fill_line` already do |

## Behaviour worth knowing
Expand Down
2 changes: 1 addition & 1 deletion docs/abap/usage-cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ zcl_ssi_parser=>coerce(

```abap
" .xlsx whose header row = the importer's accepted fields, so the filled file
" re-imports with zero config. Raises cx_static_check for an unknown entity.
" re-imports with zero config. Returns an empty xstring for an unknown entity / one with no importable fields (never raises); check xstrlen( ) > 0.
DATA(lv_xlsx) = zcl_ssi_template=>build_create_template(
iv_entity = 'ZSSI_R_S_ORD' iv_sample_rows = 1 ).
" xstrlen( lv_xlsx ) > 0 -> a valid .xlsx
Expand Down
2 changes: 1 addition & 1 deletion docs/ui5/pages/APIReference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### Overview

The `spreadsheetimporter` component provides a way to import data from a spreadsheet into a table in the UI Builder application.
The `spreadsheetimporter` component provides a way to import data from a spreadsheet into a table in the UI5 application.

### Constructor

Expand Down
2 changes: 1 addition & 1 deletion docs/ui5/pages/Checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ The following types of errors are handled by the UI5 Spreadsheet Upload Control:

- **Empty Headers** _(Available since: 1.2.0)_: The control checks if the uploaded spreadsheet contains empty headers (columns labeled "**EMPTY", "**EMPTY_1", etc.). These typically occur when Excel adds empty columns during import. When an empty header is detected, the control displays a warning message indicating the presence of empty columns and providing guidance on the expected start position for data. This check helps ensure data is properly aligned with column headers, especially when using custom start coordinates. This check can be disabled using the `skipEmptyHeadersCheck` configuration option.

- **Nullable Field Violations** _(Available since: [Next Release])_: The control validates null values against OData metadata. When you use the `__NULL__` marker in a cell to explicitly set a field to NULL, the component checks the field's Nullable attribute in the OData metadata. If the field is non-nullable (Nullable="false"), an error is displayed. This validation ensures data integrity and prevents backend errors. Key fields and mandatory fields are typically non-nullable. See [Null & Empty Value Handling](NullHandling.md) for comprehensive guide.
- **Nullable Field Violations** _(Available since: 2.4.0)_: The control validates null values against OData metadata. When you use the `__NULL__` marker in a cell to explicitly set a field to NULL, the component checks the field's Nullable attribute in the OData metadata. If the field is non-nullable (Nullable="false"), an error is displayed. This validation ensures data integrity and prevents backend errors. Key fields and mandatory fields are typically non-nullable. See [Null & Empty Value Handling](NullHandling.md) for comprehensive guide.
Loading
Loading