Products and variations

A product is what a customer recognises. A variation is what they actually buy: a size, a colour, a voltage, a length of cable.

Why the split matters

One product page shows one story — photographs, description, documents — and offers several things to buy. Without variations you either publish one page per variant (and repeat the description forty times) or you sell "a cable" without saying which one.

Lives on the product Lives on the variation
Name, description, photographs SKU
Category and tags Price and currency
Datasheets and manuals Stock
Manufacturer Weight and dimensions

Attributes

An attribute is what makes one variation different from another: size, colour, voltage, length. You create it once and then choose its value on each variation.

An attribute is not a special kind of thing the platform keeps in a hidden list. It is two ordinary parts you already know:

  • a register (a vocabulary) holding the possible values — S, M, L;
  • a field on the variation types that use it, pointing at that register.

That is why the values are managed exactly like any other register, on the Values link beside the attribute, and why an attribute's values can be translated, reordered and nested like any other terms.

Where: Products → Attributes. Give it a name, tick the variation types that should carry it, create. Ticking the same attribute on a second type later attaches the same field — you never make "size" twice.

An attribute belongs to the firm that created it

The attribute and its values belong to the organization you have open in the toolbar, and no other firm sees them.

This is not tidiness. An attribute is how a catalogue is browsed, so a shared list of values is two firms' data on one page: your customer sees sizes that belong to somebody else's products, and either firm can edit or delete what the other one sells by.

Two firms may each have an attribute called Size, with entirely different values. The name is not the identity — the owner is.

Because of that, the create form asks for an open organization. With several firms in scope and none open, the form cannot know whose attribute it would be, so it says so instead of guessing — the toolbar is where that question is answered.

Removing an attribute from a variation type is refused while variations of that type carry values for it. Empty it first, or leave it: an attribute with no values on a type costs nothing.

SKU

The SKU is the identifier the rest of the business uses — stock, orders, invoices, the scanner.

One SKU field, one meaning

A SKU is unique across the whole catalogue within an organization, however many types carry one. Two records sharing a SKU means a stock movement that cannot say what it moved. The platform refuses the duplicate rather than accepting it and being wrong later.

The uniqueness is scoped to the organization, like everything else. Two firms may each have a part called A-100; they are two different parts.

Addresses

A variation is addressable but not public: it has a real address a customer can be linked to, but it does not appear in public listings.

That is not a limitation, it is a decision. A listing of variations is exactly what would publish your cost prices and internal codes; a link to one variation is what a salesperson needs to send.

The address usually follows the product: the product's own path plus the variation's SKU.

Prices

A price belongs to a variation, in a currency. The platform is multi-currency throughout: prices, orders, invoices and reports all carry the currency they were made in, and exchange rates are a register you maintain rather than a constant somebody typed.

Three prices, three owners

An article carries up to three numbers, and mixing any two of them prices a shop wrongly in a way that looks entirely plausible.

field what it is who sets it
Manufacturer's list price what the maker's catalogue says an import; never edited by hand
Cost what you pay — the list price less your supplier's discount your purchase terms
Price what you charge you, or a bulk run over a range

Nobody in this trade sells at the list price, so the list is a reference, not an offer. It is internal, like the cost, unless you choose to show it beside your own price.

Never below cost, never below the minimum margin

Web shop → Minimum margin over the purchase price (%).

Two rules, and they hold wherever a price is set — this screen, a tool, an import, a bulk run:

  • Nothing is sold below its purchase price. Always, with no setting to relax it.
  • The margin floor is yours to choose. 20 means a €10 article cannot be priced under €12. Zero means no floor beyond the purchase price itself.

The refusal carries the numbers — what the purchase price is, what margin the attempted price would leave, and what the lowest allowed price would be — because "invalid price" tells nobody what to type instead.

Note

The floor is measured against the cost, never against the manufacturer's list price. That distinction is the whole point: comparing with the list would refuse healthy sales and permit sales below cost, while looking strict.

An article with no purchase price recorded is not blocked. Most of a large catalogue has none, and a guard that refused every one of those saves would be switched off within a day — and then it would guard nothing at all.

Is zero a price you mean?

A variation at 0.00 is ambiguous, and the ambiguity is expensive. It can mean this is free — a trial tier delivered through the customer's account — or nobody has priced this yet, which is exactly what a freshly imported catalogue looks like. Nothing in the number itself tells the two apart.

