HOCO B2B
SRSRequirements

Pricing

Three selling prices per variant chosen by the reseller's customer type, purchase and recommended prices with their history, VAT and totals, re-validation at cart and checkout, and the V2 per-reseller agreements.

This area defines what each reseller pays (cahier §4.3, §4.7, §5.1, §5.3, §8, reference screen 5, and the pricing decision of 17 Oct 2026). Every product variant carries three selling prices, one per customer type — Super-gros, Demi-gros, Détail — plus its VAT rate, a recommended retail price and the restricted purchase price. The reseller's customer type (AUTH-11) selects one of the three; promotions apply on top (PRM-04); guests see no price (AUTH-10). Segment price lists and quantity tiers are not part of V1: per-reseller agreements move to V2 (PRC-03) and tiers are dropped (D-37). The server's pricing module owns the resolver and PriceChange; the cart module applies it (API › Cart & checkout); prices are edited in the product editor (API › Catalog & pricing).

IDRequirementReleasePrioritySourceApps
PRC-01Three selling prices per variant — super-gros, demi-gros, détail — plus purchase price and recommended retail price, with change historyV1MUST§4.3, §8API · BO
PRC-02Price resolution: the reseller's customer type selects one of the three prices; then promotions; guests see no priceV1MUSTTeam decision (17 Oct 2026), §4.3, §8API
PRC-03Per-reseller price agreements and level-based prices on top of the three customer-type pricesV2SHOULD§8API · BO
PRC-04Taxes: VAT rate per product, HT/VAT/TTC totals on documentsV1MUST§4.7, reference screen 5API
PRC-05Prices and stock re-validated at cart and at checkout; the reseller only ever sees their own priceV1MUST§5.1, §5.3API · RA · RW

Settings used in this area

SettingDefaultMeaning
pricing.defaultVatRate1900VAT rate (basis points) proposed for a new variant.
pricing.priceDisplayTTCWhich value the apps show large, TTC or HT (D-17).
catalog.pricesVisibleBeforeValidationtruePending accounts see their declared type's prices (D-18).

Requirements

PRC-01 — Three selling prices, purchase and recommended prices, with history

ReleasePrioritySourceAppsOwner tasks
V1MUSTcahier §4.3, §8API · BOSRV-12 · DSH-04

Statement — The system shall store, for every product variant, three selling prices HT — super-gros, demi-gros and détail — together with its VAT rate, the recommended retail price and the purchase price, and keep the history of every change.

