Added Connectwise Compnay Syncing
This commit is contained in:
@@ -4,6 +4,7 @@ import { Prisma, PrismaClient } from "../generated/prisma/client";
|
||||
import * as msal from "@azure/msal-node";
|
||||
import { Server } from "socket.io";
|
||||
import { Server as Engine } from "@socket.io/bun-engine";
|
||||
import axios from "axios";
|
||||
|
||||
const connectionString = `${process.env.DATABASE_URL}`;
|
||||
const adapter = new PrismaPg({ connectionString });
|
||||
@@ -55,3 +56,16 @@ const engine = new Engine();
|
||||
|
||||
io.bind(engine);
|
||||
export { io, engine };
|
||||
|
||||
// Connectwise API Client
|
||||
|
||||
const connectWiseApi = axios.create({
|
||||
baseURL: `https://ttscw.totaltech.net/v4_6_release/apis/3.0/`,
|
||||
headers: {
|
||||
Authorization: `Basic ${process.env.CW_BASIC_TOKEN}`,
|
||||
clientId: `${process.env.CW_CLIENT_ID}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
export { connectWiseApi };
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import { refresh } from "./api/auth";
|
||||
import app from "./api/server";
|
||||
import { engine, PORT } from "./constants";
|
||||
import { refreshCompanies } from "./modules/cw-utils/refreshCompanies";
|
||||
import { events, setupEventDebugger } from "./modules/globalEvents";
|
||||
|
||||
// Setup global event debugger in non-production environments
|
||||
if (Bun.env.NODE_ENV == "development") setupEventDebugger();
|
||||
|
||||
// Refresh the internal list of companies every minute
|
||||
await refreshCompanies();
|
||||
setInterval(() => refreshCompanies, 60 * 1000);
|
||||
|
||||
Bun.serve({
|
||||
port: PORT,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Collection } from "@discordjs/collection";
|
||||
import { connectWiseApi } from "../../constants";
|
||||
import { Company } from "../../types/ConnectWiseTypes";
|
||||
|
||||
export const fetchAllCwCompanies = async (): Promise<
|
||||
Collection<string, Company>
|
||||
> => {
|
||||
let allCompanies = new Collection<string, Company>();
|
||||
const pageCount = 1000;
|
||||
|
||||
const count = (await connectWiseApi.get("/company/companies/count")).data
|
||||
.count;
|
||||
const totalPages = Math.ceil(count / pageCount);
|
||||
|
||||
for (let page = 0; page < totalPages; page++) {
|
||||
const response = await connectWiseApi.get(
|
||||
`/company/companies?page=${page + 1}&pageSize=${pageCount}`,
|
||||
);
|
||||
const companies = response.data;
|
||||
|
||||
for (const company of companies) {
|
||||
allCompanies.set(company.id, company);
|
||||
}
|
||||
}
|
||||
|
||||
return allCompanies;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { connectWiseApi, prisma } from "../../constants";
|
||||
import { events } from "../globalEvents";
|
||||
import { fetchAllCwCompanies } from "./fetchAllCompanies";
|
||||
|
||||
export const refreshCompanies = async () => {
|
||||
events.emit("cw:companies:refreshed:check");
|
||||
|
||||
// Dynamically import to avoid circular dependency
|
||||
const internalCompanyCount = await prisma.company.count();
|
||||
const externalCompanyCount =
|
||||
(await connectWiseApi.get("/company/companies/count")).data.count - 1;
|
||||
|
||||
if (internalCompanyCount !== externalCompanyCount) {
|
||||
console.log(
|
||||
`Company count mismatch detected. Internal: ${internalCompanyCount}, External: ${externalCompanyCount}. Refreshing...`,
|
||||
);
|
||||
|
||||
const allCompanies = await fetchAllCwCompanies();
|
||||
|
||||
await Promise.all(
|
||||
allCompanies.map(async (company) => {
|
||||
return await prisma.company.upsert({
|
||||
where: { cw_CompanyId: company.id },
|
||||
create: {
|
||||
cw_CompanyId: company.id,
|
||||
cw_Identifier: company.identifier,
|
||||
name: company.name,
|
||||
},
|
||||
update: {
|
||||
name: company.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
events.emit("cw:companies:refreshed", {
|
||||
internalCompaniesCount: internalCompanyCount,
|
||||
externalCompaniesCount: externalCompanyCount,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -53,6 +53,11 @@ interface EventTypes {
|
||||
err: Error;
|
||||
role: RoleController;
|
||||
}) => void;
|
||||
"cw:companies:refreshed:check": () => void;
|
||||
"cw:companies:refreshed": (data: {
|
||||
internalCompaniesCount: number;
|
||||
externalCompaniesCount: number;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export const events = new Eventra<EventTypes>();
|
||||
@@ -61,4 +66,4 @@ export function setupEventDebugger() {
|
||||
events.any((eventName, ...args) => {
|
||||
console.log(`[ Event Debugger ] (${eventName})`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
export interface Company {
|
||||
id: number;
|
||||
identifier: string;
|
||||
name: string;
|
||||
status: CompanyStatus;
|
||||
addressLine1: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zip: string;
|
||||
phoneNumber: string;
|
||||
faxNumber: string;
|
||||
website: string;
|
||||
territory: Territory;
|
||||
market: Market;
|
||||
accountNumber: string;
|
||||
defaultContact: Contact;
|
||||
dateAcquired: string;
|
||||
annualRevenue: number;
|
||||
numberOfEmployees: number;
|
||||
leadFlag: boolean;
|
||||
unsubscribeFlag: boolean;
|
||||
vendorIdentifier: string;
|
||||
taxIdentifier: string;
|
||||
taxCode: TaxCode;
|
||||
billingTerms: BasicEntity;
|
||||
billToCompany: LinkedCompany;
|
||||
billingSite: LinkedSite;
|
||||
billingContact: Contact;
|
||||
invoiceDeliveryMethod: BasicEntity;
|
||||
invoiceToEmailAddress: string;
|
||||
deletedFlag: boolean;
|
||||
dateDeleted: string;
|
||||
mobileGuid: string;
|
||||
resellerIdentifier: string;
|
||||
isVendorFlag: boolean;
|
||||
types: TypeItem[];
|
||||
site: LinkedSite;
|
||||
_info: CompanyInfo;
|
||||
customFields: CustomField[];
|
||||
}
|
||||
|
||||
export interface CompanyStatus {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
status_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Territory {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
location_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Market {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
Market_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TaxCode {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
taxCode_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BasicEntity {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface LinkedCompany extends BasicEntity {
|
||||
identifier: string;
|
||||
_info: {
|
||||
company_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LinkedSite extends BasicEntity {
|
||||
_info: {
|
||||
site_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Contact {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
contact_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TypeItem {
|
||||
id: number;
|
||||
name: string;
|
||||
_info: {
|
||||
type_href: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CompanyInfo {
|
||||
lastUpdated: string;
|
||||
updatedBy: string;
|
||||
dateEntered: string;
|
||||
enteredBy: string;
|
||||
contacts_href: string;
|
||||
agreements_href: string;
|
||||
tickets_href: string;
|
||||
opportunities_href: string;
|
||||
activities_href: string;
|
||||
projects_href: string;
|
||||
configurations_href: string;
|
||||
orders_href: string;
|
||||
documents_href: string;
|
||||
sites_href: string;
|
||||
teams_href: string;
|
||||
reports_href: string;
|
||||
notes_href: string;
|
||||
}
|
||||
|
||||
export interface CustomField {
|
||||
id: number;
|
||||
caption: string;
|
||||
type: string;
|
||||
entryMethod: string;
|
||||
numberOfDecimals: number;
|
||||
value: string | null;
|
||||
connectWiseId: string;
|
||||
rowNum: number;
|
||||
userDefinedFieldRecId: number;
|
||||
podId: string;
|
||||
}
|
||||
Reference in New Issue
Block a user