Add special-order product flow for sales opportunities
This commit is contained in:
+85
-6
@@ -2326,8 +2326,8 @@ Fetch a paginated list of catalog items. Supports search.
|
||||
- `rpp` (optional, default `30`) — Records per page
|
||||
- `search` (optional) — Search by name, description, part number, vendor SKU, or manufacturer
|
||||
- `includeInactive` (optional, default `false`) — Include inactive catalog items in results
|
||||
- `category` (optional) — Filter by CW category name (e.g. `Technology`, `Field`, `General`)
|
||||
- `subcategory` (optional) — Filter by CW subcategory name (e.g. `Network-Switch`, `AlarmBurg-Panels`)
|
||||
- `category` (optional) — Filter by CW category **name or CW ID** (e.g. `Technology` or `18`)
|
||||
- `subcategory` (optional) — Filter by CW subcategory **name or CW ID** (e.g. `Network-Switch` or `112`)
|
||||
- `group` (optional) — Filter by umbrella group name (e.g. `Network`, `AlarmBurg`, `Cables`). When used with `category`, returns items whose subcategory belongs to that group within the category.
|
||||
- `manufacturer` (optional) — Filter by manufacturer name (case-insensitive contains match)
|
||||
- `ecosystem` (optional) — Filter by ecosystem name (e.g. `Networking`, `Video Surveillance`, `Burg/Alarm`). Applies manufacturer + category + subcategory-prefix matching rules.
|
||||
@@ -2726,8 +2726,8 @@ Fetch the distinct values available for filter dropdowns (categories, subcategor
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
- `category` (optional) — Scope subcategories and manufacturers to items in this category
|
||||
- `subcategory` (optional) — Scope manufacturers to items in this subcategory
|
||||
- `category` (optional) — Scope subcategories and manufacturers to items in this category (accepts CW category name or CW ID)
|
||||
- `subcategory` (optional) — Scope manufacturers to items in this subcategory (accepts CW subcategory name or CW ID)
|
||||
- `includeInactive` (optional, default `false`) — Include inactive catalog items
|
||||
|
||||
**Response:**
|
||||
@@ -2988,7 +2988,7 @@ Fetch a single opportunity by its internal ID or ConnectWise opportunity ID. The
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
- `include` _(optional)_ — Comma-separated list of sub-resources to embed in the response. Supported values: `notes`, `contacts`, `products`. Example: `?include=notes,contacts,products`. Sub-resources are fetched in parallel and added as top-level keys on the response object.
|
||||
- `include` _(optional)_ — Comma-separated list of sub-resources to embed in the response. Supported values: `notes`, `contacts`, `products`. Example: `?include=notes,contacts,products`. Sub-resources are fetched in parallel and added as top-level keys on the response object. When `notes` is included, `data.notes` is returned as an array of note objects and the original opportunity text note is preserved under `data.opportunityNoteText`.
|
||||
|
||||
**Response:**
|
||||
|
||||
@@ -3238,7 +3238,7 @@ Refresh an opportunity's local data by fetching the latest from ConnectWise. The
|
||||
|
||||
**GET** `/sales/opportunities/:identifier/products`
|
||||
|
||||
Fetch products (forecast/revenue line items) for an opportunity. Data is served from the Redis cache when available; on cache miss, data is fetched live from ConnectWise and cached. Hot opportunities (updated within 3 days) have a 15-second TTL; others use a 30-minute lazy TTL. Products are returned sorted by the opportunity's local `productSequence` array when set; otherwise, items are sorted by their ConnectWise `sequenceNumber`.
|
||||
Fetch products for an opportunity, scoped to items that have a matching ConnectWise procurement product (`forecastDetailId` link). Data is served from the Redis cache when available; on cache miss, data is fetched live from ConnectWise and cached. Hot opportunities (updated within 3 days) have a 15-second TTL; others use a 30-minute lazy TTL. Products are returned sorted by the opportunity's local `productSequence` array when set; otherwise, items are sorted by their ConnectWise `sequenceNumber`.
|
||||
|
||||
**Authentication Required:** Yes
|
||||
|
||||
@@ -3490,6 +3490,85 @@ All fields are optional. Only fields the user has the corresponding `sales.oppor
|
||||
|
||||
---
|
||||
|
||||
### Add SPECIAL ORDER Product
|
||||
|
||||
**POST** `/sales/opportunities/:identifier/products/special-order`
|
||||
|
||||
Add one or more products as **SPECIAL ORDER** procurement line items. This route creates ConnectWise procurement products (not forecast items) and enforces stable defaults for quick-entry workflows.
|
||||
|
||||
**Authentication Required:** Yes
|
||||
|
||||
**Required Permissions:** `sales.opportunity.product.add.specialOrder`
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
- `identifier` — Internal ID (cuid) or ConnectWise opportunity ID (numeric)
|
||||
|
||||
**Request Body:**
|
||||
|
||||
Accepts either a single object or an array of objects.
|
||||
|
||||
```json
|
||||
{
|
||||
"desc": "SPECIAL ORDER - Lead time confirmed",
|
||||
"customerDesc": "Customer-facing special order description",
|
||||
"qty": 1,
|
||||
"price": 750,
|
||||
"cost": 500,
|
||||
"taxable": true,
|
||||
"procurementNotes": "Vendor ETA pending confirmation",
|
||||
"productNarrative": "Install with existing rack accessories"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ------------------ | ------- | -------- | --------------------------------------------------------- |
|
||||
| `desc` | string | Yes | Internal/sales line description |
|
||||
| `customerDesc` | string | No | Customer-facing line description |
|
||||
| `qty` | number | No | Quantity (defaults to `1` when omitted) |
|
||||
| `price` | number | Yes | Revenue amount |
|
||||
| `cost` | number | No | Cost amount |
|
||||
| `taxable` | boolean | No | Taxable flag (defaults to the catalog item's tax setting) |
|
||||
| `procurementNotes` | string | No | Maps to custom field `id: 29` |
|
||||
| `productNarrative` | string | No | Maps to custom field `id: 46` |
|
||||
|
||||
**Route-Enforced Defaults:**
|
||||
|
||||
- `catalogItem` is always set to the canonical catalog item with identifier `SPECIAL ORDER`
|
||||
- `description` is always set from `desc`
|
||||
- `customerDescription` is always set from `customerDesc` when provided
|
||||
- `quantity` is always set from `qty` (default `1`)
|
||||
- `price` is always set from `price`
|
||||
- `cost` is always set from `cost` when provided
|
||||
- `dropshipFlag` is always set to `false`
|
||||
- `billableOption` is always set to `Billable`
|
||||
- `taxableFlag` is set from `taxable` (or `taxableFlag`), defaulting to the catalog item's `salesTaxable` value
|
||||
- `customFields` are auto-built when notes are provided:
|
||||
- `procurementNotes` → `Procurement Notes` (`id: 29`)
|
||||
- `productNarrative` → `Product Narrative` (`id: 46`)
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 201,
|
||||
"message": "Special-order product added successfully!",
|
||||
"data": {
|
||||
"id": 88340,
|
||||
"forecastDetailId": 32280,
|
||||
"description": "SPECIAL ORDER - Lead time confirmed",
|
||||
"customerDescription": "Customer-facing special order description",
|
||||
"quantity": 1,
|
||||
"price": 750,
|
||||
"cost": 500,
|
||||
"taxableFlag": true
|
||||
},
|
||||
"successful": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Opportunity Notes
|
||||
|
||||
**GET** `/sales/opportunities/:identifier/notes`
|
||||
|
||||
Reference in New Issue
Block a user