Adding TDS Calculation in ViewExpense.

This commit is contained in:
Kartik Sharma 2025-11-17 10:31:41 +05:30
parent a21bec943d
commit b568188a3e
3 changed files with 112 additions and 77 deletions

View File

@ -10,6 +10,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema"; import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema";
import { useExpenseContext } from "../../pages/Expense/ExpensePage"; import { useExpenseContext } from "../../pages/Expense/ExpensePage";
import { import {
calculateTDSPercentage,
formatCurrency, formatCurrency,
formatFigure, formatFigure,
getColorNameFromHex, getColorNameFromHex,
@ -54,12 +55,22 @@ const ViewExpense = ({ ExpenseId }) => {
setValue, setValue,
reset, reset,
control, control,
watch,
formState: { errors }, formState: { errors },
} = useForm({ } = useForm({
resolver: zodResolver(ActionSchema), resolver: zodResolver(ActionSchema),
defaultValues: defaultActionValues, defaultValues: defaultActionValues,
}); });
const baseAmount = Number(watch("baseAmount")) || 0;
const taxAmount = Number(watch("taxAmount")) || 0;
const tdsPercentage = Number(watch("tdsPercentage")) || 0;
const { grossAmount, tdsAmount, netPayable } = useMemo(() => {
return calculateTDSPercentage(baseAmount, taxAmount, tdsPercentage);
}, [baseAmount, taxAmount, tdsPercentage]);
const userPermissions = useSelector( const userPermissions = useSelector(
(state) => state?.globalVariables?.loginUser?.featurePermissions || [] (state) => state?.globalVariables?.loginUser?.featurePermissions || []
); );
@ -132,9 +143,8 @@ const ViewExpense = ({ ExpenseId }) => {
<span>{data?.expenseUId}</span> <span>{data?.expenseUId}</span>
</div>{" "} </div>{" "}
<span <span
className={`badge bg-label-${ className={`badge bg-label-${getColorNameFromHex(data?.status?.color) || "secondary"
getColorNameFromHex(data?.status?.color) || "secondary" }`}
}`}
t t
> >
{data?.status?.name} {data?.status?.name}
@ -142,7 +152,7 @@ const ViewExpense = ({ ExpenseId }) => {
</div> </div>
{/* Row 1 */} {/* Row 1 */}
<div className="row text-start"> <div className="row text-start">
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold text-start"
@ -264,28 +274,28 @@ const ViewExpense = ({ ExpenseId }) => {
</div> */} </div> */}
{/* Row 5 */} {/* Row 5 */}
<div className="row text-start">
<div className="row text-start">
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Created At : Created At :
</label> </label>
</div> </div>
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<small className="text-muted"> <small className="text-muted">
{formatUTCToLocalTime(data?.createdAt, true)} {formatUTCToLocalTime(data?.createdAt, true)}
</small> </small>
</div>
</div> </div>
</div>
{/* Created & Paid By */} {/* Created & Paid By */}
{data.createdBy && ( {data.createdBy && (
@ -307,9 +317,8 @@ const ViewExpense = ({ ExpenseId }) => {
lastName={data.createdBy?.lastName} lastName={data.createdBy?.lastName}
/> />
<span className="text-muted"> <span className="text-muted">
{`${data.createdBy?.firstName ?? ""} ${ {`${data.createdBy?.firstName ?? ""} ${data.createdBy?.lastName ?? ""
data.createdBy?.lastName ?? "" }`.trim() || "N/A"}
}`.trim() || "N/A"}
</span> </span>
</div> </div>
</div> </div>
@ -337,31 +346,30 @@ const ViewExpense = ({ ExpenseId }) => {
</span> </span>
</div> </div>
</div> */} </div> */}
<div className="row text-start"> <div className="row text-start">
<div className="col-md-6 text-start mb-3"> <div className="col-md-6 text-start mb-3">
<label <label
className="form-label me-2 mb-0 fw-semibold" className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Paid By : Paid By :
</label> </label>
</div> </div>
<div className="col-md-6 text-start mb-3"> <div className="col-md-6 text-start mb-3">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<Avatar <Avatar
size="xs" size="xs"
classAvatar="m-0 me-1" classAvatar="m-0 me-1"
firstName={data.paidBy?.firstName} firstName={data.paidBy?.firstName}
lastName={data.paidBy?.lastName} lastName={data.paidBy?.lastName}
/> />
<span className="text-muted"> <span className="text-muted">
{`${data.paidBy?.firstName ?? ""} ${ {`${data.paidBy?.firstName ?? ""} ${data.paidBy?.lastName ?? ""
data.paidBy?.lastName ?? ""
}`.trim() || "N/A"} }`.trim() || "N/A"}
</span> </span>
</div>
</div> </div>
</div> </div>
</div>
{/* Description */} {/* Description */}
<div className="col-12 text-start mb-2"> <div className="col-12 text-start mb-2">
@ -493,19 +501,7 @@ const ViewExpense = ({ ExpenseId }) => {
projectId={null} projectId={null}
/> />
</div> </div>
<div className="col-12 col-md-6 text-start">
<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"> <div className="col-12 col-md-6 text-start">
<Label className="form-label" required> <Label className="form-label" required>
Base Amount Base Amount
@ -536,28 +532,53 @@ const ViewExpense = ({ ExpenseId }) => {
</small> </small>
)} )}
</div> </div>
<div className="col-12 col-md-6 text-start">
<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 d-flex align-items-center gap-4 mb-2 mt-1">
<div>
<span className="fw-semibold">TDS Amount: </span>
<span className="badge bg-label-secondary">{tdsAmount.toFixed(2)}</span>
</div>
<div>
<span className="fw-semibold">Net Payable: </span>
<span className="badge bg-label-secondary">{netPayable.toFixed(2)}</span>
</div>
</div>
</div> </div>
)} )}
<div className="col-12 mb-3 text-start mt-1"> <div className="col-12 mb-3 text-start mt-1">
{((nextStatusWithPermission.length > 0 && {((nextStatusWithPermission.length > 0 &&
!IsRejectedExpense) || !IsRejectedExpense) ||
(IsRejectedExpense && isCreatedBy)) && ( (IsRejectedExpense && isCreatedBy)) && (
<> <>
<Label className="form-label me-2 mb-0" required> <Label className="form-label me-2 mb-0" required>
Comment Comment
</Label> </Label>
<textarea <textarea
className="form-control form-control-sm" className="form-control form-control-sm"
{...register("comment")} {...register("comment")}
rows="2" rows="2"
/> />
{errors.comment && ( {errors.comment && (
<small className="danger-text"> <small className="danger-text">
{errors.comment.message} {errors.comment.message}
</small> </small>
)} )}
</> </>
)} )}
{nextStatusWithPermission?.length > 0 && {nextStatusWithPermission?.length > 0 &&
(!IsRejectedExpense || isCreatedBy) && ( (!IsRejectedExpense || isCreatedBy) && (

View File

@ -24,7 +24,7 @@ import { useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { usePaymentRequestContext } from "../../pages/PaymentRequest/PaymentRequestPage"; import { usePaymentRequestContext } from "../../pages/PaymentRequest/PaymentRequestPage";
import { localToUtc } from "../../utils/appUtils"; import { calculateTDSPercentage, localToUtc } from "../../utils/appUtils";
import { usePaymentMode } from "../../hooks/masterHook/useMaster"; import { usePaymentMode } from "../../hooks/masterHook/useMaster";
import Filelist from "../Expenses/Filelist"; import Filelist from "../Expenses/Filelist";
@ -69,15 +69,10 @@ const ActionPaymentRequest = ({ requestId }) => {
const taxAmount = watch("taxAmount") || 0; const taxAmount = watch("taxAmount") || 0;
const tdsPercentage = watch("tdsPercentage") || 0; const tdsPercentage = watch("tdsPercentage") || 0;
const grossAmount = baseAmount + taxAmount;
const tdsAmount = useMemo(() => (baseAmount * tdsPercentage) / 100, [
baseAmount,
tdsPercentage,
]);
const netPayable = useMemo(() => grossAmount - tdsAmount, [grossAmount, tdsAmount]);
const { grossAmount, tdsAmount, netPayable } = useMemo(() => {
return calculateTDSPercentage(baseAmount, taxAmount, tdsPercentage);
}, [baseAmount, taxAmount, tdsPercentage]);
const userPermissions = useSelector( const userPermissions = useSelector(
(state) => state?.globalVariables?.loginUser?.featurePermissions || [] (state) => state?.globalVariables?.loginUser?.featurePermissions || []
@ -428,6 +423,7 @@ const ActionPaymentRequest = ({ requestId }) => {
<span className="badge bg-label-secondary">{netPayable.toFixed(2)}</span> <span className="badge bg-label-secondary">{netPayable.toFixed(2)}</span>
</div> </div>
</div> </div>
</div> </div>
)} )}

View File

@ -228,3 +228,21 @@ export function daysLeft(startDate, dueDate) {
return { days, color }; return { days, color };
} }
export function calculateTDSPercentage(baseAmount = 0, taxAmount = 0, tdsPercentage = 0) {
baseAmount = Number(baseAmount) || 0;
taxAmount = Number(taxAmount) || 0;
tdsPercentage = Number(tdsPercentage) || 0;
const grossAmount = baseAmount + taxAmount;
const tdsAmount = (baseAmount * tdsPercentage) / 100;
const netPayable = grossAmount - tdsAmount;
return {
grossAmount,
tdsAmount,
netPayable,
};
}