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 React from "react";
import { useExpenseTransactions } from "../../hooks/useExpense"; import { useExpenseTransactions } from "../../hooks/useExpense";
import Error from "../common/Error";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
const AdvancePaymentList = ({ employeeId }) => { const AdvancePaymentList = ({ employeeId }) => {
const {data,isError, isLoading,isFetching,error} = useExpenseTransactions(employeeId) const { data, isError, isLoading, error } =
const record = { useExpenseTransactions(employeeId);
openingBalance: 25000.0,
rows: [ const records = data?.json || [];
{ id: 1, description: "Opening Balance", credit: 0.0, debit: 0.0 },
{ let currentBalance = 0;
id: 2,
description: "Sales Invoice #INV-001", const rowsWithBalance = data?.map((r) => {
credit: 15000.0, const isCredit = r.amount > 0;
debit: 0.0, const credit = isCredit ? r.amount : 0;
}, const debit = !isCredit ? Math.abs(r.amount) : 0;
{ currentBalance += credit - debit;
id: 3,
description: "Sales Invoice #INV-002", return {
credit: 12000.0, id: r.id,
debit: 0.0, description: r.title,
}, projectName: r.project?.name,
{ createdAt: r.createdAt,
id: 4, credit,
description: "Purchase - Raw Materials", debit,
credit: 0.0, balance: currentBalance,
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 columns = [ 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 ( return (
<div className="table-responsive "> <div className="row ">
<table className="table align-middle"> <div className="table-responsive">
<thead className="table_header_border "> {employeeId ? ( <table className="table align-middle">
<thead className="table_header_border">
<tr> <tr>
{columns.map((col) => ( {columns.map((col) => (
<th key={col.key} className={col.align}> <th key={col.key} className={col.align}>
@ -123,28 +78,32 @@ const AdvancePaymentList = ({ employeeId }) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{rowsWithBalance.map((row) => ( {rowsWithBalance?.map((row) => (
<tr key={row.id}> <tr key={row.id}>
{columns.map((col) => ( {columns.map((col) => (
<td key={col.key} className={`${col.align} p-6`}> <td key={col.key} className={`${col.align} p-2`}>
{col.key === "balance" || {["balance", "credit", "debit"].includes(col.key) ? (
col.key === "credit" || row[col.key].toLocaleString("en-IN", {
col.key === "debit" style: "currency",
? row[col.key].toLocaleString("en-IN", { currency: "INR",
style: "currency", })
currency: "INR", ) : (
}) <div className="d-flex flex-column text-start ">
: (<div className="d-flex flex-column"> <small className="text-muted">
<small>{new Date().toISOString()}</small> {formatUTCToLocalTime(row.createdAt)}
<small>Projec Name</small> </small>
<small>{row[col.key]}</small> <small className="fw-semibold text-dark">
</div>)} {row.projectName}
</small>
<small>{row.description}</small>
</div>
)}
</td> </td>
))} ))}
</tr> </tr>
))} ))}
</tbody> </tbody>
<tfoot className="table-secondary fw-bold "> <tfoot className="table-secondary fw-bold">
<tr> <tr>
<td className="text-start p-3">Final Balance</td> <td className="text-start p-3">Final Balance</td>
<td className="text-end" colSpan="3"> <td className="text-end" colSpan="3">
@ -155,7 +114,12 @@ const AdvancePaymentList = ({ employeeId }) => {
</td> </td>
</tr> </tr>
</tfoot> </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> </div>
); );
}; };

View File

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

View File

@ -93,6 +93,9 @@ export const PaymentRequestActionScheam = (
paidTransactionId: z.string().nullable().optional(), paidTransactionId: z.string().nullable().optional(),
paidAt: z.string().nullable().optional(), paidAt: z.string().nullable().optional(),
paidById: 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) => { .superRefine((data, ctx) => {
if (isTransaction) { if (isTransaction) {
@ -118,6 +121,20 @@ export const PaymentRequestActionScheam = (
message: "Paid By is required", 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, paidTransactionId: null,
paidAt: null, paidAt: null,
paidById: null, paidById: null,
tdsPercentage:"0",
baseAmount: null,
taxAmount:null,
}; };
export const RequestedExpenseSchema = z.object({
export const RequestedExpenseSchema = paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
location: z.string().min(1, { message: "Location is required" }),
z.object({ gstNumber: z.string().optional(),
paymentModeId: z.string().min(1, { message: "Payment mode is required" }), billAttachments: z
location: z.string().min(1, { message: "Location is required" }), .array(
gstNumber: z.string().optional(), z.object({
billAttachments: z fileName: z.string().min(1, { message: "Filename is required" }),
.array( base64Data: z.string().nullable(),
z.object({ contentType: z.string().refine((val) => ALLOWED_TYPES.includes(val), {
fileName: z.string().min(1, { message: "Filename is required" }), message: "Only PDF, PNG, JPG, or JPEG files are allowed",
base64Data: z.string().nullable(), }),
contentType: z documentId: z.string().optional(),
.string() fileSize: z.number().max(MAX_FILE_SIZE, {
.refine((val) => ALLOWED_TYPES.includes(val), { message: "File size must be less than or equal to 5MB",
message: "Only PDF, PNG, JPG, or JPEG files are allowed", }),
}), description: z.string().optional(),
documentId: z.string().optional(), isActive: z.boolean().default(true),
fileSize: z.number().max(MAX_FILE_SIZE, { })
message: "File size must be less than or equal to 5MB", )
}), .nonempty({ message: "At least one file attachment is required" }),
description: z.string().optional(), });
isActive: z.boolean().default(true),
})
)
.nonempty({ message: "At least one file attachment is required" }),
})
export const DefaultRequestedExpense = { export const DefaultRequestedExpense = {
paymentModeId:"", paymentModeId: "",
location:"", location: "",
gstNumber:"", gstNumber: "",
// amount:"", // amount:"",
billAttachments:[] billAttachments: [],
} };

View File

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

View File

@ -50,7 +50,8 @@ const ViewPaymentRequest = ({ requestId }) => {
const IsReview = useHasUserPermission(REVIEW_EXPENSE); const IsReview = useHasUserPermission(REVIEW_EXPENSE);
const [imageLoaded, setImageLoaded] = useState({}); const [imageLoaded, setImageLoaded] = useState({});
const { setDocumentView, setModalSize,setVieRequest ,setIsExpenseGenerate} = usePaymentRequestContext(); const { setDocumentView, setModalSize, setVieRequest, setIsExpenseGenerate } =
usePaymentRequestContext();
const ActionSchema = const ActionSchema =
PaymentRequestActionScheam(IsPaymentProcess, data?.createdAt) ?? PaymentRequestActionScheam(IsPaymentProcess, data?.createdAt) ??
z.object({}); z.object({});
@ -118,14 +119,15 @@ const ViewPaymentRequest = ({ requestId }) => {
const handleImageLoad = (id) => { const handleImageLoad = (id) => {
setImageLoaded((prev) => ({ ...prev, [id]: true })); setImageLoaded((prev) => ({ ...prev, [id]: true }));
}; };
const handleExpense = ()=>{ const handleExpense = () => {
setIsExpenseGenerate({ IsOpen: true, requestId: requestId });
setIsExpenseGenerate({IsOpen:true,requestId:requestId}) setVieRequest({ IsOpen: true, requestId: requestId });
setVieRequest({IsOpen:true,requestId:requestId}) };
}
return ( 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 "> <div className="col-12 mb-2 text-center ">
<h5 className="fw-semibold m-0">Payment Request Details</h5> <h5 className="fw-semibold m-0">Payment Request Details</h5>
</div> </div>
@ -201,7 +203,10 @@ const ViewPaymentRequest = ({ requestId }) => {
Amount : Amount :
</label> </label>
<div className="text-muted"> <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> </div>
</div> </div>
@ -312,10 +317,14 @@ const ViewPaymentRequest = ({ requestId }) => {
</label> </label>
<div className="d-flex flex-wrap gap-2"> <div className="d-flex flex-wrap gap-2">
{data?.attachments?.length > 0 ? (
{data?.attachments?.length > 0 ? ( <FilelistView
<FilelistView files={data?.attachments} viewFile={setDocumentView}/> files={data?.attachments}
):(<p className="m-0 text-secondary">No Attachment</p>)} viewFile={setDocumentView}
/>
) : (
<p className="m-0 text-secondary">No Attachment</p>
)}
</div> </div>
</div> </div>
@ -372,7 +381,7 @@ const ViewPaymentRequest = ({ requestId }) => {
</small> </small>
)} )}
</div> </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> <label className="form-label">Transaction Date </label>
<DatePicker <DatePicker
name="paidAt" name="paidAt"
@ -394,6 +403,49 @@ const ViewPaymentRequest = ({ requestId }) => {
projectId={null} projectId={null}
/> />
</div> </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>
)} )}
<div className="col-12 mb-3 text-start"> <div className="col-12 mb-3 text-start">
@ -441,10 +493,18 @@ const ViewPaymentRequest = ({ requestId }) => {
)} )}
</div> </div>
</> </>
): !data?.isExpenseCreated ? (<div className="text-end flex-wrap gap-2 my-2 mt-3"> ) : !data?.isExpenseCreated ? (
<button className="btn btn-sm btn-primary" onClick={handleExpense}>Make Expense</button> <div className="text-end flex-wrap gap-2 my-2 mt-3">
</div>):(<></>)} <button
className="btn btn-sm btn-primary"
onClick={handleExpense}
>
Make Expense
</button>
</div>
) : (
<></>
)}
</div> </div>
</div> </div>
<div className=" col-sm-12 my-md-0 border-top border-md-none col-md-5"> <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({ return useQuery({
queryKey:["transaction",employeeId], queryKey:["transaction",employeeId],
queryFn:async()=> { queryFn:async()=> {
debugger
const resp = await ExpenseRepository.GetTranctionList(employeeId); const resp = await ExpenseRepository.GetTranctionList(employeeId);
return resp.data return resp.data
}, },
@ -467,3 +466,4 @@ export const useCreateRecurringExpense = (onSuccessCallBack) => {
}, },
}); });
}; };

View File

@ -23,16 +23,16 @@ const AdvancePaymentPage = () => {
{ label: "Advance Payment" }, { 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="row">
<div className="col-12 "> <div className="col-12 col-md-4">
<div className="d-block w-max w-25 text-start" > <div className="d-block text-start" >
<Label>Search Employee</Label>
<EmployeeSearchInput <EmployeeSearchInput
control={control} control={control}
name="employeeId" name="employeeId"
projectId={null} projectId={null}
forAll={true} forAll={true}
placeholder={"Enter Employee Name"}
/> />
</div> </div>
</div> </div>

View File

@ -50,10 +50,10 @@ const ExpenseRepository = {
//#endregion //#endregion
//#region Advance Payment //#region Advance Payment
GetTranctionList: () => api.get(`/get/transactions/${employeeId}`), GetTranctionList: (employeeId)=>api.get(`/api/Expense/get/transactions/${employeeId}`),
//#endregion //#endregion
}; };
export default ExpenseRepository; export default ExpenseRepository;