fix(types fixed of status, method)
This commit is contained in:
@@ -8,6 +8,9 @@ import {
|
|||||||
NewTransactionPayload,
|
NewTransactionPayload,
|
||||||
newTransactionPayloadSchema,
|
newTransactionPayloadSchema,
|
||||||
paymentMethodOptions,
|
paymentMethodOptions,
|
||||||
|
PaymentStatus,
|
||||||
|
paymentStatusOptions,
|
||||||
|
claimStatusOptions,
|
||||||
} from "@repo/db/types";
|
} from "@repo/db/types";
|
||||||
import { prisma } from "@repo/db/client";
|
import { prisma } from "@repo/db/client";
|
||||||
import { PaymentStatusSchema } from "@repo/db/types";
|
import { PaymentStatusSchema } from "@repo/db/types";
|
||||||
@@ -283,7 +286,7 @@ router.put(
|
|||||||
serviceLineId: line.id,
|
serviceLineId: line.id,
|
||||||
paidAmount: line.totalDue.toNumber(),
|
paidAmount: line.totalDue.toNumber(),
|
||||||
adjustedAmount: 0,
|
adjustedAmount: 0,
|
||||||
method: paymentMethodOptions[1],
|
method: paymentMethodOptions.CHECK,
|
||||||
receivedDate: new Date(),
|
receivedDate: new Date(),
|
||||||
notes: "Full claim payment",
|
notes: "Full claim payment",
|
||||||
}));
|
}));
|
||||||
@@ -338,7 +341,7 @@ router.put(
|
|||||||
serviceLineId: line.id,
|
serviceLineId: line.id,
|
||||||
paidAmount: line.totalPaid.negated().toNumber(), // negative to undo
|
paidAmount: line.totalPaid.negated().toNumber(), // negative to undo
|
||||||
adjustedAmount: line.totalAdjusted.negated().toNumber(),
|
adjustedAmount: line.totalAdjusted.negated().toNumber(),
|
||||||
method: paymentMethodOptions[4],
|
method: paymentMethodOptions.OTHER,
|
||||||
receivedDate: new Date(),
|
receivedDate: new Date(),
|
||||||
notes: "Reverted full claim",
|
notes: "Reverted full claim",
|
||||||
}));
|
}));
|
||||||
@@ -374,18 +377,51 @@ router.patch(
|
|||||||
|
|
||||||
const paymentId = parseIntOrError(req.params.id, "Payment ID");
|
const paymentId = parseIntOrError(req.params.id, "Payment ID");
|
||||||
|
|
||||||
const status = PaymentStatusSchema.parse(req.body.data.status);
|
// Parse & coerce to PaymentStatus enum
|
||||||
|
const rawStatus = PaymentStatusSchema.parse(req.body.data.status);
|
||||||
|
if (
|
||||||
|
!Object.values(paymentStatusOptions).includes(
|
||||||
|
rawStatus as PaymentStatus
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return res.status(400).json({ message: "Invalid payment status" });
|
||||||
|
}
|
||||||
|
const status = rawStatus as PaymentStatus;
|
||||||
|
|
||||||
const updatedPayment = await prisma.payment.update({
|
// Load existing payment
|
||||||
|
const existingPayment = await storage.getPayment(paymentId);
|
||||||
|
if (!existingPayment) {
|
||||||
|
return res.status(404).json({ message: "Payment not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// If changing to VOID and linked to a claim -> update both atomically
|
||||||
|
if (status === paymentStatusOptions.VOID && existingPayment.claimId) {
|
||||||
|
const [updatedPayment, updatedClaim] = await prisma.$transaction([
|
||||||
|
prisma.payment.update({
|
||||||
where: { id: paymentId },
|
where: { id: paymentId },
|
||||||
data: { status, updatedById: userId },
|
data: { status, updatedById: userId },
|
||||||
});
|
}),
|
||||||
|
prisma.claim.update({
|
||||||
|
where: { id: existingPayment.claimId },
|
||||||
|
data: { status: claimStatusOptions.VOID },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
res.json(updatedPayment);
|
return res.json(updatedPayment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise just update payment (use storage helper)
|
||||||
|
const updatedPayment = await storage.updatePaymentStatus(
|
||||||
|
paymentId,
|
||||||
|
{ status } as any,
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.json(updatedPayment);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const message =
|
const message =
|
||||||
err instanceof Error ? err.message : "Failed to update payment status";
|
err instanceof Error ? err.message : "Failed to update payment status";
|
||||||
res.status(500).json({ message });
|
return res.status(500).json({ message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Claim,
|
Claim,
|
||||||
|
ClaimStatus,
|
||||||
ClaimWithServiceLines,
|
ClaimWithServiceLines,
|
||||||
InsertClaim,
|
InsertClaim,
|
||||||
UpdateClaim,
|
UpdateClaim,
|
||||||
@@ -13,13 +14,13 @@ export interface IStorage {
|
|||||||
limit: number,
|
limit: number,
|
||||||
offset: number
|
offset: number
|
||||||
): Promise<ClaimWithServiceLines[]>;
|
): Promise<ClaimWithServiceLines[]>;
|
||||||
|
|
||||||
getTotalClaimCountByPatient(patientId: number): Promise<number>;
|
getTotalClaimCountByPatient(patientId: number): Promise<number>;
|
||||||
getClaimsByAppointmentId(appointmentId: number): Promise<Claim[]>;
|
getClaimsByAppointmentId(appointmentId: number): Promise<Claim[]>;
|
||||||
getRecentClaims(limit: number, offset: number): Promise<Claim[]>;
|
getRecentClaims(limit: number, offset: number): Promise<Claim[]>;
|
||||||
getTotalClaimCount(): Promise<number>;
|
getTotalClaimCount(): Promise<number>;
|
||||||
createClaim(claim: InsertClaim): Promise<Claim>;
|
createClaim(claim: InsertClaim): Promise<Claim>;
|
||||||
updateClaim(id: number, updates: UpdateClaim): Promise<Claim>;
|
updateClaim(id: number, updates: UpdateClaim): Promise<Claim>;
|
||||||
|
updateClaimStatus(id: number, status: ClaimStatus): Promise<Claim>;
|
||||||
deleteClaim(id: number): Promise<void>;
|
deleteClaim(id: number): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +89,18 @@ export const claimsStorage: IStorage = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async updateClaimStatus(id: number, status: ClaimStatus): Promise<Claim> {
|
||||||
|
const existing = await db.claim.findUnique({ where: { id } });
|
||||||
|
if (!existing) {
|
||||||
|
throw new Error("Claim not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.claim.update({
|
||||||
|
where: { id },
|
||||||
|
data: { status },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
async deleteClaim(id: number): Promise<void> {
|
async deleteClaim(id: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await db.claim.delete({ where: { id } });
|
await db.claim.delete({ where: { id } });
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ export interface IStorage {
|
|||||||
getPayment(id: number): Promise<Payment | undefined>;
|
getPayment(id: number): Promise<Payment | undefined>;
|
||||||
createPayment(data: InsertPayment): Promise<Payment>;
|
createPayment(data: InsertPayment): Promise<Payment>;
|
||||||
updatePayment(id: number, updates: UpdatePayment): Promise<Payment>;
|
updatePayment(id: number, updates: UpdatePayment): Promise<Payment>;
|
||||||
|
updatePaymentStatus(
|
||||||
|
id: number,
|
||||||
|
updates: UpdatePayment,
|
||||||
|
updatedById?: number
|
||||||
|
): Promise<Payment>;
|
||||||
deletePayment(id: number, userId: number): Promise<void>;
|
deletePayment(id: number, userId: number): Promise<void>;
|
||||||
getPaymentById(id: number): Promise<PaymentWithExtras | null>;
|
getPaymentById(id: number): Promise<PaymentWithExtras | null>;
|
||||||
getRecentPaymentsByPatientId(
|
getRecentPaymentsByPatientId(
|
||||||
@@ -51,6 +56,25 @@ export const paymentsStorage: IStorage = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async updatePaymentStatus(
|
||||||
|
id: number,
|
||||||
|
updates: UpdatePayment,
|
||||||
|
updatedById?: number
|
||||||
|
): Promise<Payment> {
|
||||||
|
const existing = await db.payment.findFirst({ where: { id } });
|
||||||
|
if (!existing) {
|
||||||
|
throw new Error("Payment not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: any = { ...updates };
|
||||||
|
if (typeof updatedById === "number") data.updatedById = updatedById;
|
||||||
|
|
||||||
|
return db.payment.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
async deletePayment(id: number, userId: number): Promise<void> {
|
async deletePayment(id: number, userId: number): Promise<void> {
|
||||||
const existing = await db.payment.findFirst({ where: { id, userId } });
|
const existing = await db.payment.findFirst({ where: { id, userId } });
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ import {
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
paymentStatusOptions,
|
|
||||||
PaymentMethod,
|
PaymentMethod,
|
||||||
paymentMethodOptions,
|
paymentMethodOptions,
|
||||||
PaymentWithExtras,
|
PaymentWithExtras,
|
||||||
NewTransactionPayload,
|
NewTransactionPayload,
|
||||||
|
paymentStatusArray,
|
||||||
|
paymentMethodArray,
|
||||||
} from "@repo/db/types";
|
} from "@repo/db/types";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@@ -90,7 +91,7 @@ export default function PaymentEditModal({
|
|||||||
transactionId: "",
|
transactionId: "",
|
||||||
paidAmount: 0,
|
paidAmount: 0,
|
||||||
adjustedAmount: 0,
|
adjustedAmount: 0,
|
||||||
method: paymentMethodOptions[1] as PaymentMethod,
|
method: paymentMethodOptions.CHECK as PaymentMethod,
|
||||||
receivedDate: formatLocalDate(new Date()),
|
receivedDate: formatLocalDate(new Date()),
|
||||||
payerName: "",
|
payerName: "",
|
||||||
notes: "",
|
notes: "",
|
||||||
@@ -298,7 +299,7 @@ export default function PaymentEditModal({
|
|||||||
transactionId: "",
|
transactionId: "",
|
||||||
paidAmount: Number(line.totalDue) > 0 ? Number(line.totalDue) : 0,
|
paidAmount: Number(line.totalDue) > 0 ? Number(line.totalDue) : 0,
|
||||||
adjustedAmount: 0,
|
adjustedAmount: 0,
|
||||||
method: paymentMethodOptions[1] as PaymentMethod,
|
method: paymentMethodOptions.CHECK as PaymentMethod,
|
||||||
receivedDate: formatLocalDate(new Date()),
|
receivedDate: formatLocalDate(new Date()),
|
||||||
payerName: "",
|
payerName: "",
|
||||||
notes: "",
|
notes: "",
|
||||||
@@ -431,7 +432,7 @@ export default function PaymentEditModal({
|
|||||||
serviceLineId: line.id,
|
serviceLineId: line.id,
|
||||||
paidAmount: dueAmount,
|
paidAmount: dueAmount,
|
||||||
adjustedAmount: 0,
|
adjustedAmount: 0,
|
||||||
method: paymentMethodOptions[1] as PaymentMethod, // Maybe make dynamic later
|
method: paymentMethodOptions.CHECK as PaymentMethod, // Maybe make dynamic later
|
||||||
receivedDate: new Date(),
|
receivedDate: new Date(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -565,7 +566,7 @@ export default function PaymentEditModal({
|
|||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{paymentStatusOptions.map((status) => (
|
{paymentStatusArray.map((status) => (
|
||||||
<SelectItem key={status} value={status}>
|
<SelectItem key={status} value={status}>
|
||||||
{status}
|
{status}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
@@ -727,7 +728,7 @@ export default function PaymentEditModal({
|
|||||||
<SelectValue placeholder="Select a payment method" />
|
<SelectValue placeholder="Select a payment method" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{paymentMethodOptions.map((methodOption) => (
|
{paymentMethodArray.map((methodOption) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={methodOption}
|
key={methodOption}
|
||||||
value={methodOption}
|
value={methodOption}
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ enum ClaimStatus {
|
|||||||
APPROVED
|
APPROVED
|
||||||
CANCELLED
|
CANCELLED
|
||||||
REVIEW
|
REVIEW
|
||||||
|
VOID
|
||||||
}
|
}
|
||||||
|
|
||||||
model ServiceLine {
|
model ServiceLine {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Decimal } from "decimal.js";
|
import { Decimal } from "decimal.js";
|
||||||
import { Staff } from "@repo/db/types";
|
import { Staff } from "@repo/db/types";
|
||||||
|
import { makeEnumOptions } from "../utils";
|
||||||
|
|
||||||
export const insertClaimSchema = (
|
export const insertClaimSchema = (
|
||||||
ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
@@ -36,6 +37,14 @@ export const ExtendedClaimSchema = (
|
|||||||
|
|
||||||
export type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
export type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
||||||
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
|
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
|
||||||
|
export const claimStatusOptions =
|
||||||
|
makeEnumOptions<
|
||||||
|
typeof ClaimStatusSchema extends z.ZodTypeAny
|
||||||
|
? z.infer<typeof ClaimStatusSchema>
|
||||||
|
: string
|
||||||
|
>(ClaimStatusSchema);
|
||||||
|
export type ClaimStatusOptions =
|
||||||
|
(typeof claimStatusOptions)[keyof typeof claimStatusOptions];
|
||||||
|
|
||||||
export type ClaimFileMeta = {
|
export type ClaimFileMeta = {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "@repo/db/usedSchemas";
|
} from "@repo/db/usedSchemas";
|
||||||
import { Prisma } from "@repo/db/generated/prisma";
|
import { Prisma } from "@repo/db/generated/prisma";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { makeEnumOptions } from "../utils";
|
||||||
|
|
||||||
// ========== BASIC TYPES ==========
|
// ========== BASIC TYPES ==========
|
||||||
|
|
||||||
@@ -31,8 +32,29 @@ export type PaymentStatus = z.infer<typeof PaymentStatusSchema>;
|
|||||||
export type PaymentMethod = z.infer<typeof PaymentMethodSchema>;
|
export type PaymentMethod = z.infer<typeof PaymentMethodSchema>;
|
||||||
|
|
||||||
// ✅ Runtime arrays (used in code logic / map / select options)
|
// ✅ Runtime arrays (used in code logic / map / select options)
|
||||||
export const paymentStatusOptions = PaymentStatusSchema.options;
|
export const paymentStatusOptions =
|
||||||
export const paymentMethodOptions = PaymentMethodSchema.options;
|
makeEnumOptions<
|
||||||
|
typeof PaymentStatusSchema extends z.ZodTypeAny
|
||||||
|
? z.infer<typeof PaymentStatusSchema>
|
||||||
|
: string
|
||||||
|
>(PaymentStatusSchema);
|
||||||
|
export type PaymentStatusOptions =
|
||||||
|
(typeof paymentStatusOptions)[keyof typeof paymentStatusOptions];
|
||||||
|
export const paymentStatusArray = Object.values(
|
||||||
|
paymentStatusOptions
|
||||||
|
) as PaymentStatusOptions[];
|
||||||
|
|
||||||
|
export const paymentMethodOptions =
|
||||||
|
makeEnumOptions<
|
||||||
|
typeof PaymentMethodSchema extends z.ZodTypeAny
|
||||||
|
? z.infer<typeof PaymentMethodSchema>
|
||||||
|
: string
|
||||||
|
>(PaymentMethodSchema);
|
||||||
|
export type PaymentMethodOptions =
|
||||||
|
(typeof paymentMethodOptions)[keyof typeof paymentMethodOptions];
|
||||||
|
export const paymentMethodArray = Object.values(
|
||||||
|
paymentMethodOptions
|
||||||
|
) as PaymentMethodOptions[];
|
||||||
|
|
||||||
// ========== INPUT TYPES ==========
|
// ========== INPUT TYPES ==========
|
||||||
|
|
||||||
|
|||||||
35
packages/db/utils/index.ts
Normal file
35
packages/db/utils/index.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Extract enum values from a Zod enum or native enum schema.
|
||||||
|
* Supports z.enum([...]) and z.nativeEnum(SomeTsEnum).
|
||||||
|
*/
|
||||||
|
export function extractEnumValues<T extends string | number>(schema: any): T[] {
|
||||||
|
// z.enum([...]) => schema.options exists
|
||||||
|
if (Array.isArray(schema?.options)) {
|
||||||
|
return schema.options as T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// z.nativeEnum(SomeEnum) => schema._def?.values may exist or enum is in schema._def?.enum
|
||||||
|
if (Array.isArray(schema?._def?.values)) {
|
||||||
|
return schema._def.values as T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema?._def?.enum) {
|
||||||
|
// enum object -> values
|
||||||
|
return Object.values(schema._def.enum) as T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unsupported Zod schema type for enum extraction");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a runtime map: { VAL: "VAL", ... } with proper typing
|
||||||
|
* so callers can import paymentStatusOptions.VOID etc.
|
||||||
|
*/
|
||||||
|
export function makeEnumOptions<T extends string | number>(schema: any) {
|
||||||
|
const values = extractEnumValues<T>(schema);
|
||||||
|
const map = {} as Record<string, T>;
|
||||||
|
values.forEach((v) => {
|
||||||
|
map[String(v)] = v;
|
||||||
|
});
|
||||||
|
return map as { [K in T & (string | number)]: K };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user