PhoenixKitCatalogue.Import.Pro100TemplatePlan (PhoenixKitCatalogue v0.19.1)

Copy Markdown View Source

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.

Summary

Functions

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.

Types

catalogue()

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

category()

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

item()

@type item() :: map()

rule()

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

t()

@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()
}

Functions

build(tables, opts \\ [])

problems(catalogues)

@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.