ARONLIGHT — PRICING MECHANISM

Where pricing sits in the quote pipeline

Pricing is not the first thing that happens to a request — it's step 3. Everything below assumes the line items are already matched to real SKUs before pricing ever starts.

1 · Line itemsParsed from the email/RFQ — raw text, quantities
2 · SKU matchingLine items matched to real Aronlight product codes (separate system)
3 · PRICINGOnce we have (client, SKU, quantity) for every line — this document
4 · Quote draftAssembled in Odoo for review
Pricing needs all three pieces of step 3's input before it can do anything: which client (Step 0/1 below), which SKU (from step 2, upstream), and what quantity (also from step 2 — volume tiers depend on it). Get any one of these wrong before pricing starts, and pricing will confidently compute the wrong number without any way to catch it itself.

How Aronlight's price lists are actually structured

Confirmed directly by Nuno, screen-share walkthrough, 2026-07-13 (docs/calls/2026-07-13-nuno-pricing.md). This is the data structure — the algorithm below is how the agent should read it.

↔ this entire stack almost always exists TWICE — once under Aronlight PT, once under Aronlight España — same names, same prices (LIQUIDO-1). Verified: 40 of 43 unique pricelists confirmed as an exact pair. 3 known exceptions: 2 ES-only custom deals, 1 PT-only list.
Entry point — this is where "Lista de Preços" actually lives
Client record (default) or the order itself (override)
Odoo fields property_product_pricelist (client) / pricelist_id (order). This field is not a layer in the stack below — it's the pointer that picks ONE specific list out of everything in the row below, for this one quote.
↓ picks ONE of 43 real named pricelists — back office can choose any of them manually ↓
Confirmed via live Odoo query (LIQUIDO-1): 43 unique pricelists exist (86 records = ×2, one per company). Real names include "Lista PT 48%," "Lista PT 52% + Liquidos," "Lista España 60% + Netos (Jan 2026)," "Lista PT Cliente Contera," and dozens more. The 4 boxes below are the patterns those 43 lists follow — not a count of the choices. Back office can manually assign any of the 43 to a specific order, not just pick a "kind."
Pattern · Base / full price
e.g. "Lista PT"
A client with no negotiated discount can be assigned straight to a market's base list — 0% off, full price. Confirmed real: Nuno describes "Lista PT" itself as "the standard price list," a normal assignable record, not something internal-only.
Pattern · Pure discount
e.g. "52%," "53%," "45%"...
One rule: flat % off a named base list. Many real lists follow this exact pattern, each a different negotiated rate. Simple enough to manage in the Odoo dashboard directly — no Excel needed.
Pattern · Mixed
e.g. "52% mais líquidos"
Per-product net (líquido) price where defined, + fallback % off the base list if no net price is set for that product. (LIQUIDO-2)
Pattern · Volume tier
e.g. "España 60% + Netos"
Same as Mixed, but multiple net prices per product, gated by minimum quantity. Odoo picks the highest tier ≤ the requested quantity — it does NOT check tiers decrease monotonically. (LIQUIDO-3)
Two things multiply this further, confirmed real: customer-specific lists (e.g. "Lunisk Cop": 6 products with special net prices + standard 50% fallback) reuse the Mixed pattern scoped to one client. Visible vs. non-visible (LIQUIDO-6) — any discount-bearing list can exist as two separate assignable records computing the same price, shown differently — see below.

This enumeration may not be exhaustive. LIQUIDO-4 (sales-team ad hoc exceptions) is still unconfirmed — there may be pricing paths outside all of the above that nobody has described to us yet.
↓ discount/tier lists reference one of these as their base ↓
Base list — Portugal
Also directly assignable (see above)
Structurally unique — no separate list of fixed prices at all. Reads sales_price straight off the product record, 0% discount. Can't even be exported the same way as the other three.
Base list — Spain
Also directly assignable (see above)
Explicit fixed-price line per product — 2,959 lines, independently maintained.
Base list — France
Also directly assignable (see above)
Explicit fixed-price line per product, same pattern as Spain.
Base list — Others (Italy +)
Also directly assignable (see above)
Explicit fixed-price line per product, same pattern as Spain.
These 4 aren't a separate, hidden layer — they're the SAME "base / full price" boxes shown above, just repeated here to show what a discount/tier list's base_pricelist_id actually points to. ES / FR / Others each carry a catch-all rule: price = €0 if the product isn't listed here. Deliberate — blocks Odoo's native behavior of silently falling back to the Portuguese price on a non-PT quote.