Rules

  1. Price fields of ProductVariant, all integer centimes: priceSuperGros, priceDemiGros, priceDetail (HT, required, greater than 0), vatRate (basis points), recommendedPrice (prix conseillé public, TTC, optional), purchasePrice (prix d'achat, HT, optional, restricted).
  2. The three selling prices must be ordered priceSuperGros ≤ priceDemiGros ≤ priceDetail (422 PRICE_ORDER_INVALID). A recommended price lower than the Détail TTC price is accepted with a warning in the editor.
  3. Writing any price field needs pricing.update (on top of catalog.update); reading purchasePrice, margins and the history needs pricing.view (ACL-04). The editor (BO-28) shows the three prices in HT and TTC and, with pricing.view, the margin of each type in % ((price − purchase) ÷ price).
  4. Every change writes one PriceChange row per field (PRICE_SUPER_GROS, PRICE_DEMI_GROS, PRICE_DETAIL, RECOMMENDED_PRICE, PURCHASE_PRICE, VAT_RATE) with the old and new values, the actor, the source (MANUAL, IMPORT) and an optional reason. Rows are append-only and kept for archived variants (admin.variants.priceHistory).
  5. A change applies to the next pricing: carts are re-priced at their next read (line issue PRICE_CHANGED), catalog reads show the new price at once, orders already submitted keep their snapshot prices, and an order waiting for confirmation shows the difference in its price control as information.
  6. Prices are entered one variant at a time in BO-28 or in bulk through the product import (CAT-06); effective-dated (scheduled) price changes are not in V1.

Acceptance criteria

  • Saving 32 000 / 35 000 / 39 000 HT for super-gros / demi-gros / détail succeeds; 35 000 / 32 000 / 39 000 returns 422 PRICE_ORDER_INVALID.
  • A stock manager saving priceDetail gets 403 PERMISSION_DENIED; a finance manager succeeds and three PriceChange rows appear when the three prices change.
  • admin.variants.priceHistory answers 403 PERMISSION_DENIED to a user without pricing.view.
  • After the Demi-gros price changes, a demi-gros reseller's cart line shows PRICE_CHANGED with the old and new prices; an order submitted before keeps the old price.

Data — ProductVariant (price fields), PriceChange, AuditLog.

Interfaces

Notes / open points — Who sets the prices, whether HOCO's grid is HT or TTC, and whether price changes need scheduling are D-01.

PRC-02 — Price resolution by customer type, then promotions

ReleasePrioritySourceAppsOwner tasks
V1MUSTTeam decision (17 Oct 2026), §4.3, §8APISRV-12 · DSH-04

Statement — The system shall give every signed-in reseller exactly one unit price per variant — the price of their customer type — then apply the eligible promotion, and give guests no price.

Rules

  1. Resolution, for caller C, variant V and quantity q at time t:
    1. C is a guest (or a pending account while catalog.pricesVisibleBeforeValidation is false) → price: null, priceLocked: true. Stop.
    2. Customer type: the reseller's customerType (for a delegate, that of the reseller given in resellerId). A reseller without a type cannot be priced: catalog prices are null and the cart cannot be used until the back-office sets one.
    3. Base unit price HT U = priceSuperGros, priceDemiGros or priceDetail of V. No quantity tier, no price list in V1.
    4. Promotion: among the promotions active at t, targeted at C and covering V (PRM-02), the stacking rules (PRM-03) select the one to apply; its unit price HT P = round(U × (10 000 − bps) ÷ 10 000) for PERCENT_OFF, U − amount for AMOUNT_OFF, min(U, fixed) for FIXED_PRICE; never below 0. Without a promotion P = U.
    5. Unit TTC = round(P × (10 000 + vatRate) ÷ 10 000), rounded half-up to the centime.
    6. Line: subtotalHt = U × q; discountHt = (U − P) × q; totalHt = P × q; totalTtc = unit TTC × q; vat = totalTtc − totalHt — so the lines add up to the total to the centime.
  2. The resolver is one function in the pricing module used by the catalog, the cart, checkout, assisted orders, back-office orders on behalf and the promotion preview; no other code computes a price.
  3. The other two prices, the purchase price and any other reseller's price never leave the server in a reseller or delegate response (PRC-05).
  4. A change of customer type (AUTH-11) or of a variant price applies to the next resolution; submitted orders keep their snapshot.

Worked example — variant HOCO X14 câble USB-C 1 m: priceSuperGros 32 000, priceDemiGros 35 000, priceDetail 39 000 (HT, centimes), vatRate 1900; a promotion -10 % câbles (PERCENT_OFF 1000) targets the reseller.

CallerBase U (HT)Promotion P (HT)Unit TTCqsubtotalHtdiscountHttotalHttotalTtcvat
Guest——null (lock)——————
Super-gros reseller, no promotion32 00032 00038 08024768 0000768 000913 920145 920
Demi-gros reseller with the promotion35 00031 50037 48524840 00084 000756 000899 640143 640
Détail reseller, no promotion39 00039 00046 41024936 0000936 0001 113 840177 840

Rounding case: a Détail price of 41 990 HT gives a unit TTC of round(49 968.1) = 49 968; for 3 units totalHt = 125 970, totalTtc = 149 904 and vat = 23 934 (the exact 19 % would be 23 934.3 — the difference absorbs the rounding).

Acceptance criteria

  • With the example data, a Demi-gros reseller's cart line of 24 cables shows unit TTC 37 485 and line total 899 640 TTC, and the same reseller's catalog card shows 41 650 with the promotion price 37 485.
  • The same product for a guest returns price: null and priceLocked: true.
  • A delegate preparing an assisted order for a Détail reseller sees 46 410 TTC per unit.
  • After the back-office changes the reseller from Demi-gros to Super-gros, the next cart.get prices the line at 38 080 TTC per unit with PRICE_CHANGED.
  • No reseller or delegate response contains priceSuperGros, priceDemiGros, priceDetail or purchasePrice (contract test on every reseller and delegate schema).

Data — ProductVariant, Reseller (customerType), Promotion, PromotionScope, PromotionAudience, CartItem, OrderItem (snapshots).

Interfaces

Notes / open points — BO-31 is the three-price grid of the catalog (the three prices of every variant side by side, edited with pricing.update). admin.priceLists.* and admin.variants.tiers are V2 operations — not mounted in V1 (tiers dropped, price lists reserved for PRC-03, D-37).

PRC-03 — Per-reseller agreements and level-based prices (V2)

ReleasePrioritySourceAppsOwner tasks
V2SHOULDcahier §8API · BOSRV-30

Statement — The system should, in V2, let the back-office give a reseller negotiated prices and give loyalty levels their own prices, on top of the three customer-type prices.

Rules

  1. A PriceList of kind AGREEMENT belongs to one reseller, has validity dates and PriceListItem rows (variant → unit price HT); a kind LEVEL list belongs to a loyalty level (LOY-03).
  2. Resolution in V2 inserts them before the customer-type price: agreement item valid at t → level price → customer-type price, then promotions as in PRC-02. At most one active agreement per reseller at a time.
  3. Agreement and level prices are maintained in BO-31 through admin.priceLists.* (pricing.*), historised as PriceChange rows of field PRICE_LIST_ITEM; carts of affected resellers are re-priced at their next read.
  4. Whether quantity tiers come back with agreements is decided with D-37.

Acceptance criteria

  • With an agreement price of 30 000 HT on the X14 cable for reseller REV-000123, that reseller pays 30 000 HT while other Demi-gros resellers pay 35 000.
  • When the agreement expires, the reseller's next cart read shows the customer-type price with PRICE_CHANGED.

Data — V2 PriceList, PriceListItem; PriceChange.

Interfaces

  • Screens: none dedicated
  • API: no dedicated operation

Notes / open points — V2 (SRV-30). Nothing of it is mounted in V1.

PRC-04 — VAT and HT/VAT/TTC totals

ReleasePrioritySourceAppsOwner tasks
V1MUSTcahier §4.7, reference screen 5APISRV-12

Statement — The system shall apply a VAT rate per variant and compute HT, VAT and TTC amounts identically in the cart, orders, invoices and credit notes.

Rules

  1. vatRate is set per variant in basis points: 1900 (19 %, default pricing.defaultVatRate), 900 (9 %) or 0; another value returns 400 VALIDATION_FAILED.
  2. Amounts follow PRC-02 R1: unit TTC rounded half-up once per unit; line TTC = unit TTC × quantity; line VAT = line TTC − line HT. Totals are sums of lines plus the delivery fee: totalTtc = subtotalHt − discountTotal + deliveryFee + vatTotal, the fee being HT with VAT at 19 % added to vatTotal (DLV-06).
  3. Documents (invoice, credit note) show per line the unit price HT, quantity, discount, VAT rate and totals, and at the bottom HT, the VAT per rate and TTC, plus the amount in words (« Arrêtée la présente facture à la somme de … ») (FIN-01).
  4. The apps show TTC prices large and HT in the breakdown (pricing.priceDisplay = TTC); the cart summary (Sous-total, Remises, Frais de livraison, Total TTC, reference screen 5) uses the same figures as the order.
  5. Order and invoice lines store the VAT rate and amounts at submission; a later rate change never alters an existing document.
  6. Stamp duty on cash payments (droit de timbre) is not computed until HOCO's accountant confirms the rule (D-21); when confirmed it is a separate line on cash-on-delivery invoices.

Acceptance criteria

  • A variant saved with vatRate 1500 returns 400 VALIDATION_FAILED.
  • For the Demi-gros example of PRC-02, the invoice shows HT 7 560,00 DA, TVA 19 % 1 436,40 DA, TTC 8 996,40 DA, exactly the cart and order figures.
  • An order mixing 19 % and 9 % products shows two VAT lines on its invoice, one per rate, whose sum equals vatTotal.
  • Changing a variant's VAT rate after an order leaves that order's lines and invoice unchanged.

Data — ProductVariant (vatRate), CartItem, OrderItem, Order (totals), Invoice, InvoiceLine, CreditNote.

Interfaces

  • Screens: none dedicated
  • API: no dedicated operation

PRC-05 — Re-validation at cart and checkout; each reseller sees only their own price

ReleasePrioritySourceAppsOwner tasks
V1MUSTcahier §5.1, §5.3API · RA · RWSRV-12 · SRV-15 · MRA-02

Statement — The system shall re-validate prices and availability on every cart read and write and again at checkout, never charge a total the reseller has not seen, and show each reseller only the price of their own customer type.

Rules

  1. Every cart.get and cart mutation recomputes each line with PRC-02, the reseller's current visibility, quantity rules and availability, and returns issues: PRICE_CHANGED and PROMOTION_ENDED (warnings), PRODUCT_UNAVAILABLE, STOCK_INSUFFICIENT, QUANTITY_NOT_ALLOWED, ORDER_MIN_AMOUNT_NOT_MET (blocking) (ORD-02). A line stores the unit price it was added at to detect PRICE_CHANGED.
  2. cart.checkout carries expectedTotalTtc, the total the reseller saw. The server re-validates everything inside the checkout transaction; when its total differs, nothing is created and it answers 409 CART_CHANGED with the fresh cart; the reseller confirms again with a new Idempotency-Key (ORD-03).
  3. Order lines snapshot the unit price, promotion and totals at submission; later price changes do not touch them.
  4. A reseller or delegate response contains the caller's price only — never the two other customer-type prices, the purchase price, margins or another reseller's price. Priced responses are Cache-Control: private, no-store and are never cached across callers; guest responses carry no price.
  5. Availability is re-checked the same way and is only ever IN_STOCK / OUT_OF_STOCK (STK-07).

Acceptance criteria

  • A promotion that ends while the reseller looks at the cart makes the next cart.get show PROMOTION_ENDED and the normal price.
  • Checkout with an expectedTotalTtc 1 centime off returns 409 CART_CHANGED with the new cart and creates no order.
  • A Détail reseller's catalog, cart and order responses contain no Super-gros or Demi-gros price (checked on the JSON).
  • Two resellers of different types reading the same product page get their own prices, never each other's (no shared cache entry).

Data — Cart, CartItem (unit price snapshot), Order, OrderItem, ProductVariant, Reseller (customerType).

Interfaces

Error codes

CodeHTTPRaised when
PRICE_ORDER_INVALID422Selling prices not ordered super-gros ≤ demi-gros ≤ détail
PERMISSION_DENIED403Writing a price without pricing.update, reading purchase prices or history without pricing.view
CART_CHANGED409The re-validated total differs from the total the reseller saw
Issue codes (cart)—PRICE_CHANGED, PROMOTION_ENDED (warning); PRODUCT_UNAVAILABLE, STOCK_INSUFFICIENT, QUANTITY_NOT_ALLOWED, ORDER_MIN_AMOUNT_NOT_MET (blocking)
Field codes in 400 VALIDATION_FAILED400Invalid VAT rate, non-positive price

On this page