# `PhoenixKitCatalogue.Import.Pro100TemplatePlan`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/v0.19.1/lib/phoenix_kit_catalogue/import/pro100_template_plan.ex#L1)

Turns a parsed PRO100 estimate template into the catalogue structure to create.

Pure — no database. The plan it returns is what the applier writes and what a
dry run prints, so every judgement the load makes is inspectable before a row
is touched.

## Three judgements this module makes, and why they are not obvious

**Sub-headings.** The file has a second heading level it does not mark: a row
with no price whose name introduces the priced rows after it (`-BLANCO`
followed by twelve Blanco sorters; `HINGED` followed by fifteen hinges).
Detection is `price == 0 AND (name starts with "-" OR name is all-caps)` — but
**only among zero-price rows**. The same shape test over all 555 lines is
useless: 68 dash-prefixed and 20 all-caps rows carry real prices
(`-CARGO LISARIIULI KINNITUS,HALL = 19.10`). Confirmed against the real file
and by the catalogue owner.

**The one exception.** `-ARENA-CLASSIC RIIUL 40CM,VA/KR` matches the shape but
is a product whose price is missing — its 30 cm sibling is priced at 56.02 in
the same section. It is listed explicitly rather than handled by a cleverer
rule, because every rule tried so far had counter-examples in both directions.
The plan reports what it treated as a heading; a new export must be re-checked.

**Prices carried in the name.** Two rows state their price in text and zero in
the price column: `Kordusmõõtmine ( 60eu)`, `Prügivedu(85,00 EUR)`. Both are
gross, like every other price in the file.

Prices are gross throughout; `@vat_divisor` converts them. It is a named
constant with the date it was valid because Estonia's rate has moved before.

# `catalogue`

```elixir
@type catalogue() :: %{
  name: String.t(),
  kind: String.t(),
  template_guid: String.t(),
  position: integer(),
  categories: [category()],
  items: [item()],
  rules: [rule()]
}
```

# `category`

```elixir
@type category() :: %{name: String.t(), parent: String.t() | nil, position: integer()}
```

# `item`

```elixir
@type item() :: map()
```

# `rule`

```elixir
@type rule() :: %{
  item_name: String.t(),
  referenced_catalogue: String.t(),
  value: Decimal.t(),
  unit: String.t()
}
```

# `t`

```elixir
@type t() :: %{
  folder: %{name: String.t()},
  catalogues: [catalogue()],
  headings: [
    %{catalogue: String.t(), section: String.t() | nil, name: String.t()}
  ],
  price_from_name: [%{name: String.t(), gross: Decimal.t()}],
  problems: [map()],
  stats: map()
}
```

# `build`

```elixir
@spec build(
  [PhoenixKitCatalogue.Import.Pro100TemplateParser.table()],
  keyword()
) :: t()
```

# `problems`

```elixir
@spec problems([catalogue()]) :: [map()]
```

Everything an applier must refuse to write. Empty means the plan is safe to
apply; anything here is a defect in the export or in this module, not a row to
skip quietly.

---

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