So the shop says which it means: Web shop → "A price of zero is a real price".

  • On (the default): a 0.00 article can be bought, and a zero-total order is placed with no payment method, because there is nothing to collect. This is what a shop with a free tier needs.
  • Off: a 0.00 article is treated as not priced yet. It cannot be put in a basket, and it says so.

Warning

Switch it off before importing a catalogue that arrives without prices. A manufacturer's import typically writes the currency and leaves the amount at zero — with the switch on, every one of those articles is orderable for nothing, and the shop looks like it is working right up to the first order.

The refusal happens when the article is added, not at the checkout. An article that can be added and then blocks the whole basket is the most confusing failure a shop can have: the customer is told their order is wrong without being told which line.

The same rule decides three things at once — whether the Buy button works, whether a line in the basket counts, and whether the checkout will go through — because they read one answer. A price the shop cannot read at all (a currency the shop does not sell in) has always counted as no price, and still does.

Stock

Stock is per variation and per location. See Inventory.

Categories

A category is where a customer walks down to find a thing. It is an ordinary taxonomy term, so it translates, reorders and nests like any other — and the catalogue browses it at addresses such as /products/enclosures/wall-mounted/.

English first, translations after

The name of a category is English. Every other language is a translation of it. That is not a preference about language: a catalogue mixes suppliers and importers, and the one thing they all share is that the base name is written once and translated everywhere else. A tree half in one language and half in another shows a visitor both halves at the same time — the translated name where somebody translated it, the base name where nobody did.

So a branch is created as Enclosures and distribution boards and carries Skrine a rozvádzače as its Slovak translation, not the other way round.

Note

If a language is not switched on for the firm, the translation is quietly skipped and the tree is still created. The tree is the point; the translation is what gets added to it.

Build the tree whole, before the stock arrives

A real catalogue grows into its shape. You can create every branch you intend to sell — including the ones you do not stock yet — and let them appear as they fill up.

Taxonomy → the vocabulary → "Hide empty categories".

With it on, a category holding nothing is not shown anywhere: not in the tree, not in a filter, not in the sitemap. It exists, it can be edited, and it starts appearing the moment something published lands under it.

A branch counts what is UNDER it

A branch such as Enclosures usually holds no article of its own — its children do. The count is therefore taken over the whole sub-tree. Counting only what hangs directly off a category would hide every branch and leave a tree of leaves nobody can walk down to.

The switch is off by default, and off means exactly what the platform always did. A dictionary of attribute values wants the opposite behaviour, which is why this is decided per vocabulary rather than globally.

Filing articles into the tree

Deciding where 47 000 imported articles belong is an estimate, and estimates get corrected. Two signals are used, in this order:

  1. A classification the manufacturer supplied — an ETIM class, or the kind of device recorded by the import. It says what the thing is, whatever the description happens to say.
  2. The description, for everything else.

Warning

A description is evidence of which branch an article belongs to. It is never the source of the branch's name — manufacturers' descriptions are free text, translated from the original, and reading category names out of them produces a tree an electrician does not recognise.

An article that cannot be placed is left unplaced. There is no "other" category: a bin for whatever nobody classified looks to a customer like a promise the shop does not keep. Unplaced articles stay reachable by search and by order code, and the filing report counts them so the gap is visible.

Publishing the catalogue

A product appears publicly when it is published and its type is public. A category page is a listing with a path filter on the category term, usually including children.

Only complete articles go public

A wholesaler or manufacturer catalogue brings tens of thousands of articles, and most of them arrive without a price we can offer or without the papers an electrician asks for. The publication rule (one per firm) decides which articles of the governed product types may be public. An article is complete when it has all of the requirements the firm chose:

Requirement Met when
offer a supplier price that is active and offered, on one of its variations
parameters a value in at least one parameter field (a term field of its type — category and manufacturer are not parameters)
datasheet a product document of one of the rule's datasheet types
certificate a product document of one of the rule's certificate types

The rule has three states:

  • Off — changes nothing, and gives back every article the rule ever changed.
  • Report — only counts what is missing, per requirement, per category and per manufacturer. The exclusions below still apply.
  • Enforce — publishes complete articles and unpublishes the incomplete ones.

Enforcing only switches published on or off. It never deletes, and it remembers what each article was before, so switching the rule off restores the catalogue as it was. An unpublished article stays in the catalogue: plan32 finds it by order code for projects that already use it.

