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 box from the row directly below, for this one quote.↓ picks exactly one of these three ↓
Assignable · Pure discount
e.g. "52%"
One rule: flat % off a named base list below. Simple enough to manage in the Odoo dashboard directly — no Excel needed.
Assignable · 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)Assignable · 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)Customer-specific price lists (e.g. "Lunisk Cop": 6 products with special net prices + standard 50% fallback) are also assignable here — same pattern, just scoped to one client instead of a market. "Visible" and "non-visible" (
LIQUIDO-6) are two assignable variants of the same discount, not a separate layer — see below.↓ each of these references, as its base ↓
Base list — Portugal
Not directly assignable
Structurally unique — no separate list 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
Not directly assignable
Explicit fixed-price line per product — 2,959 lines, independently maintained.
Base list — France
Not directly assignable
Explicit fixed-price line per product, same pattern as Spain.
Base list — Others (Italy +)
Not directly assignable
Explicit fixed-price line per product, same pattern as Spain.
No client or order is ever assigned directly to one of these 4 — they only exist to be referenced by the assignable lists above. 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 the agent should read this structure
A quote line goes in as (client, SKU, quantity). This is the path it takes to come out as a final price.
✅ confirmed ready to build now
🔧 needs build rule known, no data model yet
❓ open genuinely unresolved, needs a person
0
Routing — which Odoo company owns this order?
✅ SPAIN-1
- Proposal only, no sale yet → Odoo Spain
- Actual sale (dropship) → Odoo Portugal (Aronlight España as customer, real client only in delivery address)
- Not Spain-related → default company (Portugal)
↓
1
Resolve "Lista de Preços" — the single most load-bearing field
✅ LIQUIDO-1 / LIQUIDO-6
Nothing in Step 2/3 has anything to search until this is resolved. Every price computed downstream inherits whatever mistake happens here — this is not a small error, it's a total miss.
- On the client record — Odoo field
property_product_pricelist. This is the default. It's also literally what back office reads first when investigating a quote (Carla, on a real case: "vejo: Lista PT 48%..."). - On the order/proposal itself — Odoo field
pricelist_id. Read this FIRST. Confirmed real (Carla's Dinolux order): this can differ from the client's default when back office manually switches it while building a specific quote. Only fall back to the client's default if the order doesn't have its own value yet.
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.
- Overridden (a one-off order-level switch — see "Visible vs. non-visible" above) — real and confirmed. Read the order's own field before falling back to the client default.
↓
2
Look for a líquido (fixed) price
✅ LIQUIDO-2 / LIQUIDO-3
Search the resolved pricelist for a product-specific fixed-price rule.
- 2+ quantity tiers on this product? → pick the highest tier ≤ requested quantity
- Found a líquido price? → use it, skip step 3 entirely
↓
3
Fallback — standard % discount
✅ LIQUIDO-2
Only reached if step 2 found nothing. Uses the pricelist's single global rule. Verified: one pricelist has exactly 231 fixed-price rules + 1 global percentage rule — this is Odoo's own rule-matching order, not custom logic we'd have to build.
↓
4
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.
↓
5
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.
↓
6
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
| ID | Rule | Confidence | Evidence |
|---|---|---|---|
SPAIN-1 | Proposal (Odoo Spain) vs. sale (Odoo Portugal) routing | HIGH | Confirmed by Adriana directly |
LIQUIDO-1 | Every pricelist duplicated across PT + ES companies, same name | HIGH | WhatsApp + Odoo data (40/43 pairs confirmed) |
LIQUIDO-2 | Líquido price always wins over computed discount | HIGH | WhatsApp + Odoo data (231 fixed + 1 global rule) |
LIQUIDO-3 | Volume tiers = líquido prices gated by min_quantity | HIGH | ES + Italy in Odoo; PT confirmed manual-only (Excel) |
LIQUIDO-6 | Back office manually switches pricelist on the sale order | MEDIUM | Confirmed live on Carla's Dinolux case with Nuno |
OD-4 | Tabela vs. Odoo source-of-truth split | HIGH (code) | Read directly in the matching code |
DISC-1 | Second-discount semantics are market-specific | MEDIUM | Spain + Italy confirmed; PT/France open |
LIQUIDO-4 | Ad hoc price-table exceptions — not yet codifiable | LOW | Explicitly "não sei" from Nuno — needs sales team |
LIQUIDO-5 | "Caixa fechada" — data location confirmed, pricing link not | MEDIUM (location) / LOW (link) | Odoo data + independently confirmed uncertain by Nuno too |
Answered on the 2026-07-13 Nuno call
- "Sem desconto visível" is deliberate, standard infrastructure — not ad hoc. It's a real Odoo mechanism (formula rule referencing the visible list, 0% adjustment), and Nuno independently suggested exposing both variants in the AI quoting tool.
- Portugal has zero volume tiers in Odoo — confirmed, 100% manual via an Excel owned by Manuel/José Monteiro.
- Back office does manually switch pricelists per order — confirmed live on Carla's own case. Criteria for when: still unknown.
- New risk surfaced, unprompted by Nuno: Odoo's volume-tier matching doesn't validate that tier prices decrease monotonically with quantity — a real data-entry risk our pricing engine needs to guard against itself.
Still open
Nuno named José Monteiro directly for these 2, on the 2026-07-13 call:
- What are the switching criteria for when back office moves a customer between the visible and non-visible pricelist on a specific order?
- 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:
- 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. - Why is "ITALIA 50+20%" assigned to zero customers? The combined pricelist exists and works — just needs assignment, unless something's blocking it.
- The Spain +25% markup (some ES clients see prices above PT list) — legitimate separate base pricing, or a data error nobody's caught?
- Portugal / France second-discount behavior — do either follow Spain's ad hoc pattern, Italy's fixed pattern, or something else entirely?