797 lines
19 KiB
Markdown
797 lines
19 KiB
Markdown
# dalpuri
|
|
|
|
Socket.IO data service that exposes Prisma collectors over event-based RPC.
|
|
|
|
Primary use case:
|
|
|
|
- A remote system connects over Socket.IO.
|
|
- It authenticates using a pre-shared key (PSK).
|
|
- It invokes collector events (for example, fetchCompanies) with optional query options.
|
|
- It receives normalized success or error responses.
|
|
|
|
## Stack
|
|
|
|
- Runtime: Bun
|
|
- Transport: Socket.IO
|
|
- Database: Microsoft SQL Server (READ-ONLY)
|
|
- ORM: Prisma with MSSQL adapter
|
|
|
|
## ⚠️ Important: Database is READ-ONLY
|
|
|
|
**All database operations are SELECT queries only.** No INSERT, UPDATE, DELETE, or other write operations are allowed. This service provides read-only access to ConnectWise Manage data.
|
|
|
|
## Schema Corrections Applied
|
|
|
|
All product-related tables have been corrected to map to the actual database schema:
|
|
|
|
| Prisma Model | Corrected Table | Previous (Incorrect) |
|
|
| ------------------- | ------------------------ | --------------------------- |
|
|
| ProductCategory | `IV_Category` | ~~Product_Category~~ |
|
|
| ProductSubcategory | `IV_SubCategory` | ~~Product_Subcategory~~ |
|
|
| ProductManufacturer | `Manufacturer` | ~~Product_Manufacturer~~ |
|
|
| ProductCatalog | `IV_Item` | ~~Product_Catalog~~ |
|
|
| ProductInventory | `Inventory_By_Warehouse` | ~~Product_Inventory~~ |
|
|
| ItemVendor | `iv_item_Vendor` | ~~ProductVendor~~ (removed) |
|
|
|
|
**Key Fixes:**
|
|
|
|
- ✅ All `@@map` directives point to actual database tables
|
|
- ✅ All field names match database columns exactly
|
|
- ✅ Primary keys use correct RecID naming (e.g., `ivItemRecId` not `catalogRecId`)
|
|
- ✅ Removed incorrect `ProductVendor` model (table doesn't exist)
|
|
- ✅ Added proper `ItemVendor` junction table model
|
|
- ✅ Fixed TypeScript types (`@db.UniqueIdentifier` capitalization)
|
|
- ✅ Corrected all relationship hierarchies
|
|
|
|
## Database Relations Reference
|
|
|
|
### Product Models
|
|
|
|
#### ProductCategory (`IV_Category`)
|
|
|
|
**Primary Key:** `ivCatRecId`
|
|
|
|
**Available Relations:**
|
|
|
|
- `subcategories` → ProductSubcategory[] - All subcategories in this category
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: ["subcategories"];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### ProductSubcategory (`IV_SubCategory`)
|
|
|
|
**Primary Key:** `ivSubcatRecId`
|
|
**Foreign Keys:** `ivCatRecId` → ProductCategory
|
|
|
|
**Available Relations:**
|
|
|
|
- `category` → ProductCategory - Parent category
|
|
- `products` → ProductCatalog[] - All products in this subcategory
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: ["category", "products"];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### ProductManufacturer (`Manufacturer`)
|
|
|
|
**Primary Key:** `manufacturerRecId`
|
|
|
|
**Available Relations:**
|
|
|
|
- `products` → ProductCatalog[] - All products by this manufacturer
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: ["products"];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### ProductCatalog (`IV_Item`)
|
|
|
|
**Primary Key:** `ivItemRecId`
|
|
**Foreign Keys:** `ivSubcatRecId` → ProductSubcategory, `manufacturerRecId` → ProductManufacturer
|
|
|
|
**Available Relations:**
|
|
|
|
- `subcategory` → ProductSubcategory - Category info (can nest `category` for full hierarchy)
|
|
- `manufacturer` → ProductManufacturer - Manufacturer info
|
|
- `inventory` → ProductInventory[] - All inventory records across warehouses
|
|
- `itemVendors` → ItemVendor[] - All vendor relationships
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: ["subcategory", "manufacturer", "inventory", "itemVendors"];
|
|
}
|
|
```
|
|
|
|
**Nested Include for Full Category Hierarchy:**
|
|
|
|
```json
|
|
{
|
|
"include": {
|
|
"subcategory": {
|
|
"include": {
|
|
"category": true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### ProductInventory (`Inventory_By_Warehouse`)
|
|
|
|
**Primary Key:** `inventoryByWarehouseRecId`
|
|
**Foreign Keys:** `ivItemRecId` → ProductCatalog
|
|
|
|
**Available Relations:**
|
|
|
|
- `productCatalog` → ProductCatalog - Parent product
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: ["productCatalog"];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### ItemVendor (`iv_item_Vendor`)
|
|
|
|
**Primary Key:** `ivItemVendorId`
|
|
**Foreign Keys:** `ivItemRecId` → ProductCatalog
|
|
|
|
**Available Relations:**
|
|
|
|
- `product` → ProductCatalog - Product this vendor supplies
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: ["product"];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Company Models
|
|
|
|
#### Company
|
|
|
|
**Primary Key:** `companyRecId`
|
|
|
|
**Available Relations:**
|
|
|
|
- `timeZone` → TimeZone
|
|
- `companyStatus` → CompanyStatus
|
|
- `taxCode` → TaxCode
|
|
- `currency` → Currency
|
|
- `ownerLevel` → OwnerLevel
|
|
- `market` → Market
|
|
- `ownershipType` → OwnershipType
|
|
- `billingTerms` → BillingTerms
|
|
- `billingDelivery` → BillingDelivery
|
|
- `srSignoff` → SrSignoff
|
|
- `ivPriceHeader` → IvPriceHeader
|
|
- `officeCalendar` → OfficeCalendar
|
|
- `customerUsageType` → CustomerUsageType
|
|
- `blInvtemplateSetup` → BlInvTemplateSetup
|
|
- `emailTemplate` → EmailTemplate
|
|
- `parentCompany` → Company - Parent company (self-relation)
|
|
- `childCompanies` → Company[] - Child companies
|
|
- `opportunityCompanies` → SoOpportunity[] - Opportunities where this is the primary company
|
|
- `opportunityShipToCompanies` → SoOpportunity[] - Opportunities shipping to this company
|
|
- `opportunityBillToCompanies` → SoOpportunity[] - Opportunities billing to this company
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: [
|
|
"companyStatus",
|
|
"ownerLevel",
|
|
"opportunityCompanies",
|
|
"parentCompany",
|
|
"childCompanies",
|
|
];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Opportunity Models
|
|
|
|
#### SoOpportunity (`SO_Opportunity`)
|
|
|
|
**Primary Key:** `opportunityRecId`
|
|
|
|
**Available Relations:**
|
|
|
|
- `company` → Company - Primary company
|
|
- `contact` → Contact - Primary contact
|
|
- `soPipeline` → SoPipeline - Pipeline stage
|
|
- `soInterest` → SoInterest - Interest level
|
|
- `billingUnit` → BillingUnit - Billing unit
|
|
- `contractType` → ContractType - Contract type
|
|
- `companyAddress` → CompanyAddress - Company address
|
|
- `soOppStatus` → SoOppStatus - Opportunity status
|
|
- `pmProject` → PmProject - Related project
|
|
- `ownerLevel` → OwnerLevel - Owner level
|
|
- `soType` → SoType - Opportunity type
|
|
- `marketingCampaign` → MarketingCampaign - Marketing campaign
|
|
- `agrType` → AgrType - Agreement type
|
|
- `srService` → SrService - Service type
|
|
- `soUrgency` → SoUrgency - Urgency level
|
|
- `approvedByMember` → Member - Member who approved
|
|
- `rejectedByMember` → Member - Member who rejected
|
|
- `shipToCompany` → Company - Ship to company
|
|
- `shipToContact` → Contact - Ship to contact
|
|
- `shipToCompanyAddress` → CompanyAddress - Ship to address
|
|
- `billToCompany` → Company - Bill to company
|
|
- `billToContact` → Contact - Bill to contact
|
|
- `billToCompanyAddress` → CompanyAddress - Bill to address
|
|
- `billingTerms` → BillingTerms - Billing terms
|
|
- `taxCode` → TaxCode - Tax code
|
|
- `currency` → Currency - Currency
|
|
- `techContact` → Contact - Technical contact
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: [
|
|
"company",
|
|
"contact",
|
|
"soPipeline",
|
|
"soOppStatus",
|
|
"ownerLevel",
|
|
"approvedByMember",
|
|
];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Member Models
|
|
|
|
#### Member
|
|
|
|
**Primary Key:** `memberRecId`
|
|
|
|
**Available Relations:**
|
|
|
|
- `ownerLevel` → OwnerLevel - Member owner level
|
|
- `billingUnit` → BillingUnit - Billing unit
|
|
- `activityClass` → ActivityClass - Activity class
|
|
- `memberType` → MemberType - Member type
|
|
- `srOwnerLevel` → OwnerLevel - Service request owner level
|
|
- `srBillingUnit` → BillingUnit - Service request billing unit
|
|
- `schOwnerLevel` → OwnerLevel - Schedule owner level
|
|
- `schBillingUnit` → BillingUnit - Schedule billing unit
|
|
- `srBoard` → SrBoard - Service board
|
|
- `timeZone` → TimeZone - Time zone
|
|
- `brHeader` → BrHeader - BR header
|
|
- `activityType` → ActivityType - Activity type
|
|
- `pmOwnerLevel` → OwnerLevel - Project management owner level
|
|
- `pmBillingUnit` → BillingUnit - Project management billing unit
|
|
- `warehouse` → Warehouse - Warehouse
|
|
- `warehouseBin` → WarehouseBin - Warehouse bin
|
|
- `country` → Country - Country
|
|
- `directionalSync` → DirectionalSync - Sync direction
|
|
- `approvedOpportunities` → SoOpportunity[] - Opportunities approved by this member
|
|
- `rejectedOpportunities` → SoOpportunity[] - Opportunities rejected by this member
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: [
|
|
"ownerLevel",
|
|
"billingUnit",
|
|
"memberType",
|
|
"timeZone",
|
|
"approvedOpportunities",
|
|
];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Contact Models
|
|
|
|
#### Contact
|
|
|
|
**Primary Key:** `contactRecId`
|
|
|
|
**Available Relations:**
|
|
|
|
- `opportunityContacts` → SoOpportunity[] - Opportunities where this is primary contact
|
|
- `opportunityShipToContacts` → SoOpportunity[] - Opportunities shipping to this contact
|
|
- `opportunityBillToContacts` → SoOpportunity[] - Opportunities billing to this contact
|
|
- `opportunityTechContacts` → SoOpportunity[] - Opportunities where this is technical contact
|
|
|
|
**Example Include:**
|
|
|
|
```typescript
|
|
{
|
|
include: ["opportunityContacts"];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Relationship Hierarchy Diagrams
|
|
|
|
#### Product Hierarchy
|
|
|
|
```
|
|
ProductCategory (IV_Category)
|
|
↓ one-to-many
|
|
ProductSubcategory (IV_SubCategory)
|
|
↓ one-to-many
|
|
ProductCatalog (IV_Item) ←──── ProductManufacturer (Manufacturer)
|
|
↓ one-to-many many-to-one ↗
|
|
├─→ ProductInventory (Inventory_By_Warehouse)
|
|
└─→ ItemVendor (iv_item_Vendor)
|
|
```
|
|
|
|
#### Company/Opportunity Hierarchy
|
|
|
|
```
|
|
Company
|
|
├─→ opportunityCompanies (SoOpportunity[])
|
|
├─→ parentCompany (Company)
|
|
└─→ childCompanies (Company[])
|
|
|
|
SoOpportunity
|
|
├─→ company (Company)
|
|
├─→ contact (Contact)
|
|
├─→ soPipeline (SoPipeline)
|
|
├─→ soOppStatus (SoOppStatus)
|
|
├─→ ownerLevel (OwnerLevel)
|
|
└─→ approvedByMember (Member)
|
|
```
|
|
|
|
## Environment Variables
|
|
|
|
Required variables:
|
|
|
|
- DATABASE_URL: SQL Server connection string for Prisma
|
|
- PSK: pre-shared key required for Socket.IO handshake
|
|
- PORT: optional server port (defaults to 3000)
|
|
|
|
Example shape:
|
|
|
|
```env
|
|
DATABASE_URL="sqlserver://host:1433;database=your_db;user=your_user;password=your_password;encrypt=true;trustServerCertificate=true"
|
|
PSK="replace-with-strong-shared-key"
|
|
PORT="3000"
|
|
```
|
|
|
|
## Install And Run
|
|
|
|
Install dependencies:
|
|
|
|
```bash
|
|
bun install
|
|
```
|
|
|
|
Run in watch mode:
|
|
|
|
```bash
|
|
bun run dev
|
|
```
|
|
|
|
Generate Prisma client:
|
|
|
|
```bash
|
|
bun run db:generate
|
|
```
|
|
|
|
View database in Prisma Studio:
|
|
|
|
```bash
|
|
bun run db:studio
|
|
```
|
|
|
|
Type-check:
|
|
|
|
```bash
|
|
bunx tsc --noEmit
|
|
```
|
|
|
|
## Runtime Flow
|
|
|
|
1. Server starts from src/index.ts and calls startServer.
|
|
2. Socket.IO server is created.
|
|
3. Handshake authentication validates PSK.
|
|
4. On each new socket connection, server scans src/collectors.
|
|
5. For each .ts collector file, server registers an event named after the file name.
|
|
6. Incoming event payload is passed to the collector as opts.
|
|
7. Collector result is returned via callback in a standard envelope.
|
|
|
|
## Authentication Contract
|
|
|
|
Connection is rejected unless provided PSK matches server PSK.
|
|
|
|
Accepted handshake sources (first non-empty wins):
|
|
|
|
- auth.psk
|
|
- header x-psk
|
|
- query psk
|
|
|
|
Recommended client method:
|
|
|
|
- send psk in auth.psk
|
|
|
|
If auth fails:
|
|
|
|
- socket connection is denied with Unauthorized
|
|
|
|
## Collector Event Contract
|
|
|
|
### Event Name
|
|
|
|
Event name is the collector file name without .ts.
|
|
|
|
Current collectors:
|
|
|
|
- fetchCompanies (from src/collectors/fetchCompanies.ts)
|
|
- fetchMembers (from src/collectors/fetchMembers.ts)
|
|
- fetchProducts (from src/collectors/fetchProducts.ts)
|
|
|
|
### Event Signature
|
|
|
|
Client emits:
|
|
|
|
```ts
|
|
socket.emit(eventName, opts, callback);
|
|
```
|
|
|
|
Where:
|
|
|
|
- eventName: string
|
|
- opts: optional object
|
|
- callback: function receiving envelope
|
|
|
|
### opts Shape
|
|
|
|
Collectors accept optional opts:
|
|
|
|
```ts
|
|
{
|
|
select?: string[];
|
|
include?: string[];
|
|
}
|
|
```
|
|
|
|
Rules:
|
|
|
|
- Omit opts entirely to fetch full records.
|
|
- Use select to request specific fields.
|
|
- Use include to request relations.
|
|
- Do not provide both select and include in the same call.
|
|
|
|
If both select and include are provided:
|
|
|
|
- collector throws
|
|
- callback returns success false with error message
|
|
|
|
### Response Envelope
|
|
|
|
Success:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [ ... ]
|
|
}
|
|
```
|
|
|
|
Failure:
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"error": "error message"
|
|
}
|
|
```
|
|
|
|
## Concrete Usage Examples
|
|
|
|
### 1) Fetch all companies
|
|
|
|
```ts
|
|
socket.emit("fetchCompanies", undefined, (res) => {
|
|
if (!res.success) return console.error(res.error);
|
|
console.log(res.data);
|
|
});
|
|
```
|
|
|
|
### 2) Fetch selected company fields
|
|
|
|
```ts
|
|
socket.emit(
|
|
"fetchCompanies",
|
|
{ select: ["companyId", "companyName"] },
|
|
(res) => {
|
|
if (!res.success) return console.error(res.error);
|
|
console.log(res.data);
|
|
},
|
|
);
|
|
```
|
|
|
|
### 3) Fetch companies with opportunities relation
|
|
|
|
```ts
|
|
socket.emit("fetchCompanies", { include: ["opportunityCompanies"] }, (res) => {
|
|
if (!res.success) return console.error(res.error);
|
|
console.log(res.data);
|
|
});
|
|
```
|
|
|
|
### 4) Fetch selected member fields
|
|
|
|
```ts
|
|
socket.emit(
|
|
"fetchMembers",
|
|
{ select: ["memberId", "firstName", "lastName"] },
|
|
(res) => {
|
|
if (!res.success) return console.error(res.error);
|
|
console.log(res.data);
|
|
},
|
|
);
|
|
```
|
|
|
|
### 5) Fetch all products
|
|
|
|
```ts
|
|
socket.emit("fetchProducts", undefined, (res) => {
|
|
if (!res.success) return console.error(res.error);
|
|
console.log(res.data);
|
|
});
|
|
```
|
|
|
|
### 6) Fetch selected product fields
|
|
|
|
```ts
|
|
socket.emit(
|
|
"fetchProducts",
|
|
{
|
|
select: [
|
|
"ivItemRecId",
|
|
"itemId",
|
|
"description",
|
|
"listPrice",
|
|
"currentCost",
|
|
],
|
|
},
|
|
(res) => {
|
|
if (!res.success) return console.error(res.error);
|
|
console.log(res.data);
|
|
},
|
|
);
|
|
```
|
|
|
|
### 7) Fetch products with subcategory, manufacturer, and inventory
|
|
|
|
```ts
|
|
socket.emit(
|
|
"fetchProducts",
|
|
{ include: ["subcategory", "manufacturer", "inventory", "itemVendors"] },
|
|
(res) => {
|
|
if (!res.success) return console.error(res.error);
|
|
console.log(res.data);
|
|
},
|
|
);
|
|
```
|
|
|
|
### 8) Fetch products with nested category hierarchy
|
|
|
|
```ts
|
|
// To get the full category hierarchy, you can nest includes in the collector
|
|
// This would require updating the collector to support nested includes
|
|
socket.emit(
|
|
"fetchProducts",
|
|
{
|
|
include: {
|
|
subcategory: {
|
|
include: {
|
|
category: true,
|
|
},
|
|
},
|
|
manufacturer: true,
|
|
inventory: true,
|
|
},
|
|
},
|
|
(res) => {
|
|
if (!res.success) return console.error(res.error);
|
|
console.log(res.data);
|
|
},
|
|
);
|
|
```
|
|
|
|
## Reference Client (Node)
|
|
|
|
This is the easiest template to hand to another system:
|
|
|
|
```ts
|
|
import { io } from "socket.io-client";
|
|
|
|
const socket = io("http://localhost:3000", {
|
|
auth: { psk: process.env.PSK },
|
|
transports: ["websocket"],
|
|
});
|
|
|
|
socket.on("connect", () => {
|
|
console.log("connected", socket.id);
|
|
|
|
socket.emit("fetchCompanies", { select: ["companyId"] }, (res: any) => {
|
|
if (!res.success) {
|
|
console.error("collector error", res.error);
|
|
return;
|
|
}
|
|
|
|
console.log("rows", res.data.length);
|
|
});
|
|
});
|
|
|
|
socket.on("connect_error", (err) => {
|
|
console.error("connect_error", err.message);
|
|
});
|
|
```
|
|
|
|
## Collector Field Reference
|
|
|
|
### fetchCompanies
|
|
|
|
Select fields: companyRecId, companyId, companyName, phoneNbr, websiteUrl, accountNbr, etc.
|
|
Include relations: timeZone, companyStatus, taxCode, currency, ownerLevel, market, billingTerms, billingDelivery, opportunityCompanies, etc.
|
|
|
|
### fetchMembers
|
|
|
|
Select fields: memberRecId, memberId, firstName, lastName, emailAddress,
|
|
inactiveFlag, title, etc.
|
|
Include relations: ownerLevel, billingUnit, memberType, timeZone, directionalSync, etc.
|
|
|
|
### fetchProducts
|
|
|
|
Select fields: ivItemRecId, itemId, description, longDescription, ivSubcatRecId, manufacturerRecId, listPrice, currentCost, taxableFlag, inactiveFlag, mfgItemId, vendorSku, minimumStock, recurringFlag, lastUpdate, lastUpdateUtc, etc.
|
|
|
|
Include relations: subcategory, manufacturer, inventory, itemVendors
|
|
|
|
**Note:** The product model has been corrected to map to `IV_Item` table. Use the corrected field names as shown above.
|
|
|
|
## How To Add A New Collector
|
|
|
|
1. Create a new file in src/collectors, for example fetchOpportunities.ts.
|
|
2. Export a default async function with optional opts argument.
|
|
3. Use the helper in src/helper/collectorQuery.ts to process select/include.
|
|
4. Restart server if needed.
|
|
|
|
Template:
|
|
|
|
```ts
|
|
import { prisma } from "../constants";
|
|
import { Prisma } from "../../generated/prisma/client";
|
|
import {
|
|
buildCollectorFindManyArgs,
|
|
CollectorQueryOptions,
|
|
} from "../helper/collectorQuery";
|
|
|
|
type OpportunityCollectorOpts = CollectorQueryOptions<
|
|
Prisma.SoOpportunitySelect,
|
|
Prisma.SoOpportunityInclude
|
|
>;
|
|
|
|
export default async (opts?: OpportunityCollectorOpts) => {
|
|
const args: Prisma.SoOpportunityFindManyArgs = buildCollectorFindManyArgs<
|
|
Prisma.SoOpportunitySelect,
|
|
Prisma.SoOpportunityInclude
|
|
>(opts);
|
|
|
|
return prisma.soOpportunity.findMany(args);
|
|
};
|
|
```
|
|
|
|
## Operational Notes
|
|
|
|
- The server currently trusts all CORS origins at the Socket.IO layer.
|
|
- Collector discovery happens at connection time by reading src/collectors.
|
|
- Keep collector file names stable because they define public event names.
|
|
- **Database is READ-ONLY**: Only SELECT queries are allowed.
|
|
|
|
## Generated Data Files
|
|
|
|
The repository may contain generated JSON files (e.g., `products-with-relations.json`) that export sample data with all relationships included. These are useful for:
|
|
|
|
- Understanding the data structure
|
|
- Testing client applications
|
|
- Offline development
|
|
|
|
To regenerate product data:
|
|
|
|
```bash
|
|
bun run export:products
|
|
```
|
|
|
|
## Field Name Reference (Product Models)
|
|
|
|
### Old (Incorrect) vs New (Correct) Field Names
|
|
|
|
| Old Field Name | New Field Name | Description |
|
|
| --------------------- | ----------------------- | ----------------- |
|
|
| `catalogRecId` | `ivItemRecId` | Primary key |
|
|
| `cwCatalogId` | N/A (removed) | No longer exists |
|
|
| `identifier` | `itemId` | Item identifier |
|
|
| `productName` | `description` | Product name |
|
|
| `price` | `listPrice` | List price |
|
|
| `cost` | `currentCost` | Current cost |
|
|
| `categoryRecId` | `ivSubcatRecId` | Subcategory FK |
|
|
| `subcategoryRecId` | N/A (use ivSubcatRecId) | Changed |
|
|
| `manufacturerPartNum` | `mfgItemId` | Manufacturer part |
|
|
| `salesTaxableFlag` | `taxableFlag` | Tax flag |
|
|
| `cwLastUpdated` | `lastUpdate` | Last update |
|
|
|
|
### Product Relations Name Changes
|
|
|
|
| Old Relation | New Relation | Type |
|
|
| ------------ | -------------- | ----------------------------------- |
|
|
| `category` | `subcategory` | Changed (now points to subcategory) |
|
|
| `vendor` | `itemVendors` | Changed (now junction table) |
|
|
| N/A | `manufacturer` | Added |
|
|
| `inventory` | `inventory` | Same (but different table) |
|
|
|
|
**Important:** When updating collectors or client code, use the new field names and relations shown above.
|
|
|
|
## Troubleshooting
|
|
|
|
### Prisma Client Errors
|
|
|
|
If you see Prisma client errors, regenerate the client:
|
|
|
|
```bash
|
|
bun run db:generate
|
|
```
|
|
|
|
### Connection Errors
|
|
|
|
- Verify `DATABASE_URL` in `.env`
|
|
- Check SQL Server connectivity
|
|
- Ensure MSSQL adapter is installed (`@prisma/adapter-mssql`)
|
|
|
|
### Collector Errors
|
|
|
|
- Check that relation names are correct (use the reference above)
|
|
- Use `select` OR `include`, not both
|
|
- Verify field names match the schema
|
|
|
|
## Additional Resources
|
|
|
|
- [Prisma Documentation](https://www.prisma.io/docs)
|
|
- [Socket.IO Documentation](https://socket.io/docs/v4/)
|
|
- [ConnectWise Manage API Docs](https://developer.connectwise.com/)
|