149 lines
4.0 KiB
TypeScript
149 lines
4.0 KiB
TypeScript
import { Staff, StaffCreateInput, StaffUpdateInput } from "@repo/db/types";
|
|
import { prisma as db } from "@repo/db/client";
|
|
|
|
export interface IStorage {
|
|
getStaff(id: number): Promise<Staff | undefined>;
|
|
getAllStaff(): Promise<Staff[]>;
|
|
createStaff(staff: StaffCreateInput): Promise<Staff>;
|
|
updateStaff(
|
|
id: number,
|
|
updates: StaffUpdateInput,
|
|
): Promise<Staff | undefined>;
|
|
deleteStaff(id: number): Promise<boolean>;
|
|
countAppointmentsByStaffId(staffId: number): Promise<number>;
|
|
countClaimsByStaffId(staffId: number): Promise<number>;
|
|
}
|
|
|
|
export const staffStorage: IStorage = {
|
|
// Staff methods
|
|
async getStaff(id: number): Promise<Staff | undefined> {
|
|
const staff = await db.staff.findUnique({ where: { id } });
|
|
return staff ?? undefined;
|
|
},
|
|
|
|
async getAllStaff(): Promise<Staff[]> {
|
|
return db.staff.findMany({
|
|
orderBy: { displayOrder: "asc" },
|
|
});
|
|
},
|
|
|
|
async createStaff(staff: StaffCreateInput): Promise<Staff> {
|
|
const max = await db.staff.aggregate({
|
|
where: {
|
|
userId: staff.userId,
|
|
displayOrder: { gt: 0 },
|
|
},
|
|
_max: { displayOrder: true },
|
|
});
|
|
|
|
return db.staff.create({
|
|
data: {
|
|
...staff,
|
|
displayOrder: (max._max.displayOrder ?? 0) + 1,
|
|
},
|
|
});
|
|
},
|
|
|
|
async updateStaff(
|
|
id: number,
|
|
updates: StaffUpdateInput,
|
|
): Promise<Staff | undefined> {
|
|
return db.$transaction(async (tx: any) => {
|
|
const staff = await tx.staff.findUnique({ where: { id } });
|
|
if (!staff) return undefined;
|
|
|
|
const { userId, displayOrder: oldOrder } = staff;
|
|
const newOrder = updates.displayOrder;
|
|
|
|
if (newOrder === undefined || newOrder === oldOrder) {
|
|
return tx.staff.update({
|
|
where: { id },
|
|
data: updates,
|
|
});
|
|
}
|
|
|
|
const totalStaff = await tx.staff.count({ where: { userId } });
|
|
|
|
if (newOrder < 1 || newOrder > totalStaff) {
|
|
throw new Error(`displayOrder must be between 1 and ${totalStaff}`);
|
|
}
|
|
|
|
const occupyingStaff = await tx.staff.findFirst({
|
|
where: {
|
|
userId,
|
|
displayOrder: newOrder,
|
|
},
|
|
});
|
|
|
|
// CASE 1: staff already had a slot → SWAP
|
|
if (oldOrder && oldOrder > 0 && occupyingStaff) {
|
|
await tx.staff.update({
|
|
where: { id: occupyingStaff.id },
|
|
data: { displayOrder: oldOrder },
|
|
});
|
|
}
|
|
|
|
// CASE 2: first-time assignment (oldOrder = 0)
|
|
if ((!oldOrder || oldOrder === 0) && occupyingStaff) {
|
|
// find first free slot
|
|
const usedOrders = await tx.staff.findMany({
|
|
where: {
|
|
userId,
|
|
displayOrder: { gt: 0 },
|
|
},
|
|
select: { displayOrder: true },
|
|
orderBy: { displayOrder: "asc" },
|
|
});
|
|
|
|
const usedSet = new Set(usedOrders.map((s: any) => s.displayOrder));
|
|
let freeSlot = 1;
|
|
while (usedSet.has(freeSlot)) freeSlot++;
|
|
|
|
await tx.staff.update({
|
|
where: { id: occupyingStaff.id },
|
|
data: { displayOrder: freeSlot },
|
|
});
|
|
}
|
|
|
|
return tx.staff.update({
|
|
where: { id },
|
|
data: {
|
|
...updates,
|
|
displayOrder: newOrder,
|
|
},
|
|
});
|
|
});
|
|
},
|
|
|
|
async deleteStaff(id: number): Promise<boolean> {
|
|
return db.$transaction(async (tx: any) => {
|
|
const staff = await tx.staff.findUnique({ where: { id } });
|
|
if (!staff) return false;
|
|
|
|
const { userId, displayOrder } = staff;
|
|
|
|
await tx.staff.delete({ where: { id } });
|
|
|
|
// Shift left to remove gap
|
|
await tx.staff.updateMany({
|
|
where: {
|
|
userId,
|
|
displayOrder: { gt: displayOrder },
|
|
},
|
|
data: {
|
|
displayOrder: { decrement: 1 },
|
|
},
|
|
});
|
|
|
|
return true;
|
|
});
|
|
},
|
|
async countAppointmentsByStaffId(staffId: number): Promise<number> {
|
|
return await db.appointment.count({ where: { staffId } });
|
|
},
|
|
|
|
async countClaimsByStaffId(staffId: number): Promise<number> {
|
|
return await db.claim.count({ where: { staffId } });
|
|
},
|
|
};
|