Fix UserController permission serialization and include current updates
This commit is contained in:
+673
@@ -2204,6 +2204,679 @@ A fun Easter egg endpoint that returns HTTP 418 (I'm a teapot).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Procurement Routes
|
||||||
|
|
||||||
|
### Get All Catalog Items
|
||||||
|
|
||||||
|
**GET** `/procurement/items`
|
||||||
|
|
||||||
|
Fetch a paginated list of catalog items. Supports search.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `procurement.catalog.fetch.many`
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
- `page` (optional, default `1`) — Page number
|
||||||
|
- `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
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Catalog items fetched successfully!",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "clx...",
|
||||||
|
"cwCatalogId": 123,
|
||||||
|
"name": "Dell OptiPlex 7020",
|
||||||
|
"description": "Dell OptiPlex 7020 SFF Desktop",
|
||||||
|
"customerDescription": "Business Desktop Computer",
|
||||||
|
"internalNotes": null,
|
||||||
|
"manufacturer": "Dell",
|
||||||
|
"manufactureCwId": 45,
|
||||||
|
"partNumber": "OPT7020-SFF",
|
||||||
|
"vendorName": "Dell Direct",
|
||||||
|
"vendorSku": "DELL-OPT7020",
|
||||||
|
"vendorCwId": 12,
|
||||||
|
"price": 899.99,
|
||||||
|
"cost": 650.0,
|
||||||
|
"inactive": false,
|
||||||
|
"salesTaxable": true,
|
||||||
|
"onHand": 5,
|
||||||
|
"cwLastUpdated": "2026-02-25T10:00:00.000Z",
|
||||||
|
"createdAt": "2026-01-15T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-25T10:00:00.000Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"pagination": {
|
||||||
|
"previousPage": null,
|
||||||
|
"currentPage": 1,
|
||||||
|
"nextPage": 2,
|
||||||
|
"totalPages": 10,
|
||||||
|
"totalRecords": 300,
|
||||||
|
"listedRecords": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Catalog Item
|
||||||
|
|
||||||
|
**GET** `/procurement/items/:identifier`
|
||||||
|
|
||||||
|
Fetch a single catalog item by its internal ID or ConnectWise catalog ID.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `procurement.catalog.fetch`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid) or ConnectWise catalog ID (numeric)
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
- `includeLinkedItems` (optional, default `false`) — Include linked catalog items in the response
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Catalog item fetched successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "clx...",
|
||||||
|
"cwCatalogId": 123,
|
||||||
|
"name": "Dell OptiPlex 7020",
|
||||||
|
"description": "Dell OptiPlex 7020 SFF Desktop",
|
||||||
|
"customerDescription": "Business Desktop Computer",
|
||||||
|
"internalNotes": null,
|
||||||
|
"manufacturer": "Dell",
|
||||||
|
"manufactureCwId": 45,
|
||||||
|
"partNumber": "OPT7020-SFF",
|
||||||
|
"vendorName": "Dell Direct",
|
||||||
|
"vendorSku": "DELL-OPT7020",
|
||||||
|
"vendorCwId": 12,
|
||||||
|
"price": 899.99,
|
||||||
|
"cost": 650.0,
|
||||||
|
"inactive": false,
|
||||||
|
"salesTaxable": true,
|
||||||
|
"onHand": 5,
|
||||||
|
"cwLastUpdated": "2026-02-25T10:00:00.000Z",
|
||||||
|
"linkedItems": [
|
||||||
|
{
|
||||||
|
"id": "clx...",
|
||||||
|
"cwCatalogId": 456,
|
||||||
|
"name": "Dell Warranty - 3 Year"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"createdAt": "2026-01-15T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-25T10:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Catalog Item Count
|
||||||
|
|
||||||
|
**GET** `/procurement/count`
|
||||||
|
|
||||||
|
Get the total number of catalog items.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `procurement.catalog.fetch.many`
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
- `activeOnly` (optional, default `false`) — Only count active (non-inactive) items
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Catalog item count fetched successfully!",
|
||||||
|
"data": {
|
||||||
|
"count": 300
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Refresh Catalog Item Inventory
|
||||||
|
|
||||||
|
**POST** `/procurement/items/:identifier/refresh-inventory`
|
||||||
|
|
||||||
|
Refresh the on-hand inventory count for a catalog item by fetching the latest data from ConnectWise.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `procurement.catalog.inventory.refresh`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid) or ConnectWise catalog ID (numeric)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Inventory refreshed successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "clx...",
|
||||||
|
"cwCatalogId": 123,
|
||||||
|
"name": "Dell OptiPlex 7020",
|
||||||
|
"onHand": 7,
|
||||||
|
"price": 899.99,
|
||||||
|
"cost": 650.0,
|
||||||
|
"inactive": false,
|
||||||
|
"createdAt": "2026-01-15T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-26T12:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Linked Catalog Items
|
||||||
|
|
||||||
|
**GET** `/procurement/items/:identifier/linked`
|
||||||
|
|
||||||
|
Fetch all catalog items linked to a specific item.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `procurement.catalog.fetch`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid), CW identifier string, or CW catalog ID (numeric)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Linked catalog items fetched successfully!",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "clx...",
|
||||||
|
"cwCatalogId": 456,
|
||||||
|
"identifier": "DELL-WAR-3YR",
|
||||||
|
"name": "Dell Warranty - 3 Year",
|
||||||
|
"description": "Dell 3 Year ProSupport Warranty",
|
||||||
|
"price": 199.99,
|
||||||
|
"cost": 120.0,
|
||||||
|
"inactive": false,
|
||||||
|
"onHand": 0,
|
||||||
|
"createdAt": "2026-01-15T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-25T10:00:00.000Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Link Catalog Items
|
||||||
|
|
||||||
|
**POST** `/procurement/items/:identifier/link`
|
||||||
|
|
||||||
|
Link a target catalog item to the specified source item. The source item is identified by the URL parameter and the target by the request body.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `procurement.catalog.link`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid), CW identifier string, or CW catalog ID (numeric) of the source item
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"targetId": "clx..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Catalog item linked successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "clx...",
|
||||||
|
"cwCatalogId": 123,
|
||||||
|
"identifier": "OPT7020-SFF",
|
||||||
|
"name": "Dell OptiPlex 7020",
|
||||||
|
"linkedItems": [
|
||||||
|
{
|
||||||
|
"id": "clx...",
|
||||||
|
"cwCatalogId": 456,
|
||||||
|
"identifier": "DELL-WAR-3YR",
|
||||||
|
"name": "Dell Warranty - 3 Year"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"createdAt": "2026-01-15T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-26T12:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Unlink Catalog Items
|
||||||
|
|
||||||
|
**POST** `/procurement/items/:identifier/unlink`
|
||||||
|
|
||||||
|
Remove the link between a source catalog item and a target catalog item.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `procurement.catalog.link`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid), CW identifier string, or CW catalog ID (numeric) of the source item
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"targetId": "clx..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Catalog item unlinked successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "clx...",
|
||||||
|
"cwCatalogId": 123,
|
||||||
|
"identifier": "OPT7020-SFF",
|
||||||
|
"name": "Dell OptiPlex 7020",
|
||||||
|
"linkedItems": [],
|
||||||
|
"createdAt": "2026-01-15T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-26T12:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sales Routes
|
||||||
|
|
||||||
|
Sales routes serve opportunity data stored locally and synced from ConnectWise. List, search, and count operations read from the local database. Sub-resource routes (forecasts, notes, contacts) fetch live data from ConnectWise using the opportunity's CW ID.
|
||||||
|
|
||||||
|
### Get All Opportunities
|
||||||
|
|
||||||
|
**GET** `/sales/opportunities`
|
||||||
|
|
||||||
|
Fetch a paginated list of opportunities. Supports search.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `sales.opportunity.fetch.many`
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
- `page` (optional, default `1`) — Page number
|
||||||
|
- `rpp` (optional, default `30`) — Records per page
|
||||||
|
- `search` (optional) — Search by opportunity name
|
||||||
|
- `includeClosed` (optional, default `false`) — Include closed opportunities in results
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Opportunities fetched successfully!",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "clx...",
|
||||||
|
"cwOpportunityId": 456,
|
||||||
|
"name": "Acme Corp Network Refresh",
|
||||||
|
"notes": "Full network redesign and hardware refresh",
|
||||||
|
"type": { "id": 1, "name": "New" },
|
||||||
|
"stage": { "id": 3, "name": "Proposal" },
|
||||||
|
"status": { "id": 1, "name": "Open" },
|
||||||
|
"priority": { "id": 2, "name": "High" },
|
||||||
|
"rating": { "id": 1, "name": "Hot" },
|
||||||
|
"source": "Referral",
|
||||||
|
"campaign": null,
|
||||||
|
"primarySalesRep": {
|
||||||
|
"id": 10,
|
||||||
|
"identifier": "JDoe",
|
||||||
|
"name": "John Doe"
|
||||||
|
},
|
||||||
|
"secondarySalesRep": null,
|
||||||
|
"company": { "id": 100, "name": "Acme Corp" },
|
||||||
|
"contact": { "id": 200, "name": "Jane Smith" },
|
||||||
|
"site": { "id": 50, "name": "Main Office" },
|
||||||
|
"customerPO": null,
|
||||||
|
"totalSalesTax": 0,
|
||||||
|
"location": { "id": 1, "name": "Murray" },
|
||||||
|
"department": { "id": 5, "name": "Sales" },
|
||||||
|
"expectedCloseDate": "2026-04-15T00:00:00.000Z",
|
||||||
|
"pipelineChangeDate": "2026-02-20T00:00:00.000Z",
|
||||||
|
"dateBecameLead": "2026-01-10T00:00:00.000Z",
|
||||||
|
"closedDate": null,
|
||||||
|
"closedFlag": false,
|
||||||
|
"closedBy": null,
|
||||||
|
"companyId": "clx...",
|
||||||
|
"cwLastUpdated": "2026-02-26T10:00:00.000Z",
|
||||||
|
"createdAt": "2026-02-01T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-26T10:00:00.000Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"pagination": {
|
||||||
|
"previousPage": null,
|
||||||
|
"currentPage": 1,
|
||||||
|
"nextPage": 2,
|
||||||
|
"totalPages": 5,
|
||||||
|
"totalRecords": 150,
|
||||||
|
"listedRecords": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Opportunity Count
|
||||||
|
|
||||||
|
**GET** `/sales/opportunities/count`
|
||||||
|
|
||||||
|
Get the total number of opportunities.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `sales.opportunity.fetch.many`
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
- `openOnly` (optional, default `false`) — Only count open (non-closed) opportunities
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Opportunity count fetched successfully!",
|
||||||
|
"data": {
|
||||||
|
"count": 150
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Opportunity
|
||||||
|
|
||||||
|
**GET** `/sales/opportunities/:identifier`
|
||||||
|
|
||||||
|
Fetch a single opportunity by its internal ID or ConnectWise opportunity ID.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `sales.opportunity.fetch`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid) or ConnectWise opportunity ID (numeric)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Opportunity fetched successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "clx...",
|
||||||
|
"cwOpportunityId": 456,
|
||||||
|
"name": "Acme Corp Network Refresh",
|
||||||
|
"notes": "Full network redesign and hardware refresh",
|
||||||
|
"type": { "id": 1, "name": "New" },
|
||||||
|
"stage": { "id": 3, "name": "Proposal" },
|
||||||
|
"status": { "id": 1, "name": "Open" },
|
||||||
|
"priority": { "id": 2, "name": "High" },
|
||||||
|
"rating": { "id": 1, "name": "Hot" },
|
||||||
|
"source": "Referral",
|
||||||
|
"campaign": null,
|
||||||
|
"primarySalesRep": {
|
||||||
|
"id": 10,
|
||||||
|
"identifier": "JDoe",
|
||||||
|
"name": "John Doe"
|
||||||
|
},
|
||||||
|
"secondarySalesRep": null,
|
||||||
|
"company": { "id": 100, "name": "Acme Corp" },
|
||||||
|
"contact": { "id": 200, "name": "Jane Smith" },
|
||||||
|
"site": { "id": 50, "name": "Main Office" },
|
||||||
|
"customerPO": null,
|
||||||
|
"totalSalesTax": 0,
|
||||||
|
"location": { "id": 1, "name": "Murray" },
|
||||||
|
"department": { "id": 5, "name": "Sales" },
|
||||||
|
"expectedCloseDate": "2026-04-15T00:00:00.000Z",
|
||||||
|
"pipelineChangeDate": "2026-02-20T00:00:00.000Z",
|
||||||
|
"dateBecameLead": "2026-01-10T00:00:00.000Z",
|
||||||
|
"closedDate": null,
|
||||||
|
"closedFlag": false,
|
||||||
|
"closedBy": null,
|
||||||
|
"companyId": "clx...",
|
||||||
|
"cwLastUpdated": "2026-02-26T10:00:00.000Z",
|
||||||
|
"createdAt": "2026-02-01T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-26T10:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Refresh Opportunity
|
||||||
|
|
||||||
|
**POST** `/sales/opportunities/:identifier/refresh`
|
||||||
|
|
||||||
|
Refresh an opportunity's local data by fetching the latest from ConnectWise.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `sales.opportunity.refresh`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid) or ConnectWise opportunity ID (numeric)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Opportunity refreshed from ConnectWise successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "clx...",
|
||||||
|
"cwOpportunityId": 456,
|
||||||
|
"name": "Acme Corp Network Refresh",
|
||||||
|
"notes": "Updated notes from CW",
|
||||||
|
"type": { "id": 1, "name": "New" },
|
||||||
|
"stage": { "id": 4, "name": "Negotiation" },
|
||||||
|
"status": { "id": 1, "name": "Open" },
|
||||||
|
"priority": { "id": 2, "name": "High" },
|
||||||
|
"rating": { "id": 1, "name": "Hot" },
|
||||||
|
"source": "Referral",
|
||||||
|
"campaign": null,
|
||||||
|
"primarySalesRep": {
|
||||||
|
"id": 10,
|
||||||
|
"identifier": "JDoe",
|
||||||
|
"name": "John Doe"
|
||||||
|
},
|
||||||
|
"secondarySalesRep": null,
|
||||||
|
"company": { "id": 100, "name": "Acme Corp" },
|
||||||
|
"contact": { "id": 200, "name": "Jane Smith" },
|
||||||
|
"site": { "id": 50, "name": "Main Office" },
|
||||||
|
"customerPO": null,
|
||||||
|
"totalSalesTax": 0,
|
||||||
|
"location": { "id": 1, "name": "Murray" },
|
||||||
|
"department": { "id": 5, "name": "Sales" },
|
||||||
|
"expectedCloseDate": "2026-04-15T00:00:00.000Z",
|
||||||
|
"pipelineChangeDate": "2026-02-25T00:00:00.000Z",
|
||||||
|
"dateBecameLead": "2026-01-10T00:00:00.000Z",
|
||||||
|
"closedDate": null,
|
||||||
|
"closedFlag": false,
|
||||||
|
"closedBy": null,
|
||||||
|
"companyId": "clx...",
|
||||||
|
"cwLastUpdated": "2026-02-26T14:00:00.000Z",
|
||||||
|
"createdAt": "2026-02-01T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-26T14:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Opportunity Forecasts
|
||||||
|
|
||||||
|
**GET** `/sales/opportunities/:identifier/forecasts`
|
||||||
|
|
||||||
|
Fetch forecast/revenue items for an opportunity. Data is fetched live from ConnectWise using the opportunity's CW ID.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `sales.opportunity.fetch`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid) or ConnectWise opportunity ID (numeric)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Opportunity forecasts fetched successfully!",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"forecastType": "Revenue",
|
||||||
|
"forecastMonth": "2026-03-01T00:00:00Z",
|
||||||
|
"revenue": 50000.0,
|
||||||
|
"cost": 30000.0,
|
||||||
|
"forecastPercentage": 75,
|
||||||
|
"status": { "id": 1, "name": "Open" },
|
||||||
|
"includedFlag": true,
|
||||||
|
"linkedFlag": false,
|
||||||
|
"recurringFlag": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Opportunity Notes
|
||||||
|
|
||||||
|
**GET** `/sales/opportunities/:identifier/notes`
|
||||||
|
|
||||||
|
Fetch notes for an opportunity. Data is fetched live from ConnectWise using the opportunity's CW ID.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `sales.opportunity.fetch`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid) or ConnectWise opportunity ID (numeric)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Opportunity notes fetched successfully!",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"text": "Client expressed interest in a full network refresh.",
|
||||||
|
"type": { "id": 2, "name": "Discussion" },
|
||||||
|
"flagged": false,
|
||||||
|
"enteredBy": "JDoe"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Opportunity Contacts
|
||||||
|
|
||||||
|
**GET** `/sales/opportunities/:identifier/contacts`
|
||||||
|
|
||||||
|
Fetch contacts associated with an opportunity. Data is fetched live from ConnectWise using the opportunity's CW ID.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `sales.opportunity.fetch`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `identifier` — Internal ID (cuid) or ConnectWise opportunity ID (numeric)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Opportunity contacts fetched successfully!",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"contact": { "id": 200, "name": "Jane Smith" },
|
||||||
|
"company": {
|
||||||
|
"id": 100,
|
||||||
|
"identifier": "AcmeCorp",
|
||||||
|
"name": "Acme Corp"
|
||||||
|
},
|
||||||
|
"role": { "id": 1, "name": "Decision Maker" },
|
||||||
|
"notes": "Primary point of contact for this deal",
|
||||||
|
"referralFlag": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## UniFi Routes
|
## UniFi Routes
|
||||||
|
|
||||||
All UniFi routes require the `unifi.access` permission in addition to their route-specific permission. This acts as a gate for the entire UniFi API.
|
All UniFi routes require the `unifi.access` permission in addition to their route-specific permission. This acts as a gate for the entire UniFi API.
|
||||||
|
|||||||
@@ -115,6 +115,25 @@ Admin-specific UI permissions that control visibility and data loading for admin
|
|||||||
- **Combine with API permissions**: A user with an admin UI permission should also have the corresponding API permission (e.g., `role.list`) to actually load data.
|
- **Combine with API permissions**: A user with an admin UI permission should also have the corresponding API permission (e.g., `role.list`) to actually load data.
|
||||||
- **Use wildcards for flexibility**: Grant `ui.navigation.*.view` to allow all navigation sections.
|
- **Use wildcards for flexibility**: Grant `ui.navigation.*.view` to allow all navigation sections.
|
||||||
|
|
||||||
|
### Procurement Permissions
|
||||||
|
|
||||||
|
| Permission Node | Description | Used In | Dependencies |
|
||||||
|
| --------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
|
||||||
|
| `procurement.catalog.fetch` | Fetch a single catalog item | [src/api/procurement/[id]/fetch.ts](src/api/procurement/[id]/fetch.ts) | |
|
||||||
|
| `procurement.catalog.fetch.many` | Fetch multiple catalog items or count | [src/api/procurement/fetchAll.ts](src/api/procurement/fetchAll.ts), [src/api/procurement/count.ts](src/api/procurement/count.ts) | |
|
||||||
|
| `procurement.catalog.inventory.refresh` | Refresh on-hand inventory for a catalog item from ConnectWise | [src/api/procurement/[id]/refreshInventory.ts](src/api/procurement/[id]/refreshInventory.ts) | `procurement.catalog.fetch` |
|
||||||
|
| `procurement.catalog.link` | Link or unlink catalog items to each other | [src/api/procurement/[id]/link.ts](src/api/procurement/[id]/link.ts), [src/api/procurement/[id]/unlink.ts](src/api/procurement/[id]/unlink.ts) | `procurement.catalog.fetch` |
|
||||||
|
|
||||||
|
### Sales Permissions
|
||||||
|
|
||||||
|
Permissions for accessing and managing sales opportunities. Opportunities are synced from ConnectWise and stored locally; sub-resources (forecasts, notes, contacts) are fetched live from CW.
|
||||||
|
|
||||||
|
| Permission Node | Description | Used In | Dependencies |
|
||||||
|
| ------------------------------ | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- |
|
||||||
|
| `sales.opportunity.fetch` | Fetch a single opportunity and its CW sub-resources (forecasts, notes, contacts) | [src/api/sales/[id]/fetch.ts](src/api/sales/[id]/fetch.ts), [src/api/sales/[id]/forecasts.ts](src/api/sales/[id]/forecasts.ts), [src/api/sales/[id]/notes.ts](src/api/sales/[id]/notes.ts), [src/api/sales/[id]/contacts.ts](src/api/sales/[id]/contacts.ts) | |
|
||||||
|
| `sales.opportunity.fetch.many` | Fetch multiple opportunities (paginated/searchable) or get opportunity count | [src/api/sales/fetchAll.ts](src/api/sales/fetchAll.ts), [src/api/sales/count.ts](src/api/sales/count.ts) | |
|
||||||
|
| `sales.opportunity.refresh` | Refresh a single opportunity's local data from ConnectWise | [src/api/sales/[id]/refresh.ts](src/api/sales/[id]/refresh.ts) | `sales.opportunity.fetch` |
|
||||||
|
|
||||||
### UniFi Permissions
|
### UniFi Permissions
|
||||||
|
|
||||||
Permissions for accessing and managing UniFi network infrastructure. The `unifi.access` permission is a gate permission required for **all** UniFi routes.
|
Permissions for accessing and managing UniFi network infrastructure. The `unifi.access` permission is a gate permission required for **all** UniFi routes.
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ export type Company = Prisma.CompanyModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type CatalogItem = Prisma.CatalogItemModel
|
export type CatalogItem = Prisma.CatalogItemModel
|
||||||
|
/**
|
||||||
|
* Model Opportunity
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Opportunity = Prisma.OpportunityModel
|
||||||
/**
|
/**
|
||||||
* Model CredentialType
|
* Model CredentialType
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -69,6 +69,11 @@ export type Company = Prisma.CompanyModel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type CatalogItem = Prisma.CatalogItemModel
|
export type CatalogItem = Prisma.CatalogItemModel
|
||||||
|
/**
|
||||||
|
* Model Opportunity
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Opportunity = Prisma.OpportunityModel
|
||||||
/**
|
/**
|
||||||
* Model CredentialType
|
* Model CredentialType
|
||||||
*
|
*
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -390,6 +390,7 @@ export const ModelName = {
|
|||||||
UnifiSite: 'UnifiSite',
|
UnifiSite: 'UnifiSite',
|
||||||
Company: 'Company',
|
Company: 'Company',
|
||||||
CatalogItem: 'CatalogItem',
|
CatalogItem: 'CatalogItem',
|
||||||
|
Opportunity: 'Opportunity',
|
||||||
CredentialType: 'CredentialType',
|
CredentialType: 'CredentialType',
|
||||||
SecureValue: 'SecureValue',
|
SecureValue: 'SecureValue',
|
||||||
Credential: 'Credential'
|
Credential: 'Credential'
|
||||||
@@ -408,7 +409,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
omit: GlobalOmitOptions
|
omit: GlobalOmitOptions
|
||||||
}
|
}
|
||||||
meta: {
|
meta: {
|
||||||
modelProps: "session" | "user" | "role" | "unifiSite" | "company" | "catalogItem" | "credentialType" | "secureValue" | "credential"
|
modelProps: "session" | "user" | "role" | "unifiSite" | "company" | "catalogItem" | "opportunity" | "credentialType" | "secureValue" | "credential"
|
||||||
txIsolationLevel: TransactionIsolationLevel
|
txIsolationLevel: TransactionIsolationLevel
|
||||||
}
|
}
|
||||||
model: {
|
model: {
|
||||||
@@ -856,6 +857,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Opportunity: {
|
||||||
|
payload: Prisma.$OpportunityPayload<ExtArgs>
|
||||||
|
fields: Prisma.OpportunityFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.OpportunityFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.OpportunityFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.OpportunityFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.OpportunityFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.OpportunityFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.OpportunityCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.OpportunityCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.OpportunityCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.OpportunityDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.OpportunityUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.OpportunityDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.OpportunityUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.OpportunityUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.OpportunityUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$OpportunityPayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.OpportunityAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateOpportunity>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.OpportunityGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.OpportunityGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.OpportunityCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.OpportunityCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
CredentialType: {
|
CredentialType: {
|
||||||
payload: Prisma.$CredentialTypePayload<ExtArgs>
|
payload: Prisma.$CredentialTypePayload<ExtArgs>
|
||||||
fields: Prisma.CredentialTypeFieldRefs
|
fields: Prisma.CredentialTypeFieldRefs
|
||||||
@@ -1186,6 +1261,7 @@ export type CompanyScalarFieldEnum = (typeof CompanyScalarFieldEnum)[keyof typeo
|
|||||||
export const CatalogItemScalarFieldEnum = {
|
export const CatalogItemScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
cwCatalogId: 'cwCatalogId',
|
cwCatalogId: 'cwCatalogId',
|
||||||
|
identifier: 'identifier',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
description: 'description',
|
description: 'description',
|
||||||
customerDescription: 'customerDescription',
|
customerDescription: 'customerDescription',
|
||||||
@@ -1209,6 +1285,58 @@ export const CatalogItemScalarFieldEnum = {
|
|||||||
export type CatalogItemScalarFieldEnum = (typeof CatalogItemScalarFieldEnum)[keyof typeof CatalogItemScalarFieldEnum]
|
export type CatalogItemScalarFieldEnum = (typeof CatalogItemScalarFieldEnum)[keyof typeof CatalogItemScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const OpportunityScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
cwOpportunityId: 'cwOpportunityId',
|
||||||
|
name: 'name',
|
||||||
|
notes: 'notes',
|
||||||
|
typeName: 'typeName',
|
||||||
|
typeCwId: 'typeCwId',
|
||||||
|
stageName: 'stageName',
|
||||||
|
stageCwId: 'stageCwId',
|
||||||
|
statusName: 'statusName',
|
||||||
|
statusCwId: 'statusCwId',
|
||||||
|
priorityName: 'priorityName',
|
||||||
|
priorityCwId: 'priorityCwId',
|
||||||
|
ratingName: 'ratingName',
|
||||||
|
ratingCwId: 'ratingCwId',
|
||||||
|
source: 'source',
|
||||||
|
campaignName: 'campaignName',
|
||||||
|
campaignCwId: 'campaignCwId',
|
||||||
|
primarySalesRepName: 'primarySalesRepName',
|
||||||
|
primarySalesRepIdentifier: 'primarySalesRepIdentifier',
|
||||||
|
primarySalesRepCwId: 'primarySalesRepCwId',
|
||||||
|
secondarySalesRepName: 'secondarySalesRepName',
|
||||||
|
secondarySalesRepIdentifier: 'secondarySalesRepIdentifier',
|
||||||
|
secondarySalesRepCwId: 'secondarySalesRepCwId',
|
||||||
|
companyCwId: 'companyCwId',
|
||||||
|
companyName: 'companyName',
|
||||||
|
contactCwId: 'contactCwId',
|
||||||
|
contactName: 'contactName',
|
||||||
|
siteCwId: 'siteCwId',
|
||||||
|
siteName: 'siteName',
|
||||||
|
customerPO: 'customerPO',
|
||||||
|
totalSalesTax: 'totalSalesTax',
|
||||||
|
locationName: 'locationName',
|
||||||
|
locationCwId: 'locationCwId',
|
||||||
|
departmentName: 'departmentName',
|
||||||
|
departmentCwId: 'departmentCwId',
|
||||||
|
expectedCloseDate: 'expectedCloseDate',
|
||||||
|
pipelineChangeDate: 'pipelineChangeDate',
|
||||||
|
dateBecameLead: 'dateBecameLead',
|
||||||
|
closedDate: 'closedDate',
|
||||||
|
closedFlag: 'closedFlag',
|
||||||
|
closedByName: 'closedByName',
|
||||||
|
closedByCwId: 'closedByCwId',
|
||||||
|
companyId: 'companyId',
|
||||||
|
cwLastUpdated: 'cwLastUpdated',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type OpportunityScalarFieldEnum = (typeof OpportunityScalarFieldEnum)[keyof typeof OpportunityScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const CredentialTypeScalarFieldEnum = {
|
export const CredentialTypeScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@@ -1473,6 +1601,7 @@ export type GlobalOmitConfig = {
|
|||||||
unifiSite?: Prisma.UnifiSiteOmit
|
unifiSite?: Prisma.UnifiSiteOmit
|
||||||
company?: Prisma.CompanyOmit
|
company?: Prisma.CompanyOmit
|
||||||
catalogItem?: Prisma.CatalogItemOmit
|
catalogItem?: Prisma.CatalogItemOmit
|
||||||
|
opportunity?: Prisma.OpportunityOmit
|
||||||
credentialType?: Prisma.CredentialTypeOmit
|
credentialType?: Prisma.CredentialTypeOmit
|
||||||
secureValue?: Prisma.SecureValueOmit
|
secureValue?: Prisma.SecureValueOmit
|
||||||
credential?: Prisma.CredentialOmit
|
credential?: Prisma.CredentialOmit
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export const ModelName = {
|
|||||||
UnifiSite: 'UnifiSite',
|
UnifiSite: 'UnifiSite',
|
||||||
Company: 'Company',
|
Company: 'Company',
|
||||||
CatalogItem: 'CatalogItem',
|
CatalogItem: 'CatalogItem',
|
||||||
|
Opportunity: 'Opportunity',
|
||||||
CredentialType: 'CredentialType',
|
CredentialType: 'CredentialType',
|
||||||
SecureValue: 'SecureValue',
|
SecureValue: 'SecureValue',
|
||||||
Credential: 'Credential'
|
Credential: 'Credential'
|
||||||
@@ -147,6 +148,7 @@ export type CompanyScalarFieldEnum = (typeof CompanyScalarFieldEnum)[keyof typeo
|
|||||||
export const CatalogItemScalarFieldEnum = {
|
export const CatalogItemScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
cwCatalogId: 'cwCatalogId',
|
cwCatalogId: 'cwCatalogId',
|
||||||
|
identifier: 'identifier',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
description: 'description',
|
description: 'description',
|
||||||
customerDescription: 'customerDescription',
|
customerDescription: 'customerDescription',
|
||||||
@@ -170,6 +172,58 @@ export const CatalogItemScalarFieldEnum = {
|
|||||||
export type CatalogItemScalarFieldEnum = (typeof CatalogItemScalarFieldEnum)[keyof typeof CatalogItemScalarFieldEnum]
|
export type CatalogItemScalarFieldEnum = (typeof CatalogItemScalarFieldEnum)[keyof typeof CatalogItemScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const OpportunityScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
cwOpportunityId: 'cwOpportunityId',
|
||||||
|
name: 'name',
|
||||||
|
notes: 'notes',
|
||||||
|
typeName: 'typeName',
|
||||||
|
typeCwId: 'typeCwId',
|
||||||
|
stageName: 'stageName',
|
||||||
|
stageCwId: 'stageCwId',
|
||||||
|
statusName: 'statusName',
|
||||||
|
statusCwId: 'statusCwId',
|
||||||
|
priorityName: 'priorityName',
|
||||||
|
priorityCwId: 'priorityCwId',
|
||||||
|
ratingName: 'ratingName',
|
||||||
|
ratingCwId: 'ratingCwId',
|
||||||
|
source: 'source',
|
||||||
|
campaignName: 'campaignName',
|
||||||
|
campaignCwId: 'campaignCwId',
|
||||||
|
primarySalesRepName: 'primarySalesRepName',
|
||||||
|
primarySalesRepIdentifier: 'primarySalesRepIdentifier',
|
||||||
|
primarySalesRepCwId: 'primarySalesRepCwId',
|
||||||
|
secondarySalesRepName: 'secondarySalesRepName',
|
||||||
|
secondarySalesRepIdentifier: 'secondarySalesRepIdentifier',
|
||||||
|
secondarySalesRepCwId: 'secondarySalesRepCwId',
|
||||||
|
companyCwId: 'companyCwId',
|
||||||
|
companyName: 'companyName',
|
||||||
|
contactCwId: 'contactCwId',
|
||||||
|
contactName: 'contactName',
|
||||||
|
siteCwId: 'siteCwId',
|
||||||
|
siteName: 'siteName',
|
||||||
|
customerPO: 'customerPO',
|
||||||
|
totalSalesTax: 'totalSalesTax',
|
||||||
|
locationName: 'locationName',
|
||||||
|
locationCwId: 'locationCwId',
|
||||||
|
departmentName: 'departmentName',
|
||||||
|
departmentCwId: 'departmentCwId',
|
||||||
|
expectedCloseDate: 'expectedCloseDate',
|
||||||
|
pipelineChangeDate: 'pipelineChangeDate',
|
||||||
|
dateBecameLead: 'dateBecameLead',
|
||||||
|
closedDate: 'closedDate',
|
||||||
|
closedFlag: 'closedFlag',
|
||||||
|
closedByName: 'closedByName',
|
||||||
|
closedByCwId: 'closedByCwId',
|
||||||
|
companyId: 'companyId',
|
||||||
|
cwLastUpdated: 'cwLastUpdated',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type OpportunityScalarFieldEnum = (typeof OpportunityScalarFieldEnum)[keyof typeof OpportunityScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const CredentialTypeScalarFieldEnum = {
|
export const CredentialTypeScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type * from './models/Role.ts'
|
|||||||
export type * from './models/UnifiSite.ts'
|
export type * from './models/UnifiSite.ts'
|
||||||
export type * from './models/Company.ts'
|
export type * from './models/Company.ts'
|
||||||
export type * from './models/CatalogItem.ts'
|
export type * from './models/CatalogItem.ts'
|
||||||
|
export type * from './models/Opportunity.ts'
|
||||||
export type * from './models/CredentialType.ts'
|
export type * from './models/CredentialType.ts'
|
||||||
export type * from './models/SecureValue.ts'
|
export type * from './models/SecureValue.ts'
|
||||||
export type * from './models/Credential.ts'
|
export type * from './models/Credential.ts'
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export type CatalogItemSumAggregateOutputType = {
|
|||||||
export type CatalogItemMinAggregateOutputType = {
|
export type CatalogItemMinAggregateOutputType = {
|
||||||
id: string | null
|
id: string | null
|
||||||
cwCatalogId: number | null
|
cwCatalogId: number | null
|
||||||
|
identifier: string | null
|
||||||
name: string | null
|
name: string | null
|
||||||
description: string | null
|
description: string | null
|
||||||
customerDescription: string | null
|
customerDescription: string | null
|
||||||
@@ -70,6 +71,7 @@ export type CatalogItemMinAggregateOutputType = {
|
|||||||
export type CatalogItemMaxAggregateOutputType = {
|
export type CatalogItemMaxAggregateOutputType = {
|
||||||
id: string | null
|
id: string | null
|
||||||
cwCatalogId: number | null
|
cwCatalogId: number | null
|
||||||
|
identifier: string | null
|
||||||
name: string | null
|
name: string | null
|
||||||
description: string | null
|
description: string | null
|
||||||
customerDescription: string | null
|
customerDescription: string | null
|
||||||
@@ -93,6 +95,7 @@ export type CatalogItemMaxAggregateOutputType = {
|
|||||||
export type CatalogItemCountAggregateOutputType = {
|
export type CatalogItemCountAggregateOutputType = {
|
||||||
id: number
|
id: number
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier: number
|
||||||
name: number
|
name: number
|
||||||
description: number
|
description: number
|
||||||
customerDescription: number
|
customerDescription: number
|
||||||
@@ -136,6 +139,7 @@ export type CatalogItemSumAggregateInputType = {
|
|||||||
export type CatalogItemMinAggregateInputType = {
|
export type CatalogItemMinAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
cwCatalogId?: true
|
cwCatalogId?: true
|
||||||
|
identifier?: true
|
||||||
name?: true
|
name?: true
|
||||||
description?: true
|
description?: true
|
||||||
customerDescription?: true
|
customerDescription?: true
|
||||||
@@ -159,6 +163,7 @@ export type CatalogItemMinAggregateInputType = {
|
|||||||
export type CatalogItemMaxAggregateInputType = {
|
export type CatalogItemMaxAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
cwCatalogId?: true
|
cwCatalogId?: true
|
||||||
|
identifier?: true
|
||||||
name?: true
|
name?: true
|
||||||
description?: true
|
description?: true
|
||||||
customerDescription?: true
|
customerDescription?: true
|
||||||
@@ -182,6 +187,7 @@ export type CatalogItemMaxAggregateInputType = {
|
|||||||
export type CatalogItemCountAggregateInputType = {
|
export type CatalogItemCountAggregateInputType = {
|
||||||
id?: true
|
id?: true
|
||||||
cwCatalogId?: true
|
cwCatalogId?: true
|
||||||
|
identifier?: true
|
||||||
name?: true
|
name?: true
|
||||||
description?: true
|
description?: true
|
||||||
customerDescription?: true
|
customerDescription?: true
|
||||||
@@ -292,6 +298,7 @@ export type CatalogItemGroupByArgs<ExtArgs extends runtime.Types.Extensions.Inte
|
|||||||
export type CatalogItemGroupByOutputType = {
|
export type CatalogItemGroupByOutputType = {
|
||||||
id: string
|
id: string
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier: string | null
|
||||||
name: string
|
name: string
|
||||||
description: string | null
|
description: string | null
|
||||||
customerDescription: string | null
|
customerDescription: string | null
|
||||||
@@ -338,6 +345,7 @@ export type CatalogItemWhereInput = {
|
|||||||
NOT?: Prisma.CatalogItemWhereInput | Prisma.CatalogItemWhereInput[]
|
NOT?: Prisma.CatalogItemWhereInput | Prisma.CatalogItemWhereInput[]
|
||||||
id?: Prisma.StringFilter<"CatalogItem"> | string
|
id?: Prisma.StringFilter<"CatalogItem"> | string
|
||||||
cwCatalogId?: Prisma.IntFilter<"CatalogItem"> | number
|
cwCatalogId?: Prisma.IntFilter<"CatalogItem"> | number
|
||||||
|
identifier?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
||||||
name?: Prisma.StringFilter<"CatalogItem"> | string
|
name?: Prisma.StringFilter<"CatalogItem"> | string
|
||||||
description?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
description?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
||||||
customerDescription?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
customerDescription?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
||||||
@@ -363,6 +371,7 @@ export type CatalogItemWhereInput = {
|
|||||||
export type CatalogItemOrderByWithRelationInput = {
|
export type CatalogItemOrderByWithRelationInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
cwCatalogId?: Prisma.SortOrder
|
cwCatalogId?: Prisma.SortOrder
|
||||||
|
identifier?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
description?: Prisma.SortOrderInput | Prisma.SortOrder
|
description?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
customerDescription?: Prisma.SortOrderInput | Prisma.SortOrder
|
customerDescription?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
@@ -388,6 +397,7 @@ export type CatalogItemOrderByWithRelationInput = {
|
|||||||
export type CatalogItemWhereUniqueInput = Prisma.AtLeast<{
|
export type CatalogItemWhereUniqueInput = Prisma.AtLeast<{
|
||||||
id?: string
|
id?: string
|
||||||
cwCatalogId?: number
|
cwCatalogId?: number
|
||||||
|
identifier?: string
|
||||||
AND?: Prisma.CatalogItemWhereInput | Prisma.CatalogItemWhereInput[]
|
AND?: Prisma.CatalogItemWhereInput | Prisma.CatalogItemWhereInput[]
|
||||||
OR?: Prisma.CatalogItemWhereInput[]
|
OR?: Prisma.CatalogItemWhereInput[]
|
||||||
NOT?: Prisma.CatalogItemWhereInput | Prisma.CatalogItemWhereInput[]
|
NOT?: Prisma.CatalogItemWhereInput | Prisma.CatalogItemWhereInput[]
|
||||||
@@ -411,11 +421,12 @@ export type CatalogItemWhereUniqueInput = Prisma.AtLeast<{
|
|||||||
updatedAt?: Prisma.DateTimeFilter<"CatalogItem"> | Date | string
|
updatedAt?: Prisma.DateTimeFilter<"CatalogItem"> | Date | string
|
||||||
linkedItems?: Prisma.CatalogItemListRelationFilter
|
linkedItems?: Prisma.CatalogItemListRelationFilter
|
||||||
linkedTo?: Prisma.CatalogItemListRelationFilter
|
linkedTo?: Prisma.CatalogItemListRelationFilter
|
||||||
}, "id" | "cwCatalogId">
|
}, "id" | "cwCatalogId" | "identifier">
|
||||||
|
|
||||||
export type CatalogItemOrderByWithAggregationInput = {
|
export type CatalogItemOrderByWithAggregationInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
cwCatalogId?: Prisma.SortOrder
|
cwCatalogId?: Prisma.SortOrder
|
||||||
|
identifier?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
description?: Prisma.SortOrderInput | Prisma.SortOrder
|
description?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
customerDescription?: Prisma.SortOrderInput | Prisma.SortOrder
|
customerDescription?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||||
@@ -447,6 +458,7 @@ export type CatalogItemScalarWhereWithAggregatesInput = {
|
|||||||
NOT?: Prisma.CatalogItemScalarWhereWithAggregatesInput | Prisma.CatalogItemScalarWhereWithAggregatesInput[]
|
NOT?: Prisma.CatalogItemScalarWhereWithAggregatesInput | Prisma.CatalogItemScalarWhereWithAggregatesInput[]
|
||||||
id?: Prisma.StringWithAggregatesFilter<"CatalogItem"> | string
|
id?: Prisma.StringWithAggregatesFilter<"CatalogItem"> | string
|
||||||
cwCatalogId?: Prisma.IntWithAggregatesFilter<"CatalogItem"> | number
|
cwCatalogId?: Prisma.IntWithAggregatesFilter<"CatalogItem"> | number
|
||||||
|
identifier?: Prisma.StringNullableWithAggregatesFilter<"CatalogItem"> | string | null
|
||||||
name?: Prisma.StringWithAggregatesFilter<"CatalogItem"> | string
|
name?: Prisma.StringWithAggregatesFilter<"CatalogItem"> | string
|
||||||
description?: Prisma.StringNullableWithAggregatesFilter<"CatalogItem"> | string | null
|
description?: Prisma.StringNullableWithAggregatesFilter<"CatalogItem"> | string | null
|
||||||
customerDescription?: Prisma.StringNullableWithAggregatesFilter<"CatalogItem"> | string | null
|
customerDescription?: Prisma.StringNullableWithAggregatesFilter<"CatalogItem"> | string | null
|
||||||
@@ -470,6 +482,7 @@ export type CatalogItemScalarWhereWithAggregatesInput = {
|
|||||||
export type CatalogItemCreateInput = {
|
export type CatalogItemCreateInput = {
|
||||||
id?: string
|
id?: string
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier?: string | null
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
customerDescription?: string | null
|
customerDescription?: string | null
|
||||||
@@ -495,6 +508,7 @@ export type CatalogItemCreateInput = {
|
|||||||
export type CatalogItemUncheckedCreateInput = {
|
export type CatalogItemUncheckedCreateInput = {
|
||||||
id?: string
|
id?: string
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier?: string | null
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
customerDescription?: string | null
|
customerDescription?: string | null
|
||||||
@@ -520,6 +534,7 @@ export type CatalogItemUncheckedCreateInput = {
|
|||||||
export type CatalogItemUpdateInput = {
|
export type CatalogItemUpdateInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -545,6 +560,7 @@ export type CatalogItemUpdateInput = {
|
|||||||
export type CatalogItemUncheckedUpdateInput = {
|
export type CatalogItemUncheckedUpdateInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -570,6 +586,7 @@ export type CatalogItemUncheckedUpdateInput = {
|
|||||||
export type CatalogItemCreateManyInput = {
|
export type CatalogItemCreateManyInput = {
|
||||||
id?: string
|
id?: string
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier?: string | null
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
customerDescription?: string | null
|
customerDescription?: string | null
|
||||||
@@ -593,6 +610,7 @@ export type CatalogItemCreateManyInput = {
|
|||||||
export type CatalogItemUpdateManyMutationInput = {
|
export type CatalogItemUpdateManyMutationInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -616,6 +634,7 @@ export type CatalogItemUpdateManyMutationInput = {
|
|||||||
export type CatalogItemUncheckedUpdateManyInput = {
|
export type CatalogItemUncheckedUpdateManyInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -649,6 +668,7 @@ export type CatalogItemOrderByRelationAggregateInput = {
|
|||||||
export type CatalogItemCountOrderByAggregateInput = {
|
export type CatalogItemCountOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
cwCatalogId?: Prisma.SortOrder
|
cwCatalogId?: Prisma.SortOrder
|
||||||
|
identifier?: Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
description?: Prisma.SortOrder
|
description?: Prisma.SortOrder
|
||||||
customerDescription?: Prisma.SortOrder
|
customerDescription?: Prisma.SortOrder
|
||||||
@@ -681,6 +701,7 @@ export type CatalogItemAvgOrderByAggregateInput = {
|
|||||||
export type CatalogItemMaxOrderByAggregateInput = {
|
export type CatalogItemMaxOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
cwCatalogId?: Prisma.SortOrder
|
cwCatalogId?: Prisma.SortOrder
|
||||||
|
identifier?: Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
description?: Prisma.SortOrder
|
description?: Prisma.SortOrder
|
||||||
customerDescription?: Prisma.SortOrder
|
customerDescription?: Prisma.SortOrder
|
||||||
@@ -704,6 +725,7 @@ export type CatalogItemMaxOrderByAggregateInput = {
|
|||||||
export type CatalogItemMinOrderByAggregateInput = {
|
export type CatalogItemMinOrderByAggregateInput = {
|
||||||
id?: Prisma.SortOrder
|
id?: Prisma.SortOrder
|
||||||
cwCatalogId?: Prisma.SortOrder
|
cwCatalogId?: Prisma.SortOrder
|
||||||
|
identifier?: Prisma.SortOrder
|
||||||
name?: Prisma.SortOrder
|
name?: Prisma.SortOrder
|
||||||
description?: Prisma.SortOrder
|
description?: Prisma.SortOrder
|
||||||
customerDescription?: Prisma.SortOrder
|
customerDescription?: Prisma.SortOrder
|
||||||
@@ -828,6 +850,7 @@ export type CatalogItemUncheckedUpdateManyWithoutLinkedItemsNestedInput = {
|
|||||||
export type CatalogItemCreateWithoutLinkedToInput = {
|
export type CatalogItemCreateWithoutLinkedToInput = {
|
||||||
id?: string
|
id?: string
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier?: string | null
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
customerDescription?: string | null
|
customerDescription?: string | null
|
||||||
@@ -852,6 +875,7 @@ export type CatalogItemCreateWithoutLinkedToInput = {
|
|||||||
export type CatalogItemUncheckedCreateWithoutLinkedToInput = {
|
export type CatalogItemUncheckedCreateWithoutLinkedToInput = {
|
||||||
id?: string
|
id?: string
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier?: string | null
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
customerDescription?: string | null
|
customerDescription?: string | null
|
||||||
@@ -881,6 +905,7 @@ export type CatalogItemCreateOrConnectWithoutLinkedToInput = {
|
|||||||
export type CatalogItemCreateWithoutLinkedItemsInput = {
|
export type CatalogItemCreateWithoutLinkedItemsInput = {
|
||||||
id?: string
|
id?: string
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier?: string | null
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
customerDescription?: string | null
|
customerDescription?: string | null
|
||||||
@@ -905,6 +930,7 @@ export type CatalogItemCreateWithoutLinkedItemsInput = {
|
|||||||
export type CatalogItemUncheckedCreateWithoutLinkedItemsInput = {
|
export type CatalogItemUncheckedCreateWithoutLinkedItemsInput = {
|
||||||
id?: string
|
id?: string
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier?: string | null
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
customerDescription?: string | null
|
customerDescription?: string | null
|
||||||
@@ -953,6 +979,7 @@ export type CatalogItemScalarWhereInput = {
|
|||||||
NOT?: Prisma.CatalogItemScalarWhereInput | Prisma.CatalogItemScalarWhereInput[]
|
NOT?: Prisma.CatalogItemScalarWhereInput | Prisma.CatalogItemScalarWhereInput[]
|
||||||
id?: Prisma.StringFilter<"CatalogItem"> | string
|
id?: Prisma.StringFilter<"CatalogItem"> | string
|
||||||
cwCatalogId?: Prisma.IntFilter<"CatalogItem"> | number
|
cwCatalogId?: Prisma.IntFilter<"CatalogItem"> | number
|
||||||
|
identifier?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
||||||
name?: Prisma.StringFilter<"CatalogItem"> | string
|
name?: Prisma.StringFilter<"CatalogItem"> | string
|
||||||
description?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
description?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
||||||
customerDescription?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
customerDescription?: Prisma.StringNullableFilter<"CatalogItem"> | string | null
|
||||||
@@ -992,6 +1019,7 @@ export type CatalogItemUpdateManyWithWhereWithoutLinkedItemsInput = {
|
|||||||
export type CatalogItemUpdateWithoutLinkedToInput = {
|
export type CatalogItemUpdateWithoutLinkedToInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -1016,6 +1044,7 @@ export type CatalogItemUpdateWithoutLinkedToInput = {
|
|||||||
export type CatalogItemUncheckedUpdateWithoutLinkedToInput = {
|
export type CatalogItemUncheckedUpdateWithoutLinkedToInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -1040,6 +1069,7 @@ export type CatalogItemUncheckedUpdateWithoutLinkedToInput = {
|
|||||||
export type CatalogItemUncheckedUpdateManyWithoutLinkedToInput = {
|
export type CatalogItemUncheckedUpdateManyWithoutLinkedToInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -1063,6 +1093,7 @@ export type CatalogItemUncheckedUpdateManyWithoutLinkedToInput = {
|
|||||||
export type CatalogItemUpdateWithoutLinkedItemsInput = {
|
export type CatalogItemUpdateWithoutLinkedItemsInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -1087,6 +1118,7 @@ export type CatalogItemUpdateWithoutLinkedItemsInput = {
|
|||||||
export type CatalogItemUncheckedUpdateWithoutLinkedItemsInput = {
|
export type CatalogItemUncheckedUpdateWithoutLinkedItemsInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -1111,6 +1143,7 @@ export type CatalogItemUncheckedUpdateWithoutLinkedItemsInput = {
|
|||||||
export type CatalogItemUncheckedUpdateManyWithoutLinkedItemsInput = {
|
export type CatalogItemUncheckedUpdateManyWithoutLinkedItemsInput = {
|
||||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
cwCatalogId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
identifier?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
customerDescription?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
@@ -1174,6 +1207,7 @@ export type CatalogItemCountOutputTypeCountLinkedToArgs<ExtArgs extends runtime.
|
|||||||
export type CatalogItemSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type CatalogItemSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
cwCatalogId?: boolean
|
cwCatalogId?: boolean
|
||||||
|
identifier?: boolean
|
||||||
name?: boolean
|
name?: boolean
|
||||||
description?: boolean
|
description?: boolean
|
||||||
customerDescription?: boolean
|
customerDescription?: boolean
|
||||||
@@ -1200,6 +1234,7 @@ export type CatalogItemSelect<ExtArgs extends runtime.Types.Extensions.InternalA
|
|||||||
export type CatalogItemSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type CatalogItemSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
cwCatalogId?: boolean
|
cwCatalogId?: boolean
|
||||||
|
identifier?: boolean
|
||||||
name?: boolean
|
name?: boolean
|
||||||
description?: boolean
|
description?: boolean
|
||||||
customerDescription?: boolean
|
customerDescription?: boolean
|
||||||
@@ -1223,6 +1258,7 @@ export type CatalogItemSelectCreateManyAndReturn<ExtArgs extends runtime.Types.E
|
|||||||
export type CatalogItemSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type CatalogItemSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
cwCatalogId?: boolean
|
cwCatalogId?: boolean
|
||||||
|
identifier?: boolean
|
||||||
name?: boolean
|
name?: boolean
|
||||||
description?: boolean
|
description?: boolean
|
||||||
customerDescription?: boolean
|
customerDescription?: boolean
|
||||||
@@ -1246,6 +1282,7 @@ export type CatalogItemSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.E
|
|||||||
export type CatalogItemSelectScalar = {
|
export type CatalogItemSelectScalar = {
|
||||||
id?: boolean
|
id?: boolean
|
||||||
cwCatalogId?: boolean
|
cwCatalogId?: boolean
|
||||||
|
identifier?: boolean
|
||||||
name?: boolean
|
name?: boolean
|
||||||
description?: boolean
|
description?: boolean
|
||||||
customerDescription?: boolean
|
customerDescription?: boolean
|
||||||
@@ -1266,7 +1303,7 @@ export type CatalogItemSelectScalar = {
|
|||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CatalogItemOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "cwCatalogId" | "name" | "description" | "customerDescription" | "internalNotes" | "manufacturer" | "manufactureCwId" | "partNumber" | "vendorName" | "vendorSku" | "vendorCwId" | "price" | "cost" | "inactive" | "salesTaxable" | "onHand" | "cwLastUpdated" | "createdAt" | "updatedAt", ExtArgs["result"]["catalogItem"]>
|
export type CatalogItemOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "cwCatalogId" | "identifier" | "name" | "description" | "customerDescription" | "internalNotes" | "manufacturer" | "manufactureCwId" | "partNumber" | "vendorName" | "vendorSku" | "vendorCwId" | "price" | "cost" | "inactive" | "salesTaxable" | "onHand" | "cwLastUpdated" | "createdAt" | "updatedAt", ExtArgs["result"]["catalogItem"]>
|
||||||
export type CatalogItemInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type CatalogItemInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
linkedItems?: boolean | Prisma.CatalogItem$linkedItemsArgs<ExtArgs>
|
linkedItems?: boolean | Prisma.CatalogItem$linkedItemsArgs<ExtArgs>
|
||||||
linkedTo?: boolean | Prisma.CatalogItem$linkedToArgs<ExtArgs>
|
linkedTo?: boolean | Prisma.CatalogItem$linkedToArgs<ExtArgs>
|
||||||
@@ -1284,6 +1321,7 @@ export type $CatalogItemPayload<ExtArgs extends runtime.Types.Extensions.Interna
|
|||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
cwCatalogId: number
|
cwCatalogId: number
|
||||||
|
identifier: string | null
|
||||||
name: string
|
name: string
|
||||||
description: string | null
|
description: string | null
|
||||||
customerDescription: string | null
|
customerDescription: string | null
|
||||||
@@ -1729,6 +1767,7 @@ export interface Prisma__CatalogItemClient<T, Null = never, ExtArgs extends runt
|
|||||||
export interface CatalogItemFieldRefs {
|
export interface CatalogItemFieldRefs {
|
||||||
readonly id: Prisma.FieldRef<"CatalogItem", 'String'>
|
readonly id: Prisma.FieldRef<"CatalogItem", 'String'>
|
||||||
readonly cwCatalogId: Prisma.FieldRef<"CatalogItem", 'Int'>
|
readonly cwCatalogId: Prisma.FieldRef<"CatalogItem", 'Int'>
|
||||||
|
readonly identifier: Prisma.FieldRef<"CatalogItem", 'String'>
|
||||||
readonly name: Prisma.FieldRef<"CatalogItem", 'String'>
|
readonly name: Prisma.FieldRef<"CatalogItem", 'String'>
|
||||||
readonly description: Prisma.FieldRef<"CatalogItem", 'String'>
|
readonly description: Prisma.FieldRef<"CatalogItem", 'String'>
|
||||||
readonly customerDescription: Prisma.FieldRef<"CatalogItem", 'String'>
|
readonly customerDescription: Prisma.FieldRef<"CatalogItem", 'String'>
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ export type CompanyWhereInput = {
|
|||||||
updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string
|
updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string
|
||||||
credentials?: Prisma.CredentialListRelationFilter
|
credentials?: Prisma.CredentialListRelationFilter
|
||||||
unifiSites?: Prisma.UnifiSiteListRelationFilter
|
unifiSites?: Prisma.UnifiSiteListRelationFilter
|
||||||
|
opportunities?: Prisma.OpportunityListRelationFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyOrderByWithRelationInput = {
|
export type CompanyOrderByWithRelationInput = {
|
||||||
@@ -237,6 +238,7 @@ export type CompanyOrderByWithRelationInput = {
|
|||||||
updatedAt?: Prisma.SortOrder
|
updatedAt?: Prisma.SortOrder
|
||||||
credentials?: Prisma.CredentialOrderByRelationAggregateInput
|
credentials?: Prisma.CredentialOrderByRelationAggregateInput
|
||||||
unifiSites?: Prisma.UnifiSiteOrderByRelationAggregateInput
|
unifiSites?: Prisma.UnifiSiteOrderByRelationAggregateInput
|
||||||
|
opportunities?: Prisma.OpportunityOrderByRelationAggregateInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyWhereUniqueInput = Prisma.AtLeast<{
|
export type CompanyWhereUniqueInput = Prisma.AtLeast<{
|
||||||
@@ -251,6 +253,7 @@ export type CompanyWhereUniqueInput = Prisma.AtLeast<{
|
|||||||
updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string
|
updatedAt?: Prisma.DateTimeFilter<"Company"> | Date | string
|
||||||
credentials?: Prisma.CredentialListRelationFilter
|
credentials?: Prisma.CredentialListRelationFilter
|
||||||
unifiSites?: Prisma.UnifiSiteListRelationFilter
|
unifiSites?: Prisma.UnifiSiteListRelationFilter
|
||||||
|
opportunities?: Prisma.OpportunityListRelationFilter
|
||||||
}, "id" | "cw_CompanyId" | "cw_Identifier">
|
}, "id" | "cw_CompanyId" | "cw_Identifier">
|
||||||
|
|
||||||
export type CompanyOrderByWithAggregationInput = {
|
export type CompanyOrderByWithAggregationInput = {
|
||||||
@@ -288,6 +291,7 @@ export type CompanyCreateInput = {
|
|||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
credentials?: Prisma.CredentialCreateNestedManyWithoutCompanyInput
|
credentials?: Prisma.CredentialCreateNestedManyWithoutCompanyInput
|
||||||
unifiSites?: Prisma.UnifiSiteCreateNestedManyWithoutCompanyInput
|
unifiSites?: Prisma.UnifiSiteCreateNestedManyWithoutCompanyInput
|
||||||
|
opportunities?: Prisma.OpportunityCreateNestedManyWithoutCompanyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyUncheckedCreateInput = {
|
export type CompanyUncheckedCreateInput = {
|
||||||
@@ -299,6 +303,7 @@ export type CompanyUncheckedCreateInput = {
|
|||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
credentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutCompanyInput
|
credentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutCompanyInput
|
||||||
unifiSites?: Prisma.UnifiSiteUncheckedCreateNestedManyWithoutCompanyInput
|
unifiSites?: Prisma.UnifiSiteUncheckedCreateNestedManyWithoutCompanyInput
|
||||||
|
opportunities?: Prisma.OpportunityUncheckedCreateNestedManyWithoutCompanyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyUpdateInput = {
|
export type CompanyUpdateInput = {
|
||||||
@@ -310,6 +315,7 @@ export type CompanyUpdateInput = {
|
|||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
credentials?: Prisma.CredentialUpdateManyWithoutCompanyNestedInput
|
credentials?: Prisma.CredentialUpdateManyWithoutCompanyNestedInput
|
||||||
unifiSites?: Prisma.UnifiSiteUpdateManyWithoutCompanyNestedInput
|
unifiSites?: Prisma.UnifiSiteUpdateManyWithoutCompanyNestedInput
|
||||||
|
opportunities?: Prisma.OpportunityUpdateManyWithoutCompanyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyUncheckedUpdateInput = {
|
export type CompanyUncheckedUpdateInput = {
|
||||||
@@ -321,6 +327,7 @@ export type CompanyUncheckedUpdateInput = {
|
|||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
credentials?: Prisma.CredentialUncheckedUpdateManyWithoutCompanyNestedInput
|
credentials?: Prisma.CredentialUncheckedUpdateManyWithoutCompanyNestedInput
|
||||||
unifiSites?: Prisma.UnifiSiteUncheckedUpdateManyWithoutCompanyNestedInput
|
unifiSites?: Prisma.UnifiSiteUncheckedUpdateManyWithoutCompanyNestedInput
|
||||||
|
opportunities?: Prisma.OpportunityUncheckedUpdateManyWithoutCompanyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyCreateManyInput = {
|
export type CompanyCreateManyInput = {
|
||||||
@@ -419,6 +426,22 @@ export type IntFieldUpdateOperationsInput = {
|
|||||||
divide?: number
|
divide?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CompanyCreateNestedOneWithoutOpportunitiesInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.CompanyCreateWithoutOpportunitiesInput, Prisma.CompanyUncheckedCreateWithoutOpportunitiesInput>
|
||||||
|
connectOrCreate?: Prisma.CompanyCreateOrConnectWithoutOpportunitiesInput
|
||||||
|
connect?: Prisma.CompanyWhereUniqueInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompanyUpdateOneWithoutOpportunitiesNestedInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.CompanyCreateWithoutOpportunitiesInput, Prisma.CompanyUncheckedCreateWithoutOpportunitiesInput>
|
||||||
|
connectOrCreate?: Prisma.CompanyCreateOrConnectWithoutOpportunitiesInput
|
||||||
|
upsert?: Prisma.CompanyUpsertWithoutOpportunitiesInput
|
||||||
|
disconnect?: Prisma.CompanyWhereInput | boolean
|
||||||
|
delete?: Prisma.CompanyWhereInput | boolean
|
||||||
|
connect?: Prisma.CompanyWhereUniqueInput
|
||||||
|
update?: Prisma.XOR<Prisma.XOR<Prisma.CompanyUpdateToOneWithWhereWithoutOpportunitiesInput, Prisma.CompanyUpdateWithoutOpportunitiesInput>, Prisma.CompanyUncheckedUpdateWithoutOpportunitiesInput>
|
||||||
|
}
|
||||||
|
|
||||||
export type CompanyCreateNestedOneWithoutCredentialsInput = {
|
export type CompanyCreateNestedOneWithoutCredentialsInput = {
|
||||||
create?: Prisma.XOR<Prisma.CompanyCreateWithoutCredentialsInput, Prisma.CompanyUncheckedCreateWithoutCredentialsInput>
|
create?: Prisma.XOR<Prisma.CompanyCreateWithoutCredentialsInput, Prisma.CompanyUncheckedCreateWithoutCredentialsInput>
|
||||||
connectOrCreate?: Prisma.CompanyCreateOrConnectWithoutCredentialsInput
|
connectOrCreate?: Prisma.CompanyCreateOrConnectWithoutCredentialsInput
|
||||||
@@ -441,6 +464,7 @@ export type CompanyCreateWithoutUnifiSitesInput = {
|
|||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
credentials?: Prisma.CredentialCreateNestedManyWithoutCompanyInput
|
credentials?: Prisma.CredentialCreateNestedManyWithoutCompanyInput
|
||||||
|
opportunities?: Prisma.OpportunityCreateNestedManyWithoutCompanyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyUncheckedCreateWithoutUnifiSitesInput = {
|
export type CompanyUncheckedCreateWithoutUnifiSitesInput = {
|
||||||
@@ -451,6 +475,7 @@ export type CompanyUncheckedCreateWithoutUnifiSitesInput = {
|
|||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
credentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutCompanyInput
|
credentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutCompanyInput
|
||||||
|
opportunities?: Prisma.OpportunityUncheckedCreateNestedManyWithoutCompanyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyCreateOrConnectWithoutUnifiSitesInput = {
|
export type CompanyCreateOrConnectWithoutUnifiSitesInput = {
|
||||||
@@ -477,6 +502,7 @@ export type CompanyUpdateWithoutUnifiSitesInput = {
|
|||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
credentials?: Prisma.CredentialUpdateManyWithoutCompanyNestedInput
|
credentials?: Prisma.CredentialUpdateManyWithoutCompanyNestedInput
|
||||||
|
opportunities?: Prisma.OpportunityUpdateManyWithoutCompanyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyUncheckedUpdateWithoutUnifiSitesInput = {
|
export type CompanyUncheckedUpdateWithoutUnifiSitesInput = {
|
||||||
@@ -487,6 +513,67 @@ export type CompanyUncheckedUpdateWithoutUnifiSitesInput = {
|
|||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
credentials?: Prisma.CredentialUncheckedUpdateManyWithoutCompanyNestedInput
|
credentials?: Prisma.CredentialUncheckedUpdateManyWithoutCompanyNestedInput
|
||||||
|
opportunities?: Prisma.OpportunityUncheckedUpdateManyWithoutCompanyNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompanyCreateWithoutOpportunitiesInput = {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
cw_CompanyId: number
|
||||||
|
cw_Identifier: string
|
||||||
|
createdAt?: Date | string
|
||||||
|
updatedAt?: Date | string
|
||||||
|
credentials?: Prisma.CredentialCreateNestedManyWithoutCompanyInput
|
||||||
|
unifiSites?: Prisma.UnifiSiteCreateNestedManyWithoutCompanyInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompanyUncheckedCreateWithoutOpportunitiesInput = {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
cw_CompanyId: number
|
||||||
|
cw_Identifier: string
|
||||||
|
createdAt?: Date | string
|
||||||
|
updatedAt?: Date | string
|
||||||
|
credentials?: Prisma.CredentialUncheckedCreateNestedManyWithoutCompanyInput
|
||||||
|
unifiSites?: Prisma.UnifiSiteUncheckedCreateNestedManyWithoutCompanyInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompanyCreateOrConnectWithoutOpportunitiesInput = {
|
||||||
|
where: Prisma.CompanyWhereUniqueInput
|
||||||
|
create: Prisma.XOR<Prisma.CompanyCreateWithoutOpportunitiesInput, Prisma.CompanyUncheckedCreateWithoutOpportunitiesInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompanyUpsertWithoutOpportunitiesInput = {
|
||||||
|
update: Prisma.XOR<Prisma.CompanyUpdateWithoutOpportunitiesInput, Prisma.CompanyUncheckedUpdateWithoutOpportunitiesInput>
|
||||||
|
create: Prisma.XOR<Prisma.CompanyCreateWithoutOpportunitiesInput, Prisma.CompanyUncheckedCreateWithoutOpportunitiesInput>
|
||||||
|
where?: Prisma.CompanyWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompanyUpdateToOneWithWhereWithoutOpportunitiesInput = {
|
||||||
|
where?: Prisma.CompanyWhereInput
|
||||||
|
data: Prisma.XOR<Prisma.CompanyUpdateWithoutOpportunitiesInput, Prisma.CompanyUncheckedUpdateWithoutOpportunitiesInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompanyUpdateWithoutOpportunitiesInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
cw_CompanyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
credentials?: Prisma.CredentialUpdateManyWithoutCompanyNestedInput
|
||||||
|
unifiSites?: Prisma.UnifiSiteUpdateManyWithoutCompanyNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompanyUncheckedUpdateWithoutOpportunitiesInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
cw_CompanyId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
cw_Identifier?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
credentials?: Prisma.CredentialUncheckedUpdateManyWithoutCompanyNestedInput
|
||||||
|
unifiSites?: Prisma.UnifiSiteUncheckedUpdateManyWithoutCompanyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyCreateWithoutCredentialsInput = {
|
export type CompanyCreateWithoutCredentialsInput = {
|
||||||
@@ -497,6 +584,7 @@ export type CompanyCreateWithoutCredentialsInput = {
|
|||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
unifiSites?: Prisma.UnifiSiteCreateNestedManyWithoutCompanyInput
|
unifiSites?: Prisma.UnifiSiteCreateNestedManyWithoutCompanyInput
|
||||||
|
opportunities?: Prisma.OpportunityCreateNestedManyWithoutCompanyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyUncheckedCreateWithoutCredentialsInput = {
|
export type CompanyUncheckedCreateWithoutCredentialsInput = {
|
||||||
@@ -507,6 +595,7 @@ export type CompanyUncheckedCreateWithoutCredentialsInput = {
|
|||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
updatedAt?: Date | string
|
updatedAt?: Date | string
|
||||||
unifiSites?: Prisma.UnifiSiteUncheckedCreateNestedManyWithoutCompanyInput
|
unifiSites?: Prisma.UnifiSiteUncheckedCreateNestedManyWithoutCompanyInput
|
||||||
|
opportunities?: Prisma.OpportunityUncheckedCreateNestedManyWithoutCompanyInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyCreateOrConnectWithoutCredentialsInput = {
|
export type CompanyCreateOrConnectWithoutCredentialsInput = {
|
||||||
@@ -533,6 +622,7 @@ export type CompanyUpdateWithoutCredentialsInput = {
|
|||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
unifiSites?: Prisma.UnifiSiteUpdateManyWithoutCompanyNestedInput
|
unifiSites?: Prisma.UnifiSiteUpdateManyWithoutCompanyNestedInput
|
||||||
|
opportunities?: Prisma.OpportunityUpdateManyWithoutCompanyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyUncheckedUpdateWithoutCredentialsInput = {
|
export type CompanyUncheckedUpdateWithoutCredentialsInput = {
|
||||||
@@ -543,6 +633,7 @@ export type CompanyUncheckedUpdateWithoutCredentialsInput = {
|
|||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
unifiSites?: Prisma.UnifiSiteUncheckedUpdateManyWithoutCompanyNestedInput
|
unifiSites?: Prisma.UnifiSiteUncheckedUpdateManyWithoutCompanyNestedInput
|
||||||
|
opportunities?: Prisma.OpportunityUncheckedUpdateManyWithoutCompanyNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -553,11 +644,13 @@ export type CompanyUncheckedUpdateWithoutCredentialsInput = {
|
|||||||
export type CompanyCountOutputType = {
|
export type CompanyCountOutputType = {
|
||||||
credentials: number
|
credentials: number
|
||||||
unifiSites: number
|
unifiSites: number
|
||||||
|
opportunities: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompanyCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type CompanyCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
credentials?: boolean | CompanyCountOutputTypeCountCredentialsArgs
|
credentials?: boolean | CompanyCountOutputTypeCountCredentialsArgs
|
||||||
unifiSites?: boolean | CompanyCountOutputTypeCountUnifiSitesArgs
|
unifiSites?: boolean | CompanyCountOutputTypeCountUnifiSitesArgs
|
||||||
|
opportunities?: boolean | CompanyCountOutputTypeCountOpportunitiesArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -584,6 +677,13 @@ export type CompanyCountOutputTypeCountUnifiSitesArgs<ExtArgs extends runtime.Ty
|
|||||||
where?: Prisma.UnifiSiteWhereInput
|
where?: Prisma.UnifiSiteWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CompanyCountOutputType without action
|
||||||
|
*/
|
||||||
|
export type CompanyCountOutputTypeCountOpportunitiesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
where?: Prisma.OpportunityWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type CompanySelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type CompanySelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
@@ -594,6 +694,7 @@ export type CompanySelect<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
|||||||
updatedAt?: boolean
|
updatedAt?: boolean
|
||||||
credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs>
|
credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs>
|
||||||
unifiSites?: boolean | Prisma.Company$unifiSitesArgs<ExtArgs>
|
unifiSites?: boolean | Prisma.Company$unifiSitesArgs<ExtArgs>
|
||||||
|
opportunities?: boolean | Prisma.Company$opportunitiesArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["company"]>
|
}, ExtArgs["result"]["company"]>
|
||||||
|
|
||||||
@@ -628,6 +729,7 @@ export type CompanyOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
|||||||
export type CompanyInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type CompanyInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs>
|
credentials?: boolean | Prisma.Company$credentialsArgs<ExtArgs>
|
||||||
unifiSites?: boolean | Prisma.Company$unifiSitesArgs<ExtArgs>
|
unifiSites?: boolean | Prisma.Company$unifiSitesArgs<ExtArgs>
|
||||||
|
opportunities?: boolean | Prisma.Company$opportunitiesArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.CompanyCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type CompanyIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
export type CompanyIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
||||||
@@ -638,6 +740,7 @@ export type $CompanyPayload<ExtArgs extends runtime.Types.Extensions.InternalArg
|
|||||||
objects: {
|
objects: {
|
||||||
credentials: Prisma.$CredentialPayload<ExtArgs>[]
|
credentials: Prisma.$CredentialPayload<ExtArgs>[]
|
||||||
unifiSites: Prisma.$UnifiSitePayload<ExtArgs>[]
|
unifiSites: Prisma.$UnifiSitePayload<ExtArgs>[]
|
||||||
|
opportunities: Prisma.$OpportunityPayload<ExtArgs>[]
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
@@ -1042,6 +1145,7 @@ export interface Prisma__CompanyClient<T, Null = never, ExtArgs extends runtime.
|
|||||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||||
credentials<T extends Prisma.Company$credentialsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Company$credentialsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$CredentialPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
credentials<T extends Prisma.Company$credentialsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Company$credentialsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$CredentialPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
unifiSites<T extends Prisma.Company$unifiSitesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Company$unifiSitesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$UnifiSitePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
unifiSites<T extends Prisma.Company$unifiSitesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Company$unifiSitesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$UnifiSitePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
|
opportunities<T extends Prisma.Company$opportunitiesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Company$opportunitiesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$OpportunityPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||||
@@ -1512,6 +1616,30 @@ export type Company$unifiSitesArgs<ExtArgs extends runtime.Types.Extensions.Inte
|
|||||||
distinct?: Prisma.UnifiSiteScalarFieldEnum | Prisma.UnifiSiteScalarFieldEnum[]
|
distinct?: Prisma.UnifiSiteScalarFieldEnum | Prisma.UnifiSiteScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Company.opportunities
|
||||||
|
*/
|
||||||
|
export type Company$opportunitiesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the Opportunity
|
||||||
|
*/
|
||||||
|
select?: Prisma.OpportunitySelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the Opportunity
|
||||||
|
*/
|
||||||
|
omit?: Prisma.OpportunityOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.OpportunityInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.OpportunityWhereInput
|
||||||
|
orderBy?: Prisma.OpportunityOrderByWithRelationInput | Prisma.OpportunityOrderByWithRelationInput[]
|
||||||
|
cursor?: Prisma.OpportunityWhereUniqueInput
|
||||||
|
take?: number
|
||||||
|
skip?: number
|
||||||
|
distinct?: Prisma.OpportunityScalarFieldEnum | Prisma.OpportunityScalarFieldEnum[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Company without action
|
* Company without action
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Opportunity" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"cwOpportunityId" INTEGER NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"notes" TEXT,
|
||||||
|
"typeName" TEXT,
|
||||||
|
"typeCwId" INTEGER,
|
||||||
|
"stageName" TEXT,
|
||||||
|
"stageCwId" INTEGER,
|
||||||
|
"statusName" TEXT,
|
||||||
|
"statusCwId" INTEGER,
|
||||||
|
"priorityName" TEXT,
|
||||||
|
"priorityCwId" INTEGER,
|
||||||
|
"ratingName" TEXT,
|
||||||
|
"ratingCwId" INTEGER,
|
||||||
|
"source" TEXT,
|
||||||
|
"campaignName" TEXT,
|
||||||
|
"campaignCwId" INTEGER,
|
||||||
|
"primarySalesRepName" TEXT,
|
||||||
|
"primarySalesRepIdentifier" TEXT,
|
||||||
|
"primarySalesRepCwId" INTEGER,
|
||||||
|
"secondarySalesRepName" TEXT,
|
||||||
|
"secondarySalesRepIdentifier" TEXT,
|
||||||
|
"secondarySalesRepCwId" INTEGER,
|
||||||
|
"companyCwId" INTEGER,
|
||||||
|
"companyName" TEXT,
|
||||||
|
"contactCwId" INTEGER,
|
||||||
|
"contactName" TEXT,
|
||||||
|
"siteCwId" INTEGER,
|
||||||
|
"siteName" TEXT,
|
||||||
|
"customerPO" TEXT,
|
||||||
|
"totalSalesTax" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"locationName" TEXT,
|
||||||
|
"locationCwId" INTEGER,
|
||||||
|
"departmentName" TEXT,
|
||||||
|
"departmentCwId" INTEGER,
|
||||||
|
"expectedCloseDate" TIMESTAMP(3),
|
||||||
|
"pipelineChangeDate" TIMESTAMP(3),
|
||||||
|
"dateBecameLead" TIMESTAMP(3),
|
||||||
|
"closedDate" TIMESTAMP(3),
|
||||||
|
"closedFlag" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"closedByName" TEXT,
|
||||||
|
"closedByCwId" INTEGER,
|
||||||
|
"companyId" TEXT,
|
||||||
|
"cwLastUpdated" TIMESTAMP(3),
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Opportunity_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Opportunity_cwOpportunityId_key" ON "Opportunity"("cwOpportunityId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Opportunity" ADD CONSTRAINT "Opportunity_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
+71
-2
@@ -75,8 +75,9 @@ model Company {
|
|||||||
cw_CompanyId Int @unique
|
cw_CompanyId Int @unique
|
||||||
cw_Identifier String @unique
|
cw_Identifier String @unique
|
||||||
|
|
||||||
credentials Credential[]
|
credentials Credential[]
|
||||||
unifiSites UnifiSite[]
|
unifiSites UnifiSite[]
|
||||||
|
opportunities Opportunity[]
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -85,6 +86,7 @@ model Company {
|
|||||||
model CatalogItem {
|
model CatalogItem {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
cwCatalogId Int @unique
|
cwCatalogId Int @unique
|
||||||
|
identifier String? @unique
|
||||||
name String
|
name String
|
||||||
description String?
|
description String?
|
||||||
customerDescription String?
|
customerDescription String?
|
||||||
@@ -115,6 +117,73 @@ model CatalogItem {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Opportunity {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
cwOpportunityId Int @unique
|
||||||
|
name String
|
||||||
|
notes String?
|
||||||
|
|
||||||
|
// Stage / status / priority / type / rating stored as JSON references
|
||||||
|
// so we don't need separate lookup tables for CW enums
|
||||||
|
typeName String?
|
||||||
|
typeCwId Int?
|
||||||
|
stageName String?
|
||||||
|
stageCwId Int?
|
||||||
|
statusName String?
|
||||||
|
statusCwId Int?
|
||||||
|
priorityName String?
|
||||||
|
priorityCwId Int?
|
||||||
|
ratingName String?
|
||||||
|
ratingCwId Int?
|
||||||
|
source String?
|
||||||
|
campaignName String?
|
||||||
|
campaignCwId Int?
|
||||||
|
|
||||||
|
// Sales rep references
|
||||||
|
primarySalesRepName String?
|
||||||
|
primarySalesRepIdentifier String?
|
||||||
|
primarySalesRepCwId Int?
|
||||||
|
secondarySalesRepName String?
|
||||||
|
secondarySalesRepIdentifier String?
|
||||||
|
secondarySalesRepCwId Int?
|
||||||
|
|
||||||
|
// Company / contact / site
|
||||||
|
companyCwId Int?
|
||||||
|
companyName String?
|
||||||
|
contactCwId Int?
|
||||||
|
contactName String?
|
||||||
|
siteCwId Int?
|
||||||
|
siteName String?
|
||||||
|
customerPO String?
|
||||||
|
|
||||||
|
// Financials
|
||||||
|
totalSalesTax Float @default(0)
|
||||||
|
|
||||||
|
// Location / department
|
||||||
|
locationName String?
|
||||||
|
locationCwId Int?
|
||||||
|
departmentName String?
|
||||||
|
departmentCwId Int?
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
expectedCloseDate DateTime?
|
||||||
|
pipelineChangeDate DateTime?
|
||||||
|
dateBecameLead DateTime?
|
||||||
|
closedDate DateTime?
|
||||||
|
closedFlag Boolean @default(false)
|
||||||
|
closedByName String?
|
||||||
|
closedByCwId Int?
|
||||||
|
|
||||||
|
// Internal relation to Company (optional, linked by cwCompanyId)
|
||||||
|
companyId String?
|
||||||
|
company Company? @relation(fields: [companyId], references: [id])
|
||||||
|
|
||||||
|
cwLastUpdated DateTime?
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
model CredentialType {
|
model CredentialType {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { procurement } from "../../../managers/procurement";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/procurement/items/:identifier */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/items/:identifier"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const includeLinkedItems = c.req.query("includeLinkedItems") === "true";
|
||||||
|
|
||||||
|
const item = await procurement.fetchItem(identifier);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Catalog item fetched successfully!",
|
||||||
|
item.toJson({ includeLinkedItems }),
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["procurement.catalog.fetch"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { procurement } from "../../../managers/procurement";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/procurement/items/:identifier/linked */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/items/:identifier/linked"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const item = await procurement.fetchItem(identifier);
|
||||||
|
|
||||||
|
const linkedItems = item.getLinkedItems().map((linked) => linked.toJson());
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Linked catalog items fetched successfully!",
|
||||||
|
linkedItems,
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["procurement.catalog.fetch"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { procurement } from "../../../managers/procurement";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* POST /v1/procurement/items/:identifier/link */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/items/:identifier/link"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const body = await c.req.json();
|
||||||
|
const schema = z.object({ targetId: z.string() }).strict();
|
||||||
|
const { targetId } = schema.parse(body);
|
||||||
|
|
||||||
|
const item = await procurement.linkItems(identifier, targetId);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Catalog item linked successfully!",
|
||||||
|
item.toJson({ includeLinkedItems: true }),
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["procurement.catalog.link"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { procurement } from "../../../managers/procurement";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/procurement/items/:identifier/refresh-inventory */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/items/:identifier/refresh-inventory"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const item = await procurement.fetchItem(identifier);
|
||||||
|
|
||||||
|
await item.refreshInventory();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Inventory refreshed successfully!",
|
||||||
|
item.toJson(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["procurement.catalog.inventory.refresh"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { procurement } from "../../../managers/procurement";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* POST /v1/procurement/items/:identifier/unlink */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/items/:identifier/unlink"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const body = await c.req.json();
|
||||||
|
const schema = z.object({ targetId: z.string() }).strict();
|
||||||
|
const { targetId } = schema.parse(body);
|
||||||
|
|
||||||
|
const item = await procurement.unlinkItems(identifier, targetId);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Catalog item unlinked successfully!",
|
||||||
|
item.toJson({ includeLinkedItems: true }),
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["procurement.catalog.link"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { procurement } from "../../managers/procurement";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/procurement/count */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/count"],
|
||||||
|
async (c) => {
|
||||||
|
const activeOnly = c.req.query("activeOnly") === "true";
|
||||||
|
|
||||||
|
const count = await procurement.count({ activeOnly });
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Catalog item count fetched successfully!",
|
||||||
|
{ count },
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["procurement.catalog.fetch.many"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { procurement } from "../../managers/procurement";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/procurement/items */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/items"],
|
||||||
|
async (c) => {
|
||||||
|
const page = Number(c.req.query("page") ?? 1);
|
||||||
|
const rpp = Number(c.req.query("rpp") ?? 30);
|
||||||
|
const search = c.req.query("search") as string;
|
||||||
|
const includeInactive = c.req.query("includeInactive") === "true";
|
||||||
|
|
||||||
|
const data = search
|
||||||
|
? await procurement.search(search, page, rpp, { includeInactive })
|
||||||
|
: await procurement.fetchPages(page, rpp, { includeInactive });
|
||||||
|
|
||||||
|
const totalRecords = await procurement.count({
|
||||||
|
activeOnly: !includeInactive,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Catalog items fetched successfully!",
|
||||||
|
data.map((item) => item.toJson()),
|
||||||
|
{
|
||||||
|
pagination: {
|
||||||
|
previousPage: page <= 1 ? null : page - 1,
|
||||||
|
currentPage: page,
|
||||||
|
nextPage: page >= totalRecords / rpp ? null : page + 1,
|
||||||
|
totalPages: Math.ceil(totalRecords / rpp),
|
||||||
|
totalRecords,
|
||||||
|
listedRecords: rpp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["procurement.catalog.fetch.many"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { default as fetchAll } from "./fetchAll";
|
||||||
|
import { default as fetch } from "./[id]/fetch";
|
||||||
|
import { default as refreshInventory } from "./[id]/refreshInventory";
|
||||||
|
import { default as link } from "./[id]/link";
|
||||||
|
import { default as unlink } from "./[id]/unlink";
|
||||||
|
import { default as fetchLinked } from "./[id]/fetchLinked";
|
||||||
|
import { default as count } from "./count";
|
||||||
|
|
||||||
|
export { count, fetch, fetchAll, fetchLinked, link, refreshInventory, unlink };
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Hono } from "hono";
|
||||||
|
import * as procurementRoutes from "../procurement";
|
||||||
|
|
||||||
|
const procurementRouter = new Hono();
|
||||||
|
Object.values(procurementRoutes).map((r) => procurementRouter.route("/", r));
|
||||||
|
|
||||||
|
export default procurementRouter;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Hono } from "hono";
|
||||||
|
import * as salesRoutes from "../sales";
|
||||||
|
|
||||||
|
const salesRouter = new Hono();
|
||||||
|
Object.values(salesRoutes).map((r) => salesRouter.route("/", r));
|
||||||
|
|
||||||
|
export default salesRouter;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { opportunities } from "../../../managers/opportunities";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
import { opportunityCw } from "../../../modules/cw-utils/opportunities/opportunities";
|
||||||
|
|
||||||
|
/* GET /v1/sales/opportunities/:identifier/contacts */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/opportunities/:identifier/contacts"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const item = await opportunities.fetchItem(identifier);
|
||||||
|
|
||||||
|
const contacts = await opportunityCw.fetchContacts(item.cwOpportunityId);
|
||||||
|
|
||||||
|
const data = contacts.map((ct) => ({
|
||||||
|
id: ct.id,
|
||||||
|
contact: ct.contact ? { id: ct.contact.id, name: ct.contact.name } : null,
|
||||||
|
company: ct.company
|
||||||
|
? {
|
||||||
|
id: ct.company.id,
|
||||||
|
identifier: ct.company.identifier,
|
||||||
|
name: ct.company.name,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
role: ct.role ? { id: ct.role.id, name: ct.role.name } : null,
|
||||||
|
notes: ct.notes,
|
||||||
|
referralFlag: ct.referralFlag,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Opportunity contacts fetched successfully!",
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["sales.opportunity.fetch"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { opportunities } from "../../../managers/opportunities";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/sales/opportunities/:identifier */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/opportunities/:identifier"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
|
||||||
|
const item = await opportunities.fetchItem(identifier);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Opportunity fetched successfully!",
|
||||||
|
item.toJson(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["sales.opportunity.fetch"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { opportunities } from "../../../managers/opportunities";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
import { opportunityCw } from "../../../modules/cw-utils/opportunities/opportunities";
|
||||||
|
|
||||||
|
/* GET /v1/sales/opportunities/:identifier/forecasts */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/opportunities/:identifier/forecasts"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const item = await opportunities.fetchItem(identifier);
|
||||||
|
|
||||||
|
const forecasts = await opportunityCw.fetchForecasts(item.cwOpportunityId);
|
||||||
|
|
||||||
|
const data = forecasts.map((f) => ({
|
||||||
|
id: f.id,
|
||||||
|
forecastType: f.forecastType,
|
||||||
|
forecastMonth: f.forecastMonth,
|
||||||
|
revenue: f.revenue,
|
||||||
|
cost: f.cost,
|
||||||
|
forecastPercentage: f.forecastPercentage,
|
||||||
|
status: f.status ? { id: f.status.id, name: f.status.name } : null,
|
||||||
|
includedFlag: f.includedFlag,
|
||||||
|
linkedFlag: f.linkedFlag,
|
||||||
|
recurringFlag: f.recurringFlag,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Opportunity forecasts fetched successfully!",
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["sales.opportunity.fetch"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { opportunities } from "../../../managers/opportunities";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
import { opportunityCw } from "../../../modules/cw-utils/opportunities/opportunities";
|
||||||
|
|
||||||
|
/* GET /v1/sales/opportunities/:identifier/notes */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/opportunities/:identifier/notes"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const item = await opportunities.fetchItem(identifier);
|
||||||
|
|
||||||
|
const notes = await opportunityCw.fetchNotes(item.cwOpportunityId);
|
||||||
|
|
||||||
|
const data = notes.map((n) => ({
|
||||||
|
id: n.id,
|
||||||
|
text: n.text,
|
||||||
|
type: n.type ? { id: n.type.id, name: n.type.name } : null,
|
||||||
|
flagged: n.flagged,
|
||||||
|
enteredBy: n.enteredBy,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Opportunity notes fetched successfully!",
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["sales.opportunity.fetch"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { opportunities } from "../../../managers/opportunities";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
|
||||||
|
/* POST /v1/sales/opportunities/:identifier/refresh */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/opportunities/:identifier/refresh"],
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const item = await opportunities.fetchItem(identifier);
|
||||||
|
|
||||||
|
const refreshed = await item.refreshFromCW();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Opportunity refreshed from ConnectWise successfully!",
|
||||||
|
refreshed.toJson(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["sales.opportunity.refresh"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { opportunities } from "../../managers/opportunities";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/sales/opportunities/count */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/opportunities/count"],
|
||||||
|
async (c) => {
|
||||||
|
const openOnly = c.req.query("openOnly") === "true";
|
||||||
|
|
||||||
|
const count = await opportunities.count({ openOnly });
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Opportunity count fetched successfully!",
|
||||||
|
{ count },
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["sales.opportunity.fetch.many"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { opportunities } from "../../managers/opportunities";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/sales/opportunities */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/opportunities"],
|
||||||
|
async (c) => {
|
||||||
|
const page = Number(c.req.query("page") ?? 1);
|
||||||
|
const rpp = Number(c.req.query("rpp") ?? 30);
|
||||||
|
const search = c.req.query("search") as string;
|
||||||
|
const includeClosed = c.req.query("includeClosed") === "true";
|
||||||
|
|
||||||
|
const data = search
|
||||||
|
? await opportunities.search(search, page, rpp, { includeClosed })
|
||||||
|
: await opportunities.fetchPages(page, rpp, { includeClosed });
|
||||||
|
|
||||||
|
const totalRecords = await opportunities.count({
|
||||||
|
openOnly: !includeClosed,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Opportunities fetched successfully!",
|
||||||
|
data.map((item) => item.toJson()),
|
||||||
|
{
|
||||||
|
pagination: {
|
||||||
|
previousPage: page <= 1 ? null : page - 1,
|
||||||
|
currentPage: page,
|
||||||
|
nextPage: page >= totalRecords / rpp ? null : page + 1,
|
||||||
|
totalPages: Math.ceil(totalRecords / rpp),
|
||||||
|
totalRecords,
|
||||||
|
listedRecords: rpp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["sales.opportunity.fetch.many"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { default as fetchAll } from "./fetchAll";
|
||||||
|
import { default as count } from "./count";
|
||||||
|
import { default as fetch } from "./[id]/fetch";
|
||||||
|
import { default as refresh } from "./[id]/refresh";
|
||||||
|
import { default as forecasts } from "./[id]/forecasts";
|
||||||
|
import { default as notes } from "./[id]/notes";
|
||||||
|
import { default as contacts } from "./[id]/contacts";
|
||||||
|
|
||||||
|
export { count, fetch, fetchAll, forecasts, notes, contacts, refresh };
|
||||||
@@ -55,6 +55,8 @@ v1.route("/credential-type", require("./routers/credentialTypeRouter").default);
|
|||||||
v1.route("/role", require("./routers/roleRouter").default);
|
v1.route("/role", require("./routers/roleRouter").default);
|
||||||
v1.route("/permissions", require("./routers/permissionRouter").default);
|
v1.route("/permissions", require("./routers/permissionRouter").default);
|
||||||
v1.route("/unifi", require("./routers/unifiRouter").default);
|
v1.route("/unifi", require("./routers/unifiRouter").default);
|
||||||
|
v1.route("/procurement", require("./routers/procurementRouter").default);
|
||||||
|
v1.route("/sales", require("./routers/salesRouter").default);
|
||||||
app.route("/v1", v1);
|
app.route("/v1", v1);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
import { CatalogItem } from "../../generated/prisma/client";
|
||||||
|
import { prisma } from "../constants";
|
||||||
|
import { catalogCw } from "../modules/cw-utils/procurement/catalog";
|
||||||
|
import { CatalogItem as CWCatalogItem } from "../modules/cw-utils/procurement/catalog.types";
|
||||||
|
import GenericError from "../Errors/GenericError";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catalog Item Controller
|
||||||
|
*
|
||||||
|
* This class encapsulates a catalog item entity and provides domain methods
|
||||||
|
* for accessing, refreshing, and serializing catalog item data. It bridges
|
||||||
|
* the internal database representation with ConnectWise catalog data.
|
||||||
|
*/
|
||||||
|
export class CatalogItemController {
|
||||||
|
public readonly id: string;
|
||||||
|
public name: string;
|
||||||
|
public description: string | null;
|
||||||
|
public customerDescription: string | null;
|
||||||
|
public internalNotes: string | null;
|
||||||
|
|
||||||
|
public readonly cwCatalogId: number;
|
||||||
|
public readonly identifier: string | null;
|
||||||
|
|
||||||
|
public manufacturer: string | null;
|
||||||
|
public manufactureCwId: number | null;
|
||||||
|
public partNumber: string | null;
|
||||||
|
|
||||||
|
public vendorName: string | null;
|
||||||
|
public vendorSku: string | null;
|
||||||
|
public vendorCwId: number | null;
|
||||||
|
|
||||||
|
public price: number;
|
||||||
|
public cost: number;
|
||||||
|
|
||||||
|
public inactive: boolean;
|
||||||
|
public salesTaxable: boolean;
|
||||||
|
|
||||||
|
public onHand: number;
|
||||||
|
public cwLastUpdated: Date | null;
|
||||||
|
|
||||||
|
private _linkedItems: CatalogItemController[];
|
||||||
|
|
||||||
|
public readonly createdAt: Date;
|
||||||
|
public updatedAt: Date;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
itemData: CatalogItem & {
|
||||||
|
linkedItems?: CatalogItem[];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.id = itemData.id;
|
||||||
|
this.name = itemData.name;
|
||||||
|
this.description = itemData.description;
|
||||||
|
this.customerDescription = itemData.customerDescription;
|
||||||
|
this.internalNotes = itemData.internalNotes;
|
||||||
|
this.cwCatalogId = itemData.cwCatalogId;
|
||||||
|
this.identifier = itemData.identifier;
|
||||||
|
this.manufacturer = itemData.manufacturer;
|
||||||
|
this.manufactureCwId = itemData.manufactureCwId;
|
||||||
|
this.partNumber = itemData.partNumber;
|
||||||
|
this.vendorName = itemData.vendorName;
|
||||||
|
this.vendorSku = itemData.vendorSku;
|
||||||
|
this.vendorCwId = itemData.vendorCwId;
|
||||||
|
this.price = itemData.price;
|
||||||
|
this.cost = itemData.cost;
|
||||||
|
this.inactive = itemData.inactive;
|
||||||
|
this.salesTaxable = itemData.salesTaxable;
|
||||||
|
this.onHand = itemData.onHand;
|
||||||
|
this.cwLastUpdated = itemData.cwLastUpdated;
|
||||||
|
this.createdAt = itemData.createdAt;
|
||||||
|
this.updatedAt = itemData.updatedAt;
|
||||||
|
|
||||||
|
this._linkedItems = (itemData.linkedItems ?? []).map(
|
||||||
|
(linked) => new CatalogItemController(linked),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh Inventory
|
||||||
|
*
|
||||||
|
* Fetches the latest on-hand inventory count from ConnectWise
|
||||||
|
* and updates both the controller state and the database.
|
||||||
|
*
|
||||||
|
* @returns {Promise<CatalogItemController>} - The updated controller
|
||||||
|
*/
|
||||||
|
public async refreshInventory(): Promise<CatalogItemController> {
|
||||||
|
const onHand = await catalogCw.fetchInventoryOnHand(this.cwCatalogId);
|
||||||
|
|
||||||
|
if (onHand !== this.onHand) {
|
||||||
|
await prisma.catalogItem.update({
|
||||||
|
where: { id: this.id },
|
||||||
|
data: { onHand },
|
||||||
|
});
|
||||||
|
this.onHand = onHand;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Linked Items
|
||||||
|
*
|
||||||
|
* Returns the linked catalog items as an array of controllers.
|
||||||
|
*
|
||||||
|
* @returns {CatalogItemController[]} - Array of linked item controllers
|
||||||
|
*/
|
||||||
|
public getLinkedItems(): CatalogItemController[] {
|
||||||
|
return this._linkedItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link Item
|
||||||
|
*
|
||||||
|
* Links another catalog item to this item. The relationship is bidirectional
|
||||||
|
* via the Prisma implicit many-to-many.
|
||||||
|
*
|
||||||
|
* @param targetId - The internal ID of the catalog item to link
|
||||||
|
* @returns {Promise<CatalogItemController>} - The updated controller
|
||||||
|
*/
|
||||||
|
public async linkItem(targetId: string): Promise<CatalogItemController> {
|
||||||
|
if (targetId === this.id) {
|
||||||
|
throw new GenericError({
|
||||||
|
message: "Cannot link a catalog item to itself",
|
||||||
|
name: "InvalidLinkTarget",
|
||||||
|
cause: `Item '${this.id}' cannot be linked to itself`,
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = await prisma.catalogItem.findFirst({
|
||||||
|
where: { id: targetId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
throw new GenericError({
|
||||||
|
message: "Target catalog item not found",
|
||||||
|
name: "CatalogItemNotFound",
|
||||||
|
cause: `No catalog item exists with ID '${targetId}'`,
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await prisma.catalogItem.update({
|
||||||
|
where: { id: this.id },
|
||||||
|
data: {
|
||||||
|
linkedItems: { connect: { id: targetId } },
|
||||||
|
},
|
||||||
|
include: { linkedItems: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
this._linkedItems = (updated.linkedItems ?? []).map(
|
||||||
|
(linked) => new CatalogItemController(linked),
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlink Item
|
||||||
|
*
|
||||||
|
* Removes the link between this catalog item and another.
|
||||||
|
*
|
||||||
|
* @param targetId - The internal ID of the catalog item to unlink
|
||||||
|
* @returns {Promise<CatalogItemController>} - The updated controller
|
||||||
|
*/
|
||||||
|
public async unlinkItem(targetId: string): Promise<CatalogItemController> {
|
||||||
|
const updated = await prisma.catalogItem.update({
|
||||||
|
where: { id: this.id },
|
||||||
|
data: {
|
||||||
|
linkedItems: { disconnect: { id: targetId } },
|
||||||
|
},
|
||||||
|
include: { linkedItems: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
this._linkedItems = (updated.linkedItems ?? []).map(
|
||||||
|
(linked) => new CatalogItemController(linked),
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To JSON
|
||||||
|
*
|
||||||
|
* Serializes the catalog item into a safe, API-friendly object.
|
||||||
|
*
|
||||||
|
* @param opts - Options to control output
|
||||||
|
* @returns - A JSON-safe representation of the catalog item
|
||||||
|
*/
|
||||||
|
public toJson(opts?: { includeLinkedItems?: boolean }): Record<string, any> {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
cwCatalogId: this.cwCatalogId,
|
||||||
|
identifier: this.identifier,
|
||||||
|
name: this.name,
|
||||||
|
description: this.description,
|
||||||
|
customerDescription: this.customerDescription,
|
||||||
|
internalNotes: this.internalNotes,
|
||||||
|
manufacturer: this.manufacturer,
|
||||||
|
manufactureCwId: this.manufactureCwId,
|
||||||
|
partNumber: this.partNumber,
|
||||||
|
vendorName: this.vendorName,
|
||||||
|
vendorSku: this.vendorSku,
|
||||||
|
vendorCwId: this.vendorCwId,
|
||||||
|
price: this.price,
|
||||||
|
cost: this.cost,
|
||||||
|
inactive: this.inactive,
|
||||||
|
salesTaxable: this.salesTaxable,
|
||||||
|
onHand: this.onHand,
|
||||||
|
cwLastUpdated: this.cwLastUpdated,
|
||||||
|
linkedItems: opts?.includeLinkedItems
|
||||||
|
? this._linkedItems.map((item) => item.toJson())
|
||||||
|
: undefined,
|
||||||
|
createdAt: this.createdAt,
|
||||||
|
updatedAt: this.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ export class CompanyController {
|
|||||||
public readonly cw_CompanyId: number;
|
public readonly cw_CompanyId: number;
|
||||||
public readonly cw_Data?: {
|
public readonly cw_Data?: {
|
||||||
company: CWCompany;
|
company: CWCompany;
|
||||||
defaultContact: Contact;
|
defaultContact: Contact | null;
|
||||||
allContacts: Contact[];
|
allContacts: Contact[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -96,23 +96,25 @@ export class CompanyController {
|
|||||||
},
|
},
|
||||||
primaryContact: !opts?.includePrimaryContact
|
primaryContact: !opts?.includePrimaryContact
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: this.cw_Data?.defaultContact
|
||||||
firstName: this.cw_Data?.defaultContact.firstName,
|
? {
|
||||||
lastName: this.cw_Data?.defaultContact.lastName,
|
firstName: this.cw_Data.defaultContact.firstName,
|
||||||
cwId: this.cw_Data?.defaultContact.id,
|
lastName: this.cw_Data.defaultContact.lastName,
|
||||||
inactive: this.cw_Data?.defaultContact.inactiveFlag,
|
cwId: this.cw_Data.defaultContact.id,
|
||||||
title: this.cw_Data?.defaultContact.title,
|
inactive: this.cw_Data.defaultContact.inactiveFlag,
|
||||||
phone: this.cw_Data?.defaultContact.defaultPhoneNbr,
|
title: this.cw_Data.defaultContact.title,
|
||||||
email: (() => {
|
phone: this.cw_Data.defaultContact.defaultPhoneNbr,
|
||||||
if (!this.cw_Data?.defaultContact.communicationItems)
|
email: (() => {
|
||||||
return null;
|
if (!this.cw_Data?.defaultContact?.communicationItems)
|
||||||
return (
|
return null;
|
||||||
this.cw_Data?.defaultContact.communicationItems.find(
|
return (
|
||||||
(v) => v.type.name === "Email",
|
this.cw_Data.defaultContact.communicationItems.find(
|
||||||
)?.value ?? null
|
(v) => v.type.name === "Email",
|
||||||
);
|
)?.value ?? null
|
||||||
})(),
|
);
|
||||||
},
|
})(),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
allContacts: !opts?.includeAllContacts
|
allContacts: !opts?.includeAllContacts
|
||||||
? undefined
|
? undefined
|
||||||
: this.cw_Data?.allContacts.map((contact) => ({
|
: this.cw_Data?.allContacts.map((contact) => ({
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
import { Opportunity } from "../../generated/prisma/client";
|
||||||
|
import { prisma } from "../constants";
|
||||||
|
import { fetchOpportunity } from "../modules/cw-utils/opportunities/fetchOpportunity";
|
||||||
|
import { CWOpportunity } from "../modules/cw-utils/opportunities/opportunity.types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opportunity Controller
|
||||||
|
*
|
||||||
|
* Domain model class that encapsulates an Opportunity entity and provides
|
||||||
|
* methods for accessing, refreshing from ConnectWise, and serializing
|
||||||
|
* opportunity data.
|
||||||
|
*/
|
||||||
|
export class OpportunityController {
|
||||||
|
public readonly id: string;
|
||||||
|
public readonly cwOpportunityId: number;
|
||||||
|
public name: string;
|
||||||
|
public notes: string | null;
|
||||||
|
|
||||||
|
public typeName: string | null;
|
||||||
|
public typeCwId: number | null;
|
||||||
|
public stageName: string | null;
|
||||||
|
public stageCwId: number | null;
|
||||||
|
public statusName: string | null;
|
||||||
|
public statusCwId: number | null;
|
||||||
|
public priorityName: string | null;
|
||||||
|
public priorityCwId: number | null;
|
||||||
|
public ratingName: string | null;
|
||||||
|
public ratingCwId: number | null;
|
||||||
|
public source: string | null;
|
||||||
|
public campaignName: string | null;
|
||||||
|
public campaignCwId: number | null;
|
||||||
|
|
||||||
|
public primarySalesRepName: string | null;
|
||||||
|
public primarySalesRepIdentifier: string | null;
|
||||||
|
public primarySalesRepCwId: number | null;
|
||||||
|
public secondarySalesRepName: string | null;
|
||||||
|
public secondarySalesRepIdentifier: string | null;
|
||||||
|
public secondarySalesRepCwId: number | null;
|
||||||
|
|
||||||
|
public companyCwId: number | null;
|
||||||
|
public companyName: string | null;
|
||||||
|
public contactCwId: number | null;
|
||||||
|
public contactName: string | null;
|
||||||
|
public siteCwId: number | null;
|
||||||
|
public siteName: string | null;
|
||||||
|
public customerPO: string | null;
|
||||||
|
|
||||||
|
public totalSalesTax: number;
|
||||||
|
|
||||||
|
public locationName: string | null;
|
||||||
|
public locationCwId: number | null;
|
||||||
|
public departmentName: string | null;
|
||||||
|
public departmentCwId: number | null;
|
||||||
|
|
||||||
|
public expectedCloseDate: Date | null;
|
||||||
|
public pipelineChangeDate: Date | null;
|
||||||
|
public dateBecameLead: Date | null;
|
||||||
|
public closedDate: Date | null;
|
||||||
|
public closedFlag: boolean;
|
||||||
|
public closedByName: string | null;
|
||||||
|
public closedByCwId: number | null;
|
||||||
|
|
||||||
|
public companyId: string | null;
|
||||||
|
public cwLastUpdated: Date | null;
|
||||||
|
|
||||||
|
public readonly createdAt: Date;
|
||||||
|
public updatedAt: Date;
|
||||||
|
|
||||||
|
constructor(data: Opportunity) {
|
||||||
|
this.id = data.id;
|
||||||
|
this.cwOpportunityId = data.cwOpportunityId;
|
||||||
|
this.name = data.name;
|
||||||
|
this.notes = data.notes;
|
||||||
|
|
||||||
|
this.typeName = data.typeName;
|
||||||
|
this.typeCwId = data.typeCwId;
|
||||||
|
this.stageName = data.stageName;
|
||||||
|
this.stageCwId = data.stageCwId;
|
||||||
|
this.statusName = data.statusName;
|
||||||
|
this.statusCwId = data.statusCwId;
|
||||||
|
this.priorityName = data.priorityName;
|
||||||
|
this.priorityCwId = data.priorityCwId;
|
||||||
|
this.ratingName = data.ratingName;
|
||||||
|
this.ratingCwId = data.ratingCwId;
|
||||||
|
this.source = data.source;
|
||||||
|
this.campaignName = data.campaignName;
|
||||||
|
this.campaignCwId = data.campaignCwId;
|
||||||
|
|
||||||
|
this.primarySalesRepName = data.primarySalesRepName;
|
||||||
|
this.primarySalesRepIdentifier = data.primarySalesRepIdentifier;
|
||||||
|
this.primarySalesRepCwId = data.primarySalesRepCwId;
|
||||||
|
this.secondarySalesRepName = data.secondarySalesRepName;
|
||||||
|
this.secondarySalesRepIdentifier = data.secondarySalesRepIdentifier;
|
||||||
|
this.secondarySalesRepCwId = data.secondarySalesRepCwId;
|
||||||
|
|
||||||
|
this.companyCwId = data.companyCwId;
|
||||||
|
this.companyName = data.companyName;
|
||||||
|
this.contactCwId = data.contactCwId;
|
||||||
|
this.contactName = data.contactName;
|
||||||
|
this.siteCwId = data.siteCwId;
|
||||||
|
this.siteName = data.siteName;
|
||||||
|
this.customerPO = data.customerPO;
|
||||||
|
|
||||||
|
this.totalSalesTax = data.totalSalesTax;
|
||||||
|
|
||||||
|
this.locationName = data.locationName;
|
||||||
|
this.locationCwId = data.locationCwId;
|
||||||
|
this.departmentName = data.departmentName;
|
||||||
|
this.departmentCwId = data.departmentCwId;
|
||||||
|
|
||||||
|
this.expectedCloseDate = data.expectedCloseDate;
|
||||||
|
this.pipelineChangeDate = data.pipelineChangeDate;
|
||||||
|
this.dateBecameLead = data.dateBecameLead;
|
||||||
|
this.closedDate = data.closedDate;
|
||||||
|
this.closedFlag = data.closedFlag;
|
||||||
|
this.closedByName = data.closedByName;
|
||||||
|
this.closedByCwId = data.closedByCwId;
|
||||||
|
|
||||||
|
this.companyId = data.companyId;
|
||||||
|
this.cwLastUpdated = data.cwLastUpdated;
|
||||||
|
|
||||||
|
this.createdAt = data.createdAt;
|
||||||
|
this.updatedAt = data.updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh from ConnectWise
|
||||||
|
*
|
||||||
|
* Fetches the latest opportunity data from CW and updates
|
||||||
|
* the local database record and controller state.
|
||||||
|
*/
|
||||||
|
public async refreshFromCW(): Promise<OpportunityController> {
|
||||||
|
const cwData = await fetchOpportunity(this.cwOpportunityId);
|
||||||
|
const mapped = OpportunityController.mapCwToDb(cwData);
|
||||||
|
|
||||||
|
const updated = await prisma.opportunity.update({
|
||||||
|
where: { id: this.id },
|
||||||
|
data: mapped,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new OpportunityController(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch raw CW data
|
||||||
|
*
|
||||||
|
* Returns the raw ConnectWise opportunity object without updating the DB.
|
||||||
|
*/
|
||||||
|
public async fetchCwData(): Promise<CWOpportunity> {
|
||||||
|
return fetchOpportunity(this.cwOpportunityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map CW Opportunity → Prisma create/update payload
|
||||||
|
*
|
||||||
|
* Static helper used by both the controller and the refresh sync.
|
||||||
|
*/
|
||||||
|
public static mapCwToDb(item: CWOpportunity) {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
notes: item.notes ?? null,
|
||||||
|
|
||||||
|
typeName: item.type?.name ?? null,
|
||||||
|
typeCwId: item.type?.id ?? null,
|
||||||
|
stageName: item.stage?.name ?? null,
|
||||||
|
stageCwId: item.stage?.id ?? null,
|
||||||
|
statusName: item.status?.name ?? null,
|
||||||
|
statusCwId: item.status?.id ?? null,
|
||||||
|
priorityName: item.priority?.name ?? null,
|
||||||
|
priorityCwId: item.priority?.id ?? null,
|
||||||
|
ratingName: item.rating?.name ?? null,
|
||||||
|
ratingCwId: item.rating?.id ?? null,
|
||||||
|
source: item.source ?? null,
|
||||||
|
campaignName: item.campaign?.name ?? null,
|
||||||
|
campaignCwId: item.campaign?.id ?? null,
|
||||||
|
|
||||||
|
primarySalesRepName: item.primarySalesRep?.name ?? null,
|
||||||
|
primarySalesRepIdentifier: item.primarySalesRep?.identifier ?? null,
|
||||||
|
primarySalesRepCwId: item.primarySalesRep?.id ?? null,
|
||||||
|
secondarySalesRepName: item.secondarySalesRep?.name ?? null,
|
||||||
|
secondarySalesRepIdentifier: item.secondarySalesRep?.identifier ?? null,
|
||||||
|
secondarySalesRepCwId: item.secondarySalesRep?.id ?? null,
|
||||||
|
|
||||||
|
companyCwId: item.company?.id ?? null,
|
||||||
|
companyName: item.company?.name ?? null,
|
||||||
|
contactCwId: item.contact?.id ?? null,
|
||||||
|
contactName: item.contact?.name ?? null,
|
||||||
|
siteCwId: item.site?.id ?? null,
|
||||||
|
siteName: item.site?.name ?? null,
|
||||||
|
customerPO: item.customerPO ?? null,
|
||||||
|
|
||||||
|
totalSalesTax: item.totalSalesTax ?? 0,
|
||||||
|
|
||||||
|
locationName: item.location?.name ?? null,
|
||||||
|
locationCwId: item.location?.id ?? null,
|
||||||
|
departmentName: item.department?.name ?? null,
|
||||||
|
departmentCwId: item.department?.id ?? null,
|
||||||
|
|
||||||
|
expectedCloseDate: item.expectedCloseDate
|
||||||
|
? new Date(item.expectedCloseDate)
|
||||||
|
: null,
|
||||||
|
pipelineChangeDate: item.pipelineChangeDate
|
||||||
|
? new Date(item.pipelineChangeDate)
|
||||||
|
: null,
|
||||||
|
dateBecameLead: item.dateBecameLead
|
||||||
|
? new Date(item.dateBecameLead)
|
||||||
|
: null,
|
||||||
|
closedDate: item.closedDate ? new Date(item.closedDate) : null,
|
||||||
|
closedFlag: item.closedFlag ?? false,
|
||||||
|
closedByName: item.closedBy?.name ?? null,
|
||||||
|
closedByCwId: item.closedBy?.id ?? null,
|
||||||
|
|
||||||
|
cwLastUpdated: item._info?.lastUpdated
|
||||||
|
? new Date(item._info.lastUpdated)
|
||||||
|
: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To JSON
|
||||||
|
*
|
||||||
|
* Serializes the opportunity into a safe, API-friendly object.
|
||||||
|
*/
|
||||||
|
public toJson(): Record<string, any> {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
cwOpportunityId: this.cwOpportunityId,
|
||||||
|
name: this.name,
|
||||||
|
notes: this.notes,
|
||||||
|
type: this.typeCwId ? { id: this.typeCwId, name: this.typeName } : null,
|
||||||
|
stage: this.stageCwId
|
||||||
|
? { id: this.stageCwId, name: this.stageName }
|
||||||
|
: null,
|
||||||
|
status: this.statusCwId
|
||||||
|
? { id: this.statusCwId, name: this.statusName }
|
||||||
|
: null,
|
||||||
|
priority: this.priorityCwId
|
||||||
|
? { id: this.priorityCwId, name: this.priorityName }
|
||||||
|
: null,
|
||||||
|
rating: this.ratingCwId
|
||||||
|
? { id: this.ratingCwId, name: this.ratingName }
|
||||||
|
: null,
|
||||||
|
source: this.source,
|
||||||
|
campaign: this.campaignCwId
|
||||||
|
? { id: this.campaignCwId, name: this.campaignName }
|
||||||
|
: null,
|
||||||
|
primarySalesRep: this.primarySalesRepCwId
|
||||||
|
? {
|
||||||
|
id: this.primarySalesRepCwId,
|
||||||
|
identifier: this.primarySalesRepIdentifier,
|
||||||
|
name: this.primarySalesRepName,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
secondarySalesRep: this.secondarySalesRepCwId
|
||||||
|
? {
|
||||||
|
id: this.secondarySalesRepCwId,
|
||||||
|
identifier: this.secondarySalesRepIdentifier,
|
||||||
|
name: this.secondarySalesRepName,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
company: this.companyCwId
|
||||||
|
? { id: this.companyCwId, name: this.companyName }
|
||||||
|
: null,
|
||||||
|
contact: this.contactCwId
|
||||||
|
? { id: this.contactCwId, name: this.contactName }
|
||||||
|
: null,
|
||||||
|
site: this.siteCwId ? { id: this.siteCwId, name: this.siteName } : null,
|
||||||
|
customerPO: this.customerPO,
|
||||||
|
totalSalesTax: this.totalSalesTax,
|
||||||
|
location: this.locationCwId
|
||||||
|
? { id: this.locationCwId, name: this.locationName }
|
||||||
|
: null,
|
||||||
|
department: this.departmentCwId
|
||||||
|
? { id: this.departmentCwId, name: this.departmentName }
|
||||||
|
: null,
|
||||||
|
expectedCloseDate: this.expectedCloseDate,
|
||||||
|
pipelineChangeDate: this.pipelineChangeDate,
|
||||||
|
dateBecameLead: this.dateBecameLead,
|
||||||
|
closedDate: this.closedDate,
|
||||||
|
closedFlag: this.closedFlag,
|
||||||
|
closedBy: this.closedByCwId
|
||||||
|
? { id: this.closedByCwId, name: this.closedByName }
|
||||||
|
: null,
|
||||||
|
companyId: this.companyId,
|
||||||
|
cwLastUpdated: this.cwLastUpdated,
|
||||||
|
createdAt: this.createdAt,
|
||||||
|
updatedAt: this.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -178,6 +178,46 @@ export default class UserController {
|
|||||||
return decoded.permissions;
|
return decoded.permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read Role Permissions
|
||||||
|
*
|
||||||
|
* Verifies and decodes a role permissions JWT and returns the permission nodes.
|
||||||
|
* Returns an empty array if verification fails.
|
||||||
|
*
|
||||||
|
* @param role - Role record containing the signed permissions token
|
||||||
|
* @returns {string[]} The role permission nodes
|
||||||
|
*/
|
||||||
|
private _readRolePermissions(role: Role): string[] {
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(role.permissions, permissionsPrivateKey, {
|
||||||
|
algorithms: ["RS256"],
|
||||||
|
issuer: "roles",
|
||||||
|
subject: role.id,
|
||||||
|
}) as DecodedPermissionsBlock;
|
||||||
|
|
||||||
|
return decoded.permissions;
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read All Permissions
|
||||||
|
*
|
||||||
|
* Aggregates the user's direct permissions and all permissions from their assigned roles
|
||||||
|
* into a single deduplicated array.
|
||||||
|
*
|
||||||
|
* @returns {Promise<string[]>} Combined array of all permission nodes
|
||||||
|
*/
|
||||||
|
public async readAllPermissions(): Promise<string[]> {
|
||||||
|
const directPermissions = this.readPermissions();
|
||||||
|
const rolePermissions = this._roles
|
||||||
|
.map((role) => this._readRolePermissions(role))
|
||||||
|
.flatMap((permissions) => permissions);
|
||||||
|
|
||||||
|
return [...new Set([...directPermissions, ...rolePermissions])];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch Roles
|
* Fetch Roles
|
||||||
*
|
*
|
||||||
@@ -262,7 +302,16 @@ export default class UserController {
|
|||||||
: this._roles.size > 0
|
: this._roles.size > 0
|
||||||
? this._roles.map((v) => v.moniker)
|
? this._roles.map((v) => v.moniker)
|
||||||
: undefined,
|
: undefined,
|
||||||
permissions: opts?.safeReturn ? undefined : this.readPermissions(),
|
permissions: opts?.safeReturn
|
||||||
|
? undefined
|
||||||
|
: (() => {
|
||||||
|
const directPermissions = this.readPermissions();
|
||||||
|
const rolePermissions = this._roles
|
||||||
|
.map((role) => this._readRolePermissions(role))
|
||||||
|
.flatMap((permissions) => permissions);
|
||||||
|
|
||||||
|
return [...new Set([...directPermissions, ...rolePermissions])];
|
||||||
|
})(),
|
||||||
login: opts?.safeReturn ? undefined : this.login,
|
login: opts?.safeReturn ? undefined : this.login,
|
||||||
email: opts?.safeReturn ? undefined : this.email,
|
email: opts?.safeReturn ? undefined : this.email,
|
||||||
image: this.image,
|
image: this.image,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { unifiSites } from "./managers/unifiSites";
|
|||||||
import { refreshCompanies } from "./modules/cw-utils/refreshCompanies";
|
import { refreshCompanies } from "./modules/cw-utils/refreshCompanies";
|
||||||
import { refreshCatalog } from "./modules/cw-utils/procurement/refreshCatalog";
|
import { refreshCatalog } from "./modules/cw-utils/procurement/refreshCatalog";
|
||||||
import { refreshInventory } from "./modules/cw-utils/procurement/refreshInventory";
|
import { refreshInventory } from "./modules/cw-utils/procurement/refreshInventory";
|
||||||
|
import { refreshOpportunities } from "./modules/cw-utils/opportunities/refreshOpportunities";
|
||||||
import { events, setupEventDebugger } from "./modules/globalEvents";
|
import { events, setupEventDebugger } from "./modules/globalEvents";
|
||||||
import { signPermissions } from "./modules/permission-utils/signPermissions";
|
import { signPermissions } from "./modules/permission-utils/signPermissions";
|
||||||
import { RoleController } from "./controllers/RoleController";
|
import { RoleController } from "./controllers/RoleController";
|
||||||
@@ -65,6 +66,12 @@ setInterval(
|
|||||||
2 * 60 * 1000,
|
2 * 60 * 1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Refresh opportunities every minute
|
||||||
|
await refreshOpportunities();
|
||||||
|
setInterval(() => {
|
||||||
|
return refreshOpportunities();
|
||||||
|
}, 60 * 1000);
|
||||||
|
|
||||||
await unifiSites.syncSites();
|
await unifiSites.syncSites();
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
return unifiSites.syncSites();
|
return unifiSites.syncSites();
|
||||||
|
|||||||
@@ -15,16 +15,19 @@ export const companies = {
|
|||||||
const freshCwData: { data: Company } = await connectWiseApi.get(
|
const freshCwData: { data: Company } = await connectWiseApi.get(
|
||||||
`/company/companies/${search.cw_CompanyId}`,
|
`/company/companies/${search.cw_CompanyId}`,
|
||||||
);
|
);
|
||||||
const defaultContactData = await connectWiseApi.get(
|
|
||||||
(freshCwData.data as Company).defaultContact._info.contact_href,
|
const contactHref = freshCwData.data.defaultContact?._info?.contact_href;
|
||||||
);
|
const defaultContactData = contactHref
|
||||||
|
? await connectWiseApi.get(contactHref)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const allContactsData = await connectWiseApi.get(
|
const allContactsData = await connectWiseApi.get(
|
||||||
`${freshCwData.data._info.contacts_href}&pageSize=1000`,
|
`${freshCwData.data._info.contacts_href}&pageSize=1000`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return new CompanyController(search, {
|
return new CompanyController(search, {
|
||||||
company: freshCwData.data,
|
company: freshCwData.data,
|
||||||
defaultContact: defaultContactData.data,
|
defaultContact: defaultContactData?.data ?? null,
|
||||||
allContacts: allContactsData.data,
|
allContacts: allContactsData.data,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { prisma } from "../constants";
|
||||||
|
import { OpportunityController } from "../controllers/OpportunityController";
|
||||||
|
import GenericError from "../Errors/GenericError";
|
||||||
|
|
||||||
|
export const opportunities = {
|
||||||
|
/**
|
||||||
|
* Fetch Opportunity
|
||||||
|
*
|
||||||
|
* Fetch an opportunity by its internal ID or ConnectWise opportunity ID
|
||||||
|
* and return an OpportunityController instance.
|
||||||
|
*
|
||||||
|
* @param identifier - The internal ID (string) or CW opportunity ID (number)
|
||||||
|
* @returns {Promise<OpportunityController>}
|
||||||
|
*/
|
||||||
|
async fetchItem(identifier: string | number): Promise<OpportunityController> {
|
||||||
|
const isNumeric =
|
||||||
|
typeof identifier === "number" || /^\d+$/.test(String(identifier));
|
||||||
|
|
||||||
|
const item = await prisma.opportunity.findFirst({
|
||||||
|
where: isNumeric
|
||||||
|
? { cwOpportunityId: Number(identifier) }
|
||||||
|
: { id: identifier as string },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
throw new GenericError({
|
||||||
|
message: "Opportunity not found",
|
||||||
|
name: "OpportunityNotFound",
|
||||||
|
cause: `No opportunity exists with identifier '${identifier}'`,
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OpportunityController(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch All Opportunities (Paginated)
|
||||||
|
*
|
||||||
|
* @param page - Page number (1-based)
|
||||||
|
* @param rpp - Records per page
|
||||||
|
* @param opts - Optional filters
|
||||||
|
* @returns {Promise<OpportunityController[]>}
|
||||||
|
*/
|
||||||
|
async fetchPages(
|
||||||
|
page: number,
|
||||||
|
rpp: number,
|
||||||
|
opts?: { includeClosed?: boolean },
|
||||||
|
): Promise<OpportunityController[]> {
|
||||||
|
const skip = (Math.max(page, 1) - 1) * rpp;
|
||||||
|
|
||||||
|
const items = await prisma.opportunity.findMany({
|
||||||
|
where: opts?.includeClosed ? undefined : { closedFlag: false },
|
||||||
|
skip,
|
||||||
|
take: rpp,
|
||||||
|
orderBy: { expectedCloseDate: "asc" },
|
||||||
|
});
|
||||||
|
|
||||||
|
return items.map((item) => new OpportunityController(item));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search Opportunities
|
||||||
|
*
|
||||||
|
* Search opportunities by name, company name, contact name, notes,
|
||||||
|
* sales rep, or status with pagination support.
|
||||||
|
*
|
||||||
|
* @param query - Search query string
|
||||||
|
* @param page - Page number (1-based)
|
||||||
|
* @param rpp - Records per page
|
||||||
|
* @param opts - Optional filters
|
||||||
|
* @returns {Promise<OpportunityController[]>}
|
||||||
|
*/
|
||||||
|
async search(
|
||||||
|
query: string,
|
||||||
|
page: number,
|
||||||
|
rpp: number,
|
||||||
|
opts?: { includeClosed?: boolean },
|
||||||
|
): Promise<OpportunityController[]> {
|
||||||
|
const skip = (Math.max(page, 1) - 1) * rpp;
|
||||||
|
|
||||||
|
const items = await prisma.opportunity.findMany({
|
||||||
|
where: {
|
||||||
|
...(opts?.includeClosed ? {} : { closedFlag: false }),
|
||||||
|
OR: [
|
||||||
|
{ name: { contains: query, mode: "insensitive" } },
|
||||||
|
{ companyName: { contains: query, mode: "insensitive" } },
|
||||||
|
{ contactName: { contains: query, mode: "insensitive" } },
|
||||||
|
{ notes: { contains: query, mode: "insensitive" } },
|
||||||
|
{ primarySalesRepName: { contains: query, mode: "insensitive" } },
|
||||||
|
{ statusName: { contains: query, mode: "insensitive" } },
|
||||||
|
{ stageName: { contains: query, mode: "insensitive" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: rpp,
|
||||||
|
orderBy: { expectedCloseDate: "asc" },
|
||||||
|
});
|
||||||
|
|
||||||
|
return items.map((item) => new OpportunityController(item));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count Opportunities
|
||||||
|
*
|
||||||
|
* @param opts - Optional filters
|
||||||
|
* @returns {Promise<number>}
|
||||||
|
*/
|
||||||
|
async count(opts?: { openOnly?: boolean }): Promise<number> {
|
||||||
|
return prisma.opportunity.count({
|
||||||
|
where: opts?.openOnly ? { closedFlag: false } : undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Opportunities by Company
|
||||||
|
*
|
||||||
|
* Fetch all opportunities for a company by its internal company ID.
|
||||||
|
*
|
||||||
|
* @param companyId - The internal company ID
|
||||||
|
* @param opts - Optional filters
|
||||||
|
* @returns {Promise<OpportunityController[]>}
|
||||||
|
*/
|
||||||
|
async fetchByCompany(
|
||||||
|
companyId: string,
|
||||||
|
opts?: { includeClosed?: boolean },
|
||||||
|
): Promise<OpportunityController[]> {
|
||||||
|
const items = await prisma.opportunity.findMany({
|
||||||
|
where: {
|
||||||
|
companyId,
|
||||||
|
...(opts?.includeClosed ? {} : { closedFlag: false }),
|
||||||
|
},
|
||||||
|
orderBy: { expectedCloseDate: "asc" },
|
||||||
|
});
|
||||||
|
|
||||||
|
return items.map((item) => new OpportunityController(item));
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import { prisma } from "../constants";
|
||||||
|
import { CatalogItemController } from "../controllers/CatalogItemController";
|
||||||
|
import GenericError from "../Errors/GenericError";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard include clause used by catalog item queries.
|
||||||
|
* Includes one level of linked items.
|
||||||
|
*/
|
||||||
|
const catalogItemInclude = {
|
||||||
|
linkedItems: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const procurement = {
|
||||||
|
/**
|
||||||
|
* Fetch Catalog Item
|
||||||
|
*
|
||||||
|
* Fetch a catalog item by its internal ID or ConnectWise catalog ID
|
||||||
|
* and return a CatalogItemController instance.
|
||||||
|
*
|
||||||
|
* @param identifier - The internal ID (string) or CW catalog ID (number)
|
||||||
|
* @returns {Promise<CatalogItemController>} - The catalog item controller
|
||||||
|
*/
|
||||||
|
async fetchItem(identifier: string | number): Promise<CatalogItemController> {
|
||||||
|
const isNumeric =
|
||||||
|
typeof identifier === "number" || /^\d+$/.test(String(identifier));
|
||||||
|
|
||||||
|
const item = await prisma.catalogItem.findFirst({
|
||||||
|
where: isNumeric
|
||||||
|
? { cwCatalogId: Number(identifier) }
|
||||||
|
: {
|
||||||
|
OR: [
|
||||||
|
{ id: identifier as string },
|
||||||
|
{ identifier: identifier as string },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
include: catalogItemInclude,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
throw new GenericError({
|
||||||
|
message: "Catalog item not found",
|
||||||
|
name: "CatalogItemNotFound",
|
||||||
|
cause: `No catalog item exists with identifier '${identifier}'`,
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CatalogItemController(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch All Catalog Items (Paginated)
|
||||||
|
*
|
||||||
|
* Fetch pages of catalog items for pagination.
|
||||||
|
*
|
||||||
|
* @param page - Page number (1-based)
|
||||||
|
* @param rpp - Records per page
|
||||||
|
* @returns {Promise<CatalogItemController[]>} - Array of catalog item controllers
|
||||||
|
*/
|
||||||
|
async fetchPages(
|
||||||
|
page: number,
|
||||||
|
rpp: number,
|
||||||
|
opts?: { includeInactive?: boolean },
|
||||||
|
): Promise<CatalogItemController[]> {
|
||||||
|
const skip = (Math.max(page, 1) - 1) * rpp;
|
||||||
|
const take = rpp;
|
||||||
|
|
||||||
|
const items = await prisma.catalogItem.findMany({
|
||||||
|
where: opts?.includeInactive ? undefined : { inactive: false },
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
include: catalogItemInclude,
|
||||||
|
orderBy: { name: "asc" },
|
||||||
|
});
|
||||||
|
|
||||||
|
return items.map((item) => new CatalogItemController(item));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search Catalog Items
|
||||||
|
*
|
||||||
|
* Search catalog items by name, description, part number, or vendor SKU
|
||||||
|
* with pagination support.
|
||||||
|
*
|
||||||
|
* @param query - Search query string
|
||||||
|
* @param page - Page number (1-based)
|
||||||
|
* @param rpp - Records per page
|
||||||
|
* @returns {Promise<CatalogItemController[]>} - Array of matching catalog item controllers
|
||||||
|
*/
|
||||||
|
async search(
|
||||||
|
query: string,
|
||||||
|
page: number,
|
||||||
|
rpp: number,
|
||||||
|
opts?: { includeInactive?: boolean },
|
||||||
|
): Promise<CatalogItemController[]> {
|
||||||
|
const skip = (Math.max(page, 1) - 1) * rpp;
|
||||||
|
const take = rpp;
|
||||||
|
|
||||||
|
const items = await prisma.catalogItem.findMany({
|
||||||
|
where: {
|
||||||
|
...(opts?.includeInactive ? {} : { inactive: false }),
|
||||||
|
OR: [
|
||||||
|
{ identifier: { contains: query, mode: "insensitive" } },
|
||||||
|
{ name: { contains: query, mode: "insensitive" } },
|
||||||
|
{ description: { contains: query, mode: "insensitive" } },
|
||||||
|
{ partNumber: { contains: query, mode: "insensitive" } },
|
||||||
|
{ vendorSku: { contains: query, mode: "insensitive" } },
|
||||||
|
{ manufacturer: { contains: query, mode: "insensitive" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
include: catalogItemInclude,
|
||||||
|
orderBy: { name: "asc" },
|
||||||
|
});
|
||||||
|
|
||||||
|
return items.map((item) => new CatalogItemController(item));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count Catalog Items
|
||||||
|
*
|
||||||
|
* Returns the total number of catalog items in the database.
|
||||||
|
*
|
||||||
|
* @param opts - Optional filters
|
||||||
|
* @returns {Promise<number>} - Total count
|
||||||
|
*/
|
||||||
|
async count(opts?: { activeOnly?: boolean }): Promise<number> {
|
||||||
|
return prisma.catalogItem.count({
|
||||||
|
where: opts?.activeOnly ? { inactive: false } : undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link Catalog Items
|
||||||
|
*
|
||||||
|
* Links a target catalog item to a source catalog item.
|
||||||
|
*
|
||||||
|
* @param sourceIdentifier - The source item's internal ID, identifier, or CW catalog ID
|
||||||
|
* @param targetIdentifier - The target item's internal ID, identifier, or CW catalog ID
|
||||||
|
* @returns {Promise<CatalogItemController>} - The updated source controller with linked items
|
||||||
|
*/
|
||||||
|
async linkItems(
|
||||||
|
sourceIdentifier: string | number,
|
||||||
|
targetIdentifier: string | number,
|
||||||
|
): Promise<CatalogItemController> {
|
||||||
|
const source = await procurement.fetchItem(sourceIdentifier);
|
||||||
|
const target = await procurement.fetchItem(targetIdentifier);
|
||||||
|
|
||||||
|
return source.linkItem(target.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlink Catalog Items
|
||||||
|
*
|
||||||
|
* Removes the link between a source catalog item and a target catalog item.
|
||||||
|
*
|
||||||
|
* @param sourceIdentifier - The source item's internal ID, identifier, or CW catalog ID
|
||||||
|
* @param targetIdentifier - The target item's internal ID, identifier, or CW catalog ID
|
||||||
|
* @returns {Promise<CatalogItemController>} - The updated source controller
|
||||||
|
*/
|
||||||
|
async unlinkItems(
|
||||||
|
sourceIdentifier: string | number,
|
||||||
|
targetIdentifier: string | number,
|
||||||
|
): Promise<CatalogItemController> {
|
||||||
|
const source = await procurement.fetchItem(sourceIdentifier);
|
||||||
|
const target = await procurement.fetchItem(targetIdentifier);
|
||||||
|
|
||||||
|
return source.unlinkItem(target.id);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Collection } from "@discordjs/collection";
|
||||||
|
import GenericError from "../../../Errors/GenericError";
|
||||||
|
import { opportunityCw } from "./opportunities";
|
||||||
|
import { CWOpportunity } from "./opportunity.types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all opportunities from ConnectWise with optional conditions.
|
||||||
|
*
|
||||||
|
* @param conditions - Optional CW conditions string for filtering
|
||||||
|
* @returns A Collection of CW opportunities keyed by their ID
|
||||||
|
* @throws GenericError if the fetch fails
|
||||||
|
*/
|
||||||
|
export const fetchAllOpportunities = async (
|
||||||
|
conditions?: string,
|
||||||
|
): Promise<Collection<number, CWOpportunity>> => {
|
||||||
|
try {
|
||||||
|
return await opportunityCw.fetchAll(conditions);
|
||||||
|
} catch (error) {
|
||||||
|
const errBody = (error as any).response?.data || error;
|
||||||
|
console.error("Error fetching all opportunities:", errBody);
|
||||||
|
throw new GenericError({
|
||||||
|
name: "FetchAllOpportunitiesError",
|
||||||
|
message: "Failed to fetch opportunities from ConnectWise",
|
||||||
|
cause: typeof errBody === "string" ? errBody : JSON.stringify(errBody),
|
||||||
|
status: 502,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Collection } from "@discordjs/collection";
|
||||||
|
import GenericError from "../../../Errors/GenericError";
|
||||||
|
import { opportunityCw } from "./opportunities";
|
||||||
|
import { CWOpportunity } from "./opportunity.types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all opportunities for a specific company from ConnectWise.
|
||||||
|
*
|
||||||
|
* @param cwCompanyId - The ConnectWise company ID
|
||||||
|
* @returns A Collection of CW opportunities for the company keyed by their ID
|
||||||
|
* @throws GenericError if the fetch fails
|
||||||
|
*/
|
||||||
|
export const fetchCompanyOpportunities = async (
|
||||||
|
cwCompanyId: number,
|
||||||
|
): Promise<Collection<number, CWOpportunity>> => {
|
||||||
|
try {
|
||||||
|
return await opportunityCw.fetchByCompany(cwCompanyId);
|
||||||
|
} catch (error) {
|
||||||
|
const errBody = (error as any).response?.data || error;
|
||||||
|
console.error(
|
||||||
|
`Error fetching opportunities for company ${cwCompanyId}:`,
|
||||||
|
errBody,
|
||||||
|
);
|
||||||
|
throw new GenericError({
|
||||||
|
name: "FetchCompanyOpportunitiesError",
|
||||||
|
message: `Failed to fetch opportunities for company ${cwCompanyId}`,
|
||||||
|
cause: typeof errBody === "string" ? errBody : JSON.stringify(errBody),
|
||||||
|
status: 502,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import GenericError from "../../../Errors/GenericError";
|
||||||
|
import { opportunityCw } from "./opportunities";
|
||||||
|
import { CWOpportunity } from "./opportunity.types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a single opportunity by its ConnectWise ID.
|
||||||
|
*
|
||||||
|
* @param cwOpportunityId - The ConnectWise opportunity ID
|
||||||
|
* @returns The full CW opportunity object
|
||||||
|
* @throws GenericError if the fetch fails
|
||||||
|
*/
|
||||||
|
export const fetchOpportunity = async (
|
||||||
|
cwOpportunityId: number,
|
||||||
|
): Promise<CWOpportunity> => {
|
||||||
|
try {
|
||||||
|
return await opportunityCw.fetch(cwOpportunityId);
|
||||||
|
} catch (error) {
|
||||||
|
const errBody = (error as any).response?.data || error;
|
||||||
|
console.error(
|
||||||
|
`Error fetching opportunity with ID ${cwOpportunityId}:`,
|
||||||
|
errBody,
|
||||||
|
);
|
||||||
|
throw new GenericError({
|
||||||
|
name: "FetchOpportunityError",
|
||||||
|
message: `Failed to fetch opportunity ${cwOpportunityId}`,
|
||||||
|
cause: typeof errBody === "string" ? errBody : JSON.stringify(errBody),
|
||||||
|
status: 502,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import { Collection } from "@discordjs/collection";
|
||||||
|
import { connectWiseApi } from "../../../constants";
|
||||||
|
import {
|
||||||
|
CWOpportunity,
|
||||||
|
CWOpportunitySummary,
|
||||||
|
CWForecastItem,
|
||||||
|
CWOpportunityNote,
|
||||||
|
CWOpportunityContact,
|
||||||
|
} from "./opportunity.types";
|
||||||
|
|
||||||
|
export const opportunityCw = {
|
||||||
|
/**
|
||||||
|
* Count Opportunities
|
||||||
|
*
|
||||||
|
* Returns the total number of opportunities in ConnectWise.
|
||||||
|
* Optionally accepts CW conditions string for filtered counts.
|
||||||
|
*/
|
||||||
|
countItems: async (conditions?: string): Promise<number> => {
|
||||||
|
const query = conditions
|
||||||
|
? `/sales/opportunities/count?conditions=${encodeURIComponent(conditions)}`
|
||||||
|
: "/sales/opportunities/count";
|
||||||
|
const response = await connectWiseApi.get(query);
|
||||||
|
return response.data.count;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch All Opportunity Summaries
|
||||||
|
*
|
||||||
|
* Lightweight fetch returning only id and _info (for lastUpdated comparison).
|
||||||
|
* Paginates through all opportunities.
|
||||||
|
*/
|
||||||
|
fetchAllSummaries: async (): Promise<
|
||||||
|
Collection<number, CWOpportunitySummary>
|
||||||
|
> => {
|
||||||
|
const allItems = new Collection<number, CWOpportunitySummary>();
|
||||||
|
const pageSize = 1000;
|
||||||
|
|
||||||
|
const count = await opportunityCw.countItems();
|
||||||
|
const totalPages = Math.ceil(count / pageSize);
|
||||||
|
|
||||||
|
for (let page = 0; page < totalPages; page++) {
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/sales/opportunities?page=${page + 1}&pageSize=${pageSize}&fields=id,_info`,
|
||||||
|
);
|
||||||
|
const items: CWOpportunitySummary[] = response.data;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
allItems.set(item.id, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allItems;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch All Opportunities (Full)
|
||||||
|
*
|
||||||
|
* Fetches all opportunities with complete data. Paginates through
|
||||||
|
* the full list.
|
||||||
|
*/
|
||||||
|
fetchAll: async (
|
||||||
|
conditions?: string,
|
||||||
|
): Promise<Collection<number, CWOpportunity>> => {
|
||||||
|
const allItems = new Collection<number, CWOpportunity>();
|
||||||
|
const pageSize = 1000;
|
||||||
|
|
||||||
|
const count = await opportunityCw.countItems(conditions);
|
||||||
|
const totalPages = Math.ceil(count / pageSize);
|
||||||
|
|
||||||
|
for (let page = 0; page < totalPages; page++) {
|
||||||
|
const conditionsParam = conditions
|
||||||
|
? `&conditions=${encodeURIComponent(conditions)}`
|
||||||
|
: "";
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/sales/opportunities?page=${page + 1}&pageSize=${pageSize}${conditionsParam}`,
|
||||||
|
);
|
||||||
|
const items: CWOpportunity[] = response.data;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
allItems.set(item.id, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allItems;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Single Opportunity
|
||||||
|
*
|
||||||
|
* Fetches a single opportunity by its ConnectWise ID.
|
||||||
|
*/
|
||||||
|
fetch: async (id: number): Promise<CWOpportunity> => {
|
||||||
|
const response = await connectWiseApi.get(`/sales/opportunities/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Opportunities by Company
|
||||||
|
*
|
||||||
|
* Fetches all opportunities associated with a specific ConnectWise company ID.
|
||||||
|
*/
|
||||||
|
fetchByCompany: async (
|
||||||
|
cwCompanyId: number,
|
||||||
|
): Promise<Collection<number, CWOpportunity>> => {
|
||||||
|
return opportunityCw.fetchAll(`company/id=${cwCompanyId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Opportunity Forecasts
|
||||||
|
*
|
||||||
|
* Fetches forecast/revenue items for a given opportunity.
|
||||||
|
*/
|
||||||
|
fetchForecasts: async (opportunityId: number): Promise<CWForecastItem[]> => {
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/sales/opportunities/${opportunityId}/forecast`,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Opportunity Notes
|
||||||
|
*
|
||||||
|
* Fetches notes associated with a given opportunity.
|
||||||
|
*/
|
||||||
|
fetchNotes: async (opportunityId: number): Promise<CWOpportunityNote[]> => {
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/sales/opportunities/${opportunityId}/notes`,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Opportunity Contacts
|
||||||
|
*
|
||||||
|
* Fetches contacts associated with a given opportunity.
|
||||||
|
*/
|
||||||
|
fetchContacts: async (
|
||||||
|
opportunityId: number,
|
||||||
|
): Promise<CWOpportunityContact[]> => {
|
||||||
|
const response = await connectWiseApi.get(
|
||||||
|
`/sales/opportunities/${opportunityId}/contacts`,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
interface CWReference {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CWMemberReference {
|
||||||
|
id: number;
|
||||||
|
identifier: string;
|
||||||
|
name: string;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CWCompanyReference {
|
||||||
|
id: number;
|
||||||
|
identifier: string;
|
||||||
|
name: string;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CWContactReference {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CWSiteReference {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CWCustomField {
|
||||||
|
id: number;
|
||||||
|
caption: string;
|
||||||
|
type: string;
|
||||||
|
entryMethod: string;
|
||||||
|
numberOfDecimals: number;
|
||||||
|
value: unknown;
|
||||||
|
connectWiseId: string;
|
||||||
|
rowNum: number;
|
||||||
|
userDefinedFieldRecId: number;
|
||||||
|
podId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CWOpportunity {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
expectedCloseDate: string;
|
||||||
|
type: CWReference;
|
||||||
|
stage: CWReference;
|
||||||
|
status: CWReference;
|
||||||
|
priority: CWReference;
|
||||||
|
notes: string;
|
||||||
|
source: string;
|
||||||
|
rating: CWReference;
|
||||||
|
campaign: CWReference;
|
||||||
|
primarySalesRep: CWMemberReference;
|
||||||
|
secondarySalesRep: CWMemberReference;
|
||||||
|
locationId: number;
|
||||||
|
businessUnitId: number;
|
||||||
|
company: CWCompanyReference;
|
||||||
|
contact: CWContactReference;
|
||||||
|
site: CWSiteReference;
|
||||||
|
customerPO: string;
|
||||||
|
pipelineChangeDate: string;
|
||||||
|
dateBecameLead: string;
|
||||||
|
closedDate: string;
|
||||||
|
closedBy: CWMemberReference;
|
||||||
|
totalSalesTax: number;
|
||||||
|
shipToCompany: CWCompanyReference;
|
||||||
|
shipToContact: CWContactReference;
|
||||||
|
shipToSite: CWSiteReference;
|
||||||
|
billToCompany: CWCompanyReference;
|
||||||
|
billToContact: CWContactReference;
|
||||||
|
billToSite: CWSiteReference;
|
||||||
|
billingTerms: CWReference;
|
||||||
|
taxCode: CWReference;
|
||||||
|
currency: CWReference;
|
||||||
|
companyLocationId: number;
|
||||||
|
location: CWReference;
|
||||||
|
department: CWReference;
|
||||||
|
closedFlag: boolean;
|
||||||
|
mobileGuid: string;
|
||||||
|
customFields: CWCustomField[];
|
||||||
|
_info: CWOpportunityInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CWOpportunityInfo {
|
||||||
|
lastUpdated: string;
|
||||||
|
updatedBy: string;
|
||||||
|
dateEntered: string;
|
||||||
|
enteredBy: string;
|
||||||
|
forecasts_href: string;
|
||||||
|
notes_href: string;
|
||||||
|
products_href: string;
|
||||||
|
contacts_href: string;
|
||||||
|
configurations_href: string;
|
||||||
|
team_href: string;
|
||||||
|
documents_href: string;
|
||||||
|
activities_href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CWForecastItem {
|
||||||
|
id: number;
|
||||||
|
opportunity: CWReference;
|
||||||
|
forecastType: string;
|
||||||
|
forecastMonth: string;
|
||||||
|
revenue: number;
|
||||||
|
cost: number;
|
||||||
|
forecastPercentage: number;
|
||||||
|
status: CWReference;
|
||||||
|
includedFlag: boolean;
|
||||||
|
linkedFlag: boolean;
|
||||||
|
recurringFlag: boolean;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CWOpportunityNote {
|
||||||
|
id: number;
|
||||||
|
opportunity: CWReference;
|
||||||
|
text: string;
|
||||||
|
type: CWReference;
|
||||||
|
flagged: boolean;
|
||||||
|
enteredBy: string;
|
||||||
|
mobileGuid: string;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CWOpportunityContact {
|
||||||
|
id: number;
|
||||||
|
opportunity: CWReference;
|
||||||
|
contact: CWContactReference;
|
||||||
|
company: CWCompanyReference;
|
||||||
|
role: CWReference;
|
||||||
|
notes: string;
|
||||||
|
referralFlag: boolean;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CWOpportunitySummary {
|
||||||
|
id: number;
|
||||||
|
_info?: Record<string, string>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { CWOpportunity } from "./opportunity.types";
|
||||||
|
|
||||||
|
export type ProcessedOpportunity = ReturnType<
|
||||||
|
typeof processOpportunityResponse
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes raw CW opportunity data into a cleaner, normalized shape
|
||||||
|
* suitable for API responses and internal consumption.
|
||||||
|
*/
|
||||||
|
export const processOpportunityResponse = (opportunity: CWOpportunity) => ({
|
||||||
|
id: opportunity.id,
|
||||||
|
name: opportunity.name,
|
||||||
|
expectedCloseDate: opportunity.expectedCloseDate,
|
||||||
|
closedDate: opportunity.closedDate,
|
||||||
|
closedFlag: opportunity.closedFlag,
|
||||||
|
type: opportunity.type
|
||||||
|
? { id: opportunity.type.id, name: opportunity.type.name }
|
||||||
|
: null,
|
||||||
|
stage: opportunity.stage
|
||||||
|
? { id: opportunity.stage.id, name: opportunity.stage.name }
|
||||||
|
: null,
|
||||||
|
status: opportunity.status
|
||||||
|
? { id: opportunity.status.id, name: opportunity.status.name }
|
||||||
|
: null,
|
||||||
|
priority: opportunity.priority
|
||||||
|
? { id: opportunity.priority.id, name: opportunity.priority.name }
|
||||||
|
: null,
|
||||||
|
rating: opportunity.rating
|
||||||
|
? { id: opportunity.rating.id, name: opportunity.rating.name }
|
||||||
|
: null,
|
||||||
|
source: opportunity.source,
|
||||||
|
notes: opportunity.notes,
|
||||||
|
customerPO: opportunity.customerPO,
|
||||||
|
company: opportunity.company
|
||||||
|
? {
|
||||||
|
id: opportunity.company.id,
|
||||||
|
identifier: opportunity.company.identifier,
|
||||||
|
name: opportunity.company.name,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
contact: opportunity.contact
|
||||||
|
? { id: opportunity.contact.id, name: opportunity.contact.name }
|
||||||
|
: null,
|
||||||
|
site: opportunity.site
|
||||||
|
? { id: opportunity.site.id, name: opportunity.site.name }
|
||||||
|
: null,
|
||||||
|
primarySalesRep: opportunity.primarySalesRep
|
||||||
|
? {
|
||||||
|
id: opportunity.primarySalesRep.id,
|
||||||
|
identifier: opportunity.primarySalesRep.identifier,
|
||||||
|
name: opportunity.primarySalesRep.name,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
secondarySalesRep: opportunity.secondarySalesRep
|
||||||
|
? {
|
||||||
|
id: opportunity.secondarySalesRep.id,
|
||||||
|
identifier: opportunity.secondarySalesRep.identifier,
|
||||||
|
name: opportunity.secondarySalesRep.name,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
closedBy: opportunity.closedBy
|
||||||
|
? {
|
||||||
|
id: opportunity.closedBy.id,
|
||||||
|
identifier: opportunity.closedBy.identifier,
|
||||||
|
name: opportunity.closedBy.name,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
campaign: opportunity.campaign
|
||||||
|
? { id: opportunity.campaign.id, name: opportunity.campaign.name }
|
||||||
|
: null,
|
||||||
|
totalSalesTax: opportunity.totalSalesTax,
|
||||||
|
location: opportunity.location
|
||||||
|
? { id: opportunity.location.id, name: opportunity.location.name }
|
||||||
|
: null,
|
||||||
|
department: opportunity.department
|
||||||
|
? { id: opportunity.department.id, name: opportunity.department.name }
|
||||||
|
: null,
|
||||||
|
pipelineChangeDate: opportunity.pipelineChangeDate,
|
||||||
|
dateBecameLead: opportunity.dateBecameLead,
|
||||||
|
info: opportunity._info,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes an array of raw CW opportunities.
|
||||||
|
*/
|
||||||
|
export const processOpportunitiesResponse = (opportunities: CWOpportunity[]) =>
|
||||||
|
opportunities.map(processOpportunityResponse);
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { prisma } from "../../../constants";
|
||||||
|
import { events } from "../../globalEvents";
|
||||||
|
import { opportunityCw } from "./opportunities";
|
||||||
|
import { OpportunityController } from "../../../controllers/OpportunityController";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh Opportunities
|
||||||
|
*
|
||||||
|
* Syncs local opportunity records with ConnectWise using the same
|
||||||
|
* stale-check pattern as refreshCatalog:
|
||||||
|
* 1. Fetch lightweight summaries (id + _info.lastUpdated)
|
||||||
|
* 2. Compare against local cwLastUpdated timestamps
|
||||||
|
* 3. Full-fetch only stale/new records
|
||||||
|
* 4. Upsert stale items, optionally linking to internal Company
|
||||||
|
*/
|
||||||
|
export const refreshOpportunities = async () => {
|
||||||
|
events.emit("cw:opportunities:refresh:check");
|
||||||
|
|
||||||
|
// 1. Fetch lightweight summaries from CW
|
||||||
|
const cwSummaries = await opportunityCw.fetchAllSummaries();
|
||||||
|
|
||||||
|
// 2. Fetch all DB items with their cwOpportunityId and cwLastUpdated
|
||||||
|
const dbItems = await prisma.opportunity.findMany({
|
||||||
|
select: { cwOpportunityId: true, cwLastUpdated: true },
|
||||||
|
});
|
||||||
|
const dbMap = new Map(
|
||||||
|
dbItems.map((item) => [item.cwOpportunityId, item.cwLastUpdated]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Determine stale / new IDs
|
||||||
|
const staleIds: number[] = [];
|
||||||
|
|
||||||
|
for (const [cwId, summary] of cwSummaries) {
|
||||||
|
const cwLastUpdated = summary._info?.lastUpdated
|
||||||
|
? new Date(summary._info.lastUpdated)
|
||||||
|
: null;
|
||||||
|
const dbLastUpdated = dbMap.get(cwId) ?? null;
|
||||||
|
|
||||||
|
if (!dbLastUpdated || (cwLastUpdated && cwLastUpdated > dbLastUpdated)) {
|
||||||
|
staleIds.push(cwId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (staleIds.length === 0) {
|
||||||
|
events.emit("cw:opportunities:refresh:skipped", {
|
||||||
|
totalCw: cwSummaries.size,
|
||||||
|
totalDb: dbItems.length,
|
||||||
|
staleCount: 0,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
events.emit("cw:opportunities:refresh:started", {
|
||||||
|
totalCw: cwSummaries.size,
|
||||||
|
totalDb: dbItems.length,
|
||||||
|
staleCount: staleIds.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Full-fetch all opportunities, filter to stale set
|
||||||
|
const staleIdSet = new Set(staleIds);
|
||||||
|
const allCwItems = await opportunityCw.fetchAll();
|
||||||
|
const staleItems = new Map<number, any>();
|
||||||
|
|
||||||
|
for (const [id, item] of allCwItems) {
|
||||||
|
if (staleIdSet.has(id)) {
|
||||||
|
staleItems.set(id, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Build a company CW ID → internal ID lookup for linking
|
||||||
|
const companies = await prisma.company.findMany({
|
||||||
|
select: { id: true, cw_CompanyId: true },
|
||||||
|
});
|
||||||
|
const companyMap = new Map(companies.map((c) => [c.cw_CompanyId, c.id]));
|
||||||
|
|
||||||
|
// 6. Upsert stale/new items
|
||||||
|
const updatedCount = (
|
||||||
|
await Promise.all(
|
||||||
|
staleIds.map(async (cwId) => {
|
||||||
|
const item = staleItems.get(cwId);
|
||||||
|
if (!item) return null;
|
||||||
|
|
||||||
|
const mapped = OpportunityController.mapCwToDb(item);
|
||||||
|
const companyId = item.company?.id
|
||||||
|
? (companyMap.get(item.company.id) ?? null)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return prisma.opportunity.upsert({
|
||||||
|
where: { cwOpportunityId: cwId },
|
||||||
|
create: {
|
||||||
|
cwOpportunityId: cwId,
|
||||||
|
...mapped,
|
||||||
|
companyId,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
...mapped,
|
||||||
|
companyId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
).filter(Boolean).length;
|
||||||
|
|
||||||
|
events.emit("cw:opportunities:refresh:completed", {
|
||||||
|
totalCw: cwSummaries.size,
|
||||||
|
totalDb: dbItems.length,
|
||||||
|
staleCount: staleIds.length,
|
||||||
|
itemsUpdated: updatedCount,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -91,6 +91,7 @@ export const refreshCatalog = async () => {
|
|||||||
where: { cwCatalogId: cwId },
|
where: { cwCatalogId: cwId },
|
||||||
create: {
|
create: {
|
||||||
cwCatalogId: cwId,
|
cwCatalogId: cwId,
|
||||||
|
identifier: item.identifier,
|
||||||
name: item.description,
|
name: item.description,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
customerDescription: item.customerDescription,
|
customerDescription: item.customerDescription,
|
||||||
@@ -110,6 +111,7 @@ export const refreshCatalog = async () => {
|
|||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
name: item.description,
|
name: item.description,
|
||||||
|
identifier: item.identifier,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
customerDescription: item.customerDescription,
|
customerDescription: item.customerDescription,
|
||||||
internalNotes: item.notes,
|
internalNotes: item.notes,
|
||||||
|
|||||||
@@ -158,6 +158,25 @@ interface EventTypes {
|
|||||||
totalItems: number;
|
totalItems: number;
|
||||||
updatedCount: number;
|
updatedCount: number;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
|
||||||
|
// ConnectWise Opportunities Events
|
||||||
|
"cw:opportunities:refresh:check": () => void;
|
||||||
|
"cw:opportunities:refresh:started": (data: {
|
||||||
|
totalCw: number;
|
||||||
|
totalDb: number;
|
||||||
|
staleCount: number;
|
||||||
|
}) => void;
|
||||||
|
"cw:opportunities:refresh:completed": (data: {
|
||||||
|
totalCw: number;
|
||||||
|
totalDb: number;
|
||||||
|
staleCount: number;
|
||||||
|
itemsUpdated: number;
|
||||||
|
}) => void;
|
||||||
|
"cw:opportunities:refresh:skipped": (data: {
|
||||||
|
totalCw: number;
|
||||||
|
totalDb: number;
|
||||||
|
staleCount: number;
|
||||||
|
}) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const events = new Eventra<EventTypes>();
|
export const events = new Eventra<EventTypes>();
|
||||||
|
|||||||
@@ -341,6 +341,72 @@ export const PERMISSION_NODES = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
procurement: {
|
||||||
|
name: "Procurement Permissions",
|
||||||
|
description:
|
||||||
|
"Permissions for accessing and managing procurement catalog items",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "procurement.catalog.fetch",
|
||||||
|
description: "Fetch a single catalog item",
|
||||||
|
usedIn: ["src/api/procurement/[id]/fetch.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "procurement.catalog.fetch.many",
|
||||||
|
description: "Fetch multiple catalog items or count",
|
||||||
|
usedIn: [
|
||||||
|
"src/api/procurement/fetchAll.ts",
|
||||||
|
"src/api/procurement/count.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "procurement.catalog.inventory.refresh",
|
||||||
|
description:
|
||||||
|
"Refresh on-hand inventory for a catalog item from ConnectWise",
|
||||||
|
usedIn: ["src/api/procurement/[id]/refreshInventory.ts"],
|
||||||
|
dependencies: ["procurement.catalog.fetch"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "procurement.catalog.link",
|
||||||
|
description: "Link or unlink catalog items to each other",
|
||||||
|
usedIn: [
|
||||||
|
"src/api/procurement/[id]/link.ts",
|
||||||
|
"src/api/procurement/[id]/unlink.ts",
|
||||||
|
],
|
||||||
|
dependencies: ["procurement.catalog.fetch"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
sales: {
|
||||||
|
name: "Sales Permissions",
|
||||||
|
description: "Permissions for accessing and managing sales opportunities",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "sales.opportunity.fetch",
|
||||||
|
description:
|
||||||
|
"Fetch a single opportunity and its sub-resources (forecasts, notes, contacts)",
|
||||||
|
usedIn: [
|
||||||
|
"src/api/sales/[id]/fetch.ts",
|
||||||
|
"src/api/sales/[id]/forecasts.ts",
|
||||||
|
"src/api/sales/[id]/notes.ts",
|
||||||
|
"src/api/sales/[id]/contacts.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "sales.opportunity.fetch.many",
|
||||||
|
description: "Fetch multiple opportunities or count",
|
||||||
|
usedIn: ["src/api/sales/fetchAll.ts", "src/api/sales/count.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "sales.opportunity.refresh",
|
||||||
|
description: "Refresh a single opportunity from ConnectWise",
|
||||||
|
usedIn: ["src/api/sales/[id]/refresh.ts"],
|
||||||
|
dependencies: ["sales.opportunity.fetch"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
unifi: {
|
unifi: {
|
||||||
name: "UniFi Permissions",
|
name: "UniFi Permissions",
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user