Visible vs. non-visible — same math, different display

Visible — "Lista PT 48%"
List price: €59.17
Discount: 48%
Final: €30.77
=
Non-visible — "…(sem desconto visível)"
List price: hidden
Discount: hidden
Final: €30.77
Mechanism: the non-visible variant is a formula rule referencing the visible list as its base, with 0% additional adjustment — designed to compute the identical price, just displayed without the discount column. Confirmed real: back office manually points a specific sale order at either variant (Carla's Dinolux order used the non-visible one, while the client's stored default is the visible one) — a genuine order-level override, independent of the client record. Still unknown: the criteria for when back office makes this switch. (LIQUIDO-6)

How our system should work

A quote line goes in as (client, SKU, quantity) — SKU and quantity already resolved by matching, upstream. This is the path our agent takes to come out with a final price, framed the way we'd actually build it: a pre-pricing rule, then pricing itself.

✅ confirmed ready to build now 🔧 needs build rule known, no data model yet ❓ open genuinely unresolved, needs a person
0 Before pricing starts — which country/entity does this belong to? ✅ SPAIN-1
This isn't a pricing rule — it's a gate that has to be settled first, because LIQUIDO-1 means the entire pricelist stack (all 43 lists) exists once per company. Get this wrong and Step 1 looks up the right list in the wrong company's copy.
  • Portugal (the common case) → straightforward, default company, nothing special to decide.
  • Spain → NOT automatically "use the Spain company." Depends on what this actually is: a proposal (no sale yet) lives in Odoo Spain — could even end up living entirely outside of Spain's own numbers, structurally, since it's Aronlight España as its own supplier. An actual sale (dropship) lives in Odoo Portugal instead, with Aronlight España as the customer field and the real client only in the delivery address.
  • Everything else (Italy, France, etc.) → default company (Portugal) today — no separate entity exists yet for these markets.
Confirmed directly by Adriana Fernandes, 2026-07-13 ("exactamente").
1 Pricing starts here — look up the customer's pricelist ✅ LIQUIDO-1
Since we're the ones creating this proposal, there's no existing order to inspect yet — the client's own record is the actual starting point, not a fallback. Odoo field property_product_pricelist. This is the single most load-bearing lookup in the whole flow: nothing downstream has anything to search until this resolves, and every price computed after it inherits whatever mistake happens here.
Three ways this goes wrong, each needing different handling:
  • Blank (new client, never assigned) — don't guess or default silently. Flag for manual assignment.
  • Wrong (data entry error, deal changed, nobody updated the record) — the agent has no way to catch this on its own; it will compute a confident, wrong price and never know it's wrong.
Separate, later concern — not the agent's starting assumption: back office can manually switch the pricelist on an already-existing order (Carla's Dinolux case, LIQUIDO-6). That's a human editing a quote that already exists. When WE create a new proposal, we assign the client's own pricelist onto it — we don't start from an override that doesn't exist yet.
2 Now act on what kind of list it is ✅ LIQUIDO-2 / LIQUIDO-3
Knowing WHICH list isn't enough — we need to know HOW that list prices this specific SKU. Every list resolves to one of the 4 patterns from the structure above:
  • Base / full price → use the list price as-is, 0% discount.
  • Pure discount → base list price × (1 − discount %).
  • Mixed / líquidothis is where it gets specific: search the list's own rules for a product-specific fixed (líquido) price for this exact SKU. Found one? Use it, done. Not found? Fall back to that list's own standard % discount.
  • Volume tier → same líquido search as Mixed, but check every min_quantity tier for this SKU and pick the highest one ≤ the requested quantity. No tier matches? Same % fallback.
