160 lines
4.9 KiB
JavaScript

import { z } from "zod";
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = [
"application/pdf",
"image/png",
"image/jpg",
"image/jpeg",
];
export const ExpenseSchema = (expenseTypes) => {
return z
.object({
projectId: z.string().min(1, { message: "Project is required" }),
expensesTypeId: z
.string()
.min(1, { message: "Expense type is required" }),
paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
paidById: z.string().min(1, { message: "Employee name is required" }),
transactionDate: z
.string()
.min(1, { message: "Date is required" })
.refine(
(val) => {
const selected = new Date(val);
const today = new Date();
// Set both to midnight to avoid time-related issues
selected.setHours(0, 0, 0, 0);
today.setHours(0, 0, 0, 0);
return selected <= today;
},
{ message: "Future dates are not allowed" }
),
transactionId: z.string().optional(),
description: z.string().min(1, { message: "Description is required" }),
location: z.string().min(1, { message: "Location is required" }),
supplerName: z.string().min(1, { message: "Supplier name is required" }),
amount: z.coerce
.number({
invalid_type_error: "Amount is required and must be a number",
})
.min(1, "Amount must be Enter")
.refine((val) => /^\d+(\.\d{1,2})?$/.test(val.toString()), {
message: "Amount must have at most 2 decimal places",
}),
noOfPersons: z.coerce.number().optional(),
billAttachments: z
.array(
z.object({
fileName: z.string().min(1, { message: "Filename is required" }),
base64Data: z.string().nullable(),
contentType: z
.string()
.refine((val) => ALLOWED_TYPES.includes(val), {
message: "Only PDF, PNG, JPG, or JPEG files are allowed",
}),
documentId: z.string().optional(),
fileSize: z.number().max(MAX_FILE_SIZE, {
message: "File size must be less than or equal to 5MB",
}),
description: z.string().optional(),
isActive: z.boolean().default(true),
})
)
.nonempty({ message: "At least one file attachment is required" }),
reimburseTransactionId: z.string().optional(),
reimburseDate: z.string().optional(),
reimburseById: z.string().optional(),
})
.refine(
(data) => {
return (
!data.projectId || (data.paidById && data.paidById.trim() !== "")
);
},
{
message: "Please select who paid (employee)",
path: ["paidById"],
}
)
.superRefine((data, ctx) => {
const expenseType = expenseTypes.find((et) => et.id === data.expensesTypeId);
if (expenseType?.noOfPersonsRequired && (!data.noOfPersons || data.noOfPersons < 1)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "No. of Persons is required and must be at least 1",
path: ["noOfPersons"],
});
}
if (isEndProcess) {
if (!data.reimburseTransactionId || data.reimburseTransactionId.trim() === "") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Reimburse Transaction ID is required",
path: ["reimburseTransactionId"],
});
}
if (!data.reimburseDate) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Reimburse Date is required",
path: ["reimburseDate"],
});
}
if (!data.reimburseById) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Reimburse By is required",
path: ["reimburseById"],
});
}
}
});
};
export const defaultExpense = {
projectId: "",
expensesTypeId: "",
paymentModeId: "",
paidById: "",
transactionDate: "",
transactionId: "",
description: "",
location: "",
supplerName: "",
amount: "",
noOfPersons: "",
billAttachments: [],
};
export const ActionSchema = z.object({
comment: z.string().min(1, { message: "Please leave comment" }),
selectedStatus: z.string().min(1, { message: "Please select a status" }),
});
export const SearchSchema = z.object({
projectIds: z.array(z.string()).optional(),
statusIds: z.array(z.string()).optional(),
createdByIds: z.array(z.string()).optional(),
paidById: z.array(z.string()).optional(),
startDate: z.string().optional(),
endDate: z.string().optional(),
isTransactionDate: z.boolean().default(true),
});
export const defaultFilter = {
projectIds: [],
statusIds: [],
createdByIds: [],
paidById: [],
isTransactionDate: true,
startDate: null,
endDate: null,
};