Two lists keep articles unpublished in every state but off, whatever their completeness and whatever a feed says:

  • Excluded manufacturers — for example Berker and Elcom, which fabrika does not sell. The name is compared without regard to case.
  • Excluded categories — category names (or term ids). Every article filed in such a category, or in any category below it, stays unpublished.

Note

Why both lists? A manufacturer list cannot say "no luminaires": luminaires come from a dozen brands, and a wholesaler also sells its own unbranded ones. The category says what the thing is, so Luminaires in the excluded categories covers all of them — branded or not — including categories added under it later. A name matches every category of that name, so prefer a name that is unique in the tree.

Warning

An excluded category only catches articles that are filed in it. An article the filing could not place has no category yet; it stays unpublished because imports create everything unpublished, but the rule cannot tell it is a luminaire. Check the filing report's unfiled count before switching the rule to enforce.

Claudia uses publication_rule_get, publication_rule_set (state, requirements, excluded_manufacturers, excluded_categories, document types), publication_rule_report and publication_rule_apply (dry run by default).

An article's own datasheet and certificate

A manufacturer's BMEcat catalogue lists, for every article, the files that belong to it (MIME_INFO): pictures, the datasheet, the declaration of conformity, certificates, manuals. The catalogue import reads these and makes a product document for each one that is a PDF and that is one of the following:

In the catalogue Becomes the document type
purpose data_sheet, or a description such as Datenblatt, Data sheet, Scheda tecnica the first datasheet type of the firm's publication rule (Data sheet)
Declaration of conformity, Konformitätserklärung, DoC, CE, UKCA the first certificate type (Conformity)
Certificate, Zertifikat, Zulassung the second certificate type (Certificates)

Everything else is left out. Pictures are never documents, and a safety data sheet is not a datasheet even though it says Datenblatt: it describes a hazardous substance, not the product's ratings. The types come from the publication rule's own lists, so the import and the rule always agree on what counts.

Each document names its articles in two ways: the Products field (doc_products) refers to every article it serves, and Source id (doc_source_id) carries the first article's order code. One PDF that covers a whole series (a typical declaration of conformity) is one document that names all of its articles, not one copy per article. Running the import again adds new articles to an existing document and creates nothing twice.

Warning

A document someone made by hand is never changed by the import. Imported documents are marked Imported from: bmecat (doc_origin), and only those are updated. If a hand-made document already carries the same address, the import leaves it alone and does not create a second one.

The document keeps the manufacturer's address in Document link (doc_url) rather than a copy of the PDF. A catalogue of 50 000 articles would otherwise put tens of thousands of files on the server's disk, and the manufacturer updates its datasheets at the same address. Downloading is an option of the import (BMECAT_DOWNLOAD=1), which also fills File.

On the product page the article's own documents are listed first, in a panel of their own; the documents shared by the whole category follow.

Wholesaler catalogue: Schrack

