Skip to content

Catalog import: row-level import with dry run and per-row results - #885

Merged
KrzysztofPajak merged 6 commits into
developfrom
feature/catalog-row-import
Oct 10, 2026
Merged

KrzysztofPajak merged 6 commits into
developfrom
feature/catalog-row-import

Conversation

@KrzysztofPajak

Copy link
Copy Markdown
Member

Type: feature

Issue

Catalog import exists only as the panel's spreadsheet (XLSX) import. It is all-or-nothing from the caller's point of view: it reports nothing per row, has no dry run, silently skips what it cannot use and cannot import product attributes, specification attributes or variants. Integrations and API clients that send catalog data in batches need a row-level contract: what was created, updated or rejected, and why, before anything is written.

Solution

A row-level import contract next to the existing panel import:

  • IRowImport<TDto>.Import(rows, dryRun, cancellationToken) returns an ImportBatchResult with one ImportRowResult per row (Created / Updated / Rejected, id, key, errors, warnings). Dry run and the real run share one code path.
  • A cancelled token (e.g. a time budget) does not throw: rows not reached come back Rejected with "Not processed: the batch time limit was reached; send these rows again." Nothing is written for them.
  • Implemented for categories, brands, collections, products, specification attributes, product attributes (options merged) and product variants (attribute mappings, values and explicit combinations — no "generate all combinations").
  • Two modes. Panel mode keeps the spreadsheet matching (an unknown Id still creates). Row mode rejects an unknown Id, matches by SeName / SKU, and falls back to a conservative same-name match (same parent when one is given).
  • Products created in Row mode get the panel's create defaults (visible individually, order quantity 1–10000, shipping enabled, reviews allowed, default tax category), so they can be bought. ProductDto.PictureUrls (row import only) replaces Picture1..3, with a picture cap for the row API.
  • ImportHtmlGuard rejects text fields containing HTML that the store's sanitizer would remove (only when HTML sanitization is enabled). It detects and never rewrites.
  • DI: each importer is registered once and exposed as both IImportDataObject<T> (panel) and IRowImport<T>.

Commits are split by area: categories/brands/collections → products → specification and product attributes → variants.

Breaking changes

None to public APIs. A behaviour change to note for the panel spreadsheet import of categories, brands and collections:

  • A row with an empty name is now rejected, where before it was skipped.
  • When HTML sanitization is enabled, a row whose text contains HTML the sanitizer would strip is rejected (logged) instead of imported.

Rejected rows are written to the log.

Testing

  1. dotnet test src/Tests/Grand.Business.Catalog.Tests — ExportImport tests cover every importer: created / updated / rejected rows, dry run writes nothing, unknown id in Row vs Panel mode, SeName/SKU matching, the HTML guard on and off, the time budget, attribute option merge, and variant combinations.
  2. Panel regression: Admin → Catalog → Categories / Brands / Collections / Products → Import from XLSX with an existing export file. Rows import as before; an unknown Id creates a new entity.

Copilot AI balanced review requested due to automatic review settings October 8, 2026 19:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…row failure isolation

- Name-only rows match by the stored name, not by the slug the name would get, so an entity
  whose slug was renumbered (apple-2) is found; a name shared by several entities is rejected
- Specification and product attributes: a SeName already used by another attribute is rejected
- Products (row API): a Sku already used by another product is rejected
- A row that throws is reported rejected (FailedError) and the batch goes on; the panel's
  spreadsheet import still throws
- Row-created products get the remaining panel defaults (MaxEnteredPrice, downloads, recurring)
- Brand, categories, collections and parent category are read once per row; product attributes
  once per batch
- Shared batch loop, ImportMode and slug lookup moved to ImportRows; dead LowStock line removed
@KrzysztofPajak
KrzysztofPajak merged commit 7ffe001 into develop Oct 10, 2026
6 checks passed
@KrzysztofPajak
KrzysztofPajak deleted the feature/catalog-row-import branch October 10, 2026 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants