Catalog and stock
Page specs BO-27 to BO-37 and BO-71: products, the product editor with the three customer-type prices and attributes, brands, categories, the price grid, the import wizard, stock pages and attribute definitions.
The catalog pages manage the HOCO, ACEFAST and BOROFONE ranges (cahier §4.3). The stock pages control availability. Three binding decisions shape them:
- Three selling prices per variant, one per customer type (§1b.4 in AUTHORING; PRC-01):
priceSuperGros(Super-gros),priceDemiGros(Demi-gros) andpriceDetail(Détail), in HT centimes;- plus
vatRate, the recommended retail pricerecommendedPrice, and the restrictedpurchasePrice; - the reseller's customer type selects the price, then promotions apply (PRC-02). V1 has no price lists and no quantity tiers. Per-reseller agreements are V2 (PRC-03).
- Per-category attributes (power, connector, cable length, colour, capacity, Bluetooth version…) are defined in BO-71 and valued on every variant. They feed the reseller filters (CAT-12, CAT-13).
- Stock quantities are visible only here. Resellers and guests see En stock or En rupture, never a number (STK-07). Cancellations and return receptions put quantities back automatically (STK-03).
| ID | Screen | Section | Template | Release | Design | Build |
|---|---|---|---|---|---|---|
BO-27 | Products list | Catalog | template | V1 | Day 17 | DSH-04 |
BO-28 | Product editor | Catalog | bespoke | V1 | Day 17 | DSH-04 |
BO-29 | Brands | Catalog | template | V1 | Day 17 | DSH-04 |
BO-30 | Categories | Catalog | template | V1 | Day 17 | DSH-04 |
BO-31 | Price grid — three customer-type prices (bulk edit) | Catalog | template | V1 | Day 17 | DSH-04 |
BO-32 | Product import wizard | Catalog | bespoke | V1 | Day 17 | DSH-04 |
BO-71 | Product attributes (filters) per category | Catalog | template | V1 | Day 17 | DSH-04 |
| ID | Screen | Section | Template | Release | Design | Build |
|---|---|---|---|---|---|---|
BO-33 | Stock overview | Stock | template | V1 | Day 17 | DSH-05 |
BO-34 | Stock movements journal | Stock | template | V1 | Day 17 | DSH-05 |
BO-35 | Stock receipt / adjustment | Stock | template | V1 | Day 17 | DSH-05 |
BO-36 | Inventory sessions | Stock | template | V1 | Day 17 | DSH-05 |
BO-37 | Warehouses | Stock | template | V1 | Day 17 | DSH-05 |
BO-27 — Products list
| Field | Value |
|---|---|
| Template | List |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-04 |
| Route | /catalog/products |
| Requirements | CAT-03 · CAT-04 · CAT-13 · ACL-04 |
| Permissions required | catalog.view · catalog.create (« Nouveau produit ») · catalog.update (status bulk actions) · catalog.export · catalog.administer (« Importer ») · pricing.view (purchase price and margin columns) · stock.view (stock columns) |
Purpose. Browse and maintain the catalog: find a product by name, reference or barcode, see its status, prices, stock and attribute completeness, and change statuses in bulk.
Who uses it. Stock manager (owner of the catalog), super admin. Commercial admin, finance and direction read it.
Entry points. Catalogue › Produits, global search, and the best-seller and stock cards of the dashboards.
Layout.
- Header: « Produits » with the count. Actions: « Nouveau produit » (BO-28), « Importer » (BO-32), « Exporter ».
- Tabs:
- « Tous ».
- « Actifs » (
ACTIVE). - « Brouillons » (
DRAFT). - « Masqués » (
HIDDEN). - « En rupture » (
OUT_OF_STOCK). - « Archivés » (
ARCHIVED). - « À compléter »: products with a missing required attribute value, image or price.
| Column | Source field | Sortable | Default visible |
|---|---|---|---|
| Image | mainImage.thumbnailUrl | No | Yes |
| Produit | name | Yes | Yes |
| Référence | reference (mono) | Yes | Yes |
| Marque | brand.name | Yes | Yes |
| Catégorie | category.path (« Câbles › USB-C ») | Yes | Yes |
| Variantes | variantsCount | Yes | Yes |
| Prix Détail HT | priceDetail of the default variant (range « 245,00 – 290,00 DA » when variants differ) | Yes | Yes |
| Prix Demi-gros HT / Prix Super-gros HT | priceDemiGros / priceSuperGros (same display) | Yes | No |
| Prix d'achat HT | purchasePrice (needs pricing.view) | Yes | No |
| Marge Détail | (priceDetail − purchasePrice) ÷ priceDetail (needs pricing.view) | Yes | No |
| Stock disponible | Σ available over variants and warehouses (needs stock.view) | Yes | Yes |
| Vu par les revendeurs | IN_STOCK / OUT_OF_STOCK (StockAvailability pill) | No | Yes |
| Statut | status (ProductStatus) | Yes | Yes |
| Attributs | Completeness « 4/5 » (required attribute values filled on every active variant) | Yes | Yes |
| Visibilité | « Tous » or « Restreinte » (rules exist) | No | No |
| Promo active | Badge with the promotion name | No | No |
| Mis à jour | updatedAt | Yes | No |
- Filters:
q: name, reference, SKU or barcode.brand,category(tree; includes sub-categories),status.stock=in/low/out(needsstock.view).incomplete=attributes/images/prices.visibility=restricted,promo=true,updatedFrom/updatedTo.
- Default sort:
updatedAtdescending. - Bulk actions: « Publier », « Masquer », « Archiver » (
admin.products.setStatus, item by item with the progress report), « Changer de catégorie », « Exporter la sélection ». - Row actions: « Modifier », « Dupliquer », « Voir le stock » (BO-33 filtered), « Historique des prix » (needs
pricing.view).
Data. admin.products.list, admin.products.setStatus, admin.products.update, admin.products.export.
Actions.
- « Publier »: « Publier 12 produits ? Ils deviennent visibles et commandables par les revendeurs autorisés. ». Products failing the publish checks (see BO-28) are skipped and listed with their reason.
- « Archiver »: « Archiver 3 produits ? Ils restent visibles dans les commandes passées. ».
- Each status change is audited
STATUS_CHANGE.
Rules.
OUT_OF_STOCKis set automatically when every variant's available stock is 0, and returns toACTIVEon restock. It cannot be chosen by hand (CAT-04).- Without
pricing.view, the purchase-price and margin columns are absent from the column menu and from the API response (ACL-04). - Without
stock.view, only the Vu par les revendeurs column is shown, never quantities.
Real-time. stock.alert refreshes the stock columns of visible rows.
States. First use: « Le catalogue est vide », with « Nouveau produit » and « Importer un fichier ».
Acceptance criteria.
- Searching a barcode returns the product of the matching variant.
- The « À compléter » tab lists exactly the products whose active variants miss a required attribute value, an image or one of the three prices.
- A commercial admin's response contains no
purchasePrice. - Bulk « Publier » on 10 products, two of them without an image, publishes 8 and reports the 2 with « Image principale manquante ».
BO-28 — Product editor
| Field | Value |
|---|---|
| Template | Bespoke (Form skeleton with sections and a variants grid) |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-04 |
| Route | /catalog/products/new · /catalog/products/[productId] (?section=) |
| Requirements | CAT-03 · CAT-05 · PRC-01 · CAT-13 · CAT-04 · PRC-04 · STK-07 · ACL-04 |
| Permissions required | catalog.view (read-only editor) · catalog.create (new) · catalog.update (edit, variants, the three selling prices, attribute values, visibility, status) · pricing.view (see purchase price, margins, price history) · pricing.update (edit the purchase price) · stock.view (stock section) · stock.update (alert thresholds) |
Purpose. Create and maintain one product with everything resellers and the back-office need (cahier §4.3, §15.4):
- identity and description, technical characteristics and images;
- variants with SKU, barcode and attribute values;
- the three selling prices, VAT, recommended and purchase price, and quantity limits;
- visibility by segment or reseller;
- stock and alert thresholds, and the status.
Who uses it. Stock manager, super admin. Others read it.
Entry points. BO-27 (row, « Nouveau produit », « Dupliquer »), the global search, and the import report (« Ouvrir le produit »).
Layout
- Header:
- Main image, name, reference, brand, the
ProductStatuspill and the « Vu par les revendeurs » pill (En stock or En rupture). - Primary action by status:
- « Publier » (
DRAFT/HIDDEN→ACTIVE); - « Masquer » (
ACTIVE→HIDDEN).
- « Publier » (
- « Plus » menu: « Archiver », « Dupliquer », « Historique ».
- Main image, name, reference, brand, the
- Left anchor navigation: Général, Caractéristiques, Images, Variantes et prix, Attributs, Visibilité, Stock, Historique.
- Sticky save bar, as in the Form template.
Général.
| Field | Type | Validation | Default | Help text / permission |
|---|---|---|---|---|
| Marque | Select (HOCO, ACEFAST, BOROFONE) | Required | — | — |
| Catégorie | Tree select (category ▸ sub-category) | Required; must be a leaf | — | Determines the attributes to fill. Changing it asks to remap attribute values. |
| Nom | Text | Required, 3–120 characters | — | Shown to resellers |
| Référence | Text, uppercase | Required, unique (409), 2–40 characters A–Z 0–9 - _ | — | « Référence fabricant, ex. X14 » |
| Description | Rich text (bold, lists, links only) | 5 000 characters max | — | — |
| Garantie | Number of months | 0–36 | 6 | Assumption — default to confirm |
| Taux de TVA | Select: 0 %, 9 %, 19 % | Required | 19 % (vatRate 1900) | Applies to every variant (PRC-04) |
| Nouveauté jusqu'au | Date | Optional | Creation + 30 days | Drives the « new arrivals » filter |
Caractéristiques. A free key/value list for the technical sheet: label, 60 characters max; value, 200 characters max; drag to reorder; 40 rows max. Attribute values (see Attributs) are added to the sheet automatically, so they are not repeated here.
Images.
- Up to 10 images (
PRODUCT_IMAGE: public, 5 MB max, JPEG/PNG/WebP). Recommended square format, 1 200 px or more. - Drag to reorder. The first image is the main one.
- Optional per-variant image (e.g. the colour).
- Uploads run through
files.createUpload→PUT→files.completeUpload. Thumbnails are generated by the server.
Variants and prices
A grid with one row per variant, inline editing, and « Ajouter une variante ». Each column below is a variant field:
| Field | Type | Validation | Default | Permission / help text |
|---|---|---|---|---|
| Nom de la variante | Text | Required when there is more than one variant, 60 characters max | Built from the variant attributes (« Noir · 1 m ») | — |
| SKU | Text | Required, unique across the catalog | <référence>-<n> | — |
| Code-barres | Text | Optional; EAN-13 or UPC-A with a valid check digit; unique | — | Scanner input accepted |
| Prix Super-gros HT | Money (DA) | Required, above 0 | — | catalog.update. TTC is shown below in grey. |
| Prix Demi-gros HT | Money (DA) | Required, above 0 | — | catalog.update |
| Prix Détail HT | Money (DA) | Required, above 0 | — | catalog.update |
| Prix conseillé TTC | Money (DA) | Optional; must be at least the Détail TTC (warning only) | — | The recommended retail price shown to resellers (CAT-08) |
| Prix d'achat HT | Money (DA) | Optional, above 0 | — | Rendered only with pricing.view; editable only with pricing.update (ACL-04) |
| Marges | Read-only: % per type, (price − purchase) ÷ price | — | — | Only with pricing.view. Danger tone when negative. |
| Quantité minimum | Integer | 1 or more | 1 | Per order line (ORD-02) |
| Multiple de | Integer | 1 or more; the minimum must be a multiple | 1 | « Vendu par lots de… » |
| Maximum par commande | Integer | Optional; at least the minimum | — | — |
| Active | Toggle | At least one active variant to publish | On | — |
Price rules:
- Order of the three prices:
priceSuperGros≤priceDemiGros≤priceDetail. A violation blocks saving with « Le prix Super-gros doit être inférieur ou égal au prix Demi-gros, lui-même inférieur ou égal au prix Détail. » Assumption — the strict ordering is to confirm with HOCO. - Display: prices are entered HT. The grid shows the TTC value under each price (
price × (1 + vatRate), rounded to the centime). - Who pays which price: a reseller pays the price of their customer type, then promotions (PRC-02). Guests see no price (AUTH-10).
- History: every change of a selling or purchase price writes a
PriceChange(before, after, user, time). « Historique des prix » (needspricing.view) opens a drawer per variant with a line chart of the three prices and the change table (admin.variants.priceHistory). - Effect on orders: a price change affects carts re-priced after the change. Submitted orders keep their prices.
- No price lists, no tiers in V1: no quantity-tier editor and no price-list column. Per-reseller agreements are V2 (PRC-03).
Attributes
- Grid: one row per variant, one column per attribute definition of the product's category, including those inherited from the parent category (BO-71).
- Inputs by attribute type:
- Number: numeric input with its unit (« 20 W », « 1 m », « 10 000 mAh »), checked against the min and max.
- Single-choice list: a select of the allowed values (Connecteur: USB-C, Lightning, Micro-USB).
- Multiple-choice list: chips (Ports : USB-A, USB-C).
- Yes / no: a toggle (Charge rapide).
- Required attributes are marked « * ». An empty required value blocks publication, and the cell shows « À renseigner ».
- « Appliquer à toutes les variantes » copies a value across the column.
- Values are saved with the variants through
admin.variants.crudasVariantAttributeValuerows. - They feed the reseller filters and facets (
catalog.facets) and the product sheet.
Visibilité (CAT-05):
- Mode: « Visible par tous les revendeurs » (default), « Visible uniquement pour… » (segments and/or resellers), or « Masqué pour… » (segments and/or resellers).
- Search pickers select the segments and resellers. The rule is saved with
admin.products.visibility. - Guests see products that are visible to all.
Stock (needs stock.view; back-office only):
- Read-only table per variant × warehouse: Physique, Réservé, Disponible, Seuil d'alerte (editable with
stock.updatethroughadmin.stock.thresholds), Vu par les revendeurs. - Links: « Mouvements » (BO-34 filtered) and « Nouvelle réception » (BO-35).
- The hint « Les revendeurs voient uniquement En stock / En rupture » sits above the table.
Historique. Audit entries of the product (create, update with changed fields, status changes, visibility changes) and price changes.
Complementary products and new-arrival flag
Change request 03 (NOT-15, NOT-11, DSH-14).
- Section « Produits complémentaires »: a product search picker that adds up to 5 products (no self-reference, no duplicate), ordered by drag-and-drop (
ProductComplement.position). Example: Coque iPhone 15 → Verre trempé iPhone 15. The first complement that is in stock and visible to the reseller is the one suggested the day after an order. - Checkbox « Annoncer comme nouveauté » (on by default) in Publication: at first publication the product enters the next
smart.newArrivalsDigestrun. It has no effect oncepublishedAtis set.
Actions
| Action | Endpoint | Confirmation (FR) | Optimistic | Audit | Notifies |
|---|---|---|---|---|---|
| « Créer le produit » | admin.products.create (draft + default variant) | — | No | CREATE | — |
| « Enregistrer » | admin.products.update + admin.variants.crud for changed variants (with version) | When a selling price changes on an ACTIVE product: « 3 prix modifiés. Ils s'appliqueront aux prochains paniers. » | No | UPDATE + PriceChange rows | — |
| « Publier » | admin.products.setStatus (ACTIVE) | « Publier ce produit ? Il devient visible dans les applications. » | No | STATUS_CHANGE | — |
| « Masquer » | admin.products.setStatus (HIDDEN) | « Masquer ce produit ? Il disparaît du catalogue ; les paniers qui le contiennent afficheront un avertissement. » | No | STATUS_CHANGE | — |
| « Archiver » | admin.products.setStatus (ARCHIVED) | « Archiver ce produit ? Il reste visible dans les commandes passées et ne peut plus être commandé. » | No | STATUS_CHANGE | — |
| Delete a variant | admin.variants.crud (DELETE) | « Supprimer la variante Noir · 1 m ? » A variant referenced by orders is deactivated instead. | No | DELETE / UPDATE | — |
| Save the visibility rules | admin.products.visibility | — | No | UPDATE | — |
| Set alert thresholds | admin.stock.thresholds | — | Yes | UPDATE | — |
Rules
- Publish checks, all required:
- a brand and a leaf category;
- at least one image;
- at least one active variant;
- the three prices set (above 0) on every active variant;
- every required attribute filled on every active variant (CAT-13). Failures are listed in a dialog with links to the fields.
- Statuses (CAT-04):
DRAFT,ACTIVE,HIDDENandARCHIVEDare manual.OUT_OF_STOCKis automatic. An archived product can be duplicated, not re-published. Assumption — confirm. - Purchase price: never sent to users without
pricing.view. A write withoutpricing.updateis refused (403 PERMISSION_DENIED,details.field = "purchasePrice"). - Category change: values of attributes that do not exist in the new category are dropped after confirmation (« 2 attributs ne s'appliquent pas à la catégorie Écouteurs et seront supprimés »).
- Concurrency: the product carries
version. A409 VERSION_CONFLICTshows the reload dialog.
Real-time. stock.alert for a variant of this product refreshes the Stock section.
States.
- Read-only mode for users without
catalog.update: every field is disabled and the save bar is hidden. - Uploading images: per-image progress.
- Draft: banner « Brouillon — invisible pour les revendeurs ».
- Archived: read-only banner.
Acceptance criteria
- A product cannot be published while a required attribute or one of the three prices is missing on an active variant. The dialog lists each missing item.
- Saving Super-gros 230,00 DA, Demi-gros 245,00 DA and Détail 290,00 DA HT records three
PriceChangerows. A Demi-gros reseller's next cart shows 245,00 DA HT (291,55 DA TTC at 19 %), while a submitted order keeps its old price. - A commercial admin opening the editor sees the three selling prices, but no purchase price, no margin and no « Historique des prix ».
- Attribute values entered here appear as filters with counts in the reseller catalog (
catalog.facets) after publication. - The Stock section shows physical, reserved and available quantities, while the reseller app shows only En stock or En rupture for the same variant.
BO-29 — Brands
| Field | Value |
|---|---|
| Template | List (with drawer) |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-04 |
| Route | /catalog/brands |
| Requirements | CAT-01 |
| Permissions required | catalog.view · catalog.create · catalog.update · catalog.delete |
Purpose. Manage the brands as data: HOCO, ACEFAST, BOROFONE are seeded (cahier §4.3).
Who uses it. Stock manager.
Entry points. Catalogue › Marques.
Layout.
- Table: logo, Nom, Produits (active / total), Ordre, Actif.
- Drawer:
- Nom: required, unique.
- Logo:
BRAND_LOGO, public, SVG/PNG. - Description: 300 characters max.
- Ordre d'affichage.
- Actif.
Data. admin.brands.crud.
Actions. Create and edit (audited). « Supprimer » archives the brand when products reference it: « Cette marque est utilisée par 214 produits : elle sera archivée, pas supprimée. »
Rules. An archived brand disappears from reseller filters, and its products stay unchanged.
Real-time. None.
States. The three seeded brands are always present.
Acceptance criteria.
- Deleting a referenced brand archives it.
- Changing the display order changes the brand order in the reseller filters.
BO-30 — Categories
| Field | Value |
|---|---|
| Template | List (tree variant, with drawer) |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-04 |
| Route | /catalog/categories |
| Requirements | CAT-02 · CAT-12 |
| Permissions required | catalog.view · catalog.create · catalog.update (edit, reorder) · catalog.delete |
Purpose. Maintain the category tree of categories and sub-categories (chargers, cables, earphones, power banks, car holders, audio, watches, accessories; cahier §4.3). It drives catalog navigation, the category and sub-category filters, and which attributes apply.
Who uses it. Stock manager.
Entry points. Catalogue › Catégories.
Layout.
- Tree: drag to reorder and to re-parent, two levels. Each node shows the name, active products count, attribute definitions count (link « Attributs » → BO-71 for that category), and an active toggle.
- Drawer:
- Nom: required, unique among siblings.
- Catégorie parente: none for a top-level category.
- Image / icône: public.
- Ordre.
- Actif.
Data. admin.categories.crud, admin.categories.reorder.
Actions.
- Reorder is optimistic (rolled back on error) and audited
UPDATE. - Re-parenting a category with products asks: « Déplacer Câbles USB-C sous Accessoires ? Les attributs hérités de Câbles ne s'appliqueront plus à ses 48 produits. »
- Delete is refused while products or sub-categories exist: move them first.
Rules.
- Depth: two levels, category and sub-category. Assumption — confirm.
- Products sit on leaf categories only.
- Inactive categories: an inactive category hides its products from navigation and filters, but not from search by reference.
Real-time. None.
States. The seeded top-level categories are listed on first use.
Acceptance criteria.
- Moving a sub-category updates the reseller category filters on the next catalog request.
- A category with products cannot be deleted.
BO-31 — Price lists & agreements
| Field | Value |
|---|---|
| Template | List (editable grid) |
| Release | V1 (three-price grid). Per-reseller agreements are V2. |
| Design day | Day 17 — DES-17 |
| Build task | DSH-04 |
| Route | /catalog/prices |
| Requirements | PRC-02 · PRC-01 · PRC-03 |
| Permissions required | catalog.view (page) · catalog.update (edit the three selling prices) · pricing.view (purchase price, margins, history) · pricing.update (purchase price) |
Purpose. In V1, segment price lists are replaced by the three customer-type prices (§1b.4). This page is the price grid: review and change the Super-gros, Demi-gros and Détail prices of many variants at once, check margins, and follow price history. Per-reseller agreements will be added here in V2 (PRC-03).
Who uses it. Stock manager (price updates after a supplier change), finance manager (margins), super admin.
Entry points. Catalogue › Tarifs, and « Historique des prix » from BO-27 and BO-28.
Layout.
- A banner: « Les accords tarifaires par revendeur arrivent en V2. En V1, chaque revendeur paie le prix de son type de client. »
- A grid with one row per active variant (product name, SKU, brand, category) and inline-editable price cells:
| Column | Source field | Editable | Default visible |
|---|---|---|---|
| Produit / variante | product.name, variant.name, sku | No | Yes |
| Marque, Catégorie | — | No | Yes |
| Prix d'achat HT | purchasePrice | With pricing.update (only rendered with pricing.view) | Yes, when permitted |
| Super-gros HT | priceSuperGros | With catalog.update | Yes |
| Demi-gros HT | priceDemiGros | With catalog.update | Yes |
| Détail HT | priceDetail | With catalog.update | Yes |
| TVA | vatRate | No (edited on the product) | Yes |
| Prix conseillé TTC | recommendedPrice | With catalog.update | No |
| Marge Super-gros / Demi-gros / Détail | Computed | No (pricing.view) | No |
| Dernière modification | Last PriceChange date and user | No | Yes |
- Edited cells are highlighted. The save bar shows « 46 prix modifiés sur 18 variantes · Annuler · Enregistrer ».
- Bulk tools on the selection:
- « Variation » (+/− % or amount on one or more of the three types);
- « Recalculer à partir du Détail » (Demi-gros = Détail − x %, Super-gros = Détail − y %);
- « Arrondir » (to the nearest 1, 5 or 10 DA). Each tool shows a before/after preview of the first rows.
- Filters:
q,brand,category,status,missing=price,marginBelow(needspricing.view),changedSince. - History drawer per row (needs
pricing.view): chart and table ofPriceChange. - « Exporter » / « Importer les prix » link to BO-32 in « Prix uniquement » mode.
Data.
admin.variants.prices: one row per variant, with the filters above and the last price change.admin.variants.bulkPrices: the whole grid in one call, each variant with itsversion; the failed variants come back infailed.admin.variants.priceHistory.
Actions. « Enregistrer » asks for confirmation: « Enregistrer 46 prix sur 18 variantes ? Ils s'appliqueront aux prochains paniers ; les commandes passées ne changent pas. ». The save is not optimistic. Each variant writes UPDATE and its PriceChange rows.
Rules.
- Same price rules as BO-28: Super-gros ≤ Demi-gros ≤ Détail, all above 0. Violating cells are flagged before saving.
- Rounding happens in centimes on the server. The preview shows the exact values.
- Without
catalog.update, the grid is read-only.
Real-time. None.
States.
- No variant with a missing price: « Tous les prix sont renseignés » under the
missingfilter. - A save that fails part-way lists the variants that failed. The others stay saved.
Acceptance criteria.
- A +5 % variation on Détail for brand ACEFAST, rounded to 5 DA, previews and then saves exactly the previewed values.
- After saving, each changed variant has one
PriceChangeper changed price type. - A finance manager sees margins and history but cannot edit selling prices without
catalog.update.
BO-32 — Product import wizard
| Field | Value |
|---|---|
| Template | Bespoke (Form skeleton, four steps) |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-04 |
| Route | /catalog/products/import (?job= to resume) |
| Requirements | CAT-06 · CAT-13 · PRC-01 · ADM-06 |
| Permissions required | catalog.administer · pricing.update (to import purchase prices; otherwise the column is ignored) |
Purpose. Import or update products, variants, the three prices and attribute values from an Excel/CSV file, with a validation report before anything is written (cahier §4.3 « Import et export des produits via Excel/CSV », §13.2 initial import). It is used for the initial catalog load and for price updates. Stock quantities are not imported here: initial stock is the Stock import of BO-67.
Who uses it. Stock manager, super admin (initial load, LCH-01).
Entry points. « Importer » on BO-27, « Importer les prix » on BO-31, and Administration › Imports de données (BO-67, product type).
1 · Modèle
- Type d'import:
- « Produits et variantes » (create and update everything);
- « Prix uniquement » (updates the three prices, the recommended price and, with
pricing.update, the purchase price, matched by SKU).
- Catégorie (optional): pre-builds the attribute columns of that category. Without one, the template holds all attribute keys.
- « Télécharger le modèle » returns an XLSX with three sheets:
- Produits: one row per variant (columns below);
- Référentiel: brands, category paths, allowed values of list attributes. It feeds dropdowns in Produits.
- Aide: the column rules, in French.
- Mode: « Créer uniquement », « Mettre à jour uniquement », « Créer et mettre à jour » (default). The match key is the SKU.
2 · Fichier
- Drop zone for
.xlsxor.csv(UTF-8,;or,separator, detected), 10 MB and 5 000 rows max. Assumption — limits to confirm. The file is uploaded asIMPORT_FILE. - Column mapping: file headers are matched to template columns automatically (case- and accent-insensitive). Unmatched columns are shown with a select « Ignorer / associer à… ».
- A preview of the first 10 rows follows.
3 · Analyse (dry run)
admin.products.importwithdryRun: truecreates anImportJob. The page pollsadmin.imports.getevery 2 s (JobStatusQUEUED→RUNNING→SUCCEEDED/FAILED) and shows progress.- Report:
- counters: Lignes, À créer, À mettre à jour, Inchangées, Erreurs, Avertissements;
- a table of problems: row number, SKU, column, value, code and message (« Ligne 14 — Prix Demi-gros supérieur au prix Détail »);
- filters: errors / warnings;
- « Télécharger les lignes en erreur » (XLSX with an Erreur column, ready to fix and re-upload).
- Price changes summary: number of prices raised or lowered, and the largest change in %. Changes over ±30 % are listed as warnings.
4 · Application
- « Importer » is enabled when Erreurs = 0, or when « Ignorer les lignes en erreur » is ticked.
- Confirmation: « Importer 1 182 lignes (240 créations, 942 mises à jour, 12 lignes ignorées) ? ».
- The apply runs as a second job on the same file (
dryRun: false), with progress. - Result: counts, « Voir les produits importés » (BO-27 filtered on the job), and the job in the import history.
Template columns. Fields of one product are repeated on each of its variant rows.
| Column | Required | Rule |
|---|---|---|
marque | Yes (products) | Exact brand name from Référentiel |
categorie | Yes (products) | Leaf category path « Câbles › USB-C » |
reference, nom | Yes (products) | As in BO-28 |
description, garantie_mois, tva | No | tva ∈ 0, 9, 19 |
sku | Yes | Match key; unique |
code_barres | No | EAN-13 / UPC-A, valid check digit |
variante | When the product has several variants | Variant name |
prix_super_gros_ht, prix_demi_gros_ht, prix_detail_ht | Yes on create | DA with up to two decimals; Super-gros ≤ Demi-gros ≤ Détail |
prix_conseille_ttc | No | DA |
prix_achat_ht | No | Ignored with a warning without pricing.update |
qte_min, multiple, qte_max | No | Integers, as in BO-28 |
attr.<cle> (e.g. attr.puissance_w, attr.connecteur) | Required attributes: yes, for rows to publish | Number, an allowed value, values separated by | for multiple choice, or oui / non |
images | No | Public image URLs separated by |, downloaded by the server. Assumption — confirm the image source with HOCO. |
statut | No | DRAFT (default) or ACTIVE (publish checks apply) |
Rules.
- Dry run first: nothing is written by the analysis step. Apply runs only on an analysed file of the same content (file hash), so a changed file must be re-analysed.
- Idempotent apply: applying the same file twice gives no duplicates; the second run reports everything « Inchangées ».
- Prices: every price change writes a
PriceChangewith the import job as source. - Publication: rows with
statut = ACTIVEthat fail the publish checks are created asDRAFT, with a warning. - Audit: one
IMPORTentry per apply, with the counts and the file id. - Permission gap: product import jobs are started with
catalog.administer, butadmin.imports.getis declared withsettings.administer(contract notes). - Template: the dashboard serves it, generated from the import schema in
packages/contracts. Assumption — a server endpoint per type and category would keep attribute columns exact.
Real-time. None. Progress comes from polling the job.
States.
- Analysis failure (
FAILED): « Analyse impossible : fichier illisible ou feuille Produits absente ». - Recovery: leaving the page keeps the job, and
?job=resumes at the right step. - Expired upload (after 24 h): « Fichier expiré — téléversez-le de nouveau ».
Acceptance criteria.
- A file with a Demi-gros price above the Détail price is reported as an error on the right row and column, and cannot be applied unless that row is ignored.
- Dry run writes nothing: product counts and prices are unchanged until « Importer ».
- Re-applying the same file creates no product and reports every row as unchanged.
- The error-rows download contains exactly the rows in error, with the message column.
- Attribute values imported in
attr.*columns appear in BO-28 and in the reseller filters.
BO-33 — Stock overview
| Field | Value |
|---|---|
| Template | List |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-05 |
| Route | /stock |
| Requirements | STK-01 · STK-02 · STK-05 · STK-07 |
| Permissions required | stock.view · stock.update (thresholds) · stock.export · pricing.view (value column) |
Purpose. Show physical, reserved and available stock per variant, globally or per warehouse, with alerts (cahier §4.3 « Stock global ou par entrepôt, stock réservé et seuil d'alerte »). This is the only place, with BO-28 and BO-07, where quantities are shown.
Who uses it. Stock manager. Delivery manager and commercial admin read it.
Entry points. Stock › État du stock, BO-07 cards, stock.alert toasts, and BO-27 « Voir le stock ».
Layout.
- Toggle: « Global » / « Par entrepôt » (hidden when only one warehouse exists).
| Column | Source field | Sortable | Default visible |
|---|---|---|---|
| Produit / variante | Name, SKU, image | Yes | Yes |
| Marque, Catégorie | — | Yes | Yes |
| Entrepôt | warehouse.name (per-warehouse view) | Yes | Yes |
| Physique | physical | Yes | Yes |
| Réservé | reserved (validated orders not yet shipped) | Yes | Yes |
| Disponible | available = physical − reserved | Yes | Yes |
| Seuil d'alerte | threshold (inline edit with stock.update) | Yes | Yes |
| Alerte | OUT / LOW / OK pill | Yes | Yes |
| Vu par les revendeurs | IN_STOCK / OUT_OF_STOCK | No | Yes |
| Couverture (jours) | available ÷ average daily shipped quantity over 30 days | Yes | No |
| Dernier mouvement | lastMovementAt + type | Yes | No |
| Valeur | physical × purchase price (needs pricing.view) | Yes | No |
- Filters:
q,warehouse,brand,category,alert=OUT/LOW/OK,productStatus. - Default sort:
alert(OUT, then LOW), thenavailableascending. - Header actions: « Réception » / « Ajustement » (BO-35), « Inventaire » (BO-36), « Définir les seuils » (bulk), « Exporter ».
- Row click: quick-view drawer with the variant's last 20 movements and the orders holding its reservations (number, status, quantity).
Data. admin.stock.levels, admin.stock.thresholds, admin.stock.movements (drawer).
Actions.
- Inline threshold edit is optimistic and audited
UPDATE. - « Définir les seuils » on a selection asks « Appliquer un seuil de 20 aux 35 variantes sélectionnées ? ».
- Export produces XLSX or CSV.
Rules.
- Reservation happens at
VALIDATED(the back-office confirmation) and deduction atSHIPPED(default settings, STK-03). - A cancellation releases the reservation (
RESERVATION_RELEASE). A return reception re-enters stock (RETURN). Both are automatic. - Vu par les revendeurs is En stock when available is above 0, else En rupture. No other value and no quantity reaches resellers (STK-07).
- An available stock below 0 is possible only after an authorised override at order confirmation (STK-08). It shows in danger with « Dérogation » and a link to the order.
Real-time. stock.alert updates the matching rows and the alert counters. order.status_changed refreshes the reserved quantities of visible rows (debounced 10 s).
States. « Aucune alerte » under alert=OUT. A variant without a stock level yet shows « 0 » with the hint « Aucun mouvement ».
Acceptance criteria.
- Confirming an order of 5 units raises Réservé by 5 and lowers Disponible by 5. Cancelling it brings both back without any manual action.
- A variant whose available stock reaches 0 shows En rupture in the reseller app at the next catalog request, and appears under
alert=OUThere. - Valeur is absent for users without
pricing.view.
BO-34 — Stock movements journal
| Field | Value |
|---|---|
| Template | List |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-05 |
| Route | /stock/movements |
| Requirements | STK-04 · AUD-02 · STK-03 |
| Permissions required | stock.view · stock.export |
Purpose. The immutable ledger of every stock variation (cahier §4.3 « Les variations de stock doivent être journalisées »).
Who uses it. Stock manager, finance (stock reconciliation), super admin.
Entry points. Stock › Mouvements, BO-33 drawer, BO-28 Stock, and links from orders, returns and inventory sessions.
Layout.
| Column | Source field | Sortable | Default visible |
|---|---|---|---|
| Date | occurredAt | Yes (default descending) | Yes |
| Type | type (StockMovementType) | Yes | Yes |
| Variante | Name + SKU | Yes | Yes |
| Entrepôt | warehouse.name | Yes | Yes |
| Quantité | Signed quantity (+ success / − danger) | Yes | Yes |
| Stock physique après | physicalAfter | No | Yes |
| Référence | Linked source: CMD-… (reservation, release, shipment), RET-… (return reception), receipt document, inventory session, adjustment | No | Yes |
| Motif | reason | No | Yes |
| Par | User, or « Système » | Yes | Yes |
- Filters:
from/to,type,variant,warehouse,user,reference. - Export: XLSX / CSV.
Data. admin.stock.movements.
Actions. Read-only. Nothing is edited or deleted: a correction is a new adjustment (BO-35).
Rules.
- Movements are written by the server only, in the same transaction as their cause (order status, return reception, receipt, adjustment, inventory validation).
RESERVATIONandRESERVATION_RELEASEchange the reserved quantity, not the physical one. The Stock physique après column marks them « — ».
Real-time. None.
States. A filter with no result shows « Aucun mouvement sur la période ».
Acceptance criteria.
- Receiving a return of 3 units creates one
RETURNmovement of +3 that links to itsRET-number. - Cancelling a confirmed order creates
RESERVATION_RELEASEmovements for each line. - No action on this page can modify a movement.
BO-35 — Stock receipt / adjustment
| Field | Value |
|---|---|
| Template | Form |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-05 |
| Route | /stock/operations/new?type=receipt or ?type=adjustment |
| Requirements | STK-06 · STK-04 |
| Permissions required | stock.create (receipt) · stock.update (adjustment) |
Purpose. Record goods received from suppliers, and adjust stock with a reason (breakage, loss, counting error) (cahier §3 « Responsable stock : quantités, inventaires et alertes »).
Who uses it. Stock manager.
Entry points. BO-33 header, BO-07 « Nouvelle réception », BO-28 Stock.
Layout.
- Tabs « Réception » and « Ajustement ».
- Header fields:
- Entrepôt: required; the default warehouse is preselected.
- Date: defaults to today; the past 30 days are allowed.
- Référence document: supplier delivery note, for receipts.
- Note.
- Lines:
- A variant search with barcode-scanner input: a scan adds the line, or increments it when already present.
- Receipt line: Quantité reçue (integer, 1 or more).
- Adjustment line: « Nouvelle quantité comptée » or « Écart (+/−) » (switch), showing current physical, resulting physical and difference, plus a required Motif (free text, 3–200 characters).
- Summary bar: lines, total units, and for adjustments the units added and removed.
Data. admin.stock.receipt, admin.stock.adjust, admin.stock.levels (current quantities), admin.products.list (search).
Actions.
- Receipt: « Enregistrer la réception de 12 lignes (340 pièces) dans Dépôt Oran ? » This creates
RECEIPTmovements, auditedCREATE. - Adjustment: « Appliquer 4 ajustements (+6 / −11 pièces) ? » This creates
ADJUSTMENTmovements with their reasons, auditedCREATE. - Both use an
Idempotency-Key, so a double click never posts twice.
Rules.
- Adjustments cannot bring physical stock below the reserved quantity without a warning (« 3 pièces sont réservées par des commandes confirmées »).
- An adjustment larger than 10 % of the physical stock is flagged for review in the summary. Assumption — no extra approval in V1.
- Receipts and adjustments may move a product from
OUT_OF_STOCKback toACTIVEautomatically.
Real-time. None.
States. An unknown barcode shows « Code-barres inconnu » with « Créer le produit ». Leaving with lines entered is guarded.
Acceptance criteria.
- Scanning the same barcode twice gives one line with quantity 2.
- A receipt of 50 units of an out-of-stock variant makes it En stock for resellers immediately after saving.
- An adjustment without a reason cannot be saved.
BO-36 — Inventory sessions
| Field | Value |
|---|---|
| Template | List (with a session detail) |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-05 |
| Route | /stock/inventories · /stock/inventories/[sessionId] |
| Requirements | STK-06 · STK-04 |
| Permissions required | stock.update (open, count, review; the contract also gates reading with it) · stock.validate (validate) |
Purpose. Run physical counts for a warehouse or part of it, review differences, and post the adjustments in one validated step.
Who uses it. Stock manager, with warehouse staff on a tablet.
Entry points. Stock › Inventaires (badge = open sessions), BO-33 « Inventaire ».
Layout.
- List: Nom, Entrepôt, Périmètre (all, brand or category), Ouverte le / par, Lignes comptées / attendues, Écarts, État.
- State labels: « Ouverte », « En revue », « Validée », « Annulée ». Assumption — the contract has no inventory-status enum yet.
- Session detail:
- Header: name, warehouse, scope, state.
- Count grid:
- variant and SKU;
- Attendu: snapshot at opening, hidden in « comptage à l'aveugle » mode;
- Mouvements depuis l'ouverture;
- Compté (input; a scanner adds +1);
- Écart, and its value with
pricing.view.
- Filters « Non comptés », « Avec écart ». Large touch targets for tablet use.
- Actions: « Passer en revue », « Valider l'inventaire », « Annuler ».
Data. admin.inventory.crud (sessions and lines), admin.inventory.validate.
Actions.
- Count entry autosaves every line (optimistic, with a « Enregistré » tick).
- « Valider l'inventaire » (
stock.validate): « Valider l'inventaire Dépôt Oran — Câbles ? 23 écarts seront passés en mouvements d'inventaire (+14 / −31 pièces). ». This postsINVENTORYmovements, auditedVALIDATE.
Rules.
- Posted adjustment = counted − (snapshot + movements since opening). Sales and receipts may therefore continue during the count.
- Uncounted lines are excluded from validation unless « Considérer les non comptés comme 0 » is ticked.
- A validated session is read-only.
Real-time. None.
States. « Aucun inventaire en cours ». Concurrent counting on two tablets is last-write-wins per line, with the counter's name shown.
Acceptance criteria.
- A shipment posted during a session does not create a false difference at validation.
- Validation creates exactly one
INVENTORYmovement per line with a non-zero difference. - Blind mode never shows the expected quantity.
BO-37 — Warehouses
| Field | Value |
|---|---|
| Template | List (with drawer) |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-05 |
| Route | /stock/warehouses |
| Requirements | STK-01 |
| Permissions required | stock.administer |
Purpose. Declare the warehouses when HOCO manages stock per warehouse (cahier §4.3 « Stock global ou par entrepôt »).
Who uses it. Stock manager, super admin.
Entry points. Stock › Entrepôts.
Layout.
- Table: Nom, Code, Wilaya / commune, Par défaut (expédition), Actif, Variantes en stock, Valeur (needs
pricing.view). - Drawer:
- Nom and Code: required, unique.
- Adresse, Wilaya, Commune.
- Entrepôt par défaut (one only).
- Actif.
Data. admin.warehouses.crud.
Actions. Create and edit (audited). Deactivating is refused while physical stock is above 0: « Videz l'entrepôt avant de le désactiver ».
Rules.
- Exactly one default warehouse; it reserves and ships when the order flow does not pick another.
- With a single warehouse, the stock pages hide the warehouse dimension.
Real-time. None.
States. One seeded warehouse (« Dépôt principal »).
Acceptance criteria.
- Setting a second default warehouse removes the flag from the first.
- A warehouse with stock cannot be deactivated.
BO-71 — Product attributes (filters) per category
| Field | Value |
|---|---|
| Template | List (category tree + attribute list with drawer) |
| Release | V1 |
| Design day | Day 17 — DES-17 |
| Build task | DSH-04 |
| Route | /catalog/attributes (?category=) |
| Requirements | CAT-13 · CAT-12 |
| Permissions required | catalog.view · catalog.create · catalog.update · catalog.delete |
Purpose. Define, per category, the filterable attributes resellers use to narrow the catalog: power in W, connector USB-C / Lightning / Micro-USB, cable length, colour, battery capacity in mAh, Bluetooth version (§1b.7). Each definition has a type, unit, allowed values and filter widget, and is valued on every variant in BO-28.
Who uses it. Stock manager, super admin.
Entry points. Catalogue › Attributs et filtres (badge = variants with missing required values), « Attributs » on a BO-30 node, and the BO-28 Attributs section (« Gérer les attributs de la catégorie »).
Layout.
- Left: the category tree, with the attribute count per node.
- Right: the attributes of the selected category. Those inherited from the parent category are shown greyed, with « Hérité de Câbles ». Columns: Libellé, Clé, Type, Unité, Valeurs, Obligatoire, Filtre (widget), Ordre, Variantes renseignées (« 182 / 190 »).
- Drawer:
| Field | Type | Validation | Default | Help text |
|---|---|---|---|---|
| Libellé | Text (FR; AR and EN prepared) | Required, 40 characters max | — | Shown to resellers |
| Clé | Slug a–z 0–9 _ | Required, unique within the category branch; immutable once used | Generated from the label | Used in imports (attr.<clé>) |
| Type | Select: « Nombre », « Liste à choix unique », « Liste à choix multiples », « Oui / Non » | Required | — | Assumption — the contract will carry these as NUMBER, ENUM, MULTI_ENUM, BOOLEAN |
| Unité | Text | For numbers, 8 characters max | — | « W », « m », « mAh » |
| Plage | Min, max, step | For numbers; min below max | — | Validates variant values |
| Valeurs autorisées | Ordered list: label and code, drag to reorder | For lists, 1 to 50 values; unique codes | — | « USB-C », « Lightning », « Micro-USB » |
| Widget de filtre | Select: checkboxes, chips, range slider, toggle | Compatible with the type | Inferred from the type | How the filter looks in the apps and web |
| Obligatoire sur les variantes | Toggle | — | Off | Required values block publication (BO-28) |
| Afficher dans les filtres | Toggle | — | On | — |
| Afficher sur la fiche produit | Toggle | — | On | Adds the value to the technical sheet |
Examples to seed. Assumption — to validate with HOCO.
| Category | Attribute | Type | Unit / values |
|---|---|---|---|
| Chargeurs | Puissance | Number | W, range slider |
| Chargeurs | Ports | Multiple choice | USB-A, USB-C |
| Chargeurs | Charge rapide | Yes / No | — |
| Câbles | Connecteur | Single choice | USB-C, Lightning, Micro-USB |
| Câbles | Longueur | Number | m (0,25–3) |
| Câbles, Écouteurs | Couleur | Single choice | Noir, Blanc, … |
| Powerbanks | Capacité | Number | mAh |
| Écouteurs, Audio | Version Bluetooth | Single choice | 5.0, 5.1, 5.3 |
Data. admin.attributes.crud (AttributeDefinition), admin.categories.crud (tree).
Actions.
- Create, edit, reorder: audited.
- Removing an allowed value used by variants asks: « 37 variantes utilisent Micro-USB. Remplacer par : [select] ou laisser vide ».
- Deleting a definition with values asks: « Supprimer l'attribut Longueur ? Les valeurs de 190 variantes seront supprimées et le filtre disparaîtra des applications. »
- Making an attribute required shows how many active variants miss it.
Rules.
- Inheritance: definitions apply to their category and its sub-categories.
- Keys are immutable once a variant has a value, so imports and facets stay stable.
- Facets: changes are reflected in
catalog.facetsand the reseller filters on the next request. - Price-range facets: these are computed from the caller's own customer-type price, never shown to guests (CAT-12).
Real-time. None.
States.
- A category without attributes: « Aucun attribut — les revendeurs ne pourront filtrer que par marque, prix et disponibilité ».
- The completeness bar per attribute turns amber below 100 % for required ones.
Acceptance criteria.
- Adding Connecteur (Lightning, USB-C, Micro-USB) to Câbles adds the column to every cable product in BO-28 and a filter with counts in the reseller catalog.
- A required attribute left empty on an active variant blocks that product's publication and shows it in the sidebar badge.
- A used key cannot be changed. Replacing a removed value moves all variants to the chosen value.
Field operations
Page specs BO-16 to BO-26: delegates, zones, objectives, the tour planner, tour editor and live tour detail, visits and visit configuration.
Orders and delivery
Page specs BO-38 to BO-43 and BO-69: orders list, order detail, order on behalf, the orders-to-confirm queue, delivery board, carriers, and returns and after-sales claims.