feat: sales activities, forecast products, catalog categories, member cache, procurement filters, and comprehensive tests
New features: - ActivityController and manager for CW sales activities (CRUD) - ForecastProductController for opportunity forecast/product lines - CW member cache with dual-layer (in-memory + Redis) resolution - Catalog category/subcategory/ecosystem taxonomy module - Quote statuses type definitions with CW mapping - User-defined fields (UDF) module with cache and event refresh - Company sites CW module with serialization - Procurement manager filters (category, ecosystem, manufacturer, price, stock) - Opportunity notes CRUD and product line management via CW API - Opportunity type definitions endpoint Updates: - OpportunityController: CW refresh, company hydration, activities, custom fields - UserController: cwIdentifier field for CW member linking - CatalogItemController: category/subcategory fields from CW - PermissionNodes: sales note/product CRUD nodes, subCategories, collectPermissions - API routes: procurement categories/filters, sales notes/products, opportunity types - Global events: UDF and member refresh intervals on startup Tests (414 passing): - ActivityController, ForecastProductController, OpportunityController unit tests - UserController cwIdentifier tests - catalogCategories, companySites, memberCache, procurement module tests - activityTypes, opportunityTypes, quoteStatuses type tests - permissionNodes subCategories and getAllPermissionNodes tests - Updated test setup with redis mock, API method mocks, and builder helpers
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
import { Company } from "../../generated/prisma/client";
|
||||
import { connectWiseApi } from "../constants";
|
||||
import { fetchCwCompanyById } from "../modules/cw-utils/fetchCompany";
|
||||
import { fetchCompanyConfigurations } from "../modules/cw-utils/configurations/fetchCompanyConfigurations";
|
||||
import { updateCwInternalCompany } from "../modules/cw-utils/updateCompany";
|
||||
import {
|
||||
fetchCompanySites,
|
||||
fetchCompanySite,
|
||||
serializeCwSite,
|
||||
} from "../modules/cw-utils/sites/companySites";
|
||||
import { Company as CWCompany, Contact } from "../types/ConnectWiseTypes";
|
||||
|
||||
/**
|
||||
@@ -16,7 +22,7 @@ export class CompanyController {
|
||||
public name: string;
|
||||
public readonly cw_Identifier: string;
|
||||
public readonly cw_CompanyId: number;
|
||||
public readonly cw_Data?: {
|
||||
public cw_Data?: {
|
||||
company: CWCompany;
|
||||
defaultContact: Contact | null;
|
||||
allContacts: Contact[];
|
||||
@@ -30,6 +36,38 @@ export class CompanyController {
|
||||
this.cw_Data = cwData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate CW Data
|
||||
*
|
||||
* Fetches and populates the full ConnectWise company data
|
||||
* (company, default contact, all contacts) if not already loaded.
|
||||
*
|
||||
* @returns {ThisType}
|
||||
*/
|
||||
public async hydrateCwData() {
|
||||
if (this.cw_Data) return this;
|
||||
|
||||
const cwCompany = await fetchCwCompanyById(this.cw_CompanyId);
|
||||
if (!cwCompany) return this;
|
||||
|
||||
const contactHref = cwCompany.defaultContact?._info?.contact_href;
|
||||
const defaultContactData = contactHref
|
||||
? await connectWiseApi.get(contactHref)
|
||||
: undefined;
|
||||
|
||||
const allContactsData = await connectWiseApi.get(
|
||||
`${cwCompany._info.contacts_href}&pageSize=1000`,
|
||||
);
|
||||
|
||||
this.cw_Data = {
|
||||
company: cwCompany,
|
||||
defaultContact: defaultContactData?.data ?? null,
|
||||
allContacts: allContactsData.data,
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh Internal Company Data from ConnectWise
|
||||
*
|
||||
@@ -71,6 +109,30 @@ export class CompanyController {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Company Sites
|
||||
*
|
||||
* Retrieves all sites for this company from ConnectWise
|
||||
* and returns them as serialized site objects.
|
||||
*/
|
||||
public async fetchSites() {
|
||||
const sites = await fetchCompanySites(this.cw_CompanyId);
|
||||
return sites.map(serializeCwSite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Company Site by ID
|
||||
*
|
||||
* Retrieves a single site by its ConnectWise site ID
|
||||
* and returns a serialized site object.
|
||||
*
|
||||
* @param cwSiteId - The ConnectWise site ID
|
||||
*/
|
||||
public async fetchSite(cwSiteId: number) {
|
||||
const site = await fetchCompanySite(this.cw_CompanyId, cwSiteId);
|
||||
return serializeCwSite(site);
|
||||
}
|
||||
|
||||
public toJson(opts?: {
|
||||
includeAddress: boolean;
|
||||
includePrimaryContact: boolean;
|
||||
|
||||
Reference in New Issue
Block a user