added advance payment api

This commit is contained in:
pramod.mahajan 2025-11-05 20:10:19 +05:30
parent 9b091840d6
commit bac9f25df1
8 changed files with 195 additions and 157 deletions

View File

@ -1,82 +1,31 @@
import React from "react";
import { useExpenseTransactions } from "../../hooks/useExpense";
import Error from "../common/Error";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
const AdvancePaymentList = ({ employeeId }) => {
const {data,isError, isLoading,isFetching,error} = useExpenseTransactions(employeeId)
const record = {
openingBalance: 25000.0,
rows: [
{ id: 1, description: "Opening Balance", credit: 0.0, debit: 0.0 },
{
id: 2,
description: "Sales Invoice #INV-001",
credit: 15000.0,
debit: 0.0,
},
{
id: 3,
description: "Sales Invoice #INV-002",
credit: 12000.0,
debit: 0.0,
},
{
id: 4,
description: "Purchase - Raw Materials",
credit: 0.0,
debit: 8000.0,
},
{ id: 5, description: "Office Rent - June", credit: 0.0, debit: 5000.0 },
{
id: 6,
description: "Client Payment Received (Bank)",
credit: 10000.0,
debit: 0.0,
},
{
id: 7,
description: "Supplier Payment - ABC Corp",
credit: 0.0,
debit: 6000.0,
},
{ id: 8, description: "Interest Income", credit: 750.0, debit: 0.0 },
{ id: 9, description: "Bank Charges", credit: 0.0, debit: 250.0 },
{
id: 10,
description: "Utilities - Electricity",
credit: 0.0,
debit: 1800.0,
},
{
id: 11,
description: "Service Income - Project Alpha",
credit: 5000.0,
debit: 0.0,
},
{ id: 12, description: "Misc Expense", credit: 0.0, debit: 450.0 },
{
id: 13,
description: "Adjustment - Prior Year",
credit: 0.0,
debit: 500.0,
},
{ id: 14, description: "Commission Earned", credit: 1200.0, debit: 0.0 },
{
id: 15,
description: "Employee Salary - June",
credit: 0.0,
debit: 9500.0,
},
{ id: 16, description: "GST Refund", credit: 2000.0, debit: 0.0 },
{ id: 17, description: "Insurance Premium", credit: 0.0, debit: 3000.0 },
{ id: 18, description: "Late Fee Collected", credit: 350.0, debit: 0.0 },
{ id: 19, description: "Maintenance Expense", credit: 0.0, debit: 600.0 },
{ id: 20, description: "Closing Balance", credit: 0.0, debit: 0.0 },
],
};
let currentBalance = record.openingBalance;
const rowsWithBalance = record.rows.map((r) => {
currentBalance += r.credit - r.debit;
return { ...r, balance: currentBalance };
const { data, isError, isLoading, error } =
useExpenseTransactions(employeeId);
const records = data?.json || [];
let currentBalance = 0;
const rowsWithBalance = data?.map((r) => {
const isCredit = r.amount > 0;
const credit = isCredit ? r.amount : 0;
const debit = !isCredit ? Math.abs(r.amount) : 0;
currentBalance += credit - debit;
return {
id: r.id,
description: r.title,
projectName: r.project?.name,
createdAt: r.createdAt,
credit,
debit,
balance: currentBalance,
};
});
const columns = [
@ -110,10 +59,16 @@ const AdvancePaymentList = ({ employeeId }) => {
},
];
if (isLoading) return <div>Loading...</div>;
if (isError){
return <div>{error.status === 404 ? "No advance payment transactions found." : <Error error={error}/>}</div>
};
return (
<div className="table-responsive ">
<table className="table align-middle">
<thead className="table_header_border ">
<div className="row ">
<div className="table-responsive">
{employeeId ? ( <table className="table align-middle">
<thead className="table_header_border">
<tr>
{columns.map((col) => (
<th key={col.key} className={col.align}>
@ -123,28 +78,32 @@ const AdvancePaymentList = ({ employeeId }) => {
</tr>
</thead>
<tbody>
{rowsWithBalance.map((row) => (
{rowsWithBalance?.map((row) => (
<tr key={row.id}>
{columns.map((col) => (
<td key={col.key} className={`${col.align} p-6`}>
{col.key === "balance" ||
col.key === "credit" ||
col.key === "debit"
? row[col.key].toLocaleString("en-IN", {
style: "currency",
currency: "INR",
})
: (<div className="d-flex flex-column">
<small>{new Date().toISOString()}</small>
<small>Projec Name</small>
<small>{row[col.key]}</small>
</div>)}
<td key={col.key} className={`${col.align} p-2`}>
{["balance", "credit", "debit"].includes(col.key) ? (
row[col.key].toLocaleString("en-IN", {
style: "currency",
currency: "INR",
})
) : (
<div className="d-flex flex-column text-start ">
<small className="text-muted">
{formatUTCToLocalTime(row.createdAt)}
</small>
<small className="fw-semibold text-dark">
{row.projectName}
</small>
<small>{row.description}</small>
</div>
)}
</td>
))}
</tr>
))}
</tbody>
<tfoot className="table-secondary fw-bold ">
<tfoot className="table-secondary fw-bold">
<tr>
<td className="text-start p-3">Final Balance</td>
<td className="text-end" colSpan="3">
@ -155,7 +114,12 @@ const AdvancePaymentList = ({ employeeId }) => {
</td>
</tr>
</tfoot>
</table>
</table>):(
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}>
<p className="text-muted m-0">Please Select Employee</p>
</div>
)}
</div>
</div>
);
};

View File

@ -167,9 +167,9 @@ export const defaultActionValues = {
reimburseTransactionId: null,
reimburseDate: null,
reimburseById: null,
tdsPercentage: null,
tdsPercentage: 0,
baseAmount:null,
taxAmount: null,
taxAmount: 0,
};
export const SearchSchema = z.object({

View File

@ -93,6 +93,9 @@ export const PaymentRequestActionScheam = (
paidTransactionId: z.string().nullable().optional(),
paidAt: z.string().nullable().optional(),
paidById: z.string().nullable().optional(),
tdsPercentage: z.string().nullable().optional(),
baseAmount: z.string().nullable().optional(),
taxAmount: z.string().nullable().optional(),
})
.superRefine((data, ctx) => {
if (isTransaction) {
@ -110,7 +113,7 @@ export const PaymentRequestActionScheam = (
message: "Transacion Date is required",
});
}
if (!data.paidById) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
@ -118,6 +121,20 @@ export const PaymentRequestActionScheam = (
message: "Paid By is required",
});
}
if (!data.baseAmount) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["baseAmount"],
message: "Base Amount i required",
});
}
if (!data.taxAmount) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["taxAmount"],
message: "Tax is required",
});
}
}
});
};
@ -128,40 +145,38 @@ export const defaultPaymentRequestActionValues = {
paidTransactionId: null,
paidAt: null,
paidById: null,
tdsPercentage:"0",
baseAmount: null,
taxAmount:null,
};
export const RequestedExpenseSchema =
z.object({
paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
location: z.string().min(1, { message: "Location is required" }),
gstNumber: z.string().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" }),
})
export const RequestedExpenseSchema = z.object({
paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
location: z.string().min(1, { message: "Location is required" }),
gstNumber: z.string().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" }),
});
export const DefaultRequestedExpense = {
paymentModeId:"",
location:"",
gstNumber:"",
paymentModeId: "",
location: "",
gstNumber: "",
// amount:"",
billAttachments:[]
}
billAttachments: [],
};

