2026-03-14 14:44:40 -05:00
|
|
|
import type {
|
|
|
|
|
ApiResponse,
|
|
|
|
|
CompanyProfileDto,
|
|
|
|
|
CompanyProfileInput,
|
|
|
|
|
FileAttachmentDto,
|
2026-03-15 12:11:46 -05:00
|
|
|
PlanningTimelineDto,
|
2026-03-14 14:44:40 -05:00
|
|
|
LoginRequest,
|
|
|
|
|
LoginResponse,
|
|
|
|
|
} from "@mrp/shared";
|
2026-03-14 16:08:29 -05:00
|
|
|
import type {
|
2026-03-14 18:46:06 -05:00
|
|
|
CrmContactDto,
|
|
|
|
|
CrmContactInput,
|
2026-03-14 16:50:03 -05:00
|
|
|
CrmContactEntryDto,
|
|
|
|
|
CrmContactEntryInput,
|
2026-03-14 18:46:06 -05:00
|
|
|
CrmCustomerHierarchyOptionDto,
|
2026-03-14 16:08:29 -05:00
|
|
|
CrmRecordDetailDto,
|
|
|
|
|
CrmRecordInput,
|
2026-03-14 18:58:23 -05:00
|
|
|
CrmLifecycleStage,
|
2026-03-14 16:08:29 -05:00
|
|
|
CrmRecordStatus,
|
|
|
|
|
CrmRecordSummaryDto,
|
|
|
|
|
} from "@mrp/shared/dist/crm/types.js";
|
2026-03-14 21:10:35 -05:00
|
|
|
import type {
|
|
|
|
|
InventoryItemDetailDto,
|
|
|
|
|
InventoryItemInput,
|
|
|
|
|
InventoryItemOptionDto,
|
2026-03-15 14:00:12 -05:00
|
|
|
InventoryReservationInput,
|
2026-03-14 21:10:35 -05:00
|
|
|
InventoryItemStatus,
|
|
|
|
|
InventoryItemSummaryDto,
|
2026-03-15 14:00:12 -05:00
|
|
|
InventoryTransferInput,
|
2026-03-14 22:37:09 -05:00
|
|
|
InventoryTransactionInput,
|
2026-03-14 21:10:35 -05:00
|
|
|
InventoryItemType,
|
2026-03-14 21:23:22 -05:00
|
|
|
WarehouseDetailDto,
|
|
|
|
|
WarehouseInput,
|
2026-03-14 22:37:09 -05:00
|
|
|
WarehouseLocationOptionDto,
|
2026-03-14 21:23:22 -05:00
|
|
|
WarehouseSummaryDto,
|
2026-03-14 21:10:35 -05:00
|
|
|
} from "@mrp/shared/dist/inventory/types.js";
|
2026-03-15 11:12:58 -05:00
|
|
|
import type {
|
2026-03-15 12:11:46 -05:00
|
|
|
ManufacturingStationDto,
|
|
|
|
|
ManufacturingStationInput,
|
2026-03-15 11:12:58 -05:00
|
|
|
ManufacturingItemOptionDto,
|
|
|
|
|
ManufacturingProjectOptionDto,
|
|
|
|
|
WorkOrderCompletionInput,
|
|
|
|
|
WorkOrderDetailDto,
|
|
|
|
|
WorkOrderInput,
|
|
|
|
|
WorkOrderMaterialIssueInput,
|
|
|
|
|
WorkOrderStatus,
|
|
|
|
|
WorkOrderSummaryDto,
|
|
|
|
|
} from "@mrp/shared";
|
2026-03-15 10:13:53 -05:00
|
|
|
import type {
|
|
|
|
|
ProjectCustomerOptionDto,
|
|
|
|
|
ProjectDetailDto,
|
|
|
|
|
ProjectDocumentOptionDto,
|
|
|
|
|
ProjectInput,
|
|
|
|
|
ProjectOwnerOptionDto,
|
|
|
|
|
ProjectPriority,
|
|
|
|
|
ProjectShipmentOptionDto,
|
|
|
|
|
ProjectStatus,
|
|
|
|
|
ProjectSummaryDto,
|
|
|
|
|
} from "@mrp/shared/dist/projects/types.js";
|
2026-03-14 23:03:17 -05:00
|
|
|
import type {
|
|
|
|
|
SalesCustomerOptionDto,
|
|
|
|
|
SalesDocumentDetailDto,
|
|
|
|
|
SalesDocumentInput,
|
2026-03-15 11:44:14 -05:00
|
|
|
SalesDocumentRevisionDto,
|
2026-03-14 23:03:17 -05:00
|
|
|
SalesDocumentStatus,
|
|
|
|
|
SalesDocumentSummaryDto,
|
|
|
|
|
} from "@mrp/shared/dist/sales/types.js";
|
2026-03-15 00:29:41 -05:00
|
|
|
import type {
|
|
|
|
|
PurchaseOrderDetailDto,
|
|
|
|
|
PurchaseOrderInput,
|
|
|
|
|
PurchaseOrderStatus,
|
|
|
|
|
PurchaseOrderSummaryDto,
|
|
|
|
|
PurchaseVendorOptionDto,
|
|
|
|
|
} from "@mrp/shared";
|
2026-03-15 09:04:18 -05:00
|
|
|
import type { PurchaseReceiptInput } from "@mrp/shared/dist/purchasing/types.js";
|
2026-03-14 23:48:27 -05:00
|
|
|
import type {
|
|
|
|
|
ShipmentDetailDto,
|
|
|
|
|
ShipmentInput,
|
|
|
|
|
ShipmentOrderOptionDto,
|
|
|
|
|
ShipmentStatus,
|
|
|
|
|
ShipmentSummaryDto,
|
|
|
|
|
} from "@mrp/shared/dist/shipping/types.js";
|
2026-03-14 14:44:40 -05:00
|
|
|
|
|
|
|
|
export class ApiError extends Error {
|
|
|
|
|
constructor(message: string, public readonly code: string) {
|
|
|
|
|
super(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function request<T>(input: string, init?: RequestInit, token?: string): Promise<T> {
|
|
|
|
|
const response = await fetch(input, {
|
|
|
|
|
...init,
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
|
|
|
...(init?.headers ?? {}),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const json = (await response.json()) as ApiResponse<T>;
|
|
|
|
|
if (!json.ok) {
|
|
|
|
|
throw new ApiError(json.error.message, json.error.code);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return json.data;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 16:08:29 -05:00
|
|
|
function buildQueryString(params: Record<string, string | undefined>) {
|
|
|
|
|
const searchParams = new URLSearchParams();
|
|
|
|
|
for (const [key, value] of Object.entries(params)) {
|
|
|
|
|
if (value) {
|
|
|
|
|
searchParams.set(key, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const queryString = searchParams.toString();
|
|
|
|
|
return queryString ? `?${queryString}` : "";
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 14:44:40 -05:00
|
|
|
export const api = {
|
|
|
|
|
login(payload: LoginRequest) {
|
|
|
|
|
return request<LoginResponse>("/api/v1/auth/login", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
me(token: string) {
|
|
|
|
|
return request<LoginResponse["user"]>("/api/v1/auth/me", undefined, token);
|
|
|
|
|
},
|
|
|
|
|
getCompanyProfile(token: string) {
|
|
|
|
|
return request<CompanyProfileDto>("/api/v1/company-profile", undefined, token);
|
|
|
|
|
},
|
|
|
|
|
updateCompanyProfile(token: string, payload: CompanyProfileInput) {
|
|
|
|
|
return request<CompanyProfileDto>(
|
|
|
|
|
"/api/v1/company-profile",
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
async uploadFile(token: string, file: File, ownerType: string, ownerId: string) {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("file", file);
|
|
|
|
|
formData.append("ownerType", ownerType);
|
|
|
|
|
formData.append("ownerId", ownerId);
|
|
|
|
|
|
|
|
|
|
const response = await fetch("/api/v1/files/upload", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
|
|
|
|
const json = (await response.json()) as ApiResponse<FileAttachmentDto>;
|
|
|
|
|
if (!json.ok) {
|
|
|
|
|
throw new ApiError(json.error.message, json.error.code);
|
|
|
|
|
}
|
|
|
|
|
return json.data;
|
|
|
|
|
},
|
2026-03-14 16:17:04 -05:00
|
|
|
async getFileContentBlob(token: string, fileId: string) {
|
|
|
|
|
const response = await fetch(`/api/v1/files/${fileId}/content`, {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new ApiError("Unable to load file content.", "FILE_CONTENT_FAILED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.blob();
|
|
|
|
|
},
|
2026-03-14 17:32:00 -05:00
|
|
|
getAttachments(token: string, ownerType: string, ownerId: string) {
|
|
|
|
|
return request<FileAttachmentDto[]>(
|
|
|
|
|
`/api/v1/files${buildQueryString({
|
|
|
|
|
ownerType,
|
|
|
|
|
ownerId,
|
|
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 18:58:23 -05:00
|
|
|
deleteAttachment(token: string, fileId: string) {
|
|
|
|
|
return request<FileAttachmentDto>(
|
|
|
|
|
`/api/v1/files/${fileId}`,
|
|
|
|
|
{
|
|
|
|
|
method: "DELETE",
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getCustomers(
|
|
|
|
|
token: string,
|
|
|
|
|
filters?: {
|
|
|
|
|
q?: string;
|
|
|
|
|
status?: CrmRecordStatus;
|
|
|
|
|
lifecycleStage?: CrmLifecycleStage;
|
|
|
|
|
state?: string;
|
|
|
|
|
flag?: "PREFERRED" | "STRATEGIC" | "REQUIRES_APPROVAL" | "BLOCKED";
|
|
|
|
|
}
|
|
|
|
|
) {
|
2026-03-14 16:08:29 -05:00
|
|
|
return request<CrmRecordSummaryDto[]>(
|
|
|
|
|
`/api/v1/crm/customers${buildQueryString({
|
|
|
|
|
q: filters?.q,
|
|
|
|
|
status: filters?.status,
|
2026-03-14 18:58:23 -05:00
|
|
|
lifecycleStage: filters?.lifecycleStage,
|
2026-03-14 16:08:29 -05:00
|
|
|
state: filters?.state,
|
2026-03-14 18:58:23 -05:00
|
|
|
flag: filters?.flag,
|
2026-03-14 16:08:29 -05:00
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getCustomer(token: string, customerId: string) {
|
|
|
|
|
return request<CrmRecordDetailDto>(`/api/v1/crm/customers/${customerId}`, undefined, token);
|
|
|
|
|
},
|
2026-03-14 18:46:06 -05:00
|
|
|
getCustomerHierarchyOptions(token: string, excludeCustomerId?: string) {
|
|
|
|
|
return request<CrmCustomerHierarchyOptionDto[]>(
|
|
|
|
|
`/api/v1/crm/customers/hierarchy-options${buildQueryString({
|
|
|
|
|
excludeCustomerId,
|
|
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 16:08:29 -05:00
|
|
|
createCustomer(token: string, payload: CrmRecordInput) {
|
|
|
|
|
return request<CrmRecordDetailDto>(
|
|
|
|
|
"/api/v1/crm/customers",
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
updateCustomer(token: string, customerId: string, payload: CrmRecordInput) {
|
|
|
|
|
return request<CrmRecordDetailDto>(
|
|
|
|
|
`/api/v1/crm/customers/${customerId}`,
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 16:50:03 -05:00
|
|
|
createCustomerContactEntry(token: string, customerId: string, payload: CrmContactEntryInput) {
|
|
|
|
|
return request<CrmContactEntryDto>(
|
|
|
|
|
`/api/v1/crm/customers/${customerId}/contact-history`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 18:46:06 -05:00
|
|
|
createCustomerContact(token: string, customerId: string, payload: CrmContactInput) {
|
|
|
|
|
return request<CrmContactDto>(
|
|
|
|
|
`/api/v1/crm/customers/${customerId}/contacts`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 18:58:23 -05:00
|
|
|
getVendors(
|
|
|
|
|
token: string,
|
|
|
|
|
filters?: {
|
|
|
|
|
q?: string;
|
|
|
|
|
status?: CrmRecordStatus;
|
|
|
|
|
lifecycleStage?: CrmLifecycleStage;
|
|
|
|
|
state?: string;
|
|
|
|
|
flag?: "PREFERRED" | "STRATEGIC" | "REQUIRES_APPROVAL" | "BLOCKED";
|
|
|
|
|
}
|
|
|
|
|
) {
|
2026-03-14 16:08:29 -05:00
|
|
|
return request<CrmRecordSummaryDto[]>(
|
|
|
|
|
`/api/v1/crm/vendors${buildQueryString({
|
|
|
|
|
q: filters?.q,
|
|
|
|
|
status: filters?.status,
|
2026-03-14 18:58:23 -05:00
|
|
|
lifecycleStage: filters?.lifecycleStage,
|
2026-03-14 16:08:29 -05:00
|
|
|
state: filters?.state,
|
2026-03-14 18:58:23 -05:00
|
|
|
flag: filters?.flag,
|
2026-03-14 16:08:29 -05:00
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getVendor(token: string, vendorId: string) {
|
|
|
|
|
return request<CrmRecordDetailDto>(`/api/v1/crm/vendors/${vendorId}`, undefined, token);
|
2026-03-14 14:44:40 -05:00
|
|
|
},
|
2026-03-14 16:08:29 -05:00
|
|
|
createVendor(token: string, payload: CrmRecordInput) {
|
|
|
|
|
return request<CrmRecordDetailDto>(
|
|
|
|
|
"/api/v1/crm/vendors",
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
updateVendor(token: string, vendorId: string, payload: CrmRecordInput) {
|
|
|
|
|
return request<CrmRecordDetailDto>(
|
|
|
|
|
`/api/v1/crm/vendors/${vendorId}`,
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
2026-03-14 14:44:40 -05:00
|
|
|
},
|
2026-03-14 16:50:03 -05:00
|
|
|
createVendorContactEntry(token: string, vendorId: string, payload: CrmContactEntryInput) {
|
|
|
|
|
return request<CrmContactEntryDto>(
|
|
|
|
|
`/api/v1/crm/vendors/${vendorId}/contact-history`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 18:46:06 -05:00
|
|
|
createVendorContact(token: string, vendorId: string, payload: CrmContactInput) {
|
|
|
|
|
return request<CrmContactDto>(
|
|
|
|
|
`/api/v1/crm/vendors/${vendorId}/contacts`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 21:10:35 -05:00
|
|
|
getInventoryItems(token: string, filters?: { q?: string; status?: InventoryItemStatus; type?: InventoryItemType }) {
|
|
|
|
|
return request<InventoryItemSummaryDto[]>(
|
|
|
|
|
`/api/v1/inventory/items${buildQueryString({
|
|
|
|
|
q: filters?.q,
|
|
|
|
|
status: filters?.status,
|
|
|
|
|
type: filters?.type,
|
|
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getInventoryItem(token: string, itemId: string) {
|
|
|
|
|
return request<InventoryItemDetailDto>(`/api/v1/inventory/items/${itemId}`, undefined, token);
|
|
|
|
|
},
|
|
|
|
|
getInventoryItemOptions(token: string) {
|
|
|
|
|
return request<InventoryItemOptionDto[]>("/api/v1/inventory/items/options", undefined, token);
|
|
|
|
|
},
|
2026-03-14 22:37:09 -05:00
|
|
|
getWarehouseLocationOptions(token: string) {
|
|
|
|
|
return request<WarehouseLocationOptionDto[]>("/api/v1/inventory/locations/options", undefined, token);
|
|
|
|
|
},
|
2026-03-14 21:10:35 -05:00
|
|
|
createInventoryItem(token: string, payload: InventoryItemInput) {
|
|
|
|
|
return request<InventoryItemDetailDto>(
|
|
|
|
|
"/api/v1/inventory/items",
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
updateInventoryItem(token: string, itemId: string, payload: InventoryItemInput) {
|
|
|
|
|
return request<InventoryItemDetailDto>(
|
|
|
|
|
`/api/v1/inventory/items/${itemId}`,
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 22:37:09 -05:00
|
|
|
createInventoryTransaction(token: string, itemId: string, payload: InventoryTransactionInput) {
|
|
|
|
|
return request<InventoryItemDetailDto>(
|
|
|
|
|
`/api/v1/inventory/items/${itemId}/transactions`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-15 14:00:12 -05:00
|
|
|
createInventoryTransfer(token: string, itemId: string, payload: InventoryTransferInput) {
|
|
|
|
|
return request<InventoryItemDetailDto>(
|
|
|
|
|
`/api/v1/inventory/items/${itemId}/transfers`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
createInventoryReservation(token: string, itemId: string, payload: InventoryReservationInput) {
|
|
|
|
|
return request<InventoryItemDetailDto>(
|
|
|
|
|
`/api/v1/inventory/items/${itemId}/reservations`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 21:23:22 -05:00
|
|
|
getWarehouses(token: string) {
|
|
|
|
|
return request<WarehouseSummaryDto[]>("/api/v1/inventory/warehouses", undefined, token);
|
|
|
|
|
},
|
|
|
|
|
getWarehouse(token: string, warehouseId: string) {
|
|
|
|
|
return request<WarehouseDetailDto>(`/api/v1/inventory/warehouses/${warehouseId}`, undefined, token);
|
|
|
|
|
},
|
|
|
|
|
createWarehouse(token: string, payload: WarehouseInput) {
|
|
|
|
|
return request<WarehouseDetailDto>(
|
|
|
|
|
"/api/v1/inventory/warehouses",
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
updateWarehouse(token: string, warehouseId: string, payload: WarehouseInput) {
|
|
|
|
|
return request<WarehouseDetailDto>(
|
|
|
|
|
`/api/v1/inventory/warehouses/${warehouseId}`,
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
},
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-15 10:13:53 -05:00
|
|
|
getProjects(
|
|
|
|
|
token: string,
|
|
|
|
|
filters?: { q?: string; status?: ProjectStatus; priority?: ProjectPriority; customerId?: string; ownerId?: string }
|
|
|
|
|
) {
|
|
|
|
|
return request<ProjectSummaryDto[]>(
|
|
|
|
|
`/api/v1/projects${buildQueryString({
|
|
|
|
|
q: filters?.q,
|
|
|
|
|
status: filters?.status,
|
|
|
|
|
priority: filters?.priority,
|
|
|
|
|
customerId: filters?.customerId,
|
|
|
|
|
ownerId: filters?.ownerId,
|
|
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getProject(token: string, projectId: string) {
|
|
|
|
|
return request<ProjectDetailDto>(`/api/v1/projects/${projectId}`, undefined, token);
|
|
|
|
|
},
|
|
|
|
|
createProject(token: string, payload: ProjectInput) {
|
|
|
|
|
return request<ProjectDetailDto>("/api/v1/projects", { method: "POST", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
updateProject(token: string, projectId: string, payload: ProjectInput) {
|
|
|
|
|
return request<ProjectDetailDto>(`/api/v1/projects/${projectId}`, { method: "PUT", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
getProjectCustomerOptions(token: string) {
|
|
|
|
|
return request<ProjectCustomerOptionDto[]>("/api/v1/projects/customers/options", undefined, token);
|
|
|
|
|
},
|
|
|
|
|
getProjectOwnerOptions(token: string) {
|
|
|
|
|
return request<ProjectOwnerOptionDto[]>("/api/v1/projects/owners/options", undefined, token);
|
|
|
|
|
},
|
|
|
|
|
getProjectQuoteOptions(token: string, customerId?: string) {
|
|
|
|
|
return request<ProjectDocumentOptionDto[]>(
|
|
|
|
|
`/api/v1/projects/quotes/options${buildQueryString({ customerId })}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getProjectOrderOptions(token: string, customerId?: string) {
|
|
|
|
|
return request<ProjectDocumentOptionDto[]>(
|
|
|
|
|
`/api/v1/projects/orders/options${buildQueryString({ customerId })}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getProjectShipmentOptions(token: string, customerId?: string) {
|
|
|
|
|
return request<ProjectShipmentOptionDto[]>(
|
|
|
|
|
`/api/v1/projects/shipments/options${buildQueryString({ customerId })}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-15 11:12:58 -05:00
|
|
|
getManufacturingItemOptions(token: string) {
|
|
|
|
|
return request<ManufacturingItemOptionDto[]>("/api/v1/manufacturing/items/options", undefined, token);
|
|
|
|
|
},
|
|
|
|
|
getManufacturingProjectOptions(token: string) {
|
|
|
|
|
return request<ManufacturingProjectOptionDto[]>("/api/v1/manufacturing/projects/options", undefined, token);
|
|
|
|
|
},
|
2026-03-15 12:11:46 -05:00
|
|
|
getManufacturingStations(token: string) {
|
|
|
|
|
return request<ManufacturingStationDto[]>("/api/v1/manufacturing/stations", undefined, token);
|
|
|
|
|
},
|
|
|
|
|
createManufacturingStation(token: string, payload: ManufacturingStationInput) {
|
|
|
|
|
return request<ManufacturingStationDto>("/api/v1/manufacturing/stations", { method: "POST", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
2026-03-15 11:12:58 -05:00
|
|
|
getWorkOrders(token: string, filters?: { q?: string; status?: WorkOrderStatus; projectId?: string; itemId?: string }) {
|
|
|
|
|
return request<WorkOrderSummaryDto[]>(
|
|
|
|
|
`/api/v1/manufacturing/work-orders${buildQueryString({
|
|
|
|
|
q: filters?.q,
|
|
|
|
|
status: filters?.status,
|
|
|
|
|
projectId: filters?.projectId,
|
|
|
|
|
itemId: filters?.itemId,
|
|
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getWorkOrder(token: string, workOrderId: string) {
|
|
|
|
|
return request<WorkOrderDetailDto>(`/api/v1/manufacturing/work-orders/${workOrderId}`, undefined, token);
|
|
|
|
|
},
|
|
|
|
|
createWorkOrder(token: string, payload: WorkOrderInput) {
|
|
|
|
|
return request<WorkOrderDetailDto>("/api/v1/manufacturing/work-orders", { method: "POST", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
updateWorkOrder(token: string, workOrderId: string, payload: WorkOrderInput) {
|
|
|
|
|
return request<WorkOrderDetailDto>(`/api/v1/manufacturing/work-orders/${workOrderId}`, { method: "PUT", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
updateWorkOrderStatus(token: string, workOrderId: string, status: WorkOrderStatus) {
|
|
|
|
|
return request<WorkOrderDetailDto>(
|
|
|
|
|
`/api/v1/manufacturing/work-orders/${workOrderId}/status`,
|
|
|
|
|
{ method: "PATCH", body: JSON.stringify({ status }) },
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
issueWorkOrderMaterial(token: string, workOrderId: string, payload: WorkOrderMaterialIssueInput) {
|
|
|
|
|
return request<WorkOrderDetailDto>(
|
|
|
|
|
`/api/v1/manufacturing/work-orders/${workOrderId}/issues`,
|
|
|
|
|
{ method: "POST", body: JSON.stringify(payload) },
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
recordWorkOrderCompletion(token: string, workOrderId: string, payload: WorkOrderCompletionInput) {
|
|
|
|
|
return request<WorkOrderDetailDto>(
|
|
|
|
|
`/api/v1/manufacturing/work-orders/${workOrderId}/completions`,
|
|
|
|
|
{ method: "POST", body: JSON.stringify(payload) },
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-15 12:11:46 -05:00
|
|
|
getPlanningTimeline(token: string) {
|
|
|
|
|
return request<PlanningTimelineDto>("/api/v1/gantt/timeline", undefined, token);
|
2026-03-14 14:44:40 -05:00
|
|
|
},
|
2026-03-14 23:03:17 -05:00
|
|
|
getSalesCustomers(token: string) {
|
|
|
|
|
return request<SalesCustomerOptionDto[]>("/api/v1/sales/customers/options", undefined, token);
|
|
|
|
|
},
|
2026-03-15 00:29:41 -05:00
|
|
|
getPurchaseVendors(token: string) {
|
|
|
|
|
return request<PurchaseVendorOptionDto[]>("/api/v1/purchasing/vendors/options", undefined, token);
|
|
|
|
|
},
|
2026-03-14 23:03:17 -05:00
|
|
|
getQuotes(token: string, filters?: { q?: string; status?: SalesDocumentStatus }) {
|
|
|
|
|
return request<SalesDocumentSummaryDto[]>(
|
|
|
|
|
`/api/v1/sales/quotes${buildQueryString({
|
|
|
|
|
q: filters?.q,
|
|
|
|
|
status: filters?.status,
|
|
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getQuote(token: string, quoteId: string) {
|
|
|
|
|
return request<SalesDocumentDetailDto>(`/api/v1/sales/quotes/${quoteId}`, undefined, token);
|
|
|
|
|
},
|
|
|
|
|
createQuote(token: string, payload: SalesDocumentInput) {
|
|
|
|
|
return request<SalesDocumentDetailDto>("/api/v1/sales/quotes", { method: "POST", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
updateQuote(token: string, quoteId: string, payload: SalesDocumentInput) {
|
|
|
|
|
return request<SalesDocumentDetailDto>(`/api/v1/sales/quotes/${quoteId}`, { method: "PUT", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
2026-03-14 23:16:42 -05:00
|
|
|
updateQuoteStatus(token: string, quoteId: string, status: SalesDocumentStatus) {
|
|
|
|
|
return request<SalesDocumentDetailDto>(
|
|
|
|
|
`/api/v1/sales/quotes/${quoteId}/status`,
|
|
|
|
|
{ method: "PATCH", body: JSON.stringify({ status }) },
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-15 11:44:14 -05:00
|
|
|
approveQuote(token: string, quoteId: string) {
|
|
|
|
|
return request<SalesDocumentDetailDto>(`/api/v1/sales/quotes/${quoteId}/approve`, { method: "POST" }, token);
|
|
|
|
|
},
|
|
|
|
|
getQuoteRevisions(token: string, quoteId: string) {
|
|
|
|
|
return request<SalesDocumentRevisionDto[]>(`/api/v1/sales/quotes/${quoteId}/revisions`, undefined, token);
|
|
|
|
|
},
|
2026-03-14 23:16:42 -05:00
|
|
|
convertQuoteToSalesOrder(token: string, quoteId: string) {
|
|
|
|
|
return request<SalesDocumentDetailDto>(`/api/v1/sales/quotes/${quoteId}/convert`, { method: "POST" }, token);
|
|
|
|
|
},
|
2026-03-14 23:03:17 -05:00
|
|
|
getSalesOrders(token: string, filters?: { q?: string; status?: SalesDocumentStatus }) {
|
|
|
|
|
return request<SalesDocumentSummaryDto[]>(
|
|
|
|
|
`/api/v1/sales/orders${buildQueryString({
|
|
|
|
|
q: filters?.q,
|
|
|
|
|
status: filters?.status,
|
|
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getSalesOrder(token: string, orderId: string) {
|
|
|
|
|
return request<SalesDocumentDetailDto>(`/api/v1/sales/orders/${orderId}`, undefined, token);
|
|
|
|
|
},
|
|
|
|
|
createSalesOrder(token: string, payload: SalesDocumentInput) {
|
|
|
|
|
return request<SalesDocumentDetailDto>("/api/v1/sales/orders", { method: "POST", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
updateSalesOrder(token: string, orderId: string, payload: SalesDocumentInput) {
|
|
|
|
|
return request<SalesDocumentDetailDto>(`/api/v1/sales/orders/${orderId}`, { method: "PUT", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
2026-03-14 23:16:42 -05:00
|
|
|
updateSalesOrderStatus(token: string, orderId: string, status: SalesDocumentStatus) {
|
|
|
|
|
return request<SalesDocumentDetailDto>(
|
|
|
|
|
`/api/v1/sales/orders/${orderId}/status`,
|
|
|
|
|
{ method: "PATCH", body: JSON.stringify({ status }) },
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-15 11:44:14 -05:00
|
|
|
approveSalesOrder(token: string, orderId: string) {
|
|
|
|
|
return request<SalesDocumentDetailDto>(`/api/v1/sales/orders/${orderId}/approve`, { method: "POST" }, token);
|
|
|
|
|
},
|
|
|
|
|
getSalesOrderRevisions(token: string, orderId: string) {
|
|
|
|
|
return request<SalesDocumentRevisionDto[]>(`/api/v1/sales/orders/${orderId}/revisions`, undefined, token);
|
|
|
|
|
},
|
2026-03-15 11:30:10 -05:00
|
|
|
getPurchaseOrders(token: string, filters?: { q?: string; status?: PurchaseOrderStatus; vendorId?: string }) {
|
2026-03-15 00:29:41 -05:00
|
|
|
return request<PurchaseOrderSummaryDto[]>(
|
|
|
|
|
`/api/v1/purchasing/orders${buildQueryString({
|
|
|
|
|
q: filters?.q,
|
|
|
|
|
status: filters?.status,
|
2026-03-15 11:30:10 -05:00
|
|
|
vendorId: filters?.vendorId,
|
2026-03-15 00:29:41 -05:00
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getPurchaseOrder(token: string, orderId: string) {
|
|
|
|
|
return request<PurchaseOrderDetailDto>(`/api/v1/purchasing/orders/${orderId}`, undefined, token);
|
|
|
|
|
},
|
|
|
|
|
createPurchaseOrder(token: string, payload: PurchaseOrderInput) {
|
|
|
|
|
return request<PurchaseOrderDetailDto>("/api/v1/purchasing/orders", { method: "POST", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
updatePurchaseOrder(token: string, orderId: string, payload: PurchaseOrderInput) {
|
|
|
|
|
return request<PurchaseOrderDetailDto>(`/api/v1/purchasing/orders/${orderId}`, { method: "PUT", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
updatePurchaseOrderStatus(token: string, orderId: string, status: PurchaseOrderStatus) {
|
|
|
|
|
return request<PurchaseOrderDetailDto>(
|
|
|
|
|
`/api/v1/purchasing/orders/${orderId}/status`,
|
|
|
|
|
{ method: "PATCH", body: JSON.stringify({ status }) },
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-15 09:04:18 -05:00
|
|
|
createPurchaseReceipt(token: string, orderId: string, payload: PurchaseReceiptInput) {
|
|
|
|
|
return request<PurchaseOrderDetailDto>(
|
|
|
|
|
`/api/v1/purchasing/orders/${orderId}/receipts`,
|
|
|
|
|
{ method: "POST", body: JSON.stringify(payload) },
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 23:48:27 -05:00
|
|
|
getShipmentOrderOptions(token: string) {
|
|
|
|
|
return request<ShipmentOrderOptionDto[]>("/api/v1/shipping/orders/options", undefined, token);
|
|
|
|
|
},
|
|
|
|
|
getShipments(token: string, filters?: { q?: string; status?: ShipmentStatus; salesOrderId?: string }) {
|
|
|
|
|
return request<ShipmentSummaryDto[]>(
|
|
|
|
|
`/api/v1/shipping/shipments${buildQueryString({
|
|
|
|
|
q: filters?.q,
|
|
|
|
|
status: filters?.status,
|
|
|
|
|
salesOrderId: filters?.salesOrderId,
|
|
|
|
|
})}`,
|
|
|
|
|
undefined,
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
getShipment(token: string, shipmentId: string) {
|
|
|
|
|
return request<ShipmentDetailDto>(`/api/v1/shipping/shipments/${shipmentId}`, undefined, token);
|
|
|
|
|
},
|
|
|
|
|
createShipment(token: string, payload: ShipmentInput) {
|
|
|
|
|
return request<ShipmentDetailDto>("/api/v1/shipping/shipments", { method: "POST", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
updateShipment(token: string, shipmentId: string, payload: ShipmentInput) {
|
|
|
|
|
return request<ShipmentDetailDto>(`/api/v1/shipping/shipments/${shipmentId}`, { method: "PUT", body: JSON.stringify(payload) }, token);
|
|
|
|
|
},
|
|
|
|
|
updateShipmentStatus(token: string, shipmentId: string, status: ShipmentStatus) {
|
|
|
|
|
return request<ShipmentDetailDto>(
|
|
|
|
|
`/api/v1/shipping/shipments/${shipmentId}/status`,
|
|
|
|
|
{ method: "PATCH", body: JSON.stringify({ status }) },
|
|
|
|
|
token
|
|
|
|
|
);
|
|
|
|
|
},
|
2026-03-14 23:50:41 -05:00
|
|
|
async getShipmentPackingSlipPdf(token: string, shipmentId: string) {
|
|
|
|
|
const response = await fetch(`/api/v1/documents/shipping/shipments/${shipmentId}/packing-slip.pdf`, {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new ApiError("Unable to render packing slip PDF.", "PACKING_SLIP_FAILED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.blob();
|
|
|
|
|
},
|
2026-03-15 10:13:53 -05:00
|
|
|
async getShipmentLabelPdf(token: string, shipmentId: string) {
|
|
|
|
|
const response = await fetch(`/api/v1/documents/shipping/shipments/${shipmentId}/shipping-label.pdf`, {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new ApiError("Unable to render shipping label PDF.", "SHIPPING_LABEL_FAILED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.blob();
|
|
|
|
|
},
|
|
|
|
|
async getShipmentBillOfLadingPdf(token: string, shipmentId: string) {
|
|
|
|
|
const response = await fetch(`/api/v1/documents/shipping/shipments/${shipmentId}/bill-of-lading.pdf`, {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new ApiError("Unable to render bill of lading PDF.", "BILL_OF_LADING_FAILED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.blob();
|
|
|
|
|
},
|
2026-03-15 09:22:39 -05:00
|
|
|
async getQuotePdf(token: string, quoteId: string) {
|
|
|
|
|
const response = await fetch(`/api/v1/documents/sales/quotes/${quoteId}/document.pdf`, {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new ApiError("Unable to render quote PDF.", "QUOTE_PDF_FAILED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.blob();
|
|
|
|
|
},
|
|
|
|
|
async getSalesOrderPdf(token: string, orderId: string) {
|
|
|
|
|
const response = await fetch(`/api/v1/documents/sales/orders/${orderId}/document.pdf`, {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new ApiError("Unable to render sales order PDF.", "SALES_ORDER_PDF_FAILED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.blob();
|
|
|
|
|
},
|
|
|
|
|
async getPurchaseOrderPdf(token: string, orderId: string) {
|
|
|
|
|
const response = await fetch(`/api/v1/documents/purchasing/orders/${orderId}/document.pdf`, {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new ApiError("Unable to render purchase order PDF.", "PURCHASE_ORDER_PDF_FAILED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.blob();
|
|
|
|
|
},
|
2026-03-14 14:44:40 -05:00
|
|
|
async getCompanyProfilePreviewPdf(token: string) {
|
|
|
|
|
const response = await fetch("/api/v1/documents/company-profile-preview.pdf", {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new ApiError("Unable to render company profile preview PDF.", "PDF_PREVIEW_FAILED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.blob();
|
|
|
|
|
},
|
|
|
|
|
};
|