Fix UserController permission serialization and include current updates
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
import { CatalogItem } from "../../generated/prisma/client";
|
||||
import { prisma } from "../constants";
|
||||
import { catalogCw } from "../modules/cw-utils/procurement/catalog";
|
||||
import { CatalogItem as CWCatalogItem } from "../modules/cw-utils/procurement/catalog.types";
|
||||
import GenericError from "../Errors/GenericError";
|
||||
|
||||
/**
|
||||
* Catalog Item Controller
|
||||
*
|
||||
* This class encapsulates a catalog item entity and provides domain methods
|
||||
* for accessing, refreshing, and serializing catalog item data. It bridges
|
||||
* the internal database representation with ConnectWise catalog data.
|
||||
*/
|
||||
export class CatalogItemController {
|
||||
public readonly id: string;
|
||||
public name: string;
|
||||
public description: string | null;
|
||||
public customerDescription: string | null;
|
||||
public internalNotes: string | null;
|
||||
|
||||
public readonly cwCatalogId: number;
|
||||
public readonly identifier: string | null;
|
||||
|
||||
public manufacturer: string | null;
|
||||
public manufactureCwId: number | null;
|
||||
public partNumber: string | null;
|
||||
|
||||
public vendorName: string | null;
|
||||
public vendorSku: string | null;
|
||||
public vendorCwId: number | null;
|
||||
|
||||
public price: number;
|
||||
public cost: number;
|
||||
|
||||
public inactive: boolean;
|
||||
public salesTaxable: boolean;
|
||||
|
||||
public onHand: number;
|
||||
public cwLastUpdated: Date | null;
|
||||
|
||||
private _linkedItems: CatalogItemController[];
|
||||
|
||||
public readonly createdAt: Date;
|
||||
public updatedAt: Date;
|
||||
|
||||
constructor(
|
||||
itemData: CatalogItem & {
|
||||
linkedItems?: CatalogItem[];
|
||||
},
|
||||
) {
|
||||
this.id = itemData.id;
|
||||
this.name = itemData.name;
|
||||
this.description = itemData.description;
|
||||
this.customerDescription = itemData.customerDescription;
|
||||
this.internalNotes = itemData.internalNotes;
|
||||
this.cwCatalogId = itemData.cwCatalogId;
|
||||
this.identifier = itemData.identifier;
|
||||
this.manufacturer = itemData.manufacturer;
|
||||
this.manufactureCwId = itemData.manufactureCwId;
|
||||
this.partNumber = itemData.partNumber;
|
||||
this.vendorName = itemData.vendorName;
|
||||
this.vendorSku = itemData.vendorSku;
|
||||
this.vendorCwId = itemData.vendorCwId;
|
||||
this.price = itemData.price;
|
||||
this.cost = itemData.cost;
|
||||
this.inactive = itemData.inactive;
|
||||
this.salesTaxable = itemData.salesTaxable;
|
||||
this.onHand = itemData.onHand;
|
||||
this.cwLastUpdated = itemData.cwLastUpdated;
|
||||
this.createdAt = itemData.createdAt;
|
||||
this.updatedAt = itemData.updatedAt;
|
||||
|
||||
this._linkedItems = (itemData.linkedItems ?? []).map(
|
||||
(linked) => new CatalogItemController(linked),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh Inventory
|
||||
*
|
||||
* Fetches the latest on-hand inventory count from ConnectWise
|
||||
* and updates both the controller state and the database.
|
||||
*
|
||||
* @returns {Promise<CatalogItemController>} - The updated controller
|
||||
*/
|
||||
public async refreshInventory(): Promise<CatalogItemController> {
|
||||
const onHand = await catalogCw.fetchInventoryOnHand(this.cwCatalogId);
|
||||
|
||||
if (onHand !== this.onHand) {
|
||||
await prisma.catalogItem.update({
|
||||
where: { id: this.id },
|
||||
data: { onHand },
|
||||
});
|
||||
this.onHand = onHand;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Linked Items
|
||||
*
|
||||
* Returns the linked catalog items as an array of controllers.
|
||||
*
|
||||
* @returns {CatalogItemController[]} - Array of linked item controllers
|
||||
*/
|
||||
public getLinkedItems(): CatalogItemController[] {
|
||||
return this._linkedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Link Item
|
||||
*
|
||||
* Links another catalog item to this item. The relationship is bidirectional
|
||||
* via the Prisma implicit many-to-many.
|
||||
*
|
||||
* @param targetId - The internal ID of the catalog item to link
|
||||
* @returns {Promise<CatalogItemController>} - The updated controller
|
||||
*/
|
||||
public async linkItem(targetId: string): Promise<CatalogItemController> {
|
||||
if (targetId === this.id) {
|
||||
throw new GenericError({
|
||||
message: "Cannot link a catalog item to itself",
|
||||
name: "InvalidLinkTarget",
|
||||
cause: `Item '${this.id}' cannot be linked to itself`,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const target = await prisma.catalogItem.findFirst({
|
||||
where: { id: targetId },
|
||||
});
|
||||
|
||||
if (!target) {
|
||||
throw new GenericError({
|
||||
message: "Target catalog item not found",
|
||||
name: "CatalogItemNotFound",
|
||||
cause: `No catalog item exists with ID '${targetId}'`,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const updated = await prisma.catalogItem.update({
|
||||
where: { id: this.id },
|
||||
data: {
|
||||
linkedItems: { connect: { id: targetId } },
|
||||
},
|
||||
include: { linkedItems: true },
|
||||
});
|
||||
|
||||
this._linkedItems = (updated.linkedItems ?? []).map(
|
||||
(linked) => new CatalogItemController(linked),
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink Item
|
||||
*
|
||||
* Removes the link between this catalog item and another.
|
||||
*
|
||||
* @param targetId - The internal ID of the catalog item to unlink
|
||||
* @returns {Promise<CatalogItemController>} - The updated controller
|
||||
*/
|
||||
public async unlinkItem(targetId: string): Promise<CatalogItemController> {
|
||||
const updated = await prisma.catalogItem.update({
|
||||
where: { id: this.id },
|
||||
data: {
|
||||
linkedItems: { disconnect: { id: targetId } },
|
||||
},
|
||||
include: { linkedItems: true },
|
||||
});
|
||||
|
||||
this._linkedItems = (updated.linkedItems ?? []).map(
|
||||
(linked) => new CatalogItemController(linked),
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* To JSON
|
||||
*
|
||||
* Serializes the catalog item into a safe, API-friendly object.
|
||||
*
|
||||
* @param opts - Options to control output
|
||||
* @returns - A JSON-safe representation of the catalog item
|
||||
*/
|
||||
public toJson(opts?: { includeLinkedItems?: boolean }): Record<string, any> {
|
||||
return {
|
||||
id: this.id,
|
||||
cwCatalogId: this.cwCatalogId,
|
||||
identifier: this.identifier,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
customerDescription: this.customerDescription,
|
||||
internalNotes: this.internalNotes,
|
||||
manufacturer: this.manufacturer,
|
||||
manufactureCwId: this.manufactureCwId,
|
||||
partNumber: this.partNumber,
|
||||
vendorName: this.vendorName,
|
||||
vendorSku: this.vendorSku,
|
||||
vendorCwId: this.vendorCwId,
|
||||
price: this.price,
|
||||
cost: this.cost,
|
||||
inactive: this.inactive,
|
||||
salesTaxable: this.salesTaxable,
|
||||
onHand: this.onHand,
|
||||
cwLastUpdated: this.cwLastUpdated,
|
||||
linkedItems: opts?.includeLinkedItems
|
||||
? this._linkedItems.map((item) => item.toJson())
|
||||
: undefined,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user