View File

@ -20,7 +20,6 @@ const PaymentStatusLogs = ({ data }) => {
);
const timelineData = useMemo(() => {
console.log(logsToShow)
return logsToShow.map((log, index) => ({
id: index + 1,
title: log.nextStatus?.name || "Status Updated",

View File

@ -50,7 +50,8 @@ const ViewPaymentRequest = ({ requestId }) => {
const IsReview = useHasUserPermission(REVIEW_EXPENSE);
const [imageLoaded, setImageLoaded] = useState({});
const { setDocumentView, setModalSize,setVieRequest ,setIsExpenseGenerate} = usePaymentRequestContext();
const { setDocumentView, setModalSize, setVieRequest, setIsExpenseGenerate } =
usePaymentRequestContext();
const ActionSchema =
PaymentRequestActionScheam(IsPaymentProcess, data?.createdAt) ??
z.object({});
@ -118,14 +119,15 @@ const ViewPaymentRequest = ({ requestId }) => {
const handleImageLoad = (id) => {
setImageLoaded((prev) => ({ ...prev, [id]: true }));
};
const handleExpense = ()=>{
setIsExpenseGenerate({IsOpen:true,requestId:requestId})
setVieRequest({IsOpen:true,requestId:requestId})
}
const handleExpense = () => {
setIsExpenseGenerate({ IsOpen: true, requestId: requestId });
setVieRequest({ IsOpen: true, requestId: requestId });
};
return (
<form className="container px-3 py-2 py-md-0" onSubmit={handleSubmit(onSubmit)}>
<form
className="container px-3 py-2 py-md-0"
onSubmit={handleSubmit(onSubmit)}
>
<div className="col-12 mb-2 text-center ">
<h5 className="fw-semibold m-0">Payment Request Details</h5>
</div>
@ -201,7 +203,10 @@ const ViewPaymentRequest = ({ requestId }) => {
Amount :
</label>
<div className="text-muted">
{formatFigure(data?.amount,{type:"currency",currency : data?.currency?.currencyCode})}
{formatFigure(data?.amount, {
type: "currency",
currency: data?.currency?.currencyCode,
})}
</div>
</div>
</div>
@ -312,10 +317,14 @@ const ViewPaymentRequest = ({ requestId }) => {
</label>
<div className="d-flex flex-wrap gap-2">
{data?.attachments?.length > 0 ? (
<FilelistView files={data?.attachments} viewFile={setDocumentView}/>
):(<p className="m-0 text-secondary">No Attachment</p>)}
{data?.attachments?.length > 0 ? (
<FilelistView
files={data?.attachments}
viewFile={setDocumentView}
/>
) : (
<p className="m-0 text-secondary">No Attachment</p>
)}
</div>
</div>
@ -372,7 +381,7 @@ const ViewPaymentRequest = ({ requestId }) => {
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<div className="col-12 col-md-6 text-start mb-1">
<label className="form-label">Transaction Date </label>
<DatePicker
name="paidAt"
@ -394,6 +403,49 @@ const ViewPaymentRequest = ({ requestId }) => {
projectId={null}
/>
</div>
<div className="col-12 col-md-6 text-start mb-1">
<Label className="form-label">TDS Percentage</Label>
<input
type="text"
className="form-control form-control-sm"
{...register("tdsPercentage")}
/>
{errors.tdsPercentage && (
<small className="danger-text">
{errors.tdsPercentage.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start mb-1">
<Label className="form-label" required>
Base Amount
</Label>
<input
type="text"
className="form-control form-control-sm"
{...register("baseAmount")}
/>
{errors.baseAmount && (
<small className="danger-text">
{errors.baseAmount.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start mb-1">
<Label className="form-label" required>
Tax Amount
</Label>
<input
type="text"
className="form-control form-control-sm"
{...register("taxAmount")}
/>
{errors.taxAmount && (
<small className="danger-text">
{errors.taxAmount.message}
</small>
)}
</div>
</div>
)}
<div className="col-12 mb-3 text-start">
@ -441,10 +493,18 @@ const ViewPaymentRequest = ({ requestId }) => {
)}
</div>
</>
): !data?.isExpenseCreated ? (<div className="text-end flex-wrap gap-2 my-2 mt-3">
<button className="btn btn-sm btn-primary" onClick={handleExpense}>Make Expense</button>
</div>):(<></>)}
) : !data?.isExpenseCreated ? (
<div className="text-end flex-wrap gap-2 my-2 mt-3">
<button
className="btn btn-sm btn-primary"
onClick={handleExpense}
>
Make Expense
</button>
</div>
) : (
<></>
)}
</div>
</div>
<div className=" col-sm-12 my-md-0 border-top border-md-none col-md-5">

View File

@ -436,7 +436,6 @@ export const useExpenseTransactions = (employeeId)=>{
return useQuery({
queryKey:["transaction",employeeId],
queryFn:async()=> {
debugger
const resp = await ExpenseRepository.GetTranctionList(employeeId);
return resp.data
},
@ -466,4 +465,5 @@ export const useCreateRecurringExpense = (onSuccessCallBack) => {
);
},
});
};
};

View File

@ -23,16 +23,16 @@ const AdvancePaymentPage = () => {
{ label: "Advance Payment" },
]}
/>
<div className="card px-2 py-2 page-min-h">
<div className="card px-4 py-2 page-min-h ">
<div className="row">
<div className="col-12 ">
<div className="d-block w-max w-25 text-start" >
<Label>Search Employee</Label>
<div className="col-12 col-md-4">
<div className="d-block text-start" >
<EmployeeSearchInput
control={control}
name="employeeId"
projectId={null}
forAll={true}
placeholder={"Enter Employee Name"}
/>
</div>
</div>

View File

@ -49,11 +49,11 @@ const ExpenseRepository = {
CreateRecurringExpense: (data) => api.post("/api/Expense/recurring-payment/create", data),
//#endregion
//#region Advance Payment
GetTranctionList: () => api.get(`/get/transactions/${employeeId}`),
GetTranctionList: (employeeId)=>api.get(`/api/Expense/get/transactions/${employeeId}`),
//#endregion
};
export default ExpenseRepository;