Verified directly in Odoo: one real pricelist has 66 of 232 products with confirmed multi-tier líquido breaks (identical on both PT and ES company copies), and exactly 231 product-specific fixed rules + 1 global % fallback rule — this is Odoo's own rule-matching order, not custom logic we'd have to build.
3 Second discount, market-specific 🔧 DISC-1
  • Spain → ad hoc, salesperson-entered. Never computed — just preserved.
  • Italy → fixed, per client. No storage exists yet (aronlight#398).
  • Portugal / France → unknown. No evidence either way.
4 Known gaps — flag, don't silently trust ❓ LIQUIDO-4 / LIQUIDO-5
  • Sales-team ad hoc exceptions — trigger unknown, route to a human
  • "Caixa fechada" (box quantity) — the data exists (product.packaging, 215+ products), but it's not confirmed to affect price on the one product we checked
  • Pricelist rules may get hand-tuned over time — Carla's case shows a live example
  • Validate volume-tier monotonicity ourselves — Odoo only matches minimum quantity, it does NOT check that a higher tier is actually cheaper than a lower one or the fallback. Confirmed as a real risk by Nuno unprompted, on the 2026-07-13 call. If a tier price is higher than what a smaller order would pay, flag it — don't just apply whatever Odoo returns.
5 Display — show the discount, or hide it? 🔧 needs UI
Per-user default + always-available per-case override. The "sem desconto visível" pattern found this week suggests this may already be half-built at the Odoo level — the platform may just need to surface it, not invent it.

Rules confirmed so far

IDRuleConfidenceEvidence
SPAIN-1Proposal (Odoo Spain) vs. sale (Odoo Portugal) routingHIGHConfirmed by Adriana directly
LIQUIDO-1Every pricelist duplicated across PT + ES companies, same nameHIGHWhatsApp + Odoo data (40/43 pairs confirmed)
LIQUIDO-2Líquido price always wins over computed discountHIGHWhatsApp + Odoo data (231 fixed + 1 global rule)
LIQUIDO-3Volume tiers = líquido prices gated by min_quantityHIGHES + Italy in Odoo; PT confirmed manual-only (Excel)
LIQUIDO-6Back office manually switches pricelist on the sale orderMEDIUMConfirmed live on Carla's Dinolux case with Nuno
OD-4Tabela vs. Odoo source-of-truth splitHIGH (code)Read directly in the matching code
DISC-1Second-discount semantics are market-specificMEDIUMSpain + Italy confirmed; PT/France open
LIQUIDO-4Ad hoc price-table exceptions — not yet codifiableLOWExplicitly "não sei" from Nuno — needs sales team
LIQUIDO-5"Caixa fechada" — data location confirmed, pricing link notMEDIUM (location) / LOW (link)Odoo data + independently confirmed uncertain by Nuno too

Answered on the 2026-07-13 Nuno call

Still open

Nuno named José Monteiro directly for these 2, on the 2026-07-13 call:

  1. What are the switching criteria for when back office moves a customer between the visible and non-visible pricelist on a specific order?
  2. Volume-tier + "cache complète" Excel — same table for both? Which countries does each apply to? Is cache-complète ever applied automatically in Odoo?

Carried over from earlier — likely also need José Monteiro or the sales team, but weren't raised with Nuno by name in this call:

  1. What are the known ad hoc pricing exceptions (LIQUIDO-4)? Nuno pointed to the sales team generally for this, in the earlier WhatsApp thread — still the single biggest gap in what's buildable today.
  2. Why is "ITALIA 50+20%" assigned to zero customers? The combined pricelist exists and works — just needs assignment, unless something's blocking it.
  3. The Spain +25% markup (some ES clients see prices above PT list) — legitimate separate base pricing, or a data error nobody's caught?
  4. Portugal / France second-discount behavior — do either follow Spain's ad hoc pattern, Italy's fixed pattern, or something else entirely?