fabrika.digital carries the whole Schrack Technik Slovakia catalogue (about 39 000 articles; Palo 2026-10-20: „Naimportuj ho celý, svietidlá vypni"). Schrack publishes it as its own XML (productcatalog.xml, not BMEcat): a tree of groups, and per product a Slovak name, prices, EAN, about 1 300 kinds of technical values ("facets") and links to photos and PDFs. The import is tools/import/schrack_catalog.py, run on the server in phases; every phase can run dry first and every phase can run again without creating anything twice.

Phase What it does
articles one product and one variation per Schrack number, unpublished
documents product documents from the catalogue's PDF links
offers Schrack's prices as the supplier's offer (Purchasing)
refresh rewrites only the values the import owns on articles it made earlier

What an article gets.

  • Order code — the Schrack number without its trailing dashes (IL091125-- → IL091125). That is the number plan32's Schrack library uses, so a drawing pairs with the article.
  • Manufacturer and the Manufacturer term. Schrack writes lighting brands with a list number (03-SLV); the number is dropped (SLV, Schrack Trinity). An article without a manufacturer becomes Schrack only when the supplier's own article number is the Schrack number; otherwise the manufacturer stays empty — an unknown maker is better than a guessed one.
  • Description — Schrack's Slovak name. The article's language is set to Slovak, because that is what the text is; an English translation can be added as a translation later. The same text is kept as the Manufacturer original text.
  • Technical data — every technical value of the catalogue as a table, in Schrack's Slovak wording (promotion codes, price groups and CAD placeholders are left out).
  • Parameters — the values electricians filter by, as terms of the firm's own vocabularies, so they become filters on the category pages:
Parameter Field Example terms
Protection (IP) protection_ref IP20, IP65, IP2XC
Rated current rated_current_ref 16 A, 63 A
Number of poles poles_ref 1, 1+N, 3, 3+N
Tripping characteristic tripping_characteristic_ref B, C, D, gG
Breaking capacity breaking_capacity_ref 6 kA, 10 kA
Rated residual current residual_current_ref 30 mA, 300 mA
RCD type rcd_type_ref A, AC, B
Cross-section cross_section_ref 1.5 mm², 2.5 mm²
Number of cores cores_ref 3, 5
Number of rows installation_rows_ref 4, 6
Protection class protection_class_ref I, II, III
Design product_design_ref CUBIC, CLASSIC
Colour colour_ref White, Light grey, RAL 7035

A value Schrack writes in a form the import does not recognise (a range, a colour word it does not know) is not turned into a filter; it stays in the technical data. - Device kind (kind, hidden on the web) — the Schrack group the product sits in, as schrack:<group code> (for example schrack:01-01-03-01).

Filing. Schrack's group says what a product is, so filing does not guess from the Slovak name. Each category term lists the Schrack groups that belong to it in Filing: device kinds (filing_kind), and the device kind wins over every word rule. The groups were assigned to the existing categories once (about 2 450 groups onto about 100 categories); a group added by Schrack later stays unfiled until someone adds it to a category's list — it is never dropped into a catch-all. A few Schrack ranges have no category in the tree yet and stay unfiled on purpose: product catalogues (printed matter), power factor correction, arc fault detection devices (AFDD) and fire protection, active network devices, and CCTV cameras.

Not a selling price. Schrack's ListPrice is a list price, and no purchase terms are signed. It is therefore loaded into Purchasing as the supplier's offer of the partner Schrack Technik (an XML price-list connection with the mapping @ID, Properties/EAN, Properties/Text, Properties/Prices/ListPrice, Properties/PriceUnit as the pack — cables are priced per 100 m —, Properties/DeliveryTime, Properties/MinimumOrderQuantity). The variation's own price is 0.00, which the shop shows as On request. Setting real selling prices is a decision for when the terms are known.

Documents. Schrack's media type says what a file is, so nothing is guessed: datenblaetter → Data sheet, zertifikate → Certificates, dop and dop_meinhart (Declaration of Performance under the Construction Products Regulation, for cables) → Conformity. Energy-label product fiches, mounting instructions, catalogue pages and CAD files are not imported as documents. Imported records are marked Imported from: schrack, so they never mix with the BMEcat imports' documents. Files stay at Schrack's address.

Luminaires stay off. Everything is imported unpublished. The luminaires — Schrack's whole Osvetlenie group and the emergency luminaires — are also kept off for good, two ways at once: their brands (SLV, Eglo, One Light, Autled, Schrack Trinity, 3F Filippi, Radium, …) are in the publication rule's excluded manufacturers, and Luminaires and Light sources are its excluded categories, which also covers Schrack's own unbranded lights.

Photos are not downloaded: about 71 000 pictures at about 0.5 MB each would need some 35 GB.

Note

An EAN that an article of the firm already carries is not imported a second time — the supplier offer attaches to the existing article by EAN instead.

What the web shows of a variation

A variation carries the firm's bookkeeping next to the offer: the SKU, the invoice and bill policies, tracking, the cost and costing method, the VAT rate, the ordering weight and a payment gateway's price id. None of it is a shopper's business, so a variation type's display rows for these fields are born with visibility never — the public web and the app never carry them, in the rendered rows or in the raw payload — while the back office keeps showing every row. The price, the unit and the shipping weight stay public. The firm decides otherwise per field on Manage display (a row's visibility setting); an explicit rule, either way, always wins, and an installation older than the rule took it once without touching rules already set.

The product page draws a variation in the web view mode of its type. A firm that wants the page to show only the offer — the price, its note, a one-line description — shapes that mode on Manage display (a row's visibility) and every other place keeps reading the default rows: a pricing view, the back office. A mode without a rule of its own inherits the default row's rule, so a price kept for signed-in customers stays so in every mode until that mode says otherwise.

Next

Etichete
commerceproducts