cuatrolabs-scm-ms / docs /taxonomy_api.md
rajeshbms's picture
feat: fixed adding and updaing taxonomies
d249b90
# Taxonomy API
## Endpoints
| Method | URL | Description |
| ------ | ------------ | ----------------------------------------- |
| `POST` | `/taxonomy/` | Create or append taxonomy values |
| `PUT` | `/taxonomy/` | Replace (overwrite) taxonomy field values |
Both endpoints require a Bearer token (JWT). `merchant_id` is always extracted from the JWT — do **not** pass it in the request body.
---
## POST /taxonomy/ — Create or Append
### Overview
Handles both **create** and **append** of taxonomy data. The operation is determined automatically based on whether a taxonomy document already exists for the merchant.
### How Create vs Append Works
| Scenario | Behaviour |
| -------------------------------------------- | ------------------------------------------------------------------------------------- |
| No taxonomy document exists for the merchant | Creates a new document (`operation: "created"`) |
| Document already exists | Appends new values using MongoDB `$addToSet` — no duplicates (`operation: "updated"`) |
| `is_delete: true` is passed | Removes the specified values from the arrays |
The key behaviour for updates: **values are appended, not replaced**. If you send `brands: ["Nike"]` and the document already has `["Adidas"]`, the result will be `["Adidas", "Nike"]`. To replace entirely, use `PUT /taxonomy/`.
---
## PUT /taxonomy/ — Replace Field Values
### Overview
Replaces the value of each provided field entirely. Only fields included in the request body are affected — omitted fields remain unchanged.
- **Method**: `PUT`
- **URL**: `/taxonomy/`
- **Auth**: Bearer token (JWT) required
- **Prerequisite**: A taxonomy document must already exist for the merchant (use `POST` to create it first)
### How Replace Works
| Scenario | Behaviour |
| ---------------------------------- | ----------------------------------------------------- |
| Field is included in request body | Existing array is **overwritten** with the new values |
| Field is omitted from request body | Field is left unchanged |
| Document does not exist | Returns `422` — use `POST /taxonomy/` to create first |
Example: if the document has `brands: ["Adidas", "Nike"]` and you `PUT` with `brands: ["Puma"]`, the result will be `brands: ["Puma"]`.
### Response — `200 OK`
```json
{
"success": true,
"message": "Taxonomy fields replaced successfully",
"data": {
"taxonomy_id": "64f1a2b3c4d5e6f7a8b9c0d1",
"operation": "replaced",
"merchant_id": "af59ab73-4ef4-43a0-aac3-6909f3ba87e4",
"modified_count": 1
},
"timestamp": "2026-03-14T10:30:00.000Z"
}
```
### Usage Example
```bash
curl -X PUT https://<host>/taxonomy/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"brands": ["Puma"],
"categories": ["Hair", "Skin"]
}'
```
---
---
## Request Body
All fields are optional. Only send the fields you want to add/update.
```json
{
"brands": ["L'Oréal Professional", "Schwarzkopf"],
"categories": ["Hair", "Skin", "Nails"],
"lines": ["Hair Care", "Hair Styling"],
"classes": ["Premium", "Luxury", "Basic"],
"subcategories": {
"Hair": ["Hair Cut", "Hair Color", "Hair Treatment"],
"Skin": ["Facial", "Cleanup"]
},
"job_role": ["Senior Stylist", "Director", "Massage Therapist"],
"specializations": ["Hairdresser", "Nail Technician", "Esthetician"],
"languages": ["English", "Hindi", "Tamil"],
"designation": ["CTO", "CEO", "RSM", "ASM"],
"regions": ["East", "West", "North", "South"],
"customer_group": ["VIP", "Regular", "Premium", "Gold"],
"pos_tender_modes": ["Cash", "Credit Card", "Debit Card", "Mobile Payment"],
"payment_types": ["Cash and Carry", "Credit"],
"payment_methods": ["Bank Transfer", "Cash", "Cheque"],
"pos_modes": ["Walk-in", "Appointment", "Online"],
"pricing_model": ["Weight_Based", "Fixed_Rate", "Zone_Based"],
"asset_location": ["Store Floor", "Warehouse", "Back Office"],
"asset_category": ["Electronics", "Furniture", "Equipment"],
"stock_bin_location": ["A1-01", "B2-15", "C3-07"],
"branch_types": ["Flagship", "Outlet", "Franchise"],
"store_types": ["Retail", "Wholesale", "Online"],
"expense_types": ["Travel", "Office Supplies"],
"region_codes": ["IN-KA-BLR", "IN-TN-CHN"],
"states": ["Maharashtra", "Karnataka", "Tamil Nadu"],
"relations": ["Parent", "Subsidiary", "Partner", "Vendor"],
"document_types": ["Invoice", "Receipt", "Purchase Order"],
"merchant_types": [
{ "code": "cnf", "label": "CNF", "order": 1 },
{ "code": "distributor", "label": "Distributor", "order": 2 },
{ "code": "retail", "label": "Retail", "order": 3 }
],
"is_delete": false
}
```
### Field Reference
#### Simple String Arrays
All of these follow the same pattern — send an array of strings to add values:
| Field | Description | Example |
| -------------------- | ------------------------ | -------------------------------- |
| `brands` | Product brands | `["L'Oréal", "Schwarzkopf"]` |
| `categories` | Product categories | `["Hair", "Skin", "Nails"]` |
| `lines` | Product lines | `["Hair Care", "Hair Styling"]` |
| `classes` | Product classes | `["Premium", "Luxury"]` |
| `job_role` | Employee job roles | `["Senior Stylist", "Director"]` |
| `specializations` | Employee specializations | `["Hairdresser", "Esthetician"]` |
| `languages` | Languages | `["English", "Hindi"]` |
| `designation` | Employee designations | `["CTO", "RSM", "ASM"]` |
| `regions` | Region groups | `["East", "West", "North"]` |
| `customer_group` | Customer groups | `["VIP", "Regular", "Gold"]` |
| `pos_tender_modes` | POS tender modes | `["Cash", "Credit Card"]` |
| `payment_types` | Payment types | `["Cash and Carry", "Credit"]` |
| `payment_methods` | Payment methods | `["Bank Transfer", "Cash"]` |
| `pos_modes` | POS modes | `["Walk-in", "Appointment"]` |
| `pricing_model` | Pricing models | `["Fixed_Rate", "Weight_Based"]` |
| `asset_location` | Asset locations | `["Store Floor", "Warehouse"]` |
| `asset_category` | Asset categories | `["Electronics", "Furniture"]` |
| `stock_bin_location` | Stock bin locations | `["A1-01", "B2-15"]` |
| `branch_types` | Branch types | `["Flagship", "Outlet"]` |
| `store_types` | Store types | `["Retail", "Wholesale"]` |
| `expense_types` | Expense types | `["Travel", "Office Supplies"]` |
| `region_codes` | Region codes | `["IN-KA-BLR", "IN-TN-CHN"]` |
| `states` | States | `["Maharashtra", "Karnataka"]` |
| `relations` | Merchant relations | `["Parent", "Subsidiary"]` |
| `document_types` | Document types | `["Invoice", "Receipt"]` |
#### `subcategories` — Object (category → values)
Maps a parent category to its subcategory values.
```json
{
"subcategories": {
"Hair": ["Hair Cut", "Hair Color", "Hair Treatment"],
"Skin": ["Facial", "Cleanup", "Peel"]
}
}
```
#### `merchant_types` — Structured Array
Each item must have all three fields:
| Field | Type | Required | Description |
| ------- | ----------- | -------- | -------------------------------------------------- |
| `code` | string | ✅ | Unique identifier (e.g., `"cnf"`, `"distributor"`) |
| `label` | string | ✅ | Display name (e.g., `"CNF"`, `"Distributor"`) |
| `order` | integer ≥ 1 | ✅ | Sort order — must be positive, no duplicates |
```json
{
"merchant_types": [
{ "code": "cnf", "label": "CNF", "order": 1 },
{ "code": "distributor", "label": "Distributor", "order": 2 }
]
}
```
#### `is_delete` — Boolean (default: `false`)
When `true`, the values in the payload are **removed** from the existing arrays instead of being added.
```json
{
"brands": ["OldBrand"],
"is_delete": true
}
```
---
## Response
### Success — `201 Created`
```json
{
"success": true,
"message": "Taxonomy operation completed successfully",
"data": {
"taxonomy_id": "64f1a2b3c4d5e6f7a8b9c0d1",
"operation": "created",
"merchant_id": "af59ab73-4ef4-43a0-aac3-6909f3ba87e4",
"modified_count": 1
},
"timestamp": "2026-03-14T10:30:00.000Z"
}
```
| Field | Description |
| --------------------- | ------------------------------------------------------- |
| `data.taxonomy_id` | MongoDB document ID of the taxonomy record |
| `data.operation` | `"created"` for new documents, `"updated"` for existing |
| `data.modified_count` | Number of documents modified (typically `1`) |
---
## Usage Examples
### 1. Initial Setup — Create taxonomy for a new merchant
Send all the values you want to seed:
```bash
curl -X POST https://<host>/taxonomy/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"brands": ["L'\''Oréal Professional", "Schwarzkopf"],
"categories": ["Hair", "Skin", "Nails"],
"job_role": ["Senior Stylist", "Junior Stylist"],
"customer_group": ["VIP", "Regular"]
}'
```
### 2. Add new values to existing taxonomy
Only send the new values — existing ones are preserved:
```bash
curl -X POST https://<host>/taxonomy/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"brands": ["Kerastase"],
"categories": ["Makeup"]
}'
```
Result: `brands` will now contain `["L'Oréal Professional", "Schwarzkopf", "Kerastase"]`
### 3. Delete specific values
```bash
curl -X POST https://<host>/taxonomy/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"brands": ["OldBrand"],
"is_delete": true
}'
```
### 4. Add subcategories
```bash
curl -X POST https://<host>/taxonomy/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"subcategories": {
"Hair": ["Hair Cut", "Hair Color"],
"Skin": ["Facial", "Cleanup"]
}
}'
```
### 5. Set up merchant types
```bash
curl -X POST https://<host>/taxonomy/ \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"merchant_types": [
{ "code": "cnf", "label": "CNF", "order": 1 },
{ "code": "distributor", "label": "Distributor", "order": 2 },
{ "code": "retail", "label": "Retail", "order": 3 }
]
}'
```
---
## Error Responses
| Status | Scenario |
| --------------------------- | ----------------------------------------------------- |
| `401 Unauthorized` | Missing or invalid JWT token |
| `403 Forbidden` | User has no associated merchant |
| `422 Unprocessable Entity` | Validation failure (e.g., `order <= 0`, empty `code`) |
| `500 Internal Server Error` | Database operation failed |
### Validation Error Example (`422`)
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Order must be a positive integer",
"details": {}
}
}
```
---
## Important Notes
1. **merchant_id is never sent in the body** — it is always extracted from the JWT token.
2. **Append-only by default** — sending existing values again has no effect (idempotent for duplicates).
3. **`merchant_types` deduplication** — duplicate `code` or `order` values within the same request are silently skipped; the first occurrence wins.
4. **String sanitization** — all string values are trimmed of whitespace and empty strings are ignored automatically.
5. **`subcategories` merge** — new keys are added, existing keys get new values appended. Existing subcategory values are not removed unless `is_delete: true`.