# `PhoenixKitCatalogue.Import.Mapper`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/0.1.14/lib/phoenix_kit_catalogue/import/mapper.ex#L1)

Maps file columns to catalogue fields and transforms row data into
validated item attribute maps ready for insertion.

# `column_mapping`

```elixir
@type column_mapping() :: %{
  column_index: non_neg_integer(),
  header: String.t(),
  target: target()
}
```

# `import_plan`

```elixir
@type import_plan() :: %{
  items: [map()],
  categories_to_create: [String.t()],
  manufacturers_to_create: [String.t()],
  suppliers_to_create: [String.t()],
  custom_fields: [String.t()],
  errors: [{non_neg_integer(), String.t()}],
  stats: %{
    total: non_neg_integer(),
    valid: non_neg_integer(),
    invalid: non_neg_integer()
  }
}
```

# `target`

```elixir
@type target() ::
  :name
  | :description
  | :sku
  | :base_price
  | :markup_percentage
  | :unit
  | :category
  | :manufacturer
  | :supplier
  | :skip
  | {:data, String.t()}
```

# `auto_detect_mappings`

```elixir
@spec auto_detect_mappings([String.t()]) :: [column_mapping()]
```

Auto-detects column mappings by matching headers against known patterns.
Uses score-based matching — normalizes headers (lowercase, strip diacritics/whitespace).

# `available_targets`

```elixir
@spec available_targets() :: [{target(), String.t()}]
```

Returns available mapping targets with display labels.

# `build_import_plan`

```elixir
@spec build_import_plan([column_mapping()], [[String.t()]], keyword()) ::
  import_plan()
```

Builds an import plan from column mappings and parsed rows.

Validates all rows, normalizes units and prices, collects errors.
The `unit_map` option allows custom unit value mappings from the UI.

# `detect_existing_duplicates`

```elixir
@spec detect_existing_duplicates(import_plan(), String.t(), keyword()) ::
  non_neg_integer()
```

Checks how many items from the import plan already exist in the catalogue
with identical field values, category, and language.

## Options

  * `:category_uuid` — the target category UUID (nil = uncategorized)
  * `:language` — the import language code (nil = no multilang)

# `detect_file_duplicates`

```elixir
@spec detect_file_duplicates([[String.t()]]) :: non_neg_integer()
```

Checks for duplicate rows within the import file.
Returns the count of rows that are exact duplicates of another row.

# `item_matches_existing?`

```elixir
@spec item_matches_existing?(map(), map(), keyword()) :: boolean()
```

Checks if an import item matches an existing item on all mapped fields,
including category and language.

## Options

  * `:category_uuid` — the target category UUID (nil = uncategorized)
  * `:language` — the import language code (nil = no multilang)

# `normalize_price`

```elixir
@spec normalize_price(String.t()) :: {:ok, Decimal.t()} | :error
```

Normalizes a price string to a Decimal.
Handles comma-as-decimal ("4,88"), currency symbols, whitespace.

# `normalize_unit`

```elixir
@spec normalize_unit(String.t(), map()) :: String.t()
```

Normalizes a unit value using the user-provided unit map and built-in aliases.

# `unique_column_values`

```elixir
@spec unique_column_values([[String.t()]], non_neg_integer()) :: [String.t()]
```

Extracts unique values from a specific column across all rows.
Useful for showing unit mapping UI or category preview.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
