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

View File

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

View File

@ -228,3 +228,21 @@ export function daysLeft(startDate, dueDate) {
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,
};
}