Product field access
This page lists every field of the product form with the rules that make it read-only or required, and the exact rule that computes them. The rule is resolveProductAccess in apps/web/utils/products/product-access.ts. The Nuxt client and the Nitro server use the same rule.
Field matrix
Legend:
- Needs edit: read-only on an existing product without
org:products:edit. - Catalog lock: read-only on an existing product when catalog mode is ON and the organization is not
PARTNER. - Vendor lock: read-only on an existing product when the organization is
VENDORandhasBeenStockedis true. - Required in catalog: required when catalog mode is ON and the product is not a
KIT. - Affects name: part of the catalog name formula.
| Field key | Label | Section | Product type | Needs edit | Catalog lock | Vendor lock | Required in catalog | Affects name |
|---|---|---|---|---|---|---|---|---|
name | Nombre del Producto | Información General | all | yes | no | yes | no (generated for STOCK) | — |
sku | SKU | Información General | all | yes | no | no | no (always required) | no |
brand | Marca | Información General | all | yes | no | yes | no | no |
familia | Familia de producto | Información General | KIT | yes | no | yes | no | no |
cost | Costo | Información General | all | yes | no | no | no | no |
supplier | Proveedor | Información General | STOCK | yes | no | no | no | no |
tipo | Tipo de Producto | Información General | STOCK | yes | no | yes | yes | yes |
grupo | Grupo | Información General | STOCK | yes | no | yes | no | no |
tags | Etiquetas | Información General | all | yes | no | no | no | no |
sabor | Sabor | Descripción | STOCK | yes | no | no | no | yes |
color | Color | Descripción | STOCK | yes | no | no | no | yes |
talla | Talla o Medida | Descripción | STOCK | yes | no | no | no | yes |
presentacion | Presentación | Detalles del producto | STOCK | yes | yes | yes | yes | yes |
contenido | Contenido de la presentación | Detalles del producto | STOCK | yes | yes | yes | yes (≥ 1) | yes |
unidad | Unidad de venta | Detalles del producto | STOCK | yes | yes | yes | yes | yes |
modelo | Modelo | Detalles del producto | STOCK | yes | yes | yes | yes | yes |
piecesPerBox | Piezas por empaque maestro | Detalles del producto | STOCK | yes | yes | yes | no | no |
piecesPerPallet | Piezas por pallet | Detalles del producto | STOCK | yes | yes | yes | no | no |
usesBatch | Usa Lotes | Detalles del producto | STOCK | yes | yes | yes | no | no |
usesExpiration | Usa Caducidades | Detalles del producto | STOCK | yes | yes | yes | no | no |
usesSerial | Usa Seriales | Detalles del producto | STOCK | yes | yes | yes | no | no |
dimensions | Largo, Alto, Ancho, Peso | Dimensiones | STOCK | yes | yes | yes | no | no |
Barcodes are not part of this matrix. See Barcodes.
Field lists in code
| Constant | File | Members |
|---|---|---|
CATALOG_REQUIRED_FIELDS | apps/web/utils/products/product-access.ts | presentacion, contenido, unidad, tipo, modelo |
NAME_FIELDS | apps/web/utils/products/product-access.ts | CATALOG_REQUIRED_FIELDS + color, talla, sabor |
DETAILS_FIELDS | apps/web/utils/products/product-access.ts | presentacion, contenido, unidad, modelo, piecesPerBox, piecesPerPallet, usesBatch, usesExpiration, usesSerial, dimensions |
VENDOR_LOCKED_FIELDS | apps/web/utils/products/product-access.ts | name, brand, familia, grupo, tipo, modelo, presentacion, contenido, unidad, piecesPerBox, piecesPerPallet, usesBatch, usesSerial, usesExpiration, dimensions |
PRODUCT_LOGISTICS_FIELDS | apps/web/utils/products/logistics.ts | presentation, content, unit, model, piecesPerBox, piecesPerPallet, usesBatch, usesExpiration, usesSerial, dimensions |
CATALOG_REQUIRED_FIELDS (server) | modules/inventory/src/products/domain/services/CatalogNameFields.ts | presentation, content, unit, categoryId, model |
NAME_FIELDS (server) | modules/inventory/src/products/domain/services/CatalogNameFields.ts | server CATALOG_REQUIRED_FIELDS + color, size, flavor |
Form key to PATCH key
Built by buildProductUpdateDto in apps/web/utils/products/update-product-dto.ts.
| Form key | PATCH key |
|---|---|
presentacion | presentation |
contenido | content |
unidad | unit |
modelo | model |
tipo (selected category id) | categoryId |
talla | size |
sabor | flavor |
color | color |
group | group |
The rule
Inputs
ProductAccessInput:
| Input | Source in the client (apps/web/composables/products/useProductAccess.ts) |
|---|---|
isCreate | Page option (true in new.vue and import-products.vue) |
canEdit | org:products:edit |
isCatalogMode | organization.settings.CATALOG_MODE === true, or the dev override |
orgType | organization.type (PARTNER or VENDOR) |
hasBeenStocked | product.hasBeenStocked (default false) |
isStock | product type is not KIT |
Read-only
For every field:
readonly[field] =
!isCreate && (
!canEdit
|| (field in DETAILS_FIELDS && !logisticsAllowed)
|| (field in VENDOR_LOCKED_FIELDS && vendorLocked)
)
logisticsAllowed = isCreate || (canEdit && (!isCatalogMode || orgType === 'PARTNER'))
vendorLocked = orgType === 'VENDOR' && hasBeenStocked
Other outputs
| Output | Value |
|---|---|
canEditLogistics | logisticsAllowed |
requiredFields | CATALOG_REQUIRED_FIELDS when isCatalogMode && isStock, otherwise [] |
importDimensions | false when isCatalogMode && orgType === 'VENDOR', otherwise true |
canViewCatalogTab | isCatalogMode (shows the Detalle de catálogo tab in the product list) |
Create mode
| Aspect | Behavior |
|---|---|
| Read-only fields | None. Every field is editable. |
| Required fields | requiredFields applies (catalog ON + STOCK). |
| Name (catalog ON + STOCK) | Read-only preview generated from the form. Placeholder until all five required fields are filled: Completa los datos del producto para generar el nombre... |
| Dimensions on import | Dropped from rows and from the grid when importDimensions is false. |
Edit mode
| Aspect | Behavior |
|---|---|
| Name (catalog ON + STOCK) | Read-only. Shows the stored name until the server saves a new one. |
| Required fields | Validated only when a name field changed (changedNameFields). |
| Missing required field that is read-only | Toast Faltan campos que solo un partner puede completar: <labels>; save is blocked. |
| Missing required field that is editable | Toast Completa los campos requeridos: <labels>; save is blocked. |
| Logistics keys in PATCH | Omitted when canEditLogistics is false. |
| Name in PATCH (catalog ON + STOCK) | Omitted. |
changedNameFields treats '', null, and undefined as equal, trims strings, and compares contenido numerically. tipo is compared by category id.
Server PATCH guard keys
PATCH /api/products/:id runs requireLogisticsEdit when the body has any key of PRODUCT_LOGISTICS_FIELDS, including keys with null or false values. categoryId, color, size, and flavor are not guard keys.
Server enforcement summary
| Rule | Client | Server |
|---|---|---|
org:products:edit | yes | yes (PATCH /api/products/:id) |
| Catalog lock (logistics fields) | yes | yes (requireLogisticsEdit) |
Vendor lock (hasBeenStocked) | yes | no |
| Required catalog fields | yes | yes (MissingCatalogFieldsError, 422) |
Barcodes
| Rule | Value |
|---|---|
| Create mode | Free (created with org:products:create after the product is created). |
| Existing product, UI | Each action needs its org:barcodes:* permission and canEditLogistics({ canEdit: true, isCatalogMode, orgType }). |
| Existing product, server | Same rule via requireLogisticsEdit. POST /api/products/:id/barcodes skips it when the caller has org:products:create. |
| Vendor lock | Not applied. |
Catalog mode source
| Organization | Effective CATALOG_MODE |
|---|---|
PARTNER | Its own settings.CATALOG_MODE === true |
VENDOR with a partner | true when the partner has it true; otherwise its own value |
VENDOR without a partner | Its own value |
Dev builds only (import.meta.dev): localStorage['feature-flag-catalog-products'] === 'true' forces catalog mode ON in the client. The server ignores it.
Related
- Permissions and routes: Product permissions
- Name formula: Product name generation
- Adding a field: How to add a product field to the access rules