feat(categorised files)
This commit is contained in:
198
apps/Backend/src/storage/appointements-storage.ts
Normal file
198
apps/Backend/src/storage/appointements-storage.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import {
|
||||
Appointment,
|
||||
InsertAppointment,
|
||||
Patient,
|
||||
UpdateAppointment,
|
||||
} from "@repo/db/types";
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
|
||||
export interface IStorage {
|
||||
getAppointment(id: number): Promise<Appointment | undefined>;
|
||||
getAllAppointments(): Promise<Appointment[]>;
|
||||
getAppointmentsByUserId(userId: number): Promise<Appointment[]>;
|
||||
getAppointmentsByPatientId(patientId: number): Promise<Appointment[]>;
|
||||
getPatientFromAppointmentId(
|
||||
appointmentId: number
|
||||
): Promise<Patient | undefined>;
|
||||
getRecentAppointments(limit: number, offset: number): Promise<Appointment[]>;
|
||||
getAppointmentsOnRange(start: Date, end: Date): Promise<Appointment[]>;
|
||||
createAppointment(appointment: InsertAppointment): Promise<Appointment>;
|
||||
updateAppointment(
|
||||
id: number,
|
||||
appointment: UpdateAppointment
|
||||
): Promise<Appointment>;
|
||||
deleteAppointment(id: number): Promise<void>;
|
||||
getPatientAppointmentByDateTime(
|
||||
patientId: number,
|
||||
date: Date,
|
||||
startTime: string
|
||||
): Promise<Appointment | undefined>;
|
||||
getStaffAppointmentByDateTime(
|
||||
staffId: number,
|
||||
date: Date,
|
||||
startTime: string,
|
||||
excludeId?: number
|
||||
): Promise<Appointment | undefined>;
|
||||
getPatientConflictAppointment(
|
||||
patientId: number,
|
||||
date: Date,
|
||||
startTime: string,
|
||||
excludeId: number
|
||||
): Promise<Appointment | undefined>;
|
||||
getStaffConflictAppointment(
|
||||
staffId: number,
|
||||
date: Date,
|
||||
startTime: string,
|
||||
excludeId: number
|
||||
): Promise<Appointment | undefined>;
|
||||
}
|
||||
|
||||
export const appointmentsStorage: IStorage = {
|
||||
async getAppointment(id: number): Promise<Appointment | undefined> {
|
||||
const appointment = await db.appointment.findUnique({ where: { id } });
|
||||
return appointment ?? undefined;
|
||||
},
|
||||
|
||||
async getAllAppointments(): Promise<Appointment[]> {
|
||||
return await db.appointment.findMany();
|
||||
},
|
||||
|
||||
async getAppointmentsByUserId(userId: number): Promise<Appointment[]> {
|
||||
return await db.appointment.findMany({ where: { userId } });
|
||||
},
|
||||
|
||||
async getAppointmentsByPatientId(patientId: number): Promise<Appointment[]> {
|
||||
return await db.appointment.findMany({ where: { patientId } });
|
||||
},
|
||||
|
||||
async getPatientFromAppointmentId(
|
||||
appointmentId: number
|
||||
): Promise<Patient | undefined> {
|
||||
const appointment = await db.appointment.findUnique({
|
||||
where: { id: appointmentId },
|
||||
include: { patient: true },
|
||||
});
|
||||
return appointment?.patient ?? undefined;
|
||||
},
|
||||
|
||||
async getAppointmentsOnRange(start: Date, end: Date): Promise<Appointment[]> {
|
||||
return db.appointment.findMany({
|
||||
where: {
|
||||
date: {
|
||||
gte: start,
|
||||
lte: end,
|
||||
},
|
||||
},
|
||||
orderBy: { date: "asc" },
|
||||
});
|
||||
},
|
||||
|
||||
async getRecentAppointments(
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<Appointment[]> {
|
||||
return db.appointment.findMany({
|
||||
skip: offset,
|
||||
take: limit,
|
||||
orderBy: { date: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
async createAppointment(
|
||||
appointment: InsertAppointment
|
||||
): Promise<Appointment> {
|
||||
return await db.appointment.create({ data: appointment as Appointment });
|
||||
},
|
||||
|
||||
async updateAppointment(
|
||||
id: number,
|
||||
updateData: UpdateAppointment
|
||||
): Promise<Appointment> {
|
||||
try {
|
||||
return await db.appointment.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Appointment with ID ${id} not found`);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteAppointment(id: number): Promise<void> {
|
||||
try {
|
||||
await db.appointment.delete({ where: { id } });
|
||||
} catch (err) {
|
||||
throw new Error(`Appointment with ID ${id} not found`);
|
||||
}
|
||||
},
|
||||
|
||||
async getPatientAppointmentByDateTime(
|
||||
patientId: number,
|
||||
date: Date,
|
||||
startTime: string
|
||||
): Promise<Appointment | undefined> {
|
||||
return (
|
||||
(await db.appointment.findFirst({
|
||||
where: {
|
||||
patientId,
|
||||
date,
|
||||
startTime,
|
||||
},
|
||||
})) ?? undefined
|
||||
);
|
||||
},
|
||||
|
||||
async getStaffAppointmentByDateTime(
|
||||
staffId: number,
|
||||
date: Date,
|
||||
startTime: string,
|
||||
excludeId?: number
|
||||
): Promise<Appointment | undefined> {
|
||||
return (
|
||||
(await db.appointment.findFirst({
|
||||
where: {
|
||||
staffId,
|
||||
date,
|
||||
startTime,
|
||||
NOT: excludeId ? { id: excludeId } : undefined,
|
||||
},
|
||||
})) ?? undefined
|
||||
);
|
||||
},
|
||||
|
||||
async getPatientConflictAppointment(
|
||||
patientId: number,
|
||||
date: Date,
|
||||
startTime: string,
|
||||
excludeId: number
|
||||
): Promise<Appointment | undefined> {
|
||||
return (
|
||||
(await db.appointment.findFirst({
|
||||
where: {
|
||||
patientId,
|
||||
date,
|
||||
startTime,
|
||||
NOT: { id: excludeId },
|
||||
},
|
||||
})) ?? undefined
|
||||
);
|
||||
},
|
||||
|
||||
async getStaffConflictAppointment(
|
||||
staffId: number,
|
||||
date: Date,
|
||||
startTime: string,
|
||||
excludeId: number
|
||||
): Promise<Appointment | undefined> {
|
||||
return (
|
||||
(await db.appointment.findFirst({
|
||||
where: {
|
||||
staffId,
|
||||
date,
|
||||
startTime,
|
||||
NOT: { id: excludeId },
|
||||
},
|
||||
})) ?? undefined
|
||||
);
|
||||
},
|
||||
};
|
||||
98
apps/Backend/src/storage/claims-storage.ts
Normal file
98
apps/Backend/src/storage/claims-storage.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
Claim,
|
||||
ClaimWithServiceLines,
|
||||
InsertClaim,
|
||||
UpdateClaim,
|
||||
} from "@repo/db/types";
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
|
||||
export interface IStorage {
|
||||
getClaim(id: number): Promise<Claim | undefined>;
|
||||
getRecentClaimsByPatientId(
|
||||
patientId: number,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<ClaimWithServiceLines[]>;
|
||||
|
||||
getTotalClaimCountByPatient(patientId: number): Promise<number>;
|
||||
getClaimsByAppointmentId(appointmentId: number): Promise<Claim[]>;
|
||||
getRecentClaims(limit: number, offset: number): Promise<Claim[]>;
|
||||
getTotalClaimCount(): Promise<number>;
|
||||
createClaim(claim: InsertClaim): Promise<Claim>;
|
||||
updateClaim(id: number, updates: UpdateClaim): Promise<Claim>;
|
||||
deleteClaim(id: number): Promise<void>;
|
||||
}
|
||||
|
||||
export const claimsStorage: IStorage = {
|
||||
async getClaim(id: number): Promise<Claim | undefined> {
|
||||
const claim = await db.claim.findUnique({ where: { id } });
|
||||
return claim ?? undefined;
|
||||
},
|
||||
|
||||
async getRecentClaimsByPatientId(
|
||||
patientId: number,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<ClaimWithServiceLines[]> {
|
||||
return db.claim.findMany({
|
||||
where: { patientId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
include: {
|
||||
serviceLines: true,
|
||||
staff: true,
|
||||
claimFiles: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async getTotalClaimCountByPatient(patientId: number): Promise<number> {
|
||||
return db.claim.count({
|
||||
where: { patientId },
|
||||
});
|
||||
},
|
||||
|
||||
async getClaimsByAppointmentId(appointmentId: number): Promise<Claim[]> {
|
||||
return await db.claim.findMany({ where: { appointmentId } });
|
||||
},
|
||||
|
||||
async getRecentClaims(
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<ClaimWithServiceLines[]> {
|
||||
return db.claim.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
include: { serviceLines: true, staff: true, claimFiles: true },
|
||||
});
|
||||
},
|
||||
|
||||
async getTotalClaimCount(): Promise<number> {
|
||||
return db.claim.count();
|
||||
},
|
||||
|
||||
async createClaim(claim: InsertClaim): Promise<Claim> {
|
||||
return await db.claim.create({ data: claim as Claim });
|
||||
},
|
||||
|
||||
async updateClaim(id: number, updates: UpdateClaim): Promise<Claim> {
|
||||
try {
|
||||
return await db.claim.update({
|
||||
where: { id },
|
||||
data: updates,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Claim with ID ${id} not found`);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteClaim(id: number): Promise<void> {
|
||||
try {
|
||||
await db.claim.delete({ where: { id } });
|
||||
} catch (err) {
|
||||
throw new Error(`Claim with ID ${id} not found`);
|
||||
}
|
||||
},
|
||||
};
|
||||
349
apps/Backend/src/storage/cloudStorage-storage.ts
Normal file
349
apps/Backend/src/storage/cloudStorage-storage.ts
Normal file
@@ -0,0 +1,349 @@
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
import { CloudFile, CloudFolder } from "@repo/db/types";
|
||||
import { serializeFile } from "../utils/prismaFileUtils";
|
||||
|
||||
export interface IStorage {
|
||||
// CloudFolder methods
|
||||
getFolder(id: number): Promise<CloudFolder | null>;
|
||||
getFoldersByUser(
|
||||
userId: number,
|
||||
parentId: number | null,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<CloudFolder[]>;
|
||||
getRecentFolders(limit: number, offset: number): Promise<CloudFolder[]>;
|
||||
createFolder(
|
||||
userId: number,
|
||||
name: string,
|
||||
parentId?: number | null
|
||||
): Promise<CloudFolder>;
|
||||
updateFolder(
|
||||
id: number,
|
||||
updates: Partial<{ name?: string; parentId?: number | null }>
|
||||
): Promise<CloudFolder | null>;
|
||||
deleteFolder(id: number): Promise<boolean>;
|
||||
|
||||
// CloudFile methods
|
||||
getFile(id: number): Promise<CloudFile | null>;
|
||||
listFilesByFolderByUser(
|
||||
userId: number,
|
||||
folderId: number | null,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<CloudFile[]>;
|
||||
listFilesByFolder(
|
||||
folderId: number | null,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<CloudFile[]>;
|
||||
|
||||
// chunked upload methods
|
||||
createFileInit(
|
||||
userId: number,
|
||||
name: string,
|
||||
mimeType?: string | null,
|
||||
expectedSize?: bigint | null,
|
||||
totalChunks?: number | null,
|
||||
folderId?: number | null
|
||||
): Promise<CloudFile>;
|
||||
addChunk(fileId: number, seq: number, data: Buffer): Promise<void>;
|
||||
completeFile(fileId: number): Promise<{ ok: true; size: string }>;
|
||||
deleteFile(fileId: number): Promise<boolean>;
|
||||
|
||||
// search
|
||||
searchByName(
|
||||
userId: number,
|
||||
q: string,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<{
|
||||
folders: CloudFolder[];
|
||||
files: CloudFile[];
|
||||
foldersTotal: number;
|
||||
filesTotal: number;
|
||||
}>;
|
||||
|
||||
// helper: stream file chunks via Node.js stream
|
||||
streamFileTo(resStream: NodeJS.WritableStream, fileId: number): Promise<void>;
|
||||
}
|
||||
|
||||
export const cloudStorageStorage: IStorage = {
|
||||
// --- Folders ---
|
||||
async getFolder(id: number) {
|
||||
const folder = await db.cloudFolder.findUnique({
|
||||
where: { id },
|
||||
include: { files: false },
|
||||
});
|
||||
return folder ?? null;
|
||||
},
|
||||
|
||||
async getFoldersByUser(
|
||||
userId: number,
|
||||
parentId: number | null = null,
|
||||
limit = 50,
|
||||
offset = 0
|
||||
) {
|
||||
const folders = await db.cloudFolder.findMany({
|
||||
where: { userId, parentId },
|
||||
orderBy: { name: "asc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
});
|
||||
return folders;
|
||||
},
|
||||
|
||||
async getRecentFolders(limit = 50, offset = 0) {
|
||||
const folders = await db.cloudFolder.findMany({
|
||||
orderBy: { name: "asc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
});
|
||||
return folders;
|
||||
},
|
||||
|
||||
async createFolder(
|
||||
userId: number,
|
||||
name: string,
|
||||
parentId: number | null = null
|
||||
) {
|
||||
const created = await db.cloudFolder.create({
|
||||
data: { userId, name, parentId },
|
||||
});
|
||||
return created;
|
||||
},
|
||||
|
||||
async updateFolder(
|
||||
id: number,
|
||||
updates: Partial<{ name?: string; parentId?: number | null }>
|
||||
) {
|
||||
try {
|
||||
const updated = await db.cloudFolder.update({
|
||||
where: { id },
|
||||
data: updates,
|
||||
});
|
||||
return updated;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteFolder(id: number) {
|
||||
try {
|
||||
await db.cloudFolder.delete({ where: { id } });
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("deleteFolder error", err);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// --- Files ---
|
||||
async getFile(id: number): Promise<CloudFile | null> {
|
||||
const file = await db.cloudFile.findUnique({
|
||||
where: { id },
|
||||
include: { chunks: { orderBy: { seq: "asc" } } },
|
||||
});
|
||||
return (file as unknown as CloudFile) ?? null;
|
||||
},
|
||||
|
||||
async listFilesByFolderByUser(
|
||||
userId: number,
|
||||
folderId: number | null = null,
|
||||
limit = 50,
|
||||
offset = 0
|
||||
) {
|
||||
const files = await db.cloudFile.findMany({
|
||||
where: { userId, folderId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
mimeType: true,
|
||||
fileSize: true,
|
||||
folderId: true,
|
||||
isComplete: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
});
|
||||
return files.map(serializeFile);
|
||||
},
|
||||
|
||||
async listFilesByFolder(
|
||||
folderId: number | null = null,
|
||||
limit = 50,
|
||||
offset = 0
|
||||
) {
|
||||
const files = await db.cloudFile.findMany({
|
||||
where: { folderId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
mimeType: true,
|
||||
fileSize: true,
|
||||
folderId: true,
|
||||
isComplete: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
});
|
||||
return files.map(serializeFile);
|
||||
},
|
||||
|
||||
// --- Chunked upload methods ---
|
||||
async createFileInit(
|
||||
userId,
|
||||
name,
|
||||
mimeType = null,
|
||||
expectedSize = null,
|
||||
totalChunks = null,
|
||||
folderId = null
|
||||
) {
|
||||
const created = await db.cloudFile.create({
|
||||
data: {
|
||||
userId,
|
||||
name,
|
||||
mimeType,
|
||||
fileSize: expectedSize ?? BigInt(0),
|
||||
folderId,
|
||||
totalChunks,
|
||||
isComplete: false,
|
||||
},
|
||||
});
|
||||
return serializeFile(created);
|
||||
},
|
||||
|
||||
async addChunk(fileId: number, seq: number, data: Buffer) {
|
||||
// Ensure file exists & belongs to owner will be done by caller (route)
|
||||
// Attempt insert; if unique violation => ignore (idempotent)
|
||||
try {
|
||||
await db.cloudFileChunk.create({
|
||||
data: {
|
||||
fileId,
|
||||
seq,
|
||||
data,
|
||||
},
|
||||
});
|
||||
} catch (err: any) {
|
||||
// If unique constraint violation (duplicate chunk), ignore
|
||||
if (
|
||||
err?.code === "P2002" ||
|
||||
err?.message?.includes("Unique constraint failed")
|
||||
) {
|
||||
// duplicate chunk, ignore
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
async completeFile(fileId: number) {
|
||||
// Compute total size from chunks and mark complete inside a transaction
|
||||
const chunks = await db.cloudFileChunk.findMany({ where: { fileId } });
|
||||
if (!chunks.length) {
|
||||
throw new Error("No chunks uploaded");
|
||||
}
|
||||
let total = 0;
|
||||
for (const c of chunks) total += c.data.length;
|
||||
|
||||
// Update file
|
||||
await db.cloudFile.update({
|
||||
where: { id: fileId },
|
||||
data: {
|
||||
fileSize: BigInt(total),
|
||||
isComplete: true,
|
||||
},
|
||||
});
|
||||
return { ok: true, size: BigInt(total).toString() };
|
||||
},
|
||||
|
||||
async deleteFile(fileId: number) {
|
||||
try {
|
||||
await db.cloudFile.delete({ where: { id: fileId } });
|
||||
// chunks cascade-delete via Prisma relation onDelete: Cascade
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("deleteFile error", err);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// --- Search ---
|
||||
async searchByName(userId: number, q: string, limit = 20, offset = 0) {
|
||||
const [folders, files, foldersTotal, filesTotal] = await Promise.all([
|
||||
db.cloudFolder.findMany({
|
||||
where: {
|
||||
userId,
|
||||
name: { contains: q, mode: "insensitive" },
|
||||
},
|
||||
orderBy: { name: "asc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
}),
|
||||
db.cloudFile.findMany({
|
||||
where: {
|
||||
userId,
|
||||
name: { contains: q, mode: "insensitive" },
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
mimeType: true,
|
||||
fileSize: true,
|
||||
folderId: true,
|
||||
isComplete: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
}),
|
||||
db.cloudFolder.count({
|
||||
where: {
|
||||
userId,
|
||||
name: { contains: q, mode: "insensitive" },
|
||||
},
|
||||
}),
|
||||
db.cloudFile.count({
|
||||
where: {
|
||||
userId,
|
||||
name: { contains: q, mode: "insensitive" },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return {
|
||||
folders,
|
||||
files: files.map(serializeFile),
|
||||
foldersTotal,
|
||||
filesTotal,
|
||||
};
|
||||
},
|
||||
|
||||
// --- Streaming helper ---
|
||||
async streamFileTo(resStream: NodeJS.WritableStream, fileId: number) {
|
||||
// Stream chunks in batches to avoid loading everything at once.
|
||||
const batchSize = 100;
|
||||
let offset = 0;
|
||||
while (true) {
|
||||
const chunks = await db.cloudFileChunk.findMany({
|
||||
where: { fileId },
|
||||
orderBy: { seq: "asc" },
|
||||
take: batchSize,
|
||||
skip: offset,
|
||||
});
|
||||
if (!chunks.length) break;
|
||||
for (const c of chunks) {
|
||||
resStream.write(Buffer.from(c.data));
|
||||
}
|
||||
offset += chunks.length;
|
||||
if (chunks.length < batchSize) break;
|
||||
}
|
||||
// caller will end the response stream
|
||||
},
|
||||
};
|
||||
39
apps/Backend/src/storage/database-backup-storage.ts
Normal file
39
apps/Backend/src/storage/database-backup-storage.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { DatabaseBackup } from "@repo/db/types";
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
|
||||
export interface IStorage {
|
||||
// Database Backup methods
|
||||
createBackup(userId: number): Promise<DatabaseBackup>;
|
||||
getLastBackup(userId: number): Promise<DatabaseBackup | null>;
|
||||
getBackups(userId: number, limit?: number): Promise<DatabaseBackup[]>;
|
||||
deleteBackups(userId: number): Promise<number>; // clears all for user
|
||||
}
|
||||
|
||||
export const databaseBackupStorage: IStorage = {
|
||||
// ==============================
|
||||
// Database Backup methods
|
||||
// ==============================
|
||||
async createBackup(userId) {
|
||||
return await db.databaseBackup.create({ data: { userId } });
|
||||
},
|
||||
|
||||
async getLastBackup(userId) {
|
||||
return await db.databaseBackup.findFirst({
|
||||
where: { userId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
async getBackups(userId, limit = 10) {
|
||||
return await db.databaseBackup.findMany({
|
||||
where: { userId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: limit,
|
||||
});
|
||||
},
|
||||
|
||||
async deleteBackups(userId) {
|
||||
const result = await db.databaseBackup.deleteMany({ where: { userId } });
|
||||
return result.count;
|
||||
},
|
||||
};
|
||||
218
apps/Backend/src/storage/general-pdf-storage.ts
Normal file
218
apps/Backend/src/storage/general-pdf-storage.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import { PdfFile, PdfGroup } from "@repo/db/types";
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
import { PdfTitleKey } from "@repo/db/generated/prisma";
|
||||
|
||||
export interface IStorage {
|
||||
// General PDF Methods
|
||||
createPdfFile(
|
||||
groupId: number,
|
||||
filename: string,
|
||||
pdfData: Buffer
|
||||
): Promise<PdfFile>;
|
||||
getPdfFileById(id: number): Promise<PdfFile | undefined>;
|
||||
getPdfFilesByGroupId(
|
||||
groupId: number,
|
||||
opts?: { limit?: number; offset?: number; withGroup?: boolean }
|
||||
): Promise<PdfFile[] | { total: number; data: PdfFile[] }>;
|
||||
getRecentPdfFiles(limit: number, offset: number): Promise<PdfFile[]>;
|
||||
deletePdfFile(id: number): Promise<boolean>;
|
||||
updatePdfFile(
|
||||
id: number,
|
||||
updates: Partial<Pick<PdfFile, "filename" | "pdfData">>
|
||||
): Promise<PdfFile | undefined>;
|
||||
|
||||
// PDF Group management
|
||||
createPdfGroup(
|
||||
patientId: number,
|
||||
title: string,
|
||||
titleKey: PdfTitleKey
|
||||
): Promise<PdfGroup>;
|
||||
findPdfGroupByPatientTitleKey(
|
||||
patientId: number,
|
||||
titleKey: PdfTitleKey
|
||||
): Promise<PdfGroup | undefined>;
|
||||
getAllPdfGroups(): Promise<PdfGroup[]>;
|
||||
getPdfGroupById(id: number): Promise<PdfGroup | undefined>;
|
||||
getPdfGroupsByPatientId(patientId: number): Promise<PdfGroup[]>;
|
||||
updatePdfGroup(
|
||||
id: number,
|
||||
updates: Partial<Pick<PdfGroup, "title">>
|
||||
): Promise<PdfGroup | undefined>;
|
||||
deletePdfGroup(id: number): Promise<boolean>;
|
||||
}
|
||||
|
||||
export const generalPdfStorage: IStorage = {
|
||||
// PDF Files
|
||||
async createPdfFile(groupId, filename, pdfData) {
|
||||
return db.pdfFile.create({
|
||||
data: {
|
||||
groupId,
|
||||
filename,
|
||||
pdfData,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async getAllPdfGroups(): Promise<PdfGroup[]> {
|
||||
return db.pdfGroup.findMany({
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async getPdfFileById(id) {
|
||||
return (await db.pdfFile.findUnique({ where: { id } })) ?? undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* getPdfFilesByGroupId: supports
|
||||
* - getPdfFilesByGroupId(groupId) => Promise<PdfFile[]>
|
||||
* - getPdfFilesByGroupId(groupId, { limit, offset }) => Promise<{ total, data }>
|
||||
* - getPdfFilesByGroupId(groupId, { limit, offset, withGroup: true }) => Promise<{ total, data: PdfFileWithGroup[] }>
|
||||
*/
|
||||
async getPdfFilesByGroupId(groupId, opts) {
|
||||
// if pagination is requested (limit provided) return total + page
|
||||
const wantsPagination =
|
||||
!!opts &&
|
||||
(typeof opts.limit === "number" || typeof opts.offset === "number");
|
||||
|
||||
if (wantsPagination) {
|
||||
const limit = Math.min(Number(opts?.limit ?? 5), 1000);
|
||||
const offset = Number(opts?.offset ?? 0);
|
||||
|
||||
if (opts?.withGroup) {
|
||||
// return total + data with group included
|
||||
const [total, data] = await Promise.all([
|
||||
db.pdfFile.count({ where: { groupId } }),
|
||||
db.pdfFile.findMany({
|
||||
where: { groupId },
|
||||
orderBy: { uploadedAt: "desc" },
|
||||
take: limit,
|
||||
skip: offset,
|
||||
include: { group: true }, // only include
|
||||
}),
|
||||
]);
|
||||
|
||||
return { total, data };
|
||||
} else {
|
||||
// return total + data with limited fields via select
|
||||
const [total, data] = await Promise.all([
|
||||
db.pdfFile.count({ where: { groupId } }),
|
||||
db.pdfFile.findMany({
|
||||
where: { groupId },
|
||||
orderBy: { uploadedAt: "desc" },
|
||||
take: limit,
|
||||
skip: offset,
|
||||
select: { id: true, filename: true, uploadedAt: true }, // only select
|
||||
}),
|
||||
]);
|
||||
|
||||
// Note: selected shape won't have all PdfFile fields; cast if needed
|
||||
return { total, data: data as unknown as PdfFile[] };
|
||||
}
|
||||
}
|
||||
|
||||
// non-paginated: return all files (keep descending order)
|
||||
if (opts?.withGroup) {
|
||||
const all = await db.pdfFile.findMany({
|
||||
where: { groupId },
|
||||
orderBy: { uploadedAt: "desc" },
|
||||
include: { group: true },
|
||||
});
|
||||
return all as PdfFile[];
|
||||
} else {
|
||||
const all = await db.pdfFile.findMany({
|
||||
where: { groupId },
|
||||
orderBy: { uploadedAt: "desc" },
|
||||
// no select or include -> returns full PdfFile
|
||||
});
|
||||
return all as PdfFile[];
|
||||
}
|
||||
},
|
||||
|
||||
async getRecentPdfFiles(limit: number, offset: number): Promise<PdfFile[]> {
|
||||
return db.pdfFile.findMany({
|
||||
skip: offset,
|
||||
take: limit,
|
||||
orderBy: { uploadedAt: "desc" },
|
||||
include: { group: true },
|
||||
});
|
||||
},
|
||||
|
||||
async updatePdfFile(id, updates) {
|
||||
try {
|
||||
return await db.pdfFile.update({
|
||||
where: { id },
|
||||
data: updates,
|
||||
});
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
async deletePdfFile(id) {
|
||||
try {
|
||||
await db.pdfFile.delete({ where: { id } });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// ----------------------
|
||||
// PdfGroup CRUD
|
||||
// ----------------------
|
||||
|
||||
async createPdfGroup(patientId, title, titleKey) {
|
||||
return db.pdfGroup.create({
|
||||
data: {
|
||||
patientId,
|
||||
title,
|
||||
titleKey,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async findPdfGroupByPatientTitleKey(patientId, titleKey) {
|
||||
return (
|
||||
(await db.pdfGroup.findFirst({
|
||||
where: {
|
||||
patientId,
|
||||
titleKey,
|
||||
},
|
||||
})) ?? undefined
|
||||
);
|
||||
},
|
||||
|
||||
async getPdfGroupById(id) {
|
||||
return (await db.pdfGroup.findUnique({ where: { id } })) ?? undefined;
|
||||
},
|
||||
|
||||
async getPdfGroupsByPatientId(patientId) {
|
||||
return db.pdfGroup.findMany({
|
||||
where: { patientId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
async updatePdfGroup(id, updates) {
|
||||
try {
|
||||
return await db.pdfGroup.update({
|
||||
where: { id },
|
||||
data: updates,
|
||||
});
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
async deletePdfGroup(id) {
|
||||
try {
|
||||
await db.pdfGroup.delete({ where: { id } });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
63
apps/Backend/src/storage/insurance-creds-storage.ts
Normal file
63
apps/Backend/src/storage/insurance-creds-storage.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { InsertInsuranceCredential, InsuranceCredential } from "@repo/db/types";
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
|
||||
export interface IStorage {
|
||||
getInsuranceCredential(id: number): Promise<InsuranceCredential | null>;
|
||||
getInsuranceCredentialsByUser(userId: number): Promise<InsuranceCredential[]>;
|
||||
createInsuranceCredential(
|
||||
data: InsertInsuranceCredential
|
||||
): Promise<InsuranceCredential>;
|
||||
updateInsuranceCredential(
|
||||
id: number,
|
||||
updates: Partial<InsuranceCredential>
|
||||
): Promise<InsuranceCredential | null>;
|
||||
deleteInsuranceCredential(userId: number, id: number): Promise<boolean>;
|
||||
getInsuranceCredentialByUserAndSiteKey(
|
||||
userId: number,
|
||||
siteKey: string
|
||||
): Promise<InsuranceCredential | null>;
|
||||
}
|
||||
|
||||
export const insuranceCredsStorage: IStorage = {
|
||||
async getInsuranceCredential(id: number) {
|
||||
return await db.insuranceCredential.findUnique({ where: { id } });
|
||||
},
|
||||
|
||||
async getInsuranceCredentialsByUser(userId: number) {
|
||||
return await db.insuranceCredential.findMany({ where: { userId } });
|
||||
},
|
||||
|
||||
async createInsuranceCredential(data: InsertInsuranceCredential) {
|
||||
return await db.insuranceCredential.create({
|
||||
data: data as InsuranceCredential,
|
||||
});
|
||||
},
|
||||
|
||||
async updateInsuranceCredential(
|
||||
id: number,
|
||||
updates: Partial<InsuranceCredential>
|
||||
) {
|
||||
return await db.insuranceCredential.update({
|
||||
where: { id },
|
||||
data: updates,
|
||||
});
|
||||
},
|
||||
|
||||
async deleteInsuranceCredential(userId: number, id: number) {
|
||||
try {
|
||||
await db.insuranceCredential.delete({ where: { userId, id } });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async getInsuranceCredentialByUserAndSiteKey(
|
||||
userId: number,
|
||||
siteKey: string
|
||||
): Promise<InsuranceCredential | null> {
|
||||
return await db.insuranceCredential.findFirst({
|
||||
where: { userId, siteKey },
|
||||
});
|
||||
},
|
||||
};
|
||||
80
apps/Backend/src/storage/notifications-storage.ts
Normal file
80
apps/Backend/src/storage/notifications-storage.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Notification, NotificationTypes } from "@repo/db/types";
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
|
||||
export interface IStorage {
|
||||
// Notification methods
|
||||
createNotification(
|
||||
userId: number,
|
||||
type: NotificationTypes,
|
||||
message: string
|
||||
): Promise<Notification>;
|
||||
getNotifications(
|
||||
userId: number,
|
||||
limit?: number,
|
||||
offset?: number
|
||||
): Promise<Notification[]>;
|
||||
markNotificationRead(
|
||||
userId: number,
|
||||
notificationId: number
|
||||
): Promise<boolean>;
|
||||
markAllNotificationsRead(userId: number): Promise<number>;
|
||||
deleteNotificationsByType(
|
||||
userId: number,
|
||||
type: NotificationTypes
|
||||
): Promise<number>;
|
||||
deleteAllNotifications(userId: number): Promise<number>;
|
||||
}
|
||||
|
||||
export const notificationsStorage: IStorage = {
|
||||
// ==============================
|
||||
// Notification methods
|
||||
// ==============================
|
||||
async createNotification(userId, type, message) {
|
||||
return await db.notification.create({
|
||||
data: { userId, type, message },
|
||||
});
|
||||
},
|
||||
|
||||
async getNotifications(
|
||||
userId: number,
|
||||
limit = 50,
|
||||
offset = 0
|
||||
): Promise<Notification[]> {
|
||||
return await db.notification.findMany({
|
||||
where: { userId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: limit,
|
||||
skip: offset,
|
||||
});
|
||||
},
|
||||
|
||||
async markNotificationRead(userId, notificationId) {
|
||||
const result = await db.notification.updateMany({
|
||||
where: { id: notificationId, userId },
|
||||
data: { read: true },
|
||||
});
|
||||
return result.count > 0;
|
||||
},
|
||||
|
||||
async markAllNotificationsRead(userId) {
|
||||
const result = await db.notification.updateMany({
|
||||
where: { userId },
|
||||
data: { read: true },
|
||||
});
|
||||
return result.count;
|
||||
},
|
||||
|
||||
async deleteNotificationsByType(userId, type) {
|
||||
const result = await db.notification.deleteMany({
|
||||
where: { userId, type },
|
||||
});
|
||||
return result.count;
|
||||
},
|
||||
|
||||
async deleteAllNotifications(userId: number): Promise<number> {
|
||||
const result = await db.notification.deleteMany({
|
||||
where: { userId },
|
||||
});
|
||||
return result.count;
|
||||
},
|
||||
};
|
||||
141
apps/Backend/src/storage/patients-storage.ts
Normal file
141
apps/Backend/src/storage/patients-storage.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { InsertPatient, Patient, UpdatePatient } from "@repo/db/types";
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
|
||||
export interface IStorage {
|
||||
// Patient methods
|
||||
getPatient(id: number): Promise<Patient | undefined>;
|
||||
getPatientByInsuranceId(insuranceId: string): Promise<Patient | null>;
|
||||
getPatientsByUserId(userId: number): Promise<Patient[]>;
|
||||
getRecentPatients(limit: number, offset: number): Promise<Patient[]>;
|
||||
getPatientsByIds(ids: number[]): Promise<Patient[]>;
|
||||
createPatient(patient: InsertPatient): Promise<Patient>;
|
||||
updatePatient(id: number, patient: UpdatePatient): Promise<Patient>;
|
||||
deletePatient(id: number): Promise<void>;
|
||||
searchPatients(args: {
|
||||
filters: any;
|
||||
limit: number;
|
||||
offset: number;
|
||||
}): Promise<
|
||||
{
|
||||
id: number;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
phone: string | null;
|
||||
gender: string | null;
|
||||
dateOfBirth: Date;
|
||||
insuranceId: string | null;
|
||||
insuranceProvider: string | null;
|
||||
status: string;
|
||||
}[]
|
||||
>;
|
||||
getTotalPatientCount(): Promise<number>;
|
||||
countPatients(filters: any): Promise<number>; // optional but useful
|
||||
}
|
||||
|
||||
export const patientsStorage: IStorage = {
|
||||
// Patient methods
|
||||
async getPatient(id: number): Promise<Patient | undefined> {
|
||||
const patient = await db.patient.findUnique({ where: { id } });
|
||||
return patient ?? undefined;
|
||||
},
|
||||
|
||||
async getPatientsByUserId(userId: number): Promise<Patient[]> {
|
||||
return await db.patient.findMany({ where: { userId } });
|
||||
},
|
||||
|
||||
async getPatientByInsuranceId(insuranceId: string): Promise<Patient | null> {
|
||||
return db.patient.findFirst({
|
||||
where: { insuranceId },
|
||||
});
|
||||
},
|
||||
|
||||
async getRecentPatients(limit: number, offset: number): Promise<Patient[]> {
|
||||
return db.patient.findMany({
|
||||
skip: offset,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
async getPatientsByIds(ids: number[]): Promise<Patient[]> {
|
||||
if (!ids || ids.length === 0) return [];
|
||||
const uniqueIds = Array.from(new Set(ids));
|
||||
return db.patient.findMany({
|
||||
where: { id: { in: uniqueIds } },
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
phone: true,
|
||||
email: true,
|
||||
dateOfBirth: true,
|
||||
gender: true,
|
||||
insuranceId: true,
|
||||
insuranceProvider: true,
|
||||
status: true,
|
||||
userId: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async createPatient(patient: InsertPatient): Promise<Patient> {
|
||||
return await db.patient.create({ data: patient as Patient });
|
||||
},
|
||||
|
||||
async updatePatient(id: number, updateData: UpdatePatient): Promise<Patient> {
|
||||
try {
|
||||
return await db.patient.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Patient with ID ${id} not found`);
|
||||
}
|
||||
},
|
||||
|
||||
async deletePatient(id: number): Promise<void> {
|
||||
try {
|
||||
await db.patient.delete({ where: { id } });
|
||||
} catch (err) {
|
||||
console.error("Error deleting patient:", err);
|
||||
throw new Error(`Failed to delete patient: ${err}`);
|
||||
}
|
||||
},
|
||||
|
||||
async searchPatients({
|
||||
filters,
|
||||
limit,
|
||||
offset,
|
||||
}: {
|
||||
filters: any;
|
||||
limit: number;
|
||||
offset: number;
|
||||
}) {
|
||||
return db.patient.findMany({
|
||||
where: filters,
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: limit,
|
||||
skip: offset,
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
phone: true,
|
||||
gender: true,
|
||||
dateOfBirth: true,
|
||||
insuranceId: true,
|
||||
insuranceProvider: true,
|
||||
status: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async getTotalPatientCount(): Promise<number> {
|
||||
return db.patient.count();
|
||||
},
|
||||
|
||||
async countPatients(filters: any) {
|
||||
return db.patient.count({ where: filters });
|
||||
},
|
||||
};
|
||||
239
apps/Backend/src/storage/payments-storage.ts
Normal file
239
apps/Backend/src/storage/payments-storage.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import {
|
||||
InsertPayment,
|
||||
Payment,
|
||||
PaymentWithExtras,
|
||||
UpdatePayment,
|
||||
} from "@repo/db/types";
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
|
||||
export interface IStorage {
|
||||
// Payment methods:
|
||||
getPayment(id: number): Promise<Payment | undefined>;
|
||||
createPayment(data: InsertPayment): Promise<Payment>;
|
||||
updatePayment(id: number, updates: UpdatePayment): Promise<Payment>;
|
||||
deletePayment(id: number, userId: number): Promise<void>;
|
||||
getPaymentById(id: number): Promise<PaymentWithExtras | null>;
|
||||
getRecentPaymentsByPatientId(
|
||||
patientId: number,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<PaymentWithExtras[] | null>;
|
||||
getTotalPaymentCountByPatient(patientId: number): Promise<number>;
|
||||
getPaymentsByClaimId(claimId: number): Promise<PaymentWithExtras | null>;
|
||||
getRecentPayments(
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<PaymentWithExtras[]>;
|
||||
getPaymentsByDateRange(from: Date, to: Date): Promise<PaymentWithExtras[]>;
|
||||
getTotalPaymentCount(): Promise<number>;
|
||||
}
|
||||
|
||||
export const paymentsStorage: IStorage = {
|
||||
// Payment Methods
|
||||
async getPayment(id: number): Promise<Payment | undefined> {
|
||||
const payment = await db.payment.findUnique({ where: { id } });
|
||||
return payment ?? undefined;
|
||||
},
|
||||
|
||||
async createPayment(payment: InsertPayment): Promise<Payment> {
|
||||
return db.payment.create({ data: payment as Payment });
|
||||
},
|
||||
|
||||
async updatePayment(id: number, updates: UpdatePayment): Promise<Payment> {
|
||||
const existing = await db.payment.findFirst({ where: { id } });
|
||||
if (!existing) {
|
||||
throw new Error("Payment not found");
|
||||
}
|
||||
|
||||
return db.payment.update({
|
||||
where: { id },
|
||||
data: updates,
|
||||
});
|
||||
},
|
||||
|
||||
async deletePayment(id: number, userId: number): Promise<void> {
|
||||
const existing = await db.payment.findFirst({ where: { id, userId } });
|
||||
if (!existing) {
|
||||
throw new Error("Not authorized or payment not found");
|
||||
}
|
||||
|
||||
await db.payment.delete({ where: { id } });
|
||||
},
|
||||
|
||||
async getRecentPaymentsByPatientId(
|
||||
patientId: number,
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<PaymentWithExtras[]> {
|
||||
const payments = await db.payment.findMany({
|
||||
where: { patientId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
include: {
|
||||
claim: {
|
||||
include: {
|
||||
serviceLines: true,
|
||||
},
|
||||
},
|
||||
serviceLines: true,
|
||||
serviceLineTransactions: {
|
||||
include: {
|
||||
serviceLine: true,
|
||||
},
|
||||
},
|
||||
updatedBy: true,
|
||||
patient: true,
|
||||
},
|
||||
});
|
||||
|
||||
return payments.map((payment) => ({
|
||||
...payment,
|
||||
patientName: payment.claim?.patientName ?? "",
|
||||
paymentDate: payment.createdAt,
|
||||
paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER",
|
||||
}));
|
||||
},
|
||||
|
||||
async getTotalPaymentCountByPatient(patientId: number): Promise<number> {
|
||||
return db.payment.count({
|
||||
where: { patientId },
|
||||
});
|
||||
},
|
||||
|
||||
async getPaymentById(id: number): Promise<PaymentWithExtras | null> {
|
||||
const payment = await db.payment.findFirst({
|
||||
where: { id },
|
||||
include: {
|
||||
claim: {
|
||||
include: {
|
||||
serviceLines: true,
|
||||
},
|
||||
},
|
||||
serviceLines: true,
|
||||
serviceLineTransactions: {
|
||||
include: {
|
||||
serviceLine: true,
|
||||
},
|
||||
},
|
||||
updatedBy: true,
|
||||
patient: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!payment) return null;
|
||||
|
||||
return {
|
||||
...payment,
|
||||
patientName: payment.claim?.patientName ?? "",
|
||||
paymentDate: payment.createdAt,
|
||||
paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER",
|
||||
};
|
||||
},
|
||||
|
||||
async getPaymentsByClaimId(
|
||||
claimId: number
|
||||
): Promise<PaymentWithExtras | null> {
|
||||
const payment = await db.payment.findFirst({
|
||||
where: { claimId },
|
||||
include: {
|
||||
claim: {
|
||||
include: {
|
||||
serviceLines: true,
|
||||
},
|
||||
},
|
||||
serviceLines: true,
|
||||
serviceLineTransactions: {
|
||||
include: {
|
||||
serviceLine: true,
|
||||
},
|
||||
},
|
||||
updatedBy: true,
|
||||
patient: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!payment) return null;
|
||||
|
||||
return {
|
||||
...payment,
|
||||
patientName: payment.claim?.patientName ?? "",
|
||||
paymentDate: payment.createdAt,
|
||||
paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER",
|
||||
};
|
||||
},
|
||||
|
||||
async getRecentPayments(
|
||||
limit: number,
|
||||
offset: number
|
||||
): Promise<PaymentWithExtras[]> {
|
||||
const payments = await db.payment.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
include: {
|
||||
claim: {
|
||||
include: {
|
||||
serviceLines: true,
|
||||
},
|
||||
},
|
||||
serviceLines: true,
|
||||
serviceLineTransactions: {
|
||||
include: {
|
||||
serviceLine: true,
|
||||
},
|
||||
},
|
||||
updatedBy: true,
|
||||
patient: true,
|
||||
},
|
||||
});
|
||||
|
||||
return payments.map((payment) => ({
|
||||
...payment,
|
||||
patientName: payment.claim?.patientName ?? "",
|
||||
paymentDate: payment.createdAt,
|
||||
paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER",
|
||||
}));
|
||||
},
|
||||
|
||||
async getPaymentsByDateRange(
|
||||
from: Date,
|
||||
to: Date
|
||||
): Promise<PaymentWithExtras[]> {
|
||||
const payments = await db.payment.findMany({
|
||||
where: {
|
||||
createdAt: {
|
||||
gte: from,
|
||||
lte: to,
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: {
|
||||
claim: {
|
||||
include: {
|
||||
serviceLines: true,
|
||||
},
|
||||
},
|
||||
serviceLines: true,
|
||||
serviceLineTransactions: {
|
||||
include: {
|
||||
serviceLine: true,
|
||||
},
|
||||
},
|
||||
updatedBy: true,
|
||||
patient: true,
|
||||
},
|
||||
});
|
||||
|
||||
return payments.map((payment) => ({
|
||||
...payment,
|
||||
patientName: payment.claim?.patientName ?? "",
|
||||
paymentDate: payment.createdAt,
|
||||
paymentMethod: payment.serviceLineTransactions[0]?.method ?? "OTHER",
|
||||
}));
|
||||
},
|
||||
|
||||
async getTotalPaymentCount(): Promise<number> {
|
||||
return db.payment.count();
|
||||
},
|
||||
};
|
||||
61
apps/Backend/src/storage/staff-storage.ts
Normal file
61
apps/Backend/src/storage/staff-storage.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Staff } 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: Staff): Promise<Staff>;
|
||||
updateStaff(id: number, updates: Partial<Staff>): 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[]> {
|
||||
const staff = await db.staff.findMany();
|
||||
return staff;
|
||||
},
|
||||
|
||||
async createStaff(staff: Staff): Promise<Staff> {
|
||||
const createdStaff = await db.staff.create({
|
||||
data: staff,
|
||||
});
|
||||
return createdStaff;
|
||||
},
|
||||
|
||||
async updateStaff(
|
||||
id: number,
|
||||
updates: Partial<Staff>
|
||||
): Promise<Staff | undefined> {
|
||||
const updatedStaff = await db.staff.update({
|
||||
where: { id },
|
||||
data: updates,
|
||||
});
|
||||
return updatedStaff ?? undefined;
|
||||
},
|
||||
|
||||
async deleteStaff(id: number): Promise<boolean> {
|
||||
try {
|
||||
await db.staff.delete({ where: { id } });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error deleting staff:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
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 } });
|
||||
},
|
||||
};
|
||||
53
apps/Backend/src/storage/users-storage.ts
Normal file
53
apps/Backend/src/storage/users-storage.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { InsertUser, User } from "@repo/db/types";
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
|
||||
export interface IUsersStorage {
|
||||
// User methods
|
||||
getUser(id: number): Promise<User | undefined>;
|
||||
getUsers(limit: number, offset: number): Promise<User[]>;
|
||||
getUserByUsername(username: string): Promise<User | undefined>;
|
||||
createUser(user: InsertUser): Promise<User>;
|
||||
updateUser(id: number, updates: Partial<User>): Promise<User | undefined>;
|
||||
deleteUser(id: number): Promise<boolean>;
|
||||
}
|
||||
|
||||
export const usersStorage: IUsersStorage = {
|
||||
// User methods
|
||||
async getUser(id: number): Promise<User | undefined> {
|
||||
const user = await db.user.findUnique({ where: { id } });
|
||||
return user ?? undefined;
|
||||
},
|
||||
|
||||
async getUsers(limit: number, offset: number): Promise<User[]> {
|
||||
return await db.user.findMany({ skip: offset, take: limit });
|
||||
},
|
||||
|
||||
async getUserByUsername(username: string): Promise<User | undefined> {
|
||||
const user = await db.user.findUnique({ where: { username } });
|
||||
return user ?? undefined;
|
||||
},
|
||||
|
||||
async createUser(user: InsertUser): Promise<User> {
|
||||
return await db.user.create({ data: user as User });
|
||||
},
|
||||
|
||||
async updateUser(
|
||||
id: number,
|
||||
updates: Partial<User>
|
||||
): Promise<User | undefined> {
|
||||
try {
|
||||
return await db.user.update({ where: { id }, data: updates });
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteUser(id: number): Promise<boolean> {
|
||||
try {
|
||||
await db.user.delete({ where: { id } });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user