feat: add procurement and sales sections
This commit is contained in:
@@ -9,6 +9,8 @@ import { permission } from "./optima-api/modules/permissions";
|
||||
import { user } from "./optima-api/modules/user";
|
||||
import { users } from "./optima-api/modules/users";
|
||||
import { unifi } from "./optima-api/modules/unifi";
|
||||
import { procurement } from "./optima-api/modules/procurement";
|
||||
import { sales } from "./optima-api/modules/sales";
|
||||
|
||||
export const optima = {
|
||||
auth,
|
||||
@@ -20,6 +22,8 @@ export const optima = {
|
||||
user,
|
||||
users,
|
||||
unifi,
|
||||
procurement,
|
||||
sales,
|
||||
};
|
||||
/**
|
||||
* @TODO
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { error } from "@sveltejs/kit";
|
||||
import { error, redirect } from "@sveltejs/kit";
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
@@ -11,9 +11,43 @@ export class ApiError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects "invalid signature" or malformed-token errors from the API,
|
||||
* which indicate the access or refresh token has been tampered with or
|
||||
* the server signing key has changed.
|
||||
*/
|
||||
export function isInvalidSignatureError(err: unknown): boolean {
|
||||
if (err && typeof err === "object") {
|
||||
const axiosErr = err as Record<string, unknown>;
|
||||
const responseData = (axiosErr?.response as Record<string, unknown>)
|
||||
?.data as Record<string, unknown> | undefined;
|
||||
const candidates = [
|
||||
responseData?.message,
|
||||
responseData?.error,
|
||||
(err as Error)?.message,
|
||||
];
|
||||
return candidates.some((val) => {
|
||||
if (typeof val !== "string") return false;
|
||||
const lower = val.toLowerCase();
|
||||
return (
|
||||
lower.includes("invalid signature") ||
|
||||
lower.includes("jwt malformed") ||
|
||||
lower.includes("invalid token")
|
||||
);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function handleApiError(err: unknown): never {
|
||||
console.error("API Error:", err);
|
||||
|
||||
// Treat invalid-signature errors as a forced logout
|
||||
if (isInvalidSignatureError(err)) {
|
||||
console.warn("Invalid token signature detected — forcing logout.");
|
||||
throw redirect(303, "/logout");
|
||||
}
|
||||
|
||||
if (err instanceof ApiError) {
|
||||
throw error(err.statusCode, {
|
||||
message: err.message,
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import api from "../axios";
|
||||
|
||||
export const procurement = {
|
||||
async fetchMany(
|
||||
accessToken: string,
|
||||
page: number = 1,
|
||||
search?: string,
|
||||
rpp: number = 30,
|
||||
includeInactive: boolean = false,
|
||||
) {
|
||||
const params: Record<string, unknown> = { page, rpp };
|
||||
if (search && search.length > 0) params.search = search;
|
||||
if (includeInactive) params.includeInactive = true;
|
||||
|
||||
const response = await api.get("/v1/procurement/items", {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async fetch(
|
||||
accessToken: string,
|
||||
identifier: string,
|
||||
options?: { includeLinkedItems?: boolean },
|
||||
) {
|
||||
const params: Record<string, string> = {};
|
||||
if (options?.includeLinkedItems) params.includeLinkedItems = "true";
|
||||
|
||||
const response = await api.get(`/v1/procurement/items/${identifier}`, {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async count(accessToken: string, activeOnly: boolean = false) {
|
||||
const params: Record<string, string> = {};
|
||||
if (activeOnly) params.activeOnly = "true";
|
||||
|
||||
const response = await api.get("/v1/procurement/count", {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
return response.data.data.count;
|
||||
},
|
||||
|
||||
async refreshInventory(accessToken: string, identifier: string) {
|
||||
const response = await api.post(
|
||||
`/v1/procurement/items/${identifier}/refresh-inventory`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async fetchLinkedItems(accessToken: string, identifier: string) {
|
||||
const response = await api.get(
|
||||
`/v1/procurement/items/${identifier}/linked`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async linkItem(accessToken: string, identifier: string, targetId: string) {
|
||||
const response = await api.post(
|
||||
`/v1/procurement/items/${identifier}/link`,
|
||||
{ targetId },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async unlinkItem(accessToken: string, identifier: string, targetId: string) {
|
||||
const response = await api.post(
|
||||
`/v1/procurement/items/${identifier}/unlink`,
|
||||
{ targetId },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import api from "../axios";
|
||||
|
||||
export interface SalesOpportunity {
|
||||
id: string;
|
||||
cwOpportunityId?: number;
|
||||
name: string;
|
||||
notes?: string | null;
|
||||
type?: { id?: number; name?: string } | null;
|
||||
stage?: { id?: number; name?: string } | null;
|
||||
status?: { id?: number; name?: string } | null;
|
||||
priority?: { id?: number; name?: string } | null;
|
||||
rating?: { id?: number; name?: string } | null;
|
||||
source?: string | null;
|
||||
campaign?: string | null;
|
||||
primarySalesRep?: {
|
||||
id?: number;
|
||||
identifier?: string;
|
||||
name?: string;
|
||||
} | null;
|
||||
secondarySalesRep?: {
|
||||
id?: number;
|
||||
identifier?: string;
|
||||
name?: string;
|
||||
} | null;
|
||||
company?: { id?: number | string; name?: string } | null;
|
||||
contact?: { id?: number | string; name?: string } | null;
|
||||
site?: { id?: number | string; name?: string } | null;
|
||||
customerPO?: string | null;
|
||||
totalSalesTax?: number | null;
|
||||
expectedCloseDate?: string | null;
|
||||
pipelineChangeDate?: string | null;
|
||||
dateBecameLead?: string | null;
|
||||
closedDate?: string | null;
|
||||
closedFlag?: boolean;
|
||||
closedBy?: string | null;
|
||||
companyId?: string;
|
||||
cwLastUpdated?: string | null;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export const sales = {
|
||||
async fetchMany(
|
||||
accessToken: string,
|
||||
page: number = 1,
|
||||
search: string = "",
|
||||
rpp: number = 30,
|
||||
includeClosed: boolean = true,
|
||||
) {
|
||||
const params: Record<string, unknown> = { page, rpp };
|
||||
if (search) params.search = search;
|
||||
if (includeClosed) params.includeClosed = true;
|
||||
|
||||
const response = await api.get("/v1/sales/opportunities", {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user