import axios from "axios";
import { Response } from "express";
import fs from "fs";
import fsPromises from "fs/promises";
import jwt from "jsonwebtoken";
import moment from "moment";
import CONSTANTS from "../config/constants";
import log from "../config/log";
import bedDB from "../models/beds.model";
import clientDB from "../models/client.model";
import documentDB from "../models/document.model";
import duesDB from "../models/dues.model";
import noticeDB from "../models/notice.model";
import occupancyDB from "../models/occupancy.model";
import otpDB from "../models/otp.model";
import propertyDB from "../models/property.model";
import requestDB from "../models/request.model";
import roomDB from "../models/room.model";
import tenantDB from "../models/tenant.model";
import notificationPrefDB from "../models/notificationPref.model";
import CustomRequest from "../types/requestType";
import sendSMS from "../utils/sendSMS";
// @ts-ignore
import pdf from "pdf-creator-node";
//import { PDFDocument } from "pdf-lib";
import imageDB from "../models/images.model";
import moveInDB from "../models/moveIn.model";
import moveOutDB from "../models/moveOut.model";
import staffDB from "../models/staff.model";
import transactionDB from "../models/transaction.model";
import bedTypes from "../schemas/bed.schema";
import createReceipt from "../utils/createReceipt";
import createReceiptMultipleDues from "../utils/createReceiptMultipleDues";
import generateTransId from "../utils/generateTransId";
import getDueName from "../utils/getDueName";
import sendNotification from "../utils/sendNotification";
import { sendWhatsappNewRequest, sendWhatsappRequestRejectionWithReason, sendWhatsappRequestUpdates, sendWhatsappTenantBookingAmountWithLink, sendWhatsappTenantKycReminder, sendWhatsappTenantKycReminderWithAppLink, sendWhatsappTenantSeparateSecurity, sendWhatsappTenantSettlement, sendWhatsappTenantSingleDue, sendWhatsappTenantWelcome, sendWhatsappTenantWelcomeWithLink, sendWhatsappToParentOnAttendance, sendWhatsappToParentOnIN, sendWhatsappToParentOnOut } from "../utils/sendWhatsappWithConfig";
import sendWhatsappGharMumbai from "../utils/sendWhatsappGharMumbai";
import generateTenantGId from "../utils/generateTenantGId";
import generateOccupancyGId from "../utils/generateOccupancyGId";
import {
  tenantsForClient,
  tenantsForStaff,
  tenantsOfSelectedFlat,
  tenantsOfSelectedProperty,
  tenantsOfSelectedPropertyForWeb,
} from "../utils/client/tenantsByUserType";
import occupanciesTypes from "../schemas/occupancy.schema";
import extraChargeDB from "../models/extraCharges.model";
import serviceProviderDB from "../models/serviceProvider.model";
import {
  addAadhaarNumberToSignzy,
  uploadAadhaarToSignzy,
  verifyOTPFromSignzy,
} from "../utils/client/id_verification/signzy";
import electricityDB from "../models/electricity.model";
import {
  addAadhaarNumberToCashfree,
  addPanNumberToCashfree,
  uploadAadhaarToCashfree,
  verifyOTPFromCashfree,
  createDigiLockerCashfreeLink,
  createDigiLockerVerifyRequest,
  createDigiLockerCashfreeLinkViaClient,
} from "../utils/client/id_verification/cashfree";
import bankDB from "../models/bank.model";
import ipAddressDB from "../models/ipAddress.model";
import rentAgreementRecordDB from "../models/rentAgreementRecord.model";
import convertTypes from "../utils/convertTypes";
import flatDB from "../models/flat.model";
import getModeName from "../utils/getModeName";
import {
  calculateRentPerDay,
  calculateMonthlyRent,
  calculateRentAsPerRentalType,
  calculateRentAsPerRentalTypeForReserved,
  calculateAgreementEndRent,
  calculateRentAsPerRentalTypeForReservedRentBooking,
} from "../utils/calculateRentPerDay";
import ledgerDB from "../models/ledger.model";
import generateLedgerReferenceId from "../utils/generateLedgerReferenceId";
import adjustInitialSettlement from "../utils/adjustInitialSettlement";
import getDueDescription from "../utils/getDueDescription";
import staffLedgerDB from "../models/staffLedger.model";
import transactions from "./transaction.controller";
import staffBalanceDB from "../models/staffBalance.model";
import settingsDB from "../models/settings.model";
import eqaroTenantsDB from "../models/eqaroTenants.model";
import { isUserPartner } from "../utils/isUserPartner";
import propertiesTypes from "../schemas/property.schema";
import { clientIncomeDetails } from "../utils/client/getDashboardData";
import tenantGuardianDB from "../models/tenantGuardians.model";
import moveOutDuesDB from "../models/moveOutDues.model";
import transactionsTypes from "../schemas/transaction.schema";
import { json } from "stream/consumers";
import { eqaroStatus } from "../utils/checkEqaroStatus";
import getDocumentTitle from "../utils/getDocumentTitle";
import foodDB from "../models/food.model";
import { sendWhatsappOtp } from "../utils/sendWhatsappWithConfig";
import { logActivity, logRentalCycleActivity, logTenantEvictionActivity, logTenantRequestActivity } from "../utils/logActivity";
import tenantAttendanceDB from "../models/tenantAttendance.model";
import getBloodGroupNames from "../utils/getBloodGroupNames";
import { AddDues } from "../utils/dueHandler";
import editLogsDB from "../models/editLogs.model";
import fittingsAndFixturesDB from "../models/fittingsAndFixtures.model";
import generateTenantTId from "../utils/generateTenantTId";
import roomOptionDB from "../models/roomOption.model";
import addTransactions, { addTransactionsMoveOut } from "../utils/addTransaction";
import { AddAmountSchema } from "../schemas/electricityBill.schema";
import { mapOccupancyStatusToTenantStatus } from "../utils/mapOccupancyStatusToTenantStatus";
import notificationDB from "../models/notification.model";
import { mergePdf } from "../utils/mergePdf";
import addRentForMoveIn from "../utils/addRentWhileMoveIn";
import clientConfigDB from "../models/clientConfig.model";
import tenantGuestsDB from "../models/tenantGuests.model";
import getRequestName from "../utils/getRequestName";
import hiddenDuesDB from "../models/hiddenDues.model";
import { notEqual } from "assert";
import tenantNotesDB from "../models/tenantNotes.model";
import { getVacantSlots } from "../utils/calculateVacantSlots";
import staffCommissionDB from "../models/staffCommission.model";
import argeementRenewConfigDB from "../models/argeementRenewConfig.model";
import evictionConfigDB from "../models/evictionConfig.model";
import { setDueTallyStatus, setOccupancyTallyStatus } from "../utils/setTallyStatus";
import tenantBankDB from "../models/tenantBank.model";
import stampPaperDB from "../models/stampPapers.model";
import sosAlertDB from "../models/sosAlert.model";
import tenantMovementDB from "../models/tenantMovement.model";
import cashfreePayout from "../utils/cashfreePayout";
import bookingsDB from "../models/bookings.model";
import { sendWhatsappMC } from "../utils/sendWhatsappMessageCentral";
import { getClientWhatsappCredentialsMessageCentral } from "../utils/getClientWhatsappCredentials";
import tenantVehicleInfoDB from "../models/tenantVehicleInfo.model";
import wifiCredentialsDB from "../models/wifiCredential.model";
import { getWalletBalanceAndLimits } from "../utils/walletHelper";

const tenants: any = {};

tenants.Login = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "login";

  try {
    const {
      mobile,
      platform = CONSTANTS.TENANT_DEVICE_TYPE.ANDROID,
      clientId,
      alternateClientId = null,
    } = req.body;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Alternate Client Id [${alternateClientId}], Mobile [${mobile}], Platform [${platform}]`
    );
    //For Whatsapp OTP
    let sendToName = "";
    const isDev = mobile === "7888888888" ? true : false;

    const isExists = await tenantDB.getByMobile({ mobile });
    if (clientId && !isDev) {
      //const tenant = await tenantDB.getByMobile({ mobile });
      const client = await clientDB.getById({ id: clientId });
      let isTenantVacant = true;
      if (isExists) {
        let isTenantOccupied = await occupancyDB.getByTenantIdWithStatus({ tenantId: isExists?.id, clientId });
        if (isTenantOccupied) {
          isTenantVacant = false;
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Alternate Client Id [${alternateClientId}], Mobile [${mobile}], Platform [${platform}] Tenant occupied`
          );
          let propId = isTenantOccupied?.propId;
          let property = await propertyDB.getById({ id: propId });
          if(property && property?.isStampPaperRequired === 1 && Number(isTenantOccupied?.isRentAgreementSigned) === 0) {
              let stampPaper = await stampPaperDB.getByClientIdAndPropId({clientId, propId});
              if(!stampPaper) {
                log.info(
                  `[${C}], [${F}], Client Id [${clientId}], Alternate Client Id [${alternateClientId}], Mobile [${mobile}], Platform [${platform}], Login not allowed as estamp not available`
                );
                return res.status(400).json({
                  msg: "Login restricted. Check with admin.",
                  isSuccess: false,
                });
              }
          }
        }
      }

      //if (Number(client.canTenantRequestRoom) === 0 && (!isExists || (isExists && isExists.status === CONSTANTS.TENANT_STATUS.VACANT))) {
      if (Number(client.canTenantRequestRoom) === 0 && (!isExists || (isExists && isTenantVacant))) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Alternate Client Id [${alternateClientId}], Mobile [${mobile}], Platform [${platform}] Tenant not linked with client`
        );
        return res.status(400).json({
          msg: "Tenant not linked with the client",
          isSuccess: false,
        });
      }

      if (!isExists) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Alternate Client Id [${alternateClientId}], Mobile [${mobile}], Platform [${platform}] Tenant not found, skiping client check`
        );
        //} else if (isExists.status === CONSTANTS.TENANT_STATUS.VACANT) {
      } else if (isTenantVacant) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Alternate Client Id [${alternateClientId}], Mobile [${mobile}], Platform [${platform}] Tenant found but vacant, skiping client check`
        );
      } else {
        const isValidTenant = await occupancyDB.getByClientIdAndTenantId({
          clientId: clientId,
          tenantId: isExists?.id,
        });

        const isValidTenantPartner = await occupancyDB.getByClientIdAndTenantIdAndPartner({
          clientId,
          tenantId: isExists?.id,
        });

        let isValidTenantAlterante = false;

        if (alternateClientId && alternateClientId !== "undefined") {
          isValidTenantAlterante = await occupancyDB.getByClientIdAndTenantId({
            clientId: alternateClientId,
            tenantId: isExists?.id,
          });
        }

        if (!isValidTenant && !isValidTenantAlterante && !isValidTenantPartner) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Alternate Client Id [${alternateClientId}], Tenant Id [${req.id}], Mobile [${mobile}], Platform [${platform}] Tenant not linked with the client`
          );

          return res.status(400).json({
            msg: "Tenant not linked with the client",
            isSuccess: false,
          });
        }

        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Alternate Client Id [${alternateClientId}], Tenant Id [${req.id}], Mobile [${mobile}], Platform [${platform}] Tenant linked with the client`
        );
      }
    }

    //const isExists = await tenantDB.getByMobile({ mobile });

    if (isExists) {
      sendToName = isExists?.name || "";
      log.info(`[${C}], [${F}], Mobile [${mobile}], Tenant Found`);
    } else {
      await tenantDB.create({ mobile });
      log.info(
        `[${C}], [${F}], Mobile [${mobile}], Tenant successfully created`
      );
    }

    await tenantDB.updateTenantDevice({
      mobile,
      device: platform,
    });

    let otp = Math.floor(Math.random() * (999999 - 100000) + 100000).toString();
    if ("7888888888" == mobile) otp = "111111";

    if (process.env.ENABLE_RANDOM_OTP === "false") {
      otp = "111111";
    }

    await otpDB.create({ mobile, otp });

    const msg = `${otp} ${CONSTANTS.MSG.OTP}`;

    //let templateId = "1707172182376945542";

    const isSent = await sendSMS(mobile, msg, CONSTANTS.SMS_TEMPLATE_IDS.OTP);
    log.info(
      `[${C}], [${F}], Mobile [${mobile}], OTP [${otp}], ${isSent ? "OTP sent successfully" : "Failed to send OTP SMS"
      }`
    );

    sendWhatsappOtp(mobile, otp, sendToName);

    return res
      .status(200)
      .json({ msg: "OTP has been sent successfully", isSuccess: true });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.OTPVerify = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "OTPVerify";

  try {
    const { otp, mobile, clientId, alternateClientId = null } = req.body;
    log.info(`[${C}], [${F}], Client Id [${clientId}], Mobile [${mobile}], OTP [${otp}], Alternate Client Id [${alternateClientId}]`);

    const record = await otpDB.getByMobile({ mobile });
    let movedOutTenant = false;
    const isDev = mobile === "7888888888" ? true : false;

    if (!record) {
      log.info(
        `[${C}], [${F}], Mobile [${mobile}], OTP [${otp}] No Record Found`
      );

      return res.status(400).json({
        msg: "Invalid mobile number",
        isSuccess: false,
        isValid: true,
      });
    }

    if (record?.otp !== otp) {
      log.info(
        `[${C}], [${F}], Mobile [${mobile}], OTP [${otp}], DB OTP [${record?.otp}], Incorrect OTP`
      );

      return res.status(400).json({
        msg: "Please enter a valid OTP",
        isSuccess: false,
        isValid: true,
      });
    }

    log.info(`[${C}], [${F}], Client Id [${clientId}], Mobile [${mobile}], OTP [${otp}], OTP Matched`);

    let tenant = await tenantDB.getByMobile({ mobile });

    if (!tenant) {
      log.info(`[${C}], [${F}], Mobile [${mobile}], No Tenant Found`);
      return res.status(400).json({
        msg: "Invalid mobile number",
        isSuccess: false,
        isValid: true,
      });
    }

    // Sukhbir - MultiOccupancy
    // if (
    //   tenant?.status === CONSTANTS.TENANT_STATUS.MOVING_OUT &&
    //   tenant?.kycStatus < CONSTANTS.KYC_STATUS.SELFI_UPLOADED
    // ) {
    //   log.info(
    //     `[${C}], [${F}], Mobile [${mobile}], Tenant Id [${tenant?.id}], Tenant with move-out status but without kyc verification`
    //   );

    //   return res.status(400).json({
    //     msg: "You account has been set to be move out, please contact your owner.",
    //     isSuccess: false,
    //     isValid: false,
    //   });
    // }

    await otpDB.updateStatus({
      otp,
      status: CONSTANTS.OTP_STATUS.VERIFIED,
      mobile,
    });

    // Sukhbir - MultiOccupancy - Keep this
    if (tenant?.status === CONSTANTS.TENANT_STATUS.UNVERIFIED) {
      await tenantDB.updateStatus({
        id: tenant?.id,
        status: CONSTANTS.TENANT_STATUS.VACANT,
      });

      log.info(
        `[${C}], [${F}], Mobile [${mobile}], Mobile has been verified successfully`
      );
    }

    let propGId = null;
    let roomId = null;
    let requestId = null;
    let occupancy = null;
    let kycOptions = null;
    let evictionSetting = null;
    let eqaroTenant = null;

    // Sukhbir - MultiOccupancy - Need to Review this throughly
    // let occupancyDetails = await occupancyDB.getByTenantIdForLogin({
    //   tenantId: tenant?.id,
    // });
    let occupancyDetails = await occupancyDB.getTenantStayDetails({
      tenantId: tenant?.id,
    });

    if (Number(clientId) && !isDev) {
      occupancyDetails = await occupancyDB.getTenantStayDetailsWithClientId({
        tenantId: tenant?.id,
        clientId,
      });
    }

    let alternateOccupancies = [];

    if (Number(alternateClientId) && !isDev) {
      alternateOccupancies = await occupancyDB.getTenantStayDetailsWithClientId({
        tenantId: tenant?.id,
        clientId: alternateClientId,
      });

      if (alternateOccupancies) {
        if (!occupancyDetails) occupancyDetails = [];
        occupancyDetails = [...occupancyDetails, ...alternateOccupancies];
      }
    }

    if (Number(clientId) && !isDev) {
      let partnerOccupancies = [];
      partnerOccupancies = await occupancyDB.getTenantStayDetailsWithPartnerId({
        tenantId: tenant?.id,
        clientId: clientId,
      });

      if (partnerOccupancies && partnerOccupancies.length > 0) {
        if (!occupancyDetails) occupancyDetails = [];
        occupancyDetails = [...occupancyDetails, ...partnerOccupancies];
      }
    }


    if (!occupancyDetails) {
      log.info(
        `[${C}], [${F}], Mobile [${mobile}], Client Id [${clientId}], Tenant Id [${tenant?.id}], No Occupancy Found`
      );
      // occupancyDetails = await moveOutDB.getMovedOutTenants({
      //   tenantId: tenant?.id,
      // });
      // if (occupancyDetails) movedOutTenant = true;
    }

    log.info(
      `[${C}], [${F}], KycOptions [${JSON.stringify(
        occupancyDetails
      )}]`
    );

    if (occupancyDetails && occupancyDetails.length === 1 && occupancyDetails[0].type === "moveOut") movedOutTenant = true;

    // Sukhbir - MultiOccupancy
    // if (
    //   tenant?.status === CONSTANTS.TENANT_STATUS.LINKED ||
    //   tenant?.status === CONSTANTS.TENANT_STATUS.REQUESTED ||
    //   tenant?.status === CONSTANTS.TENANT_STATUS.OCCUPIED ||
    //   tenant?.status === CONSTANTS.TENANT_STATUS.RESERVED ||
    //   movedOutTenant
    // ) 
    if (!occupancyDetails) {
      log.info(
        `[${C}], [${F}], Mobile [${mobile}], Tenant Id [${tenant?.id}], No Occupancy Found`
      );
      // Sukhbir - MultiOccupancy - Need to return here
      // return res.status(400).json({
      //   msg: "No occupancy found",
      //   isSuccess: false,
      // });
    } else {
      occupancy = occupancyDetails[0];

      const property = await propertyDB.getById({
        id: occupancy?.propId,
      });

      if (!property) {
        log.info(
          `[${C}], [${F}], Mobile [${mobile}], Tenant Id [${tenant?.id}], No Property Found`
        );
      } else {
        log.info(
          `[${C}], [${F}], Client Id [${occupancy?.clientId}], Mobile [${mobile}], Tenant Id [${tenant?.id}], `
        );

        // let bondAvailable = property?.isBondAvailable || 0;
        let bondAvailable =
          property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
            ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
              ? CONSTANTS.RENTAL_BOND.MANDATORY
              : 0
            : 0;
        if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
          eqaroTenant = await eqaroTenantsDB.getByTenantId({
            tenantId: tenant?.id,
          });
          if (
            !eqaroTenant ||
            eqaroTenant?.status < CONSTANTS.EQARO_TENANT_STATUS.BOND_ISSUED
          ) {
            const eqaroStatusResponse = await eqaroStatus(tenant);
            // if (eqaroStatusResponse === true) {
            //   eqaroTenant = await eqaroTenantsDB.getByTenantId({
            //     tenantId: tenant?.id,
            //   });
            // }
            eqaroTenant = await eqaroTenantsDB.getByTenantId({
              tenantId: tenant?.id,
            });
          }
          if (
            eqaroTenant?.status === CONSTANTS.EQARO_TENANT_STATUS.INELIGIBLE
          ) {
            log.info(`
              [${C}], [${F}], Mobile [${mobile}], Tenant Id [${tenant?.id}], Bond Status [${eqaroTenant?.status}], Sending Bond Available as 0
              `);

            bondAvailable = 0;
          }
        }

        let isSkipKycEnabled = await clientConfigDB.getClientConfigByPropId({
          clientId: occupancy.clientId,
          propId: Number(property.id),
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.TENANT_ONBOARDING_SETTINGS,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.TENANT_ONBOARDING_SETTINGS.SKIP_KYC,
        });

        if (!isSkipKycEnabled) {
          isSkipKycEnabled = 0;
        } else {
          isSkipKycEnabled = isSkipKycEnabled?.value || 0;
        }
        propGId = property.gId;
        roomId = occupancy.roomId;
        kycOptions = {
          isPoliceVerificationEnabled: property.isPoliceVerificationEnabled,
          isRentAgreementEnabled: property.isRentAgreementEnabled,
          isIdVerificationEnabled: property.isIdVerificationEnabled,
          isSkipKycEnabled: isSkipKycEnabled,
          isPanVerificationEnabled: property.isPanVerificationEnabled,
          isOnlinePaymentEnabled: property.isOnlinePaymentEnabled,
          isPartialPaymentEnabled: property.isPartialPaymentEnabled,
          isBondAvailable: bondAvailable,
        };

        evictionSetting = await evictionConfigDB.getByClientIdAndPropId({
          clientId: occupancy.clientId,
          propId: property.id,
        });
      }
    }

    if (occupancy && occupancy?.status === CONSTANTS.OCCUPANCY_STATUS.REQUESTED) {
      const request = await requestDB.getByOccupancyAndType({
        occupancyId: occupancy.id,
        type: CONSTANTS.REQUEST_TYPE.ROOM_SELECTION,
      });
      // log.info(
      //   JSON.stringify(request)
      // );
      if (!request) {
        log.info(
          `[${C}], [${F}], Mobile [${mobile}], Tenant Id [${tenant?.id}], No Request Found`
        );
      } else {
        requestId = request.id;
      }
    }

    const token = jwt.sign(
      {
        id: tenant?.id,
        type: CONSTANTS.USER_TYPE.TENANT,
        isEvicted: movedOutTenant ? true : false,
        clientId: occupancyDetails ? occupancyDetails[0].clientId : null,
      },
      process.env.TOKEN_SECRET!,
      { expiresIn: process.env.TOKEN_EXPIRY }
    );

    tenant = await tenantDB.getByMobile({ mobile });

    log.info(`[${C}], [${F}], Mobile [${mobile}], Status [${occupancy?.status}], Moved Out Tenant [${movedOutTenant}], 1. OTP Matched`);


    if (movedOutTenant) {
      tenant.status = 8; //moved out tenant
      tenant.kycStatus = occupancy.kycStatus;
      tenant.statusx = occupancy.status;
    }
    else {//Sukhbir - MultiOccupancy
      tenant.status = mapOccupancyStatusToTenantStatus(occupancy?.status);
      tenant.statusx = occupancy ? occupancy.status : CONSTANTS.TENANT_STATUS.VACANT;
      tenant.kycStatus = occupancy ? occupancy.kycStatus : CONSTANTS.KYC_STATUS.PENDING;
    }

    // const eqaroTenant = await eqaroTenantsDB.getByTenantId({
    //   tenantId: updatedTenant?.id,
    // });

    log.info(
      `[${C}], [${F}], Mobile [${mobile}], Tenant Id [${tenant?.id
      }], Token [${token}], PropGId [${propGId}], RoomId [${roomId}], RequestId [${requestId}], Occupancy [${occupancy}], KycOptions [${JSON.stringify(
        kycOptions
      )}], EqaroTenant [${eqaroTenant}]`
    );
    // log.info(
    //   JSON.stringify({
    //     msg: "Mobile has been verified successfully",
    //     isSuccess: true,
    //     tenant: tenant,
    //     propGId,
    //     roomId,
    //     requestId,
    //     kycOptions,
    //     token,
    //     eqaroStatus: eqaroTenant?.status || 0,
    //     isValid: true,
    //   })
    // );
    return res.status(200).json({
      msg: "Mobile has been verified successfully",
      isSuccess: true,
      tenant: tenant,
      propGId,
      roomId,
      requestId,
      kycOptions,
      evictionSetting,
      token,
      eqaroStatus: eqaroTenant?.status || 0,
      isValid: true,
      isPoliceVerified: occupancy?.isPoliceVerified || 0,
      isRentAgreementSigned: occupancy?.isRentAgreementSigned || 0,
      existingOccupancies: isDev
        ? occupancyDetails
          ? occupancyDetails[0]
          : []
        : occupancyDetails,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
      isValid: true,
    });
  }
};

tenants.LoginWithProperty = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "LoginWithProperty";

  try {
    const { clientId, isEvicted, occupancyId } = req.body;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Is Evicted [${isEvicted}], Occupancy/MoveOut Id [${occupancyId}]`
    );

    let propGId = null;
    let roomId = null;
    let kycOptions = null;
    let evictionSetting = null;
    let eqaroTenant = null;

    let occupancy = await occupancyDB.getById({
      id: occupancyId,
    });

    if (Number(isEvicted) === 1) {
      occupancy = await moveOutDB.getById({
        id: occupancyId,
      });
    }

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Occupancy Id [${occupancyId}], Is Evicted [${isEvicted}], No Occupancy Found`
      );
    }
    log.info(
      `[${C}], [${F}], Occupancy Details [${JSON.stringify(occupancy)}]`
    );
    const tenant = await tenantDB.getById({
      id: occupancy.tenantId,
    });

    const property = await propertyDB.getById({
      id: occupancy?.propId,
    });

    if (!property) {
      log.info(
        `[${C}], [${F}],Prop Id [${occupancy?.propId}] No Property Found`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${occupancy?.clientId}]`
      );

      let bondAvailable =
        property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
          ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
            ? CONSTANTS.RENTAL_BOND.MANDATORY
            : 0
          : 0;
      if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
        eqaroTenant = await eqaroTenantsDB.getByTenantId({
          tenantId: tenant?.id,
        });
        if (
          !eqaroTenant ||
          eqaroTenant?.status < CONSTANTS.EQARO_TENANT_STATUS.BOND_ISSUED
        ) {
          const eqaroStatusResponse = await eqaroStatus(tenant);
          eqaroTenant = await eqaroTenantsDB.getByTenantId({
            tenantId: tenant?.id,
          });
        }
        if (
          eqaroTenant?.status === CONSTANTS.EQARO_TENANT_STATUS.INELIGIBLE
        ) {
          log.info(`
            [${C}], [${F}], Tenant Id [${occupancy?.tenantId}], Bond Status [${eqaroTenant?.status}], Sending Bond Available as 0
          `);

          bondAvailable = 0;
        }
      }

      let isSkipKycEnabled = await clientConfigDB.getClientConfigByPropId({
        clientId: occupancy.clientId,
        propId: Number(property.id),
        provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.TENANT_ONBOARDING_SETTINGS,
        type: CONSTANTS.CLIENT_CONFIG_TYPE.TENANT_ONBOARDING_SETTINGS.SKIP_KYC,
      });

      if (!isSkipKycEnabled) {
        isSkipKycEnabled = 0;
      } else {
        isSkipKycEnabled = isSkipKycEnabled.value || 0;
      }

      propGId = property.gId;
      roomId = occupancy.roomId;
      kycOptions = {
        isPoliceVerificationEnabled: property.isPoliceVerificationEnabled,
        isRentAgreementEnabled: property.isRentAgreementEnabled,
        isIdVerificationEnabled: property.isIdVerificationEnabled,
        isSkipKycEnabled: isSkipKycEnabled,
        isPanVerificationEnabled: property.isPanVerificationEnabled,
        isOnlinePaymentEnabled: property.isOnlinePaymentEnabled,
        isPartialPaymentEnabled: property.isPartialPaymentEnabled,
        isBondAvailable: bondAvailable,
      };

      evictionSetting = await evictionConfigDB.getByClientIdAndPropId({
        clientId: occupancy.clientId,
        propId: property.id,
      });
    }

    const token = jwt.sign(
      {
        id: tenant?.id,
        type: CONSTANTS.USER_TYPE.TENANT,
        isEvicted: Number(isEvicted) === 1 ? true : false,
        clientId: clientId,
      },
      process.env.TOKEN_SECRET!,
      { expiresIn: process.env.TOKEN_EXPIRY }
    );

    if (isEvicted) {
      tenant.status = 8; //moved out tenant
      tenant.kycStatus = occupancy.kycStatus;
    }
    else {//Sukhbir - MultiOccupancy
      tenant.status = mapOccupancyStatusToTenantStatus(occupancy.status);
      tenant.statusx = occupancy.status;
      tenant.kycStatus = occupancy.kycStatus;
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${occupancy.tenantId}], Property Id [${occupancy.propId}], Login Successfully`
    );

    return res.status(200).json({
      msg: "Mobile has been verified successfully",
      isSuccess: true,
      tenant: tenant,
      propGId,
      roomId,
      kycOptions,
      evictionSetting,
      token,
      eqaroStatus: eqaroTenant?.status || 0,
      isValid: true,
      isPoliceVerified: occupancy?.isPoliceVerified || 0,
      isRentAgreementSigned: occupancy?.isRentAgreementSigned || 0,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
      isValid: true,
    });
  }
};

tenants.AuthenticationDetails = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AuthenticationDetails";

  try {
    const tenantId = req.id;
    let isEvicted = req.isEvicted;
    let clientId = req.clientId;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Evicted Tenant [${isEvicted}], Client Id [${clientId}]`);
    if (!clientId) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Client Id Found in Token`);
      return res.status(401).json({
        msg: "Session Expired",
        isSuccess: false,
      });
    }

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let propGId = "";
    let roomId = 0;
    let bank = null;
    let kycOptions = {
      isPoliceVerificationEnabled: 0,
      isRentAgreementEnabled: 0,
      isIdVerificationEnabled: 0,
      isSkipKycEnabled: 0,
      isPanVerificationEnabled: 0,
      isOnlinePaymentEnabled: 0,
      isOnlinePaymentEnabledTenant: 0,
      isPartialPaymentEnabled: 0,
      isBondAvailable: 0,
    };
    let eqaroTenant = null;

    // let occupancy = await occupancyDB.getByTenantId({
    //   tenantId: tenant?.id,
    // });
    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenant?.id,
      clientId: clientId,
    });

    if (Number(isEvicted) === 1) {
      // occupancy = await moveOutDB.getMovedOutTenants({
      //   tenantId: tenant.id,
      // });
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        tenantId: tenant.id,
        clientId: clientId,
      });
    }

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
    } else {
      const property = await propertyDB.getById({
        id: occupancy?.propId,
      });

      if (!property) {
        log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Property Found`);
      } else {

        if(property && property?.isStampPaperRequired === 1 && Number(occupancy?.isRentAgreementSigned) === 0) {
          let stampPaper = await stampPaperDB.getByClientIdAndPropId({clientId, propId: occupancy?.propId});
          if(!stampPaper) {
            log.info(
              `[${C}], [${F}], Tenant ID [${tenant.id}], Client Id [${clientId}], Login access not allowed as estamp not available`
            );
            return res.status(401).json({
              msg: "Login restricted. Check with admin.",
              isSuccess: false,
            });
          }
        }
        propGId = property?.gId || "";
        roomId = occupancy?.roomId || 0;

        if (property?.bankId) {
          bank = await bankDB.getById({
            id: property?.bankId,
          });

          if (!bank) bank = null;
        }

        // let bondAvailable = property?.isBondAvailable || 0;
        let bondAvailable =
          property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
            ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
              ? CONSTANTS.RENTAL_BOND.MANDATORY
              : 0
            : 0;
        if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
          eqaroTenant = await eqaroTenantsDB.getByTenantId({
            tenantId: tenant?.id,
          });
          if (
            !eqaroTenant ||
            eqaroTenant?.status < CONSTANTS.EQARO_TENANT_STATUS.BOND_ISSUED
          ) {
            const eqaroStatusResponse = await eqaroStatus(tenant);
            // if (eqaroStatusResponse === true) {
            //   eqaroTenant = await eqaroTenantsDB.getByTenantId({
            //     tenantId: tenant?.id,
            //   });
            // }
            eqaroTenant = await eqaroTenantsDB.getByTenantId({
              tenantId: tenant?.id,
            });
          }
          if (
            eqaroTenant?.status === CONSTANTS.EQARO_TENANT_STATUS.INELIGIBLE
          ) {
            log.info(`
              [${C}], [${F}], Tenant Id [${tenant?.id}], Bond Status [${eqaroTenant?.status}], Sending Bond Available as 0
            `);

            bondAvailable = 0;
          }
        }

        let isSkipKycEnabled = await clientConfigDB.getClientConfigByPropId({
          clientId: occupancy.clientId,
          propId: Number(property.id),
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.TENANT_ONBOARDING_SETTINGS,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.TENANT_ONBOARDING_SETTINGS.SKIP_KYC,
        });

        if (!isSkipKycEnabled) {
          isSkipKycEnabled = 0;
        } else {
          isSkipKycEnabled = isSkipKycEnabled?.value || 0;
        }

        kycOptions = {
          isPoliceVerificationEnabled:
            property?.isPoliceVerificationEnabled || 0,
          isRentAgreementEnabled: property?.isRentAgreementEnabled || 0,
          isIdVerificationEnabled: property?.isIdVerificationEnabled || 0,
          isSkipKycEnabled: isSkipKycEnabled,
          isPanVerificationEnabled: property?.isPanVerificationEnabled || 0,
          isOnlinePaymentEnabled: property?.isOnlinePaymentEnabled || 0,
          isOnlinePaymentEnabledTenant: occupancy?.isOnlinePaymentEnabled || 0,
          isPartialPaymentEnabled: property?.isPartialPaymentEnabled || 0,
          isBondAvailable: bondAvailable,
        };
        if(property && property?.isStampPaperRequired === 1 && Number(occupancy?.isRentAgreementSigned) === 0 ) {
          let stampPaper = await stampPaperDB.getByClientIdAndPropId({clientId, propId: property?.id});
          if(!stampPaper) {
            log.info(
              `[${C}], [${F}], Stamp Paper Not Found for Client [${clientId}] & Property [${property?.id}]`
            );
            kycOptions.isRentAgreementEnabled = 0;
          }
        }
      }
    }
    let photo = null;
    if (kycOptions.isIdVerificationEnabled === 2) { // 2 is for DigiLocker
      let isSelfiUploded = await documentDB.getIDByType({ tenantId, clientId: occupancy?.clientId, type: CONSTANTS.DOCUMENT_TYPES.SELFI, moveOut: 0, });
      if (false == isSelfiUploded) {
        const filePath = `uploads/tmp/img-buffer-${tenantId}.jpeg`;
        try {
          photo = await fsPromises.readFile(filePath);
          photo = photo.toString('base64');
        } catch (err) {
          photo = null;
          // log.info(
          //   `[${C}], [${F}], Tenant Id [${tenantId}], Error reading file [${err}]`
          // );
        }
      }
    }

    // const currentAppVersion = CONSTANTS.CURRENT_TENANT_VERSION;

    const configTenantAppVersion = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.TENANT_APP_VERSION,
    });

    let currentAppVersion = CONSTANTS.CURRENT_TENANT_VERSION;

    if (configTenantAppVersion) {
      currentAppVersion = configTenantAppVersion?.value || CONSTANTS.CURRENT_TENANT_VERSION;
    }

    if (Number(isEvicted) === 1) {
      tenant.status = 8; //moved out tenant
      tenant.kycStatus = occupancy.kycStatus;
    }
    else {//Sukhbir - MultiOccupancy
      tenant.status = mapOccupancyStatusToTenantStatus(occupancy.status);
      tenant.statusx = occupancy.status;
      tenant.kycStatus = occupancy.kycStatus;
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Tenant details sent successfully`
    );

    return res.status(200).json({
      msg: "Tenant details sent successfully",
      isSuccess: true,
      tenant,
      bank,
      propGId,
      roomId,
      kycOptions,
      eqaroStatus: eqaroTenant?.status || 0,
      isBankDetailsShown: 0,
      isPoliceVerified: occupancy?.isPoliceVerified || 0,
      isRentAgreementSigned: occupancy?.isRentAgreementSigned || 0,
      image: photo,
      currentVersion: currentAppVersion,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
      isValid: true,
    });
  }
};

tenants.OnBoarding = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "OnBoarding";

  try {
    const { propGId, name } = req.body;
    const tenantId = req.id;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}]`
    );

    if (!propGId.includes(CONSTANTS.PROP_GID_SUFFIX)) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], Invalid Property Id`
      );

      return res
        .status(400)
        .json({ msg: "Invalid Property Id", isSuccess: false });
    }

    let property = await propertyDB.getByGId({
      gId: propGId,
    });

    if (!property) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], No Property Found`
      );

      return res
        .status(400)
        .json({ msg: "Invalid Property Id", isSuccess: false });
    }

    if (property.status !== CONSTANTS.PROPERTY_STATUS.ACTIVE) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], Property is not active`
      );
      return res.status(400).json({
        msg: "This Property is not active",
        isSuccess: false,
      });
    }

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], No Tenant Found`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const rooms = await roomDB.getVacantRoomsByPropId({
      propId: property?.id,
    });

    if (!rooms) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], No Room Available`
      );

      return res.status(400).json({
        msg: "This Property doesn't have any vacant room available.",
        isSuccess: false,
      });
    }

    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId: tenant?.id,
    // });

    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenant?.id,
      clientId: property?.clientId,
    });

    if (occupancy) {
      if (
        occupancy.status === CONSTANTS.OCCUPANCY_STATUS.PENDING ||
        occupancy.status === CONSTANTS.OCCUPANCY_STATUS.REQUESTED
      ) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], Occupancy Found`
        );

        await occupancyDB.updatePropId({
          id: occupancy?.id,
          propId: property.id,
          clientId: property.clientId,
        });

        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], Occupancy Updated Successfully`
        );
      } else {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], Occupancy Already Exists`
        );

        return res.status(400).json({
          msg: "You are already occupied with another bed.",
          isSuccess: false,
        });
      }
    }

    if (!occupancy) {
      await occupancyDB.addPropId({
        tenantId: tenant?.id,
        propId: property.id,
        clientId: property.clientId,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], Occupancy Created Successfully`
      );
    }

    await tenantDB.updateNameGender({ name, id: tenantId });

    // Sukhbir - MultiOccupancy
    // await tenantDB.updateStatus({
    //   id: tenantId,
    //   status: CONSTANTS.TENANT_STATUS.LINKED,
    // });

    const updatedTenant = await tenantDB.getById({ id: tenantId });

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Prop GId [${propGId}], Tenant Name, Status & Details Updated Updated`
    );

    return res.status(200).json({
      msg: "Details updated successfully",
      isSuccess: true,
      data: { tenant: updatedTenant },
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.UploadID = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "UploadID";

  const files = req.files as Express.Multer.File[];
  const removeTmpImages = async () => {
    for (const file of files) {
      try {
        await fsPromises.unlink(file.path);
      } catch (err) { }
    }
  };

  try {
    const tenantId = req.id;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], `);

    if (!files) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], , No files uploaded`);
      return res
        .status(400)
        .json({ msg: "No files uploaded", isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], , No Tenant Found`);

      await removeTmpImages();
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenant?.id,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], , No Occupancy Found`);

      await removeTmpImages();

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const folderName = `tenant_${occupancy.tenantId}`;
    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;
    const urlBase = folderPath.replace(
      "uploads/",
      `${process.env.UPLOAD_PATH}/`
    );

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const provider = await serviceProviderDB.getActiveByType({
      type: CONSTANTS.SERVICE_PROVIDERS_TYPE.AADHAAR_UPLOAD,
    });

    if (!provider) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], , No service provider is active`
      );

      await removeTmpImages();

      return res.status(400).json({
        msg: "Service is currently unavailable, please try again later.",
        isSuccess: false,
      });
    }

    const oldFrontPath = `${process.env.UPLOAD_PATH}/tmp/${files[0].filename}`;
    let oldBackPath = "";
    if (files[1])
      oldBackPath = `${process.env.UPLOAD_PATH}/tmp/${files[1].filename}`;

    let DOB = null;
    let GENDER = 0;
    let ADDRESS = "";
    let NAME = "";
    let UID = null;

    if (provider.name === CONSTANTS.SERVICE_PROVIDERS.SIGNZY) {
      const { msg, isSuccess, isServerError, dob, genderVal, address, name } =
        await uploadAadhaarToSignzy({
          tenantId: Number(tenantId),
          oldFrontPath: String(oldFrontPath),
          oldBackPath: String(oldBackPath),
          folderPath,
          urlBase,
          occupancy,
        });

      if (!isSuccess) {
        await removeTmpImages();

        if (!isServerError) {
          return res.status(400).json({
            msg: msg,
            isSuccess: false,
          });
        } else {
          return res.status(500).json({
            msg: CONSTANTS.MSG.ERROR_MESSAGE,
            isSuccess: false,
          });
        }
      }
      //Sukbir - Change Variable Name
      DOB = dob;
      GENDER = Number(genderVal) || 0;
      ADDRESS = address;
      NAME = name;
    }

    if (provider.name === CONSTANTS.SERVICE_PROVIDERS.CASHFREE) {
      const { msg, isSuccess, isServerError, yob, genderVal, address, name, uid } =
        await uploadAadhaarToCashfree({
          tenantId: Number(tenantId),
          oldFrontPath: String(oldFrontPath),
          oldBackPath: String(oldBackPath),
          folderPath,
          urlBase,
          occupancy,
        });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], MSG [${msg}], Is Success [${isSuccess}], Is Server Error [${isServerError}], Yob [${yob}], Gender Val [${genderVal}], Address [${address}], Name [${name}]`
      );

      if (!isSuccess) {
        await removeTmpImages();

        if (!isServerError) {
          return res.status(400).json({
            msg: msg,
            isSuccess: false,
          });
        } else {
          return res.status(500).json({
            msg: CONSTANTS.MSG.ERROR_MESSAGE,
            isSuccess: false,
          });
        }
      }
      DOB = yob;
      GENDER = Number(genderVal) || 0;
      ADDRESS = address;
      NAME = name;
      UID = uid;
    }

    await tenantDB.updatePersonalInfoX({
      name: NAME,
      fatherName: "",
      dob: moment(DOB, "DD-MM-YYYY").format("YYYY-MM-DD") || null,
      gender: GENDER,
      address: ADDRESS || "",
      id: tenantId,
      kycStatus: CONSTANTS.KYC_STATUS.ID_UPLOADED,
      aadharNumber: UID || null,
    });

    const updatedTenant = await tenantDB.getById({ id: tenantId });

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Id doc(s) uploaded sucessfully`
    );

    return res.status(200).json({
      msg: "Id doc(s) uploaded sucessfully",
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    await removeTmpImages();

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.AddIDNumber = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddIDNumber";

  try {
    const { IdNumber } = req.body;
    const tenantId = req.id;

    const maskedIdNumber = "XXXXXXXX" + IdNumber.slice(-4);
    const IdNumberDB = "XXXX-XXXX-" + IdNumber.slice(-4);

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}], No Tenant Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
        isDown: false,
      });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId,
    });

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
      moveOut: 0,
    });

    if (isExists && isExists.status === CONSTANTS.DOCUMENT_STATUS.VERIFIED) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}], ID Number Already Exists`
      );
      return res.status(400).json({
        msg: "ID Number Already Exists",
        isDown: false,
        isSuccess: false,
      });
    }

    const provider = await serviceProviderDB.getActiveByType({
      type: CONSTANTS.SERVICE_PROVIDERS_TYPE.AADHAAR_NUMBER,
    });

    if (!provider) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}], No service provider is active`
      );

      return res.status(400).json({
        msg: "Service is currently unavailable, please try again later.",
        isSuccess: false,
      });
    }

    let providerFunc: any = null;

    if (provider.name === CONSTANTS.SERVICE_PROVIDERS.SIGNZY)
      providerFunc = addAadhaarNumberToSignzy;
    else if (provider.name === CONSTANTS.SERVICE_PROVIDERS.CASHFREE)
      providerFunc = addAadhaarNumberToCashfree;
    else {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}], SP Name [${provider.name}] Invalid service provider`
      );

      return res.status(400).json({
        msg: "Service is currently unavailable, please try again later.",
        isSuccess: false,
      });
    }

    const { isSuccess, isServerError, msg, isDown, requestId, docId } =
      await providerFunc({
        tenantId: Number(tenantId),
        IdNumber,
        maskedIdNumber,
        isExists,
      });

    if (!isSuccess) {
      if (!isServerError) {
        return res.status(400).json({
          msg: msg,
          isSuccess: false,
          isDown,
        });
      } else {
        return res.status(500).json({
          msg: CONSTANTS.MSG.ERROR_MESSAGE,
          isSuccess: false,
          isDown,
        });
      }
    }

    await tenantDB.updateAadhaarNumber({
      id: tenantId,
      aadharNumber: IdNumberDB,
    });

    return res.status(200).json({
      msg: "OTP sent to registered mobile number",
      requestId,
      docId,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isDown: true,
      isSuccess: false,
    });
  }
};

tenants.VerifyIDNumber = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "VerifyIDNumber";

  try {
    const { requestId, otp, docId } = req.body;
    const tenantId = req.id;
    let IP = req.headers["x-forwarded-for"] || req.socket.remoteAddress;

    //log.info(`IP [${IP}]`);

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}],  Request Id [${requestId}], OTP [${otp}], Doc Id [${docId}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}],  Request Id [${requestId}], OTP [${otp}], Doc Id [${docId}], No Tenant Found`
      );
      res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
        isDown: false,
      });
    }

    const hitCount = await ipAddressDB.getCountByHour({ ip: IP });

    if (hitCount > 3) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], OTP [${otp}], Doc Id [${docId}], Hit more than thrice in an hour`
      );

      return res.status(400).json({
        msg: "Service is currently unavailable. Please try to upload aadhaar photo or try agian later.",
        isSuccess: false,
      });
    }

    await ipAddressDB.add({
      userId: tenantId,
      userType: CONSTANTS.USER_TYPE.TENANT,
      ip: IP,
      target: CONSTANTS.IP_ADRESSES_TARGET.AADHAAR_OTP_VERIFICATION,
    });

    const provider = await serviceProviderDB.getActiveByType({
      type: CONSTANTS.SERVICE_PROVIDERS_TYPE.AADHAAR_NUMBER,
    });

    if (!provider) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], OTP [${otp}], Doc Id [${docId}], No service provider is active`
      );

      return res.status(400).json({
        msg: "Service is currently unavailable, please try again later.",
        isSuccess: false,
      });
    }

    let providerFunc: any = null;

    if (provider.name === CONSTANTS.SERVICE_PROVIDERS.SIGNZY)
      providerFunc = verifyOTPFromSignzy;
    else if (provider.name === CONSTANTS.SERVICE_PROVIDERS.CASHFREE)
      providerFunc = verifyOTPFromCashfree;
    else {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], OTP [${otp}], Doc Id [${docId}], SP Name [${provider.name}] Invalid service provider`
      );

      return res.status(400).json({
        msg: "Service is currently unavailable, please try again later.",
        isSuccess: false,
      });
    }

    const { isServerError, isSuccess, msg, isDown, isTimeout, photo, result, frontUrl, backUrl } =
      await providerFunc({
        tenantId: Number(tenantId),
        requestId,
        otp,
        docId,
      });

    if (!isSuccess) {
      if (!isServerError) {
        return res.status(400).json({
          msg: msg,
          isSuccess: false,
          isDown,
          isTimeout,
        });
      } else {
        return res.status(500).json({
          msg: CONSTANTS.MSG.ERROR_MESSAGE,
          isSuccess: false,
          isDown,
          isTimeout,
        });
      }
    }

    //Sukhbir - 20th Jan 2025 to make sure there should not be conflict with Eqaro Verification
    const { name, dob, gender, address, care_of, zip, photo_link } = result;

    let genderVal = 0;
    if (gender && gender.toLowerCase() === "m") {
      genderVal = CONSTANTS.GENDER.MALE;
    } else if (gender && gender.toLowerCase() === "f") {
      genderVal = CONSTANTS.GENDER.FEMALE;
    } else {
      genderVal = CONSTANTS.GENDER.OTHER;
    }

    await tenantDB.updatePersonalInfo({
      name: name,
      fatherName: care_of?.slice(4) || "",
      dob: (dob && moment(dob, "DD-MM-YYYY").format("YYYY-MM-DD")) || null,
      gender: genderVal,
      address,
      id: tenantId,
      kycStatus: CONSTANTS.KYC_STATUS.ID_UPLOADED,
    });

    await documentDB.updateStatus({
      id: docId,
      status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
    });

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenantId,
    });

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
      moveOut: 0,
    });

    if (isExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
        value: frontUrl,
        id: isExists.id,
      });

    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
        value: frontUrl,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });
    }


    await documentDB.addDocWithStatus({
      tenantId,
      clientId: occupancy.clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR_BACK,
      value: backUrl,
      status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
    });
    /********************* End *********************/

    const updatedTenant = await tenantDB.getById({ id: tenantId });

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], OTP [${otp}], Doc Id [${docId}], Details fetched & sent sucessfully`
    );

    return res.status(200).json({
      msg: "Details fetched sucessfully",
      data: { image: photo || null },
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isDown: true,
      isSuccess: false,
    });
  }
};

tenants.AddPersonalInfo = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddPersonalInfo";

  try {
    const {
      name,
      fatherName,
      fatherMobile=null,
      dob,
      gender,
      occupation,
      email,
      alternateMobile,
      address,
      companyName = null,
      aadharNumber = null
    } = req.body;
    const tenantId = req.id;
    const clientId = req.clientId;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Name [${name}], Father Name [${fatherName}], Father Mobile [${fatherMobile}], DOB [${dob}], Gender [${gender}], Occupation [${occupation}], Email [${email}], Alternate Mobile [${alternateMobile}], Address [${address}], Company Name [${companyName}], Aadhar Number [${aadharNumber}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Name [${name}], No Tenant Found`
      );
      res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    await tenantDB.addPersonalInfo({
      name,
      fatherName,
      fatherMobile: fatherMobile || null,
      dob,
      gender,
      occupation,
      email,
      alternateMobile: alternateMobile || "",
      address: address || "",
      id: tenantId,
      kycStatus: CONSTANTS.KYC_STATUS.PERSONAL_INFO,
      institutionName: companyName || "",
    });

    // Sukhbir - MultiOccupancy
    await occupancyDB.updateKycStatusWithClientId({
      tenantId: tenantId,
      clientId: clientId,
      kycStatus: CONSTANTS.KYC_STATUS.SELFI_UPLOADED
    });

    if (aadharNumber) {
      await tenantDB.updateAadhaarNumber({ id: tenantId, aadharNumber });
    }
    let guardianDetail = await tenantGuardianDB.getByTenantId({ tenantId });

    if (false == guardianDetail) {
      await tenantGuardianDB.addFatherName({ tenantId, fatherName });
      await tenantGuardianDB.addFatherMobile({ tenantId, fatherMobile });
    } else {
      await tenantGuardianDB.updateFatherName({ tenantId, fatherName });
      await tenantGuardianDB.updateFatherMobile({ tenantId, fatherMobile });
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Name [${name}], Personal info added successfully`
    );

    const updatedTenant = await tenantDB.getById({ id: tenantId });

    return res.status(200).json({
      msg: "Personal info added successfully",
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.AddPanNumber = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddPanNumber";

  try {
    const { IdNumber, IdName } = req.body;
    const tenantId = req.id;

    const maskedIdNumber = "XXXXXX" + IdNumber.slice(-4);

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}], IdName [${IdName}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}], IdName [${IdName}], No Tenant Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
        isDown: false,
      });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId,
    });

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.PAN,
      moveOut: 0,
    });

    if (isExists && isExists.status === CONSTANTS.DOCUMENT_STATUS.VERIFIED) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}], IdName [${IdName}], ID Number Already Exists`
      );
      return res.status(400).json({
        msg: "ID Number Already Exists",
        isDown: false,
        isSuccess: false,
      });
    }

    const provider = await serviceProviderDB.getActiveByType({
      type: CONSTANTS.SERVICE_PROVIDERS_TYPE.PAN,
    });

    if (!provider) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}], IdName [${IdName}], No service provider is active`
      );

      return res.status(400).json({
        msg: "Service is currently unavailable, please try again later.",
        isSuccess: false,
      });
    }

    let providerFunc: any = null;

    if (provider.name === CONSTANTS.SERVICE_PROVIDERS.CASHFREE)
      providerFunc = addPanNumberToCashfree;
    else {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number [${maskedIdNumber}], IdName [${IdName}], SP Name [${provider.name}] Invalid service provider`
      );

      return res.status(400).json({
        msg: "Service is currently unavailable, please try again later.",
        isSuccess: false,
      });
    }

    const { isSuccess, isServerError, msg, isDown } = await providerFunc({
      tenantId: Number(tenantId),
      IdNumber,
      maskedIdNumber,
      name: IdName,
    });

    if (!isSuccess) {
      if (!isServerError) {
        return res.status(400).json({
          msg: msg,
          isSuccess: false,
          isDown,
        });
      } else {
        return res.status(500).json({
          msg: CONSTANTS.MSG.ERROR_MESSAGE,
          isSuccess: false,
          isDown,
        });
      }
    }

    return res.status(200).json({
      msg: "PAN verified successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isDown: true,
      isSuccess: false,
    });
  }
};

tenants.UploadSelfi = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "UploadSelfi";

  const file = req.file as Express.Multer.File;

  try {
    const tenantId = req.id;
    const clientId = req.clientId;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}]`);

    if (!file?.path) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Selfi not uploaded correctly`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);

      await fsPromises.unlink(file.path);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    //Pallav - Multi Tenant senerio
    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId: tenant?.id,
    // });
    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenant?.id,
      clientId: clientId,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      await fsPromises.unlink(file.path);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const folderName = `tenant_${occupancy.tenantId}`;
    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;
    const filename = `Selfi.${file.mimetype.split("/")[1]}`;

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const oldPath = `uploads/tmp/${file.filename}`;
    const newPath = `${folderPath}/${filename}`;
    const url = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}/${filename}`;

    await fsPromises.copyFile(oldPath, newPath);

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    if (isExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.SELFI,
        value: url,
        id: isExists.id,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Selfi re-uploaded sucessfully`
      );
    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.SELFI,
        value: url,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Selfi uploaded sucessfully`
      );
    }

    await fsPromises.unlink(oldPath);

    // await tenantDB.updateKycStatus({
    //   id: tenantId,
    //   kycStatus: CONSTANTS.KYC_STATUS.SELFI_UPLOADED,
    // });

    // await occupancyDB.updateKycStatus({
    //   tenantId: tenantId,
    //   kycStatus: CONSTANTS.KYC_STATUS.SELFI_UPLOADED
    // });
    //Pallav - Multi Tenant senerio
    await occupancyDB.updateKycStatusWithClientId({
      tenantId: tenantId,
      clientId: clientId,
      kycStatus: CONSTANTS.KYC_STATUS.SELFI_UPLOADED
    });

    const updatedTenant = await tenantDB.getById({ id: tenantId });

    occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenantId,
      clientId: clientId,
    });
    updatedTenant.kycStatus = occupancy.kycStatus;

    return res.status(200).json({
      msg: "Selfi uploaded sucessfully",
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    await fsPromises.unlink(file.path);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetRentAgreementTerms = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetAgreementTerms";

  try {
    const tenantId = req.id;
    const clientId = req.clientId;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}]`);

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }


    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId: tenant?.id,
    // });
    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenant?.id,
      clientId,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }
    const profilePic = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    const property = await propertyDB.getById({ id: occupancy.propId });
    if (!property) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Property Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (property.isStampPaperRequired === 1) {
      const isStampAvailable = await stampPaperDB.getByClientIdAndPropId({
        clientId,
        propId: property.id,
      });

      if (!isStampAvailable) {
        log.info(`[${C}], [${F}], Client Id [${clientId}], Property Id [${property.id}], Tenant Id [${tenantId}], E-Stamp Enabled, But No Stamp Paper Available, Blocking Agreement Generation`);
        return res.status(400).json({
          msg: "E-Stamp not available, contact the admin",
          isSuccess: false,
        });
      }
    }

    let isAgreementAvailable = false;
    let confTemplateUrl = "";
    let confOwnerName = "";
    let confOwnerSignature = "";
    if (occupancy?.stayType === CONSTANTS.STAY_TYPE.SHORT) {
      let shortStayAgreementAvailable = await clientConfigDB.getClientConfig({
        clientId,
        provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
        type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.SHORT_STAY_AGREEMENTS.IS_AVAILABLE
      });
      if (shortStayAgreementAvailable && Number(shortStayAgreementAvailable?.value) === 1) {
        isAgreementAvailable = true
        let getAgreementTemplateUrl = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.SHORT_STAY_AGREEMENTS.AGREEMENT_URL
        });

        let getAgreementOwnerName = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.SHORT_STAY_AGREEMENTS.OWNER_NAME
        });

        let getAgreementOwnerSignature = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.SHORT_STAY_AGREEMENTS.SIGNATURE_LINK
        });

        const isAgreementConfigAvailable =
          getAgreementTemplateUrl?.value &&
          getAgreementOwnerName?.value &&
          getAgreementOwnerSignature?.value;

        if (!isAgreementConfigAvailable) {
          isAgreementAvailable = false;
        }
        confTemplateUrl = getAgreementTemplateUrl?.value;
        confOwnerName = getAgreementOwnerName?.value;
        confOwnerSignature = getAgreementOwnerSignature?.value;
      }
    }
    const template = await propertyDB.getRentAgreementTemplate({
      clientId: occupancy.clientId,
      propId: property.id,
    });

    if (!template) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] No Template Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }
    let templateUrl = template.path;
    if (isAgreementAvailable) {
      templateUrl = confTemplateUrl;
    }
    let { data: html } = await axios.get(templateUrl);

    if (!html) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] Template found without content`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    html = html.toString();
    // log.info(
    //   `[${C}], [${F}], Tenant Id [${tenantId}], Owner Signature [${template.signaturePath}]`
    // );
    //New Added
    let client = await clientDB.getById({ id: occupancy.clientId });

    let settings = await settingsDB.getByClientIdAndPropId({
      clientId: client.id,
      propId: occupancy.propId,
    });

    let logo = settings?.logo
      ? process.env.RS_LOGO_URI + client?.id + "/" + occupancy?.propId + "/" + settings?.logo
      : client?.logo
        ? process.env.RS_LOGO_URI + client?.id + "/" + client?.logo
        : String(process.env.RS_DEFAULT_LOGO_URI);
    // log.info(
    //   `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], [${client?.logo}], Owner Logo [${logo}]`
    // );

    let propertyName = property.name;
    if (settings?.brandName) {
      propertyName = settings?.brandName;
    }
    const room = await roomDB.getById({ id: occupancy.roomId });
    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
      flatName += `, Room No. ${room.roomNum}`;
    }

    // html = html.replace(/{{letterSpacing}}/g, "0.5");
    // html = html.replace(/{{lineHeight}}/g, "9");
    // html = html.replace(/{{stampImage}}/g, "https://cms.kipinn.com/backend/uploads/dummyEstamp.jpg");
    /*
    Stamp Paper
    */
    if (property.isStampPaperRequired === 1) {
      let stampUrl = process.env.STAMP_DEFAULT_URL;
      // let stamp = await stampPaperDB.getByClientId({
      //   clientId
      // });
      let stamp = await stampPaperDB.getByClientIdAndPropId({
        clientId,
        propId: occupancy.propId,
      });
      if (stamp) {
        stampUrl = stamp?.url;
      }
      html = html.replace(/{{letterSpacing}}/g, "0.5");
      html = html.replace(/{{lineHeight}}/g, "9");
      html = html.replace(/{{stampImage}}/g, stampUrl);
    }
    if (false == profilePic) {
      html = html.replace(/{{tenantPhoto}}/g, "");
    } else {
      html = html.replace(/{{tenantPhoto}}/g, `<img src="${profilePic?.value}" style="width: 100px;"/>`);
    }
    html = html.replace(/{{tenantMobile}}/g, tenant.mobile);

    if (null != tenant.aadharNumber) {
      html = html.replace(/{{tenantAadhar}}/g, `, ID Proof:Aadhar No. ${tenant.aadharNumber}`);
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, `${tenant.aadharNumber}`);
    }
    else {
      html = html.replace(/{{tenantAadhar}}/g, "");
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, "");
    }

    html = html.replace(/{{logo}}/g, logo);
    //html = html.replace(/{{noticePeriod}}/g, occupancy.noticePeriod);
    html = html.replace(/{{unitNum}}/g, flatName);
    if (isAgreementAvailable) {
      html = html.replace(
        /{{ownerSignature}}/g,
        `<img src="${confOwnerSignature}" style="width: 150px;"/>`
      );
    } else {
      html = html.replace(
        /{{ownerSignature}}/g,
        `<img src="${template.signaturePath}" style="width: 150px;"/>`
      );
    }
    html = html.replace(/{{propertyName}}/g, propertyName);
    html = html.replace(/{{institutionName}}/g, tenant.institutionName);
    html = html.replace(/{{alternateMobile}}/g, tenant.alternateMobile);
    html = html.replace(/{{email}}/g, tenant.email);
    //html = html.replace(/{{ownerMobile}}/g, property.ownerMobile);

    html = html.replace(/{{address}}/g, property.address);
    html = html.replace(/{{streetAddress}}/g, property.streetAddress);
    if (occupancy?.stayType === CONSTANTS.STAY_TYPE.SHORT) {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementDate}}/g,
          moment(occupancy.moveInDate).format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementDate}}/g,
          moment(occupancy.moveInDate).format("YYYY-MM-DD")
        );
      }
    } else {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementDate}}/g,
          moment(occupancy.agreementStartDate).format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementDate}}/g,
          moment(occupancy.agreementStartDate).format("YYYY-MM-DD")
        );
      }
    }
    if (isAgreementAvailable) {
      html = html.replace(/{{ownerName}}/g, confOwnerName);
    } else {
      html = html.replace(/{{ownerName}}/g, property.ownerName);
    }
    html = html.replace(/{{ownerAddress}}/g, property.address);
    html = html.replace(/{{tenantName}}/g, tenant.name);
    html = html.replace(/{{tenantAddress}}/g, tenant.address);
    if (clientId === 201 || clientId === 660) {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("MMMM YYYY")
      );
    } else {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("YYYY-MM-DD")
      );
    }

    if (occupancy?.stayType === CONSTANTS.STAY_TYPE.SHORT) {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.moveOutDate)
            .format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.moveOutDate)
            .format("YYYY-MM-DD")
        );
      }
    } else {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.agreementStartDate)
            .add(occupancy.agreementPeriod, "months")
            .subtract(1, "day")
            .format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.agreementStartDate)
            .add(occupancy.agreementPeriod, "months")
            .subtract(1, "day")
            .format("YYYY-MM-DD")
        );
      }
    }

    html = html.replace(/{{monthlyRent}}/g, occupancy.rent);
    html = html.replace(/{{securityDeposit}}/g, occupancy.security);
    html = html.replace(/{{lockInPeriod}}/g, occupancy.lockInPeriod);
    html = html.replace(/{{fatherName}}/g, tenant?.fatherName);
    html = html.replace(/{{tenantSignature}}/g, "<b>Your Signature</b>");
    html = html.replace(/{{parentSignature}}/g, "<b>Parent Signature</b>");
    html = html.replace(/{{secondSignature}}/g, "<b>Your Signature</b>");
    let eqaroTenant = null;
    // let bondAvailable = property?.isBondAvailable || 0;
    let bondAvailable =
      property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
        ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
          ? CONSTANTS.RENTAL_BOND.MANDATORY
          : 0
        : 0;

    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      eqaroTenant = await eqaroTenantsDB.getByTenantId({
        tenantId: tenant?.id,
      });
      if (eqaroTenant?.status === CONSTANTS.EQARO_TENANT_STATUS.INELIGIBLE) {
        bondAvailable = 0;
      }
    }
    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      html = html.replace(
        /{{rentalBondTerms}}/g,
        `<li>That the Landlord has agreed to accept and acknowledge the Rent Bond issued on behalf of Tenant by Eqaro Surety Private Limited for a value upto INR ${eqaroTenant?.bondAmount
        }, dated ${moment(eqaroTenant?.bondEffectiveDate).format(
          "YYYY-MM-DD"
        )}, as consideration in lieu of interest free security deposit, towards letting out the demised premises under this agreement.</li><li>That the Landlord would be liable to handover the possession of the premises to the Tenant only after the duly signing of the present lease agreement and clearance of the security cheque or the submission of Eqaro Rental Bond. </li><li>That the Tenant shall permit the Landlord and/or his representatives and agents or Eqaro Surety Private Limited and/or its representative at all reasonable times to enter upon the demised premises or any portion therefore for the inspection of the premises or to leave notice of defects to be repaired if any.  Upon invocation of claim against Rent Bond, The Landlord shall permit Eqaro Surety Private Limited and/or its representative at all reasonable times to enter the demised premises or any portion therefore for the inspection and claim investigation</li>`
      );
    } else {
      html = html.replace(/{{rentalBondTerms}}/g, "");
    }

    //Adding items to rent agreement
    let roomOption = await roomOptionDB.getById({ id: room.roomOptionId });
    if (roomOption && roomOption.furnishingType != CONSTANTS.ROOM_OPTION_FURNISHING_TYPE.UNFURNISHED) {
      let furnishingBlock = `<div><B><U>Annexure 'A': Schedule of Fittings & Fixtures</U></b></div>
      <p>This document serves as a record of all fittings and fixtures included with the property. It is to be signed by both the <b>Landlord</b> and <b>Tenant</b> as part of the tenancy agreement.</p><p><b>Property Address:</b> ${property.address}</p>`
      let fittings = await fittingsAndFixturesDB.getByClientId({ clientId: occupancy.clientId, propId: occupancy.propId, type: roomOption.furnishingType });
      if (fittings) {
        furnishingBlock += `<p><b>Fittings & Fixtures:</b></p><table style="border:1px solid black; border-collapse:collapse;">`;
        furnishingBlock += `<tr><th style="border:1px solid black;">Item</th><th style="border:1px solid black">No. of Units</th></tr>`;
        fittings.forEach((item: any) => {
          furnishingBlock += `<tr><td style="border:1px solid black;">${item.itemName}</td><td style="border:1px solid black;">${item.itemCount}</td></tr>`;
        });
        furnishingBlock += `</table>`;
      } else {
        furnishingBlock += ``;
      }
      html = html.replace(/{{furnishing}}/g, furnishingBlock);
    } else {
      html = html.replace(/{{furnishing}}/g, "");
    }

    html = html.replace(/\n/g, "");

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Agreement terms sent successfully`
    );

    return res.status(200).json({
      msg: "Agreement terms sent successfully",
      data: html,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.CreateRentAgreement = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "CreateRentAgreement";

  const file = req.file as Express.Multer.File;

  try {
    const tenantId = req.id;
    const clientId = req.clientId;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}]`);

    if (!file?.path) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Rent agreement not uploaded correctly`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Tenant Found`);

      await fsPromises.unlink(file.path);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }
    //Pallav - Multi Tenant senerio
    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId: tenant?.id,
    // });
    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenant?.id,
      clientId: clientId,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      await fsPromises.unlink(file.path);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const profilePic = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    const property = await propertyDB.getById({ id: occupancy.propId });

    if (!property) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Property Found`);
      await fsPromises.unlink(file.path);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let isAgreementAvailable = false;
    let confTemplateUrl = "";
    let confOwnerName = "";
    let confOwnerSignature = "";
    if (occupancy?.stayType === CONSTANTS.STAY_TYPE.SHORT) {
      let shortStayAgreementAvailable = await clientConfigDB.getClientConfig({
        clientId,
        provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
        type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.SHORT_STAY_AGREEMENTS.IS_AVAILABLE
      });
      if (shortStayAgreementAvailable && Number(shortStayAgreementAvailable?.value) === 1) {
        isAgreementAvailable = true
        let getAgreementTemplateUrl = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.SHORT_STAY_AGREEMENTS.AGREEMENT_URL
        });

        let getAgreementOwnerName = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.SHORT_STAY_AGREEMENTS.OWNER_NAME
        });

        let getAgreementOwnerSignature = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.SHORT_STAY_AGREEMENTS.SIGNATURE_LINK
        });

        const isAgreementConfigAvailable =
          getAgreementTemplateUrl?.value &&
          getAgreementOwnerName?.value &&
          getAgreementOwnerSignature?.value;

        if (!isAgreementConfigAvailable) {
          isAgreementAvailable = false;
        }
        confTemplateUrl = getAgreementTemplateUrl?.value;
        confOwnerName = getAgreementOwnerName?.value;
        confOwnerSignature = getAgreementOwnerSignature?.value;
      }
    }

    const template = await propertyDB.getRentAgreementTemplate({
      clientId: occupancy.clientId,
      propId: property.id,
    });

    if (!template) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] No Template Found`
      );
      await fsPromises.unlink(file.path);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    //let { data: html } = await axios.get(template.path);
    let templateUrl = template.path;
    if (isAgreementAvailable) {
      templateUrl = confTemplateUrl;
    }
    let { data: html } = await axios.get(templateUrl);

    if (!html) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] Template found without content`
      );
      await fsPromises.unlink(file.path);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    const folderName = `tenant_${occupancy.tenantId}`;

    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const urlBasePath = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    await fsPromises.copyFile(
      file.path,
      `${folderPath}/tenant_signature.${file.mimetype.split("/")[1]}`
    );
    //IMAGE Buffer Convert
    // const imageBuffer = await fsPromises.readFile(`${folderPath}/tenant_signature.${file.mimetype.split("/")[1]}`);
    // const base64String = imageBuffer.toString("base64");
    // const tenantSignatureBase64 = `data:image/jpeg;base64,${base64String}`;

    const signatureUrl = `${urlBasePath}/tenant_signature.${file.mimetype.split("/")[1]
      }`;
    // log.info(
    //   `[${C}], [${F}], Tenant Id [${tenantId}],  Tenant Signature [${signatureUrl}]`
    // );

    await documentDB.addDocWithStatus({
      tenantId,
      clientId: occupancy.clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      type: CONSTANTS.DOCUMENT_TYPES.SIGNATURE,
      value: signatureUrl,
      status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
    });

    html = html.toString();

    //New Added
    let client = await clientDB.getById({ id: occupancy.clientId });
    let settings = await settingsDB.getByClientIdAndPropId({
      clientId: client.id,
      propId: occupancy.propId,
    });

    let logo = settings?.logo
      ? process.env.RS_LOGO_URI + client?.id + "/" + occupancy?.propId + "/" + settings?.logo
      : client?.logo
        ? process.env.RS_LOGO_URI + client?.id + "/" + client?.logo
        : String(process.env.RS_DEFAULT_LOGO_URI);

    let propertyName = property.name;
    if (settings?.brandName) {
      propertyName = settings?.brandName;
    }

    const room = await roomDB.getById({ id: occupancy.roomId });
    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
      flatName += `, Room No. ${room.roomNum}`;
    }
    /*
    Stamp Paper
    */
    if (property.isStampPaperRequired === 1) {
      let stampUrl = process.env.STAMP_DEFAULT_URL;
      // let stamp = await stampPaperDB.getByClientId({
      //   clientId
      // });

      let stamp = await stampPaperDB.getByClientIdAndPropId({
        clientId,
        propId: occupancy.propId,
      });

      if (stamp) {
        stampUrl = stamp?.url;
      }
      html = html.replace(/{{letterSpacing}}/g, "1");
      html = html.replace(/{{lineHeight}}/g, "12");
      html = html.replace(/{{stampImage}}/g, stampUrl);
      if (stamp) {
        await stampPaperDB.updateAllotment({
          id: stamp?.id,
          tenantId: occupancy?.tenantId,
          propId: occupancy?.propId,
          tenantName: tenant?.name,
          status: CONSTANTS.STAMP_PAPERS_STATUS.ALLOTTED
        })
      } else {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], [ERROR], No stamp found`
        );
      }
    }
    if (false == profilePic) {
      html = html.replace(/{{tenantPhoto}}/g, "");
    } else {
      html = html.replace(/{{tenantPhoto}}/g, `<img src="${profilePic?.value}" style="width: 100px;"/>`);
    }
    html = html.replace(/{{tenantMobile}}/g, tenant.mobile);
    if (null != tenant.aadharNumber) {
      html = html.replace(/{{tenantAadhar}}/g, `, Aadhar No. ${tenant.aadharNumber}`);
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, `${tenant.aadharNumber}`);
    }
    else {
      html = html.replace(/{{tenantAadhar}}/g, "");
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, "");
    }

    html = html.replace(/{{logo}}/g, logo);
    // html = html.replace(/{{noticePeriod}}/g, occupancy.noticePeriod);
    html = html.replace(/{{unitNum}}/g, flatName);
    // html = html.replace(
    //   /{{ownerSignature}}/g,
    //   `<img src="${template.signaturePath}" style="width: 150px;"/>`
    // );
    if (isAgreementAvailable) {
      html = html.replace(
        /{{ownerSignature}}/g,
        `<img src="${confOwnerSignature}" style="width: 150px;"/>`
      );
    } else {
      html = html.replace(
        /{{ownerSignature}}/g,
        `<img src="${template.signaturePath}" style="width: 150px;"/>`
      );
    }
    html = html.replace(/{{propertyName}}/g, propertyName);
    html = html.replace(/{{institutionName}}/g, tenant.institutionName);
    html = html.replace(/{{alternateMobile}}/g, tenant.alternateMobile);
    html = html.replace(/{{email}}/g, tenant.email);
    // html = html.replace(/{{ownerMobile}}/g, property.ownerMobile);

    html = html.replace(/{{address}}/g, property.address);
    html = html.replace(/{{streetAddress}}/g, property.streetAddress);
    // if (clientId === 201 || clientId === 660) {
    //   html = html.replace(
    //     /{{agreementDate}}/g,
    //     moment(occupancy.agreementStartDate).format("MMMM YYYY")
    //   );
    // } else {
    //   html = html.replace(
    //     /{{agreementDate}}/g,
    //     moment(occupancy.agreementStartDate).format("YYYY-MM-DD")
    //   );
    // }
    //html = html.replace(/{{ownerName}}/g, property.ownerName);

    if (occupancy?.stayType === CONSTANTS.STAY_TYPE.SHORT) {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementDate}}/g,
          moment(occupancy.moveInDate).format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementDate}}/g,
          moment(occupancy.moveInDate).format("YYYY-MM-DD")
        );
      }
    } else {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementDate}}/g,
          moment(occupancy.agreementStartDate).format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementDate}}/g,
          moment(occupancy.agreementStartDate).format("YYYY-MM-DD")
        );
      }
    }
    if (isAgreementAvailable) {
      html = html.replace(/{{ownerName}}/g, confOwnerName);
    } else {
      html = html.replace(/{{ownerName}}/g, property.ownerName);
    }
    html = html.replace(/{{ownerAddress}}/g, property.address);
    html = html.replace(/{{tenantName}}/g, tenant.name);
    html = html.replace(/{{tenantAddress}}/g, tenant.address);
    if (clientId === 201 || clientId === 660) {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("MMMM YYYY")
      );
    } else {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("YYYY-MM-DD")
      );
    }
    // if (clientId === 201 || clientId === 660) {
    //   html = html.replace(
    //     /{{agreementEndDate}}/g,
    //     moment(occupancy.agreementStartDate)
    //       .add(occupancy.agreementPeriod, "months")
    //       .format("MMMM YYYY")
    //   );
    // } else {
    //   html = html.replace(
    //     /{{agreementEndDate}}/g,
    //     moment(occupancy.agreementStartDate)
    //       .add(occupancy.agreementPeriod, "months")
    //       .format("YYYY-MM-DD")
    //   );
    // }
    if (occupancy?.stayType === CONSTANTS.STAY_TYPE.SHORT) {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.moveOutDate)
            .format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.moveOutDate)
            .format("YYYY-MM-DD")
        );
      }
    } else {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.agreementStartDate)
            .add(occupancy.agreementPeriod, "months")
            .subtract(1, "day")
            .format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.agreementStartDate)
            .add(occupancy.agreementPeriod, "months")
            .subtract(1, "day")
            .format("YYYY-MM-DD")
        );
      }
    }
    html = html.replace(/{{monthlyRent}}/g, occupancy.rent);
    html = html.replace(/{{securityDeposit}}/g, occupancy.security);
    html = html.replace(/{{lockInPeriod}}/g, occupancy.lockInPeriod);

    html = html.replace(
      /{{tenantSignature}}/g,
      `<img src='${signatureUrl}' width="150"  />`
    );
    let eqaroTenant = null;
    // let bondAvailable = property?.isBondAvailable || 0;
    let bondAvailable =
      property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
        ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
          ? CONSTANTS.RENTAL_BOND.MANDATORY
          : 0
        : 0;

    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      eqaroTenant = await eqaroTenantsDB.getByTenantId({
        tenantId: tenant?.id,
      });
      if (eqaroTenant?.status === CONSTANTS.EQARO_TENANT_STATUS.INELIGIBLE) {
        bondAvailable = 0;
      }
    }
    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      html = html.replace(
        /{{rentalBondTerms}}/g,
        `<li>That the Landlord has agreed to accept and acknowledge the Rent Bond issued on behalf of Tenant by Eqaro Surety Private Limited for a value upto INR ${eqaroTenant?.bondAmount
        }, dated ${moment(eqaroTenant?.bondEffectiveDate).format(
          "YYYY-MM-DD"
        )}, as consideration in lieu of interest free security deposit, towards letting out the demised premises under this agreement.</li><li>That the Landlord would be liable to handover the possession of the premises to the Tenant only after the duly signing of the present lease agreement and clearance of the security cheque or the submission of Eqaro Rental Bond. </li><li>That the Tenant shall permit the Landlord and/or his representatives and agents or Eqaro Surety Private Limited and/or its representative at all reasonable times to enter upon the demised premises or any portion therefore for the inspection of the premises or to leave notice of defects to be repaired if any.  Upon invocation of claim against Rent Bond, The Landlord shall permit Eqaro Surety Private Limited and/or its representative at all reasonable times to enter the demised premises or any portion therefore for the inspection and claim investigation</li>`
      );
    } else {
      html = html.replace(/{{rentalBondTerms}}/g, "");
    }

    //Adding items to rent agreement
    let roomOption = await roomOptionDB.getById({ id: room.roomOptionId });
    if (roomOption && roomOption.furnishingType != CONSTANTS.ROOM_OPTION_FURNISHING_TYPE.UNFURNISHED) {
      let furnishingBlock = `<div><B><U>Annexure 'A': Schedule of Fittings & Fixtures</U></b></div>
      <p>This document serves as a record of all fittings and fixtures included with the property. It is to be signed by both the <b>Landlord</b> and <b>Tenant</b> as part of the tenancy agreement.</p><p><b>Property Address:</b> ${property.address}</p>`;
      let fittings = await fittingsAndFixturesDB.getByClientId({ clientId: occupancy.clientId, propId: occupancy.propId, type: roomOption.furnishingType });
      if (fittings) {
        furnishingBlock += `<p><b>Fittings & Fixtures:</b></p><table style="border:1px solid black; border-collapse:collapse;">`;
        furnishingBlock += `<tr><th style="border:1px solid black;">Item</th><th style="border:1px solid black">No. of Units</th></tr>`;
        fittings.forEach((item: any) => {
          furnishingBlock += `<tr><td style="border:1px solid black;">${item.itemName}</td><td style="border:1px solid black;">${item.itemCount}</td></tr>`;
        });
        furnishingBlock += `</table>`;
      } else {
        furnishingBlock += ``;
      }
      html = html.replace(/{{furnishing}}/g, furnishingBlock);
    } else {
      html = html.replace(/{{furnishing}}/g, "");
    }

    html = html.replace(/\n/g, "");

    const options = {
      format: "A4",
      orientation: "portrait",
      border: "10mm",
      childProcessOptions: {
        env: {
          OPENSSL_CONF: "/dev/null",
        },
      },
    };
    const filename = `RentAgreement${moment().format("YYYYMMDDHHmmss")}.pdf`;
    const url = `${urlBasePath}/${filename}`;

    var document = {
      html: html,
      path: `${folderPath}/${filename}`,
      data: {},
      type: "",
    };

    await pdf.create(document, options);

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
      moveOut: 0,
    });

    if (isExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        id: isExists.id,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], URL [${url}], Rent Agreement Updated Successfully`
      );
    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], URL [${url}], Rent Agreement Created Successfully`
      );
    }

    await fsPromises.unlink(file.path);

    await tenantDB.updateKycStatus({
      id: tenantId,
      kycStatus: CONSTANTS.KYC_STATUS.RENT_AGREEMENT_DONE,
    });
    // await occupancyDB.updateKycStatus({
    //   tenantId: tenantId,
    //   kycStatus: CONSTANTS.KYC_STATUS.RENT_AGREEMENT_DONE
    // });
    await occupancyDB.updateKycStatusWithClientId({
      tenantId: tenantId,
      clientId: occupancy.clientId,
      kycStatus: CONSTANTS.KYC_STATUS.RENT_AGREEMENT_DONE
    });

    // Update rent agreement status for this occupancy record
    await occupancyDB.setRentAgreementStatus({
      tenantId: tenantId,
      clientId: occupancy.clientId,
      isRentAgreementSigned: 1
    });

    const updatedTenant = await tenantDB.getById({ id: tenantId });

    return res.status(200).json({
      msg: "Rent Agreement Created Successfully",
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    await fsPromises.unlink(file.path);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.CreateRentAgreementWithParentSignature = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "CreateRentAgreementWithParentSignature";

  const files = req.files as Express.Multer.File[];
  const removeTmpImages = async () => {
    for (const file of files) {
      try {
        await fsPromises.unlink(file.path);
      } catch (err) { }
    }
  };

  try {
    const tenantId = req.id;
    const clientId = req.clientId;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}]`);

    if (!files[0]) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Rent agreement not uploaded correctly`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Tenant Found`);

      await removeTmpImages();
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }
    //Pallav - Multi Tenant senerio
    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId: tenant?.id,
    // });
    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenant?.id,
      clientId: clientId,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      await removeTmpImages();
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const profilePic = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    const property = await propertyDB.getById({ id: occupancy.propId });

    if (!property) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Property Found`);
      await removeTmpImages();
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const template = await propertyDB.getRentAgreementTemplate({
      clientId: occupancy.clientId,
      propId: property.id,
    });

    if (!template) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] No Template Found`
      );
      await removeTmpImages();
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    let { data: html } = await axios.get(template.path);

    if (!html) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] Template found without content`
      );
      await removeTmpImages();
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    const folderName = `tenant_${occupancy.tenantId}`;

    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const urlBasePath = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    await fsPromises.copyFile(
      files[0].path,
      `${folderPath}/tenant_signature.${files[0].mimetype.split("/")[1]}`
    );
    //IMAGE Buffer Convert
    // const imageBuffer = await fsPromises.readFile(`${folderPath}/tenant_signature.${file.mimetype.split("/")[1]}`);
    // const base64String = imageBuffer.toString("base64");
    // const tenantSignatureBase64 = `data:image/jpeg;base64,${base64String}`;

    const signatureUrl = `${urlBasePath}/tenant_signature.${files[0].mimetype.split("/")[1]}`;
    // log.info(
    //   `[${C}], [${F}], Tenant Id [${tenantId}],  Tenant Signature [${signatureUrl}]`
    // );

    await documentDB.addDocWithStatus({
      tenantId,
      clientId: occupancy.clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      type: CONSTANTS.DOCUMENT_TYPES.SIGNATURE,
      value: signatureUrl,
      status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
    });

    await fsPromises.copyFile(
      files[1].path,
      `${folderPath}/parent_signature.${files[1].mimetype.split("/")[1]}`
    );

    const parentSignatureUrl = `${urlBasePath}/parent_signature.${files[1].mimetype.split("/")[1]}`;

    await documentDB.addDocWithStatus({
      tenantId,
      clientId: occupancy.clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      type: CONSTANTS.DOCUMENT_TYPES.PARENT_SIGNATURE,
      value: parentSignatureUrl,
      status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
    });

    html = html.toString();

    //New Added
    let client = await clientDB.getById({ id: occupancy.clientId });
    let settings = await settingsDB.getByClientIdAndPropId({
      clientId: client.id,
      propId: occupancy.propId,
    });

    let logo = settings?.logo
      ? process.env.RS_LOGO_URI + client?.id + "/" + occupancy?.propId + "/" + settings?.logo
      : client?.logo
        ? process.env.RS_LOGO_URI + client?.id + "/" + client?.logo
        : String(process.env.RS_DEFAULT_LOGO_URI);

    let propertyName = property.name;
    if (settings?.brandName) {
      propertyName = settings?.brandName;
    }

    const room = await roomDB.getById({ id: occupancy.roomId });
    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
      flatName += `, Room No. ${room.roomNum}`;
    }


    if (false == profilePic) {
      html = html.replace(/{{tenantPhoto}}/g, "");
    } else {
      html = html.replace(/{{tenantPhoto}}/g, `<img src="${profilePic?.value}" style="width: 100px;"/>`);
    }
    html = html.replace(/{{tenantMobile}}/g, tenant.mobile);
    if (null != tenant.aadharNumber) {
      html = html.replace(/{{tenantAadhar}}/g, `, Aadhar No. ${tenant.aadharNumber}`);
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, `${tenant.aadharNumber}`);
    }
    else {
      html = html.replace(/{{tenantAadhar}}/g, "");
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, "");
    }

    html = html.replace(/{{logo}}/g, logo);
    // html = html.replace(/{{noticePeriod}}/g, occupancy.noticePeriod);
    html = html.replace(/{{unitNum}}/g, flatName);
    html = html.replace(
      /{{ownerSignature}}/g,
      `<img src="${template.signaturePath}" style="width: 150px;"/>`
    );
    html = html.replace(/{{propertyName}}/g, propertyName);
    html = html.replace(/{{institutionName}}/g, tenant.institutionName);
    html = html.replace(/{{alternateMobile}}/g, tenant.alternateMobile);
    html = html.replace(/{{email}}/g, tenant.email);
    // html = html.replace(/{{ownerMobile}}/g, property.ownerMobile);

    html = html.replace(/{{address}}/g, property.address);
    html = html.replace(/{{streetAddress}}/g, property.streetAddress);
    if (clientId === 201 || clientId === 660) {
      html = html.replace(
        /{{agreementDate}}/g,
        moment(occupancy.agreementStartDate).format("MMMM YYYY")
      );
    } else {
      html = html.replace(
        /{{agreementDate}}/g,
        moment(occupancy.agreementStartDate).format("YYYY-MM-DD")
      );
    }
    html = html.replace(/{{ownerName}}/g, property.ownerName);
    html = html.replace(/{{ownerAddress}}/g, property.address);
    html = html.replace(/{{tenantName}}/g, tenant.name);
    html = html.replace(/{{tenantAddress}}/g, tenant.address);
    if (clientId === 201 || clientId === 660) {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("MMMM YYYY")
      );
    } else {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("YYYY-MM-DD")
      );
    }
    if (occupancy?.stayType === CONSTANTS.STAY_TYPE.SHORT) {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.moveOutDate)
            .format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.moveOutDate)
            .format("YYYY-MM-DD")
        );
      }
    } else {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.agreementStartDate)
            .add(occupancy.agreementPeriod, "months")
            .subtract(1, "day")
            .format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.agreementStartDate)
            .add(occupancy.agreementPeriod, "months")
            .subtract(1, "day")
            .format("YYYY-MM-DD")
        );
      }
    }
    html = html.replace(/{{monthlyRent}}/g, occupancy.rent);
    html = html.replace(/{{securityDeposit}}/g, occupancy.security);
    html = html.replace(/{{lockInPeriod}}/g, occupancy.lockInPeriod);
    html = html.replace(/{{fatherName}}/g, tenant?.fatherName);

    html = html.replace(
      /{{tenantSignature}}/g,
      `<img src='${signatureUrl}' width="150"  />`
    );
    html = html.replace(
      /{{parentSignature}}/g,
      `<img src='${parentSignatureUrl}' width="150"  />`
    );
    let eqaroTenant = null;
    // let bondAvailable = property?.isBondAvailable || 0;
    let bondAvailable =
      property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
        ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
          ? CONSTANTS.RENTAL_BOND.MANDATORY
          : 0
        : 0;

    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      eqaroTenant = await eqaroTenantsDB.getByTenantId({
        tenantId: tenant?.id,
      });
      if (eqaroTenant?.status === CONSTANTS.EQARO_TENANT_STATUS.INELIGIBLE) {
        bondAvailable = 0;
      }
    }
    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      html = html.replace(
        /{{rentalBondTerms}}/g,
        `<li>That the Landlord has agreed to accept and acknowledge the Rent Bond issued on behalf of Tenant by Eqaro Surety Private Limited for a value upto INR ${eqaroTenant?.bondAmount
        }, dated ${moment(eqaroTenant?.bondEffectiveDate).format(
          "YYYY-MM-DD"
        )}, as consideration in lieu of interest free security deposit, towards letting out the demised premises under this agreement.</li><li>That the Landlord would be liable to handover the possession of the premises to the Tenant only after the duly signing of the present lease agreement and clearance of the security cheque or the submission of Eqaro Rental Bond. </li><li>That the Tenant shall permit the Landlord and/or his representatives and agents or Eqaro Surety Private Limited and/or its representative at all reasonable times to enter upon the demised premises or any portion therefore for the inspection of the premises or to leave notice of defects to be repaired if any.  Upon invocation of claim against Rent Bond, The Landlord shall permit Eqaro Surety Private Limited and/or its representative at all reasonable times to enter the demised premises or any portion therefore for the inspection and claim investigation</li>`
      );
    } else {
      html = html.replace(/{{rentalBondTerms}}/g, "");
    }

    //Adding items to rent agreement
    let roomOption = await roomOptionDB.getById({ id: room.roomOptionId });
    if (roomOption && roomOption.furnishingType != CONSTANTS.ROOM_OPTION_FURNISHING_TYPE.UNFURNISHED) {
      let furnishingBlock = `<div><B><U>Annexure 'A': Schedule of Fittings & Fixtures</U></b></div>
      <p>This document serves as a record of all fittings and fixtures included with the property. It is to be signed by both the <b>Landlord</b> and <b>Tenant</b> as part of the tenancy agreement.</p><p><b>Property Address:</b> ${property.address}</p>`;
      let fittings = await fittingsAndFixturesDB.getByClientId({ clientId: occupancy.clientId, propId: occupancy.propId, type: roomOption.furnishingType });
      if (fittings) {
        furnishingBlock += `<p><b>Fittings & Fixtures:</b></p><table style="border:1px solid black; border-collapse:collapse;">`;
        furnishingBlock += `<tr><th style="border:1px solid black;">Item</th><th style="border:1px solid black">No. of Units</th></tr>`;
        fittings.forEach((item: any) => {
          furnishingBlock += `<tr><td style="border:1px solid black;">${item.itemName}</td><td style="border:1px solid black;">${item.itemCount}</td></tr>`;
        });
        furnishingBlock += `</table>`;
      } else {
        furnishingBlock += ``;
      }
      html = html.replace(/{{furnishing}}/g, furnishingBlock);
    } else {
      html = html.replace(/{{furnishing}}/g, "");
    }

    html = html.replace(/\n/g, "");

    const options = {
      format: "A4",
      orientation: "portrait",
      border: "10mm",
      childProcessOptions: {
        env: {
          OPENSSL_CONF: "/dev/null",
        },
      },
    };
    const filename = `RentAgreement${moment().format("YYYYMMDDHHmmss")}.pdf`;
    const url = `${urlBasePath}/${filename}`;

    var document = {
      html: html,
      path: `${folderPath}/${filename}`,
      data: {},
      type: "",
    };

    await pdf.create(document, options);

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
      moveOut: 0,
    });

    if (isExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        id: isExists.id,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], URL [${url}], Rent Agreement Updated Successfully`
      );
    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], URL [${url}], Rent Agreement Created Successfully`
      );
    }

    await removeTmpImages();

    await tenantDB.updateKycStatus({
      id: tenantId,
      kycStatus: CONSTANTS.KYC_STATUS.RENT_AGREEMENT_DONE,
    });
    // await occupancyDB.updateKycStatus({
    //   tenantId: tenantId,
    //   kycStatus: CONSTANTS.KYC_STATUS.RENT_AGREEMENT_DONE
    // });
    await occupancyDB.updateKycStatusWithClientId({
      tenantId: tenantId,
      clientId: occupancy.clientId,
      kycStatus: CONSTANTS.KYC_STATUS.RENT_AGREEMENT_DONE
    });

    // Update rent agreement status for this occupancy record
    await occupancyDB.setRentAgreementStatus({
      tenantId: tenantId,
      clientId: occupancy.clientId,
      isRentAgreementSigned: 1
    });

    const updatedTenant = await tenantDB.getById({ id: tenantId });

    return res.status(200).json({
      msg: "Rent Agreement Created Successfully",
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    await removeTmpImages();

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.CreateRentAgreementWithMultiSignature = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "CreateRentAgreementWithMultiSignature";

  const files = req.files as Express.Multer.File[];
  const removeTmpImages = async () => {
    for (const file of files) {
      try {
        await fsPromises.unlink(file.path);
      } catch (err) { }
    }
  };

  try {
    const tenantId = req.id;
    const clientId = req.clientId;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}]`);

    if (!files[0]) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Rent agreement not uploaded correctly`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Tenant Found`);

      await removeTmpImages();
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }
    //Pallav - Multi Tenant senerio
    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId: tenant?.id,
    // });
    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenant?.id,
      clientId: clientId,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      await removeTmpImages();
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const profilePic = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    const property = await propertyDB.getById({ id: occupancy.propId });

    if (!property) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Property Found`);
      await removeTmpImages();
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const template = await propertyDB.getRentAgreementTemplate({
      clientId: occupancy.clientId,
      propId: property.id,
    });

    if (!template) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] No Template Found`
      );
      await removeTmpImages();
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    let { data: html } = await axios.get(template.path);

    if (!html) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] Template found without content`
      );
      await removeTmpImages();
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    const folderName = `tenant_${occupancy.tenantId}`;

    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const urlBasePath = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    await fsPromises.copyFile(
      files[0].path,
      `${folderPath}/tenant_signature.${files[0].mimetype.split("/")[1]}`
    );
    //IMAGE Buffer Convert
    // const imageBuffer = await fsPromises.readFile(`${folderPath}/tenant_signature.${file.mimetype.split("/")[1]}`);
    // const base64String = imageBuffer.toString("base64");
    // const tenantSignatureBase64 = `data:image/jpeg;base64,${base64String}`;

    const signatureUrl = `${urlBasePath}/tenant_signature.${files[0].mimetype.split("/")[1]}`;
    // log.info(
    //   `[${C}], [${F}], Tenant Id [${tenantId}],  Tenant Signature [${signatureUrl}]`
    // );

    await documentDB.addDocWithStatus({
      tenantId,
      clientId: occupancy.clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      type: CONSTANTS.DOCUMENT_TYPES.SIGNATURE,
      value: signatureUrl,
      status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
    });

    await fsPromises.copyFile(
      files[1].path,
      `${folderPath}/second_signature.${files[1].mimetype.split("/")[1]}`
    );

    const secondSignatureUrl = `${urlBasePath}/second_signature.${files[1].mimetype.split("/")[1]}`;

    // await documentDB.addDocWithStatus({
    //   tenantId,
    //   clientId: occupancy.clientId,
    //   propId: occupancy.propId,
    //   roomId: occupancy.roomId,
    //   type: CONSTANTS.DOCUMENT_TYPES.PARENT_SIGNATURE,
    //   value: parentSignatureUrl,
    //   status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
    // });

    html = html.toString();

    //New Added
    let client = await clientDB.getById({ id: occupancy.clientId });
    let settings = await settingsDB.getByClientIdAndPropId({
      clientId: client.id,
      propId: occupancy.propId,
    });

    let logo = settings?.logo
      ? process.env.RS_LOGO_URI + client?.id + "/" + occupancy?.propId + "/" + settings?.logo
      : client?.logo
        ? process.env.RS_LOGO_URI + client?.id + "/" + client?.logo
        : String(process.env.RS_DEFAULT_LOGO_URI);

    let propertyName = property.name;
    if (settings?.brandName) {
      propertyName = settings?.brandName;
    }

    const room = await roomDB.getById({ id: occupancy.roomId });
    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
      flatName += `, Room No. ${room.roomNum}`;
    }


    if (false == profilePic) {
      html = html.replace(/{{tenantPhoto}}/g, "");
    } else {
      html = html.replace(/{{tenantPhoto}}/g, `<img src="${profilePic?.value}" style="width: 100px;"/>`);
    }
    html = html.replace(/{{tenantMobile}}/g, tenant.mobile);
    if (null != tenant.aadharNumber) {
      html = html.replace(/{{tenantAadhar}}/g, `, Aadhar No. ${tenant.aadharNumber}`);
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, `${tenant.aadharNumber}`);
    }
    else {
      html = html.replace(/{{tenantAadhar}}/g, "");
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, "");
    }

    html = html.replace(/{{logo}}/g, logo);
    // html = html.replace(/{{noticePeriod}}/g, occupancy.noticePeriod);
    html = html.replace(/{{unitNum}}/g, flatName);
    html = html.replace(
      /{{ownerSignature}}/g,
      `<img src="${template.signaturePath}" style="width: 150px;"/>`
    );
    html = html.replace(/{{propertyName}}/g, propertyName);
    html = html.replace(/{{institutionName}}/g, tenant.institutionName);
    html = html.replace(/{{alternateMobile}}/g, tenant.alternateMobile);
    html = html.replace(/{{email}}/g, tenant.email);
    // html = html.replace(/{{ownerMobile}}/g, property.ownerMobile);

    html = html.replace(/{{address}}/g, property.address);
    html = html.replace(/{{streetAddress}}/g, property.streetAddress);
    if (clientId === 201 || clientId === 660) {
      html = html.replace(
        /{{agreementDate}}/g,
        moment(occupancy.agreementStartDate).format("MMMM YYYY")
      );
    } else {
      html = html.replace(
        /{{agreementDate}}/g,
        moment(occupancy.agreementStartDate).format("YYYY-MM-DD")
      );
    }
    html = html.replace(/{{ownerName}}/g, property.ownerName);
    html = html.replace(/{{ownerAddress}}/g, property.address);
    html = html.replace(/{{tenantName}}/g, tenant.name);
    html = html.replace(/{{tenantAddress}}/g, tenant.address);
    if (clientId === 201 || clientId === 660) {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("MMMM YYYY")
      );
    } else {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("YYYY-MM-DD")
      );
    }
    if (occupancy?.stayType === CONSTANTS.STAY_TYPE.SHORT) {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.moveOutDate)
            .format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.moveOutDate)
            .format("YYYY-MM-DD")
        );
      }
    } else {
      if (clientId === 201 || clientId === 660) {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.agreementStartDate)
            .add(occupancy.agreementPeriod, "months")
            .subtract(1, "day")
            .format("MMMM YYYY")
        );
      } else {
        html = html.replace(
          /{{agreementEndDate}}/g,
          moment(occupancy.agreementStartDate)
            .add(occupancy.agreementPeriod, "months")
            .subtract(1, "day")
            .format("YYYY-MM-DD")
        );
      }
    }
    html = html.replace(/{{monthlyRent}}/g, occupancy.rent);
    html = html.replace(/{{securityDeposit}}/g, occupancy.security);
    html = html.replace(/{{lockInPeriod}}/g, occupancy.lockInPeriod);
    html = html.replace(/{{fatherName}}/g, tenant?.fatherName);

    html = html.replace(
      /{{tenantSignature}}/g,
      `<img src='${signatureUrl}' width="150"  />`
    );
    html = html.replace(
      /{{secondSignature}}/g,
      `<img src='${secondSignatureUrl}' width="150"  />`
    );
    let eqaroTenant = null;
    // let bondAvailable = property?.isBondAvailable || 0;
    let bondAvailable =
      property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
        ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
          ? CONSTANTS.RENTAL_BOND.MANDATORY
          : 0
        : 0;

    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      eqaroTenant = await eqaroTenantsDB.getByTenantId({
        tenantId: tenant?.id,
      });
      if (eqaroTenant?.status === CONSTANTS.EQARO_TENANT_STATUS.INELIGIBLE) {
        bondAvailable = 0;
      }
    }
    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      html = html.replace(
        /{{rentalBondTerms}}/g,
        `<li>That the Landlord has agreed to accept and acknowledge the Rent Bond issued on behalf of Tenant by Eqaro Surety Private Limited for a value upto INR ${eqaroTenant?.bondAmount
        }, dated ${moment(eqaroTenant?.bondEffectiveDate).format(
          "YYYY-MM-DD"
        )}, as consideration in lieu of interest free security deposit, towards letting out the demised premises under this agreement.</li><li>That the Landlord would be liable to handover the possession of the premises to the Tenant only after the duly signing of the present lease agreement and clearance of the security cheque or the submission of Eqaro Rental Bond. </li><li>That the Tenant shall permit the Landlord and/or his representatives and agents or Eqaro Surety Private Limited and/or its representative at all reasonable times to enter upon the demised premises or any portion therefore for the inspection of the premises or to leave notice of defects to be repaired if any.  Upon invocation of claim against Rent Bond, The Landlord shall permit Eqaro Surety Private Limited and/or its representative at all reasonable times to enter the demised premises or any portion therefore for the inspection and claim investigation</li>`
      );
    } else {
      html = html.replace(/{{rentalBondTerms}}/g, "");
    }

    //Adding items to rent agreement
    let roomOption = await roomOptionDB.getById({ id: room.roomOptionId });
    if (roomOption && roomOption.furnishingType != CONSTANTS.ROOM_OPTION_FURNISHING_TYPE.UNFURNISHED) {
      let furnishingBlock = `<div><B><U>Annexure 'A': Schedule of Fittings & Fixtures</U></b></div>
      <p>This document serves as a record of all fittings and fixtures included with the property. It is to be signed by both the <b>Landlord</b> and <b>Tenant</b> as part of the tenancy agreement.</p><p><b>Property Address:</b> ${property.address}</p>`;
      let fittings = await fittingsAndFixturesDB.getByClientId({ clientId: occupancy.clientId, propId: occupancy.propId, type: roomOption.furnishingType });
      if (fittings) {
        furnishingBlock += `<p><b>Fittings & Fixtures:</b></p><table style="border:1px solid black; border-collapse:collapse;">`;
        furnishingBlock += `<tr><th style="border:1px solid black;">Item</th><th style="border:1px solid black">No. of Units</th></tr>`;
        fittings.forEach((item: any) => {
          furnishingBlock += `<tr><td style="border:1px solid black;">${item.itemName}</td><td style="border:1px solid black;">${item.itemCount}</td></tr>`;
        });
        furnishingBlock += `</table>`;
      } else {
        furnishingBlock += ``;
      }
      html = html.replace(/{{furnishing}}/g, furnishingBlock);
    } else {
      html = html.replace(/{{furnishing}}/g, "");
    }

    html = html.replace(/\n/g, "");

    const options = {
      format: "A4",
      orientation: "portrait",
      border: "10mm",
      childProcessOptions: {
        env: {
          OPENSSL_CONF: "/dev/null",
        },
      },
    };
    const filename = `RentAgreement${moment().format("YYYYMMDDHHmmss")}.pdf`;
    const url = `${urlBasePath}/${filename}`;

    var document = {
      html: html,
      path: `${folderPath}/${filename}`,
      data: {},
      type: "",
    };

    await pdf.create(document, options);

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
      moveOut: 0,
    });

    if (isExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        id: isExists.id,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], URL [${url}], Rent Agreement Updated Successfully`
      );
    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], URL [${url}], Rent Agreement Created Successfully`
      );
    }

    await removeTmpImages();

    await tenantDB.updateKycStatus({
      id: tenantId,
      kycStatus: CONSTANTS.KYC_STATUS.RENT_AGREEMENT_DONE,
    });
    // await occupancyDB.updateKycStatus({
    //   tenantId: tenantId,
    //   kycStatus: CONSTANTS.KYC_STATUS.RENT_AGREEMENT_DONE
    // });
    await occupancyDB.updateKycStatusWithClientId({
      tenantId: tenantId,
      clientId: occupancy.clientId,
      kycStatus: CONSTANTS.KYC_STATUS.RENT_AGREEMENT_DONE
    });

    // Update rent agreement status for this occupancy record
    await occupancyDB.setRentAgreementStatus({
      tenantId: tenantId,
      clientId: occupancy.clientId,
      isRentAgreementSigned: 1
    });

    const updatedTenant = await tenantDB.getById({ id: tenantId });

    return res.status(200).json({
      msg: "Rent Agreement Created Successfully",
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    await removeTmpImages();

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.MoveInInspectionTypeList = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "MoveInInspectionTypeList";

  try {
    const tenantId = req.id;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}]`);

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenant?.id,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const typeList = await moveInDB.getTypeList({
      clientId: occupancy.clientId,
      propId: occupancy.propId,
      tenantId: tenantId,
    });

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Move-in inspection type list has been sent successfully`
    );

    return res.status(200).json({
      msg: "Move-in inspection type list has been sent successfully",
      data: typeList || [],
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.MoveInInspection = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "MoveInInspection";
  const files = req.files as Express.Multer.File[];

  const removeTmpImages = async () => {
    for (const file of files) {
      try {
        await fsPromises.unlink(file.path);
      } catch (err) { }
    }
  };

  try {
    const { desc, typeId } = req.body;
    const tenantId = req.id;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], Description [${desc}]`
    );

    if (!files) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], Description [${desc}], No files uploaded`
      );
      return res
        .status(400)
        .json({ msg: "No files uploaded", isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], Description [${desc}], No Tenant Found`
      );

      await removeTmpImages();
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenant?.id,
    });

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], Description [${desc}], No Occupancy Found`
      );

      await removeTmpImages();

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const folderName = `move_in_images`;
    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/tenant_${occupancy.tenantId}/${folderName}`;

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const moveInInspectionId = await moveInDB.add({
      tenantId,
      roomId: occupancy.roomId,
      propId: occupancy.propId,
      clientId: occupancy.clientId,
      comment: desc,
      typeId,
    });

    for (const file of files) {
      try {
        const oldPath = `uploads/tmp/${file.filename}`;
        const newPath = `${folderPath}/${file.filename}`;

        const url = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/tenant_${occupancy.tenantId}/${folderName}/${file.filename}`;

        await fsPromises.copyFile(oldPath, newPath);

        await imageDB.add({
          tenantId,
          roomId: occupancy.roomId,
          moveInInspectionId,
          url,
        });

        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], Description [${desc}], Images uploaded successfully`
        );

        await fsPromises.unlink(oldPath);
      } catch (error: any) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], Description [${desc}], Error while copying file: ${error?.message || error
          }`
        );
        await removeTmpImages();
      }
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], Description [${desc}], Move-In Inspection has been done successfully`
    );

    const updatedTenant = await tenantDB.getById({ id: tenantId });
    return res.status(200).json({
      msg: "Move-In Inspection has been done successfully",
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    await removeTmpImages();

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetMoveInInspectionImages = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "GetMoveInInspectionImages";

  try {
    const tenantId = req.id;
    const { typeId } = req.params;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}]`);

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], No Tenant Found`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenant?.id,
    });

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], No Occupancy Found`
      );

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const moveinDetails = await moveInDB.getMovinDetails({
      tenantId,
      typeId,
      clientId: occupancy.clientId,
    });

    if (moveinDetails) {
      let moveInImage = await imageDB.getByMoveInId({
        moveInInspectionId: moveinDetails.id,
      });

      moveinDetails.images = moveInImage;
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Type Id [${typeId}], Move-in inspection details has been done successfully`
    );

    return res.status(200).json({
      msg: "Move-in inspection details has been done successfully",
      data: moveinDetails || null,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.Dashboard = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "Dashboard";

  try {
    // const { isEvicted=0 } = req.query;
    let isEvicted = req.isEvicted;
    const tenantId = req.id;
    const clientId = req.clientId;
    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Is Evicted [${isEvicted}]`);
    if (!clientId) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Client Id Found in Token`);
      return res.status(401).json({
        msg: "Session Expired",
        isSuccess: false,
      });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }
    await tenantDB.updateLastLogin({id: tenantId});
    // let occupancy = await occupancyDB.getByTenantId({
    //   tenantId: tenant.id,
    // });
    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenant.id,
      clientId: clientId,
    });

    if (Number(isEvicted) === 1) {
      // occupancy = await moveOutDB.getMovedOutTenants({
      //   tenantId: tenant.id,
      // });
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        tenantId: tenant.id,
        clientId: clientId,
      });
    }

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      //Sending 401 status in case of tenant eviction.
      return res
        .status(401)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let property = await propertyDB.getById({ id: occupancy.propId });

    if (occupancy.flatId) {
      const flat = await flatDB.getById({
        id: occupancy.flatId,
      });
      if (flat?.wifiPassword) {
        property.wifiPassword = flat?.wifiPassword;
      }
    }

    const client = await clientDB.getById({ id: property.clientId });

    let bank = null;
    if (property?.bankId) {
      bank = await bankDB.getById({
        id: property?.bankId,
      });

      if (!bank) bank = null;
    }

    const {
      id: roomId,
      floor,
      roomNum,
      type: roomType,
      flatId,
    } = await roomDB.getById({ id: occupancy.roomId });

    let flatName = "";

    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: flatId });
      flatName = "Flat No " + name;
    } else {
      flatName = floor === "G" ? "Ground Floor" : "Floor " + floor;
    }

    let documents = await documentDB.getByTenantIdAndClientId({
      tenantId: tenant.id,
      clientId: client.id,
      moveOut: 0,
    });

    if (Number(isEvicted) === 1) {
      documents = await documentDB.getByTenantIdAndClientIdMovedOut({
        tenantId: tenant.id,
        clientId: client.id,
        moveOut: 1,
      });
    }
    // log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Documents Fetched: ${JSON.stringify(documents)}`);
    if (documents && documents.length > 0) {
      for (let doc of documents) {
        const title = await getDocumentTitle(doc.type);
        doc.title = title;
      }
    }
    let totalDues = 0;
    let rentDues = 0;
    if (Number(isEvicted) === 1) {
      let allDues = await moveOutDuesDB.getTotalDuesByTenantId({
        tenantId: tenant.id,
        propId: occupancy.propId,
      });
      totalDues = allDues?.totalDues || 0;
      let allRent = await moveOutDuesDB.getTotalRentDuesByTenantId({
        tenantId: tenant.id,
        propId: occupancy.propId,
      });
      rentDues = allRent?.totalDues || 0;
    } else {
      let allDues = await duesDB.getTotalDuesByTenantId({
        tenantId: tenant.id,
        propId: occupancy.propId,
      });
      totalDues = allDues?.totalDues || 0;
      let allRent = await duesDB.getTotalRentDuesByTenantId ({
        tenantId: tenant.id,
        propId: occupancy.propId,
      });
      rentDues = allRent?.totalDues || 0;
    }

    const notices = await noticeDB.getByPropIdAndLimit({
      propId: property.id,
      limit: 3,
    });

    const allNotices = await noticeDB.getByPropId({
      propId: property.id,
    });

    if (notices && notices.length > 0) {
      for (let notice of allNotices) {
        const noticeViewed = await noticeDB.isNoticeViewed({
          clientId: occupancy.clientId,
          tenantId: tenant.id,
          noticeId: notice.id,
        });

        if (!noticeViewed) {
          await noticeDB.addView({
            clientId: occupancy.clientId,
            tenantId: tenant.id,
            noticeId: notice.id,
          })
        }
      }
    }

    let isMoveOutPending = false;
    let moveOutRequestId = null;

    // if (tenant.status !== CONSTANTS.TENANT_STATUS.MOVING_OUT) {
    if (occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
      const request = await requestDB.getByTenantId({
        tenantId: tenant.id,
        type: CONSTANTS.REQUEST_TYPE.MOVE_OUT,
        status: CONSTANTS.REQUEST_STATUS.PENDING,
      });

      if (request) {
        isMoveOutPending = true;
        moveOutRequestId = request.id;
      }
    }

    let moveInInspection = await moveInDB.getByTenantIdandClientId({
      tenantId: tenant.id,
      clientId: occupancy?.clientId,
    });

    if (moveInInspection) {
      let moveInImage = await imageDB.getByMoveInId({
        moveInInspectionId: moveInInspection.id,
      });

      moveInInspection.images = moveInImage;
    }

    let bondAvailable =
      property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
        ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
          ? CONSTANTS.RENTAL_BOND.MANDATORY
          : 0
        : 0;

    let eqaroStatus = 0;

    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      const eqaroTenant = await eqaroTenantsDB.getByTenantId({
        tenantId: tenant?.id,
      });
      if (!eqaroTenant) {
        eqaroStatus = 0;
      } else {
        eqaroStatus = eqaroTenant?.status;
      }
    }

    const isFoodAvailable = await foodDB.isFoodAvailableForTenant({
      propId: property.id,
      clientId: occupancy.clientId,
    });

    // const currentAppVersion = CONSTANTS.CURRENT_TENANT_VERSION;

    const configTenantAppVersion = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.TENANT_APP_VERSION,
    });

    let currentAppVersion = CONSTANTS.CURRENT_TENANT_VERSION;

    if (configTenantAppVersion) {
      currentAppVersion = configTenantAppVersion?.value || CONSTANTS.CURRENT_TENANT_VERSION;
    }

    if (Number(isEvicted) === 1) {
      tenant.status = 8; //moved out tenant
      tenant.kycStatus = occupancy.kycStatus;
      tenant.statusx = occupancy.status;
    }
    else {//Sukhbir - MultiOccupancy
      tenant.status = mapOccupancyStatusToTenantStatus(occupancy.status);
      tenant.statusx = occupancy.status;
      tenant.kycStatus = occupancy.kycStatus;
    }

    const refundLedgerEntry = await ledgerDB.getLastRefundEntry({
      tenantId,
      clientId,
      moveInDate: occupancy.moveInDate,
    });

    const refundRequest = await requestDB.getByTenantIdAndOccupancyIdAndType({
      clientId,
      tenantId,
      occupancyId: occupancy.id,
      // status: CONSTANTS.REQUEST_STATUS.PENDING,
      type: CONSTANTS.REQUEST_TYPE.REFUND,
    });

    let refundRejectionReason = null;
    if (refundRequest && (refundRequest?.status === CONSTANTS.REQUEST_STATUS.REJECTED || refundRequest?.status === CONSTANTS.REQUEST_STATUS.REOPEN_ALLOWED)) {
      refundRejectionReason = refundRequest?.reason;
    }

    let totalSecurityReceived = await ledgerDB.getSecurityTransactionAmountX({
      tenantId,
      type: CONSTANTS.DUES_TYPES.SECURITY,
      roomId: occupancy.roomId,
      propId: occupancy.propId,
    });

    let securityReceived = Math.abs(totalSecurityReceived.amount) || 0

    let refundedAmount = await transactionDB.getRefundedAmountByClientIdAndTenantId({
      clientId,
      tenantId,
      createdAt: occupancy?.moveInDate,
    });

    let excessAmount = 0;

    let collection = 0;
    let advance = 0;
    let total = 0;
    if (isEvicted) {
      const { totalCollection, advancePaid } =
        await ledgerDB.getTotalByTenantIdForMovingOut({
          tenantId: occupancy.tenantId,
          clientId,
        });
      collection = totalCollection;
      advance = advancePaid;

      collection = collection || 0;
      advance = advance || 0;

      if (advance < 0 && collection >= 0) {
        total = Number(collection) + Number(advance);
      } else if (advance < 0 && collection < 0) {
        total = Number(advance);
      } else {
        total = Number(collection);
      }
    }
    excessAmount = total < 0 ? Math.abs(total) : 0;

    const stayRulesData = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.STAY_RULES,
    });
    let stayRules = null;
    if (stayRulesData) {
      stayRules = stayRulesData?.value || null;
    }

    let canRequestMoveOut = 1;
    let moveOutBlockReasons = [];

    //Committing as discussed 2026-03-14, will be handled by eviction setting restrict moveOut
    // if ((Number(clientId) === 732 || Number(clientId) === 201) && Number(moment().date()) !== 1) {
    //   canRequestMoveOut = 0
    // }

    const evictionSetting = await evictionConfigDB.getByClientIdAndPropId({
      clientId: occupancy.clientId,
      propId: property.id,
    });

    if (evictionSetting) {
      
      // if (Number(evictionSetting?.clearDues) === 1 && (totalDues && totalDues > 0)) {
      //   canRequestMoveOut = 0;
      //   moveOutBlockReasons.push(`Clear pending dues before requesting move-out`);
      // }
      const securityAmount = Math.abs(Number(totalSecurityReceived?.amount || 0));
      const totalDueAmount = Number(totalDues || 0);
      const rentDueAmount = Number(rentDues || 0);

      if (Number(evictionSetting?.clearDues) === 1 && totalDueAmount > 0) {
        const allowDueToSecurity =
          Number(evictionSetting?.onlyRentDueLeft) === 1 &&
          totalDueAmount === rentDueAmount &&
          rentDueAmount <= securityAmount;

        if (!allowDueToSecurity) {
          canRequestMoveOut = 0;
          moveOutBlockReasons.push("Clear pending dues before requesting move-out");
        }
      }

      if((!evictionSetting?.clearDues || Number(evictionSetting?.clearDues) === 0) && Number(evictionSetting?.onlyRentDueLeft) === 1 && totalDueAmount > 0) {
        const allowDueToSecurity =
          Number(evictionSetting?.onlyRentDueLeft) === 1 &&
          totalDueAmount === rentDueAmount &&
          rentDueAmount <= securityAmount;

        if (!allowDueToSecurity) {
          canRequestMoveOut = 0;
          moveOutBlockReasons.push("Clear pending dues before requesting move-out");
        }
      }

      if (Number(evictionSetting?.restrictMoveOut) === 1 && moment().date() !== Number(occupancy.rentalCycle)) {
        canRequestMoveOut = 0;
        moveOutBlockReasons.push(`You can only request move-out on ${occupancy.rentalCycle} every month`);
      }

      if (Number(evictionSetting?.lockInServe) === 1 && (moment().isSameOrBefore(moment(occupancy.moveInDate).add(occupancy.lockInPeriod, "months"), "day"))) {
        canRequestMoveOut = 0;
        moveOutBlockReasons.push(`You are unable to move out before the agreed lock-in period is over`);
      }

      if (Number(evictionSetting?.midCycleCharge) === 1) {
        moveOutBlockReasons.push(`You are required to pay 1 month extra rent if moving out mid month`);
      }
    }

    let gateway = client.paymentGateway;
    if (property.paymentGateway && Number(property.paymentGateway) > 0) {
      gateway = property.paymentGateway;
    }
    delete property.paymentGateway;

    if(property && property?.isStampPaperRequired === 1 && Number(occupancy.isRentAgreementSigned) === 0) {
      let stampPaper = await stampPaperDB.getByClientIdAndPropId({clientId, propId: property?.id});
      if(!stampPaper) {
        property.isRentAgreementEnabled = 0;
      }
    }

    let dueSelection = await clientConfigDB.getClientConfig({
      clientId: client.id,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.ENABLE_DUE_SELECTION
    });
    let enableDueSelection = false;
    if (dueSelection && Number(dueSelection.value) === 1) {
      enableDueSelection = true;
    }

    const foodSelectionCutOff = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.FOOD_SELECTION_CUT_OFF_TIME,
    });

    let mealSelectionCutoffTime = "6:00 PM";
    if(foodSelectionCutOff && foodSelectionCutOff?.value && String(foodSelectionCutOff?.value).trim() !== "") {
      mealSelectionCutoffTime = foodSelectionCutOff.value;
    }

    const wifiCreds = await wifiCredentialsDB.getByTenantIdAndClientId({
      tenantId,
      clientId,
    });

    let helplineNumber = null;

    let helplineNumberConfig = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.HELPLINE_NUMBER,
    });

    helplineNumber = helplineNumberConfig && helplineNumberConfig?.value ? Number(helplineNumberConfig?.value) : null;

    let payWith = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.TENANT_PAYMENT,
    })
    let payWithValue = CONSTANTS.CLIENT_CONFIG_VALUE.TENANT_PAYMENT.LINK;
    if(payWith && payWith?.value && Number(payWith?.value) && Number(payWith?.value)) {
        payWithValue = Number(payWith?.value);
    }

    log.info(
      `[${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${tenantId}], Can Request Move-Out [${canRequestMoveOut}], Block Move-out Reasons [${JSON.stringify(moveOutBlockReasons)}], Dashboard Details Sent Successfully`
    );
    return res.status(200).json({
      msg: "Dashboard data sent successfully",
      isSuccess: true,
      data: {
        wifiCreds: wifiCreds || [],
        helplineNumber,
        mealSelectionCutoffTime: mealSelectionCutoffTime,
        canRequestMoveOut: canRequestMoveOut,
        enableDueSelection: enableDueSelection,
        moveOutBlockReasons: moveOutBlockReasons || [],
        stayRules,
        evictionSetting: evictionSetting || [],
        currentVersion: currentAppVersion,
        kycStatus: occupancy.kycStatus,
        eqaroStatus: eqaroStatus,
        tenantStatus: tenant.status,
        tenantName: tenant.name,
        isMoveOutPending,
        moveInInspection,
        moveOutRequestId,
        tenantId,
        documents: documents || [],
        isOnlinePaymentEnabled: occupancy?.isOnlinePaymentEnabled || 0,
        propertyDetails: {
          ...property,
          ownerPan: client.panNumber,
          paymentGateway: gateway,
          payWith: payWithValue, // Link or SDK
          roomNum,
          flatName,
          floor: floor || flatName,
          roomId,
          rent: occupancy.rent,
          security: occupancy.security,
          roomType,
          moveInDate: occupancy.moveInDate,
          moveOutDate: occupancy.moveOutDate,
          bank,
          isBankDetailsShown: client.isBankDetailsShown,
          rentalCycle: occupancy.rentalCycle,
          isPoliceVerified: occupancy.isPoliceVerified,
          isRentAgreementSigned: occupancy.isRentAgreementSigned
        },
        dues: {
          totalDues: Number(totalDues) || 0,
        },
        notices: notices || [],
        isFoodAvailable: isFoodAvailable ? (occupancy?.isFoodOpted ?? 0) : 0,
        refundRequestStatus: refundLedgerEntry
          ? CONSTANTS.REQUEST_STATUS.APPROVED
          : refundRequest
            ? refundRequest?.status
            : null,
        refundRejectionReason: refundRejectionReason,
        totalSecurityPaid: Math.abs(totalSecurityReceived.amount) || 0,
        refundAmount: Number(isEvicted) === 0
          ? securityReceived
          : refundLedgerEntry
            ? refundedAmount
            : excessAmount > 0
              ? Math.abs(excessAmount)
              : 0,
      },
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetWebCheckinDetails = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetWebCheckinDetails";

  try {
    const { encryptedId } = req.params;

    log.info(`[${C}], [${F}], Encrypted Id [${encryptedId}]`);

    const tenant = await tenantDB.getByGId({ gId: encryptedId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Encrypted Id [${encryptedId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantIdForWeb({
      tenantId: tenant.id,
    });

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Encrypted Id [${encryptedId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const { totalDues } = await duesDB.getTotalDuesByTenantId({
      tenantId: tenant.id,
      propId: occupancy.propId,
    });
    const dues = await duesDB.getByTenantId({
      tenantId: tenant.id,
    });

    const docs = await documentDB.getByTenantIdAndClientId({
      tenantId: tenant.id,
      clientId: occupancy.clientId,
      moveOut: 0
    });
    if (docs && docs.length > 0) {
      for (let doc of docs) {
        const title = await getDocumentTitle(doc.type);
        doc.title = title;
      }
    }

    const token = jwt.sign(
      {
        id: tenant?.id,
        type: CONSTANTS.USER_TYPE.TENANT,
      },
      process.env.TOKEN_SECRET!,
      { expiresIn: process.env.TOKEN_EXPIRY }
    );

    log.info(
      `[${C}], [${F}], Encrypted Id [${encryptedId}], Tenant details sent successfully`
    );

    return res.status(200).json({
      msg: "Tenant details sent successfully",
      data: {
        token,
        ...tenant,
        totalDues: Number(totalDues) || 0,
        occupancy,
        dues: dues || [],
        docs: docs || [],
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetDetails = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetDetails";

  try {
    const tenantId = req.id;
    const clientId = req.clientId;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}]`);

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      clientId: clientId,
      tenantId: tenantId,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const docs = await documentDB.getByTenantIdAndClientId({ tenantId, clientId: occupancy.clientId, moveOut: 0 });
    if (docs && docs.length > 0) {
      for (let doc of docs) {
        const title = await getDocumentTitle(doc.type);
        doc.title = title;
      }
    }

    const { totalDues } = await duesDB.getTotalDuesByTenantId({
      tenantId,
      propId: occupancy.propId,
    });
    const dues = await duesDB.getByTenantId({ tenantId });

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Tenant details sent successfully`
    );

    const lastSavedElecReading = await electricityDB.getForPreviousMonth({
      clientId: occupancy.clientId,
      propId: occupancy?.propId,
      roomId: occupancy?.roomId,
    });

    return res.status(200).json({
      msg: "Tenant details sent successfully",
      data: {
        ...tenant,
        totalDues: Number(totalDues) || 0,
        occupancy,
        docs,
        dues,
        lastSavedElecReading: Number(lastSavedElecReading) || 0,
      },

      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.DeleteAccount = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "DeleteAccount";

  try {
    const tenantId = req.id;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}]`);

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    return res.status(200).json({
      msg: "Your account has been deleted successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.PayOffline = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "PayOffline";

  try {
    const tenantId = req.id;
    const { dueIds, userType, userId, amount } = req.body;
    let clientId = userId;
    let isEvicted = req.isEvicted;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Dues Count [${dueIds.length}], Due Ids [${JSON.stringify(dueIds)}], Amount [${amount}], User Type [${userType}], User Id [${userId}], Is Evicted [${isEvicted}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Dues Count [${dueIds}], User Type [${userType}], User Id [${userId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let isDueFound = false;

    for (const dueId of dueIds) {
      let due;
      if (Number(isEvicted) === 1) {
        due = await moveOutDuesDB.getById({ id: dueId });
      } else {
        due = await duesDB.getById({ id: dueId });
      }
      if (!due) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Dues Count [${dueIds}], User Type [${userType}], User Id [${userId}], Due Id [${dueId}] No Due Found`
        );
        continue;
      } else {
        isDueFound = true;
      }
    }

    if (!isDueFound) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Dues Count [${dueIds}], User Type [${userType}], User Id [${userId}], No Due Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let user = null;

    if (userType === CONSTANTS.USER_TYPE.CLIENT) {
      const client = await clientDB.getById({ id: userId });
      if (!client) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Dues Count [${dueIds}], User Type [${userType}], User Id [${userId}], No Client Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      user = client;
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Dues Count [${dueIds}], User Type [${userType}], User Id [${userId}], Client Found`
      );
    } else {
      user = await staffDB.getById({ id: userId });
      if (!user) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Dues Count [${dueIds}], User Type [${userType}], User Id [${userId}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }
      clientId = user.clientId;
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Dues Count [${dueIds}], User Type [${userType}], Staff Found`
      );
    }

    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId,
      clientId,
    });
    if (Number(isEvicted) === 1) {
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        tenantId,
        clientId,
      });
    }
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }
    let flatName = "";
    if (occupancy.propType === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = "Flat No " + name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
    }

    const title = `Confirmation of offline payment`;
    const description = `${tenant?.name} has requested you to confirm the offline payment. Please share the confirmation code with him/her`;

    let isNotiSent = false;

    if (user.regId) {
      isNotiSent = await sendNotification({
        title: title,
        message: description,
        regId: user.regId,
        userId: user.id,
        userType,
        notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.OFFLINE_PAYMENT_CONFIRM,
        clientId,
      });
    } else {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Dues Count [${dueIds}], User Type [${userType}], User Id [${userId}], No RegId Found`
      );
    }

    let otp = Math.floor(Math.random() * (999999 - 100000) + 100000).toString();

    if ("7888888888" == user.mobile) otp = "111111";

    if (process.env.ENABLE_RANDOM_OTP === "false") {
      otp = "111111";
    }

    await otpDB.create({ mobile: user.mobile, otp });

    // const msg = CONSTANTS.MSG.PAY_OFFLINE.replace(
    //   "{#var#}",
    //   tenant.name
    // ).replace("{#var2#}", otp);
    let secondVar =
      "Rs " + amount + " for " + flatName + ", " + occupancy.propName;
    const msg = CONSTANTS.MSG.PAY_OFFLINE.replace("{#var#}", tenant.name)
      .replace("{#var2#}", secondVar)
      .replace("{#var3#}", otp);

    const isSent = await sendSMS(
      user.mobile,
      msg,
      CONSTANTS.SMS_TEMPLATE_IDS.PAY_OFFLINE
    );
    log.info(
      `[${C}], [${F}],Tenant Id [${tenantId}], Dues Count [${dueIds}], User Type [${userType}], User Id [${userId}], Is Noti Sent [${isNotiSent}] OTP [${otp}], ${isSent ? "OTP sent successfully" : "Failed to send OTP SMS"
      }`
    );

    return res.status(200).json({
      msg: "OTP has been sent to the payment receiver.",
      receiverName: user.name,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.PayOfflineOTPVerify = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "PayOfflineOTPVerify";

  try {
    const tenantId = req.id;
    const clientId = req.clientId;
    let isEvicted = req.isEvicted;
    const { dueIds, otp, ledgerReferenceIds, userId, userType, amount } =
      req.body;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], OTP [${otp}], LedgerReferenceId [${ledgerReferenceIds}], Amount Paid [${amount}], User Type [${userType}], User Id [${userId}], is Evicted [${isEvicted}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], User Type [${userType}], User Id [${userId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancy = await occupancyDB.getByClientIdAndTenantId({
      clientId: clientId,
      tenantId: tenantId,
    });
    if (Number(isEvicted) === 1) {
      occupancy = await moveOutDB.getByClientIdAndTenantId({
        clientId: clientId,
        tenantId: tenantId,
      });
    }
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], User Type [${userType}], User Id [${userId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    occupancy = occupancy[0];

    const property = await propertyDB.getById({ id: occupancy.propId });
    const room = await roomDB.getById({ id: occupancy.roomId });

    let user = null;
    let client = null;
    let isStaff = 0;

    if (userType === CONSTANTS.USER_TYPE.CLIENT) {
      user = await clientDB.getById({ id: userId });
      if (!user) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], User Type [${userType}], User Id [${userId}], No Client Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      client = user;
    } else {
      user = await staffDB.getById({ id: userId });
      isStaff = 1;
      if (!user) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], User Type [${userType}], User Id [${userId}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      client = await clientDB.getById({ id: user.clientId });
    }

    const record = await otpDB.getByMobile({ mobile: user.mobile });
    if (!record) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], User Type [${userType}], User Id [${userId}], OTP has been expired`
      );

      return res.status(400).json({
        msg: "OTP has been expired",
        isSuccess: false,
      });
    }

    if (record?.otp !== otp) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], User Type [${userType}], User Id [${userId}], Incorrect OTP`
      );

      return res
        .status(400)
        .json({ msg: "Please enter a valid OTP", isSuccess: false });
    }

    log.info(`[${C}], [${F}], dueIds: ${dueIds}`);
    let allDues;
    if (Number(isEvicted) === 1) {
      allDues = await moveOutDuesDB.getAllDues({
        ids: dueIds,
      });
    } else {
      allDues = await duesDB.getAllDues({
        ids: dueIds,
      });
    }


    // let allDues = await duesDB.getByIdForTransaction({
    //   tenantId,
    //   ledgerReferenceId,
    // });

    let duesStatsArr = [];
    let dueStats = {};
    let excessDueStats = {};
    let excessDueflag = false;
    if (!allDues) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], User Type [${userType}], User Id [${userId}], No Dues Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }
    let count = 1;
    let lengthOfDues = allDues.length;
    let isTransactionRecorded = false;
    let amtPaid = amount;
    let security = await allDues.find(
      (due: any) => due.type === CONSTANTS.DUES_TYPES.SECURITY
    );
    let receipts = [];
    let dueDescription = "";

    let totalDue = 0;
    let discountAmount = 0;

    let transTitle: string = "Payment for ";
    for (let i = 0; i < allDues.length; i++) {
      const due = allDues[i];
      if (due.title === null) {
        transTitle += await getDueDescription(due.type);
      } else {
        transTitle += `${due.title}`;
      }
      if (i !== allDues.length - 1) {
        transTitle += ", ";
      }
      totalDue += due.balance;
      if (totalDue <= amount) {
        discountAmount += due.discount;
      }
    }

    if (Number(totalDue) > Number(amount)) {
      transTitle = `Partial ` + transTitle;
    }

    transTitle = allDues.length === 1
      ? allDues[0].rentStartDate !== null
        ? `${transTitle} for ${moment(allDues[0].rentStartDate).format("DD MMM YY")} to ${moment(allDues[0].rentEndDate).format("DD MMM YY")}`
        : `${transTitle} for ${moment(allDues[0].dueDate).format("MMM YYYY")}`
      : transTitle;

    const transGId: string = await generateTransId();
    let transId;

    if (isStaff == 1) {
      log.info(
        `[${C}], [${F}], Staff Id [${userId}], Amount [${amount}] from Tenant [${tenant.name}], Tenant Id [${tenant.id}]`
      );

      await staffLedgerDB.add({
        staffId: userId,
        amount: amount,
        type: CONSTANTS.STAFF_LEDGER_TYPES.CASH,
        mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
        description: `Payment from tenant ${tenant.name} living in property ${property.name
          } on ${moment().format("YYYY-MM-DD HH:mm:ss")}`,
        transactionId: transId,
        paidDate: moment().format("YYYY-MM-DD"),
      });

      const staffExistsInBalance = await staffBalanceDB.getByStaffId({
        staffId: userId,
      });

      if (!staffExistsInBalance) {
        await staffBalanceDB.add({
          staffId: userId,
          totalCollected: amount,
          totalGivenToOwner: 0,
          totalExpense: 0,
        });
      } else {
        await staffBalanceDB.updateTotalCollected({
          staffId: userId,
          amount: amount,
        });
      }
    }

    if (security && security.balance > 0) {
      let due = security;
      dueDescription = getDueDescription(due.type);
      if (security.balance <= amtPaid) {
        if (count == lengthOfDues) {
          //To check if this is last Due, for adding excess balance to this due
          if (Number(isEvicted) === 1) {
            transId = await addTransactionsMoveOut({
              due: due,
              clientId: clientId,
              tenantId: tenantId,
              amount: -due.balance,
              description: `Payment for Initial security deposit`,
              mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
              recordedBy: user.name || "",
              duesStatsArr: [
                { title: dueDescription, amount: due.balance },
                { title: "Excess Payment", amount: Math.abs(amtPaid - due.balance) },
              ]
            });
          } else {
            transId = await addTransactions({
              due: due,
              clientId: clientId,
              tenantId: tenantId,
              amount: -due.balance,
              description: `Payment for Initial security deposit`,
              mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
              recordedBy: user.name || "",
              duesStatsArr: [
                { title: dueDescription, amount: due.balance },
                { title: "Excess Payment", amount: Math.abs(amtPaid - due.balance) },
              ]
            });
          }
          await ledgerDB.add({
            tenantId,
            roomId: occupancy.roomId,
            propId: occupancy.propId,
            clientId: occupancy.clientId,
            amount: -due.balance,
            balance: -(amtPaid - due.balance),
            referenceId: due.ledgerReferenceId,
            transactionId: transId,
            type: due.type,
            rentStartDate: null,
            rentEndDate: null,
            dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
            description: dueDescription,
            title: due?.title || null,
          });
          //Creating DueStats for Recipt
          dueStats = { title: dueDescription, amount: due.balance };
          duesStatsArr.push(dueStats);
          amtPaid = 0; // To prevent it from going in for loop that is used for checking rest of the dues
        } else {
          if (Number(isEvicted) === 1) {
            transId = await addTransactionsMoveOut({
              due: due,
              clientId: clientId,
              tenantId: tenantId,
              amount: due.balance,
              description: `Payment for Initial security deposit`,
              mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
              recordedBy: user.name || "",
              duesStatsArr: [
                { title: dueDescription, amount: due.balance },
              ]
            });
          } else {
            transId = await addTransactions({
              due: due,
              clientId: clientId,
              tenantId: tenantId,
              amount: due.balance,
              description: `Payment for Initial security deposit`,
              mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
              recordedBy: user.name || "",
              duesStatsArr: [
                { title: dueDescription, amount: due.balance },
              ]
            });
          }
          await ledgerDB.add({
            tenantId,
            roomId: occupancy.roomId,
            propId: occupancy.propId,
            clientId: occupancy.clientId,
            amount: -due.balance,
            balance: 0,
            referenceId: due.ledgerReferenceId,
            transactionId: transId,
            type: due.type,
            rentStartDate: null,
            rentEndDate: null,
            dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
            description: dueDescription,
            title: due?.title || null,
          });
          //Creating DueStats for Recipt
          dueStats = { title: dueDescription, amount: due.balance };
          duesStatsArr.push(dueStats);
          amtPaid -= due.balance;
        }
        await duesDB.removeDue({ id: due.id }); // Removing due from dues table when balance is 0

        isTransactionRecorded = true; //for sending sms confirmation of the payment
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], Due Id [${due.id}], User Type [${userType}], User Id [${userId}], Transaction has been recorded successfully`
        );
      } else if (security.balance > amtPaid) {
        if (Number(isEvicted) === 1) {
          transId = await addTransactionsMoveOut({
            due: due,
            clientId: clientId,
            tenantId: tenantId,
            amount: amtPaid,
            description: `Partial Payment for Initial security deposit`,
            mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
            recordedBy: user.name || "",
            duesStatsArr: [
              { title: dueDescription, amount: amtPaid },
            ]
          });
        } else {
          transId = await addTransactions({
            due: due,
            clientId: clientId,
            tenantId: tenantId,
            amount: amtPaid,
            description: `Partial Payment for Initial security deposit`,
            mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
            recordedBy: user.name || "",
            duesStatsArr: [
              { title: dueDescription, amount: amtPaid },
            ]
          });
        }
        await ledgerDB.add({
          tenantId,
          roomId: occupancy.roomId,
          propId: occupancy.propId,
          clientId: occupancy.clientId,
          amount: -amtPaid,
          balance: due.balance - amtPaid,
          referenceId: due.ledgerReferenceId,
          transactionId: transId,
          type: due.type,
          rentStartDate: null,
          rentEndDate: null,
          dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
          description: dueDescription,
          title: due?.title || null,
        });
        //Creating DueStats for Recipt
        dueStats = { title: dueDescription, amount: amtPaid };
        duesStatsArr.push(dueStats);
        if (Number(isEvicted) === 1) {
          await moveOutDuesDB.updateBalance({
            id: due.id,
            balance: due.balance - amtPaid,
          });
        } else {
          await duesDB.updateBalance({
            id: due.id,
            balance: due.balance - amtPaid,
          });
        }
        amtPaid = 0;
        isTransactionRecorded = true;
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], Due Id [${due.id}], User Type [${userType}], User Id [${userId}], Transaction has been recorded in ledger successfully`
        );
      }
      count++;
    }

    if (amtPaid != 0) {
      // If paid amount is already settled from security, then not need to check for rest of the dues
      for (const due of allDues) {
        let securityAdjustEntry = await ledgerDB.getLastEntryX({
          tenantId,
          clientId: due.clientId,
          createdAt: occupancy?.moveInDate,
        });
        if (amtPaid == 0) break; //if amount paid is already settled, then not need to check for rest of the dues
        if (due.type === CONSTANTS.DUES_TYPES.SECURITY) {
          //security is already handled above, thus skip it here
          continue;
        }
        dueDescription = getDueDescription(due.type);
        dueDescription = `${dueDescription} for ${moment(due.dueDate).format(
          "MMM YYYY"
        )}`;
        let dueName = getDueDescription(due.type);
        if (due.type === CONSTANTS.DUES_TYPES.OTHER) {
          dueName = due.title;
          dueDescription = `${due.title} for ${moment(due.rentStartDate).format("DD MMM YY")} to ${moment(due.rentStartDate).format("DD MMM YY")}`;
        }
        if (amtPaid <= due.balance) {
          if (Number(isEvicted) === 1) {
            transId = await addTransactionsMoveOut({
              due: due,
              clientId: clientId,
              tenantId: tenantId,
              amount: amtPaid,
              description: due.balance == amtPaid ? `Payment for ${dueName}` : `Partial Payment for ${dueName}`,
              mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
              recordedBy: user.name || "",
              duesStatsArr: due.balance == amtPaid
                ? due.discount > 0
                  ? [{ title: dueDescription, amount: amtPaid }, { title: "Discount", amount: -due.discount },]
                  : [{ title: dueDescription, amount: amtPaid },]
                : [{ title: dueDescription, amount: amtPaid },],
            });
          } else {
            transId = await addTransactions({
              due: due,
              clientId: clientId,
              tenantId: tenantId,
              amount: amtPaid,
              description: due.balance == amtPaid ? `Payment for ${dueName}` : `Partial Payment for ${dueName}`,
              mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
              recordedBy: user.name || "",
              duesStatsArr: due.balance == amtPaid
                ? due.discount > 0
                  ? [{ title: dueDescription, amount: amtPaid }, { title: "Discount", amount: -due.discount },]
                  : [{ title: dueDescription, amount: amtPaid },]
                : [{ title: dueDescription, amount: amtPaid },],
            });
          }
          await ledgerDB.add({
            tenantId,
            roomId: occupancy.roomId,
            propId: occupancy.propId,
            clientId: occupancy.clientId,
            amount: -amtPaid,
            balance: due.balance - amtPaid,
            referenceId: due.ledgerReferenceId,
            transactionId: transId,
            type: due.type,
            rentStartDate: null,
            rentEndDate: null,
            dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
            description: due.discount > 0 ? amtPaid === due.balance ? `${dueDescription}, Discount of ₹${due.discount} given for this due` : dueDescription : dueDescription,
            discount: due.discount,
            title: due?.title || null,
          });
          if (amtPaid == due.balance) {
            //due is completely paid
            if (Number(isEvicted) === 1) {
              await moveOutDuesDB.removeDue({ id: due.id });
            } else {
              await duesDB.removeDue({ id: due.id });
            }
            //Creating DueStats for Recipt
            dueStats = { title: dueDescription, amount: due.balance + due.discount };
            duesStatsArr.push(dueStats);
          } else {
            //due is partially paid
            if (Number(isEvicted) === 1) {
              await moveOutDuesDB.updateBalance({
                id: due.id,
                balance: due.balance - amtPaid,
              });
            } else {
              await duesDB.updateBalance({
                id: due.id,
                balance: due.balance - amtPaid,
              });
            }

            //Creating DueStats for Recipt
            dueStats = { title: dueDescription, amount: amtPaid };
            duesStatsArr.push(dueStats);
          }
          amtPaid = 0;
        } else {
          if (count == lengthOfDues) {
            //To check if this is last Due, for adding excess balance to this due
            // log.info(
            //   `[${C}], [${F}], 77. Debug issue ${dueDescription} ${due.type} ${due.balance} ${amtPaid}`
            // );
            if (Number(isEvicted) === 1) {
              transId = await addTransactionsMoveOut({
                due: due,
                clientId: clientId,
                tenantId: tenantId,
                amount: amtPaid,
                description: `Payment for ${dueName}`,
                mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
                recordedBy: user.name || "",
                duesStatsArr: due.discount > 0
                  ? [{ title: dueDescription, amount: due.balance }, { title: "Discount", amount: -due.discount }, { title: "Excess Payment", amount: Math.abs(amtPaid - due.balance) },]
                  : [{ title: dueDescription, amount: due.balance }, { title: "Excess Payment", amount: Math.abs(amtPaid - due.balance) },],
              });
            } else {
              transId = await addTransactions({
                due: due,
                clientId: clientId,
                tenantId: tenantId,
                amount: amtPaid,
                description: `Payment for ${dueName}`,
                mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
                recordedBy: user.name || "",
                duesStatsArr: due.discount > 0
                  ? [{ title: dueDescription, amount: due.balance }, { title: "Discount", amount: -due.discount }, { title: "Excess Payment", amount: Math.abs(amtPaid - due.balance) },]
                  : [{ title: dueDescription, amount: due.balance }, { title: "Excess Payment", amount: Math.abs(amtPaid - due.balance) },],
              });
            }
            amtPaid -= due.balance;
            await ledgerDB.add({
              tenantId,
              roomId: occupancy.roomId,
              propId: occupancy.propId,
              clientId: occupancy.clientId,
              amount: -due.balance,
              balance: -(amtPaid - due.balance),
              referenceId: due.ledgerReferenceId,
              transactionId: transId,
              type: due.type,
              rentStartDate: null,
              rentEndDate: null,
              dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
              description: due.discount > 0 ? `${dueDescription}, Discount of ₹${due.discount} given for this due` : dueDescription,
              discount: due.discount,
              title: due?.title || null,
            });
            //Creating DueStats for Recipt
            dueStats = { title: dueDescription, amount: due.balance + due.discount };
            duesStatsArr.push(dueStats);
            //Excess Dues
            if (Math.abs(amtPaid - due.balance) > 0) {
              excessDueflag = true;
              excessDueStats = {
                title: "Excess Payment",
                amount: Math.abs(amtPaid - due.balance),
              };
            }
          } else {
            //if not last due, then just record the transaction and update the balance
            if (Number(isEvicted) === 1) {
              transId = await addTransactionsMoveOut({
                due: due,
                clientId: clientId,
                tenantId: tenantId,
                amount: due.balance,
                description: `Payment for ${dueName}`,
                mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
                recordedBy: user.name || "",
                duesStatsArr: due.discount > 0
                  ? [{ title: dueDescription, amount: due.balance }, { title: "Discount", amount: -due.discount }]
                  : [{ title: dueDescription, amount: due.balance },],
              });
            } else {
              transId = await addTransactions({
                due: due,
                clientId: clientId,
                tenantId: tenantId,
                amount: due.balance,
                description: `Payment for ${dueName}`,
                mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
                recordedBy: user.name || "",
                duesStatsArr: due.discount > 0
                  ? [{ title: dueDescription, amount: due.balance }, { title: "Discount", amount: -due.discount }]
                  : [{ title: dueDescription, amount: due.balance },],
              });
            }
            await ledgerDB.add({
              tenantId,
              roomId: occupancy.roomId,
              propId: occupancy.propId,
              clientId: occupancy.clientId,
              amount: -due.balance,
              balance: 0,
              referenceId: due.ledgerReferenceId,
              transactionId: transId,
              type: due.type,
              rentStartDate: null,
              rentEndDate: null,
              dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
              description: due.discount > 0 ? `${dueDescription}, Discount of ₹${due.discount} given for this due` : dueDescription,
              discount: due.discount,
              title: due?.title || null,
            });
            amtPaid -= due.balance;
            //Creating DueStats for Recipt
            dueStats = { title: dueDescription, amount: due.balance + due.discount };
            duesStatsArr.push(dueStats);
          }
          if (Number(isEvicted) === 1) {
            await moveOutDuesDB.removeDue({ id: due.id });
          } else {
            await duesDB.removeDue({ id: due.id });
          }
        }
        isTransactionRecorded = true;
        count++;

        if (securityAdjustEntry !== false) {
          await ledgerDB.removeById({
            id: securityAdjustEntry.id,
          });

          //creating new security entry after deleting previous
          await ledgerDB.add({
            tenantId: securityAdjustEntry.tenantId,
            roomId: securityAdjustEntry.roomId,
            propId: securityAdjustEntry.propId,
            clientId: due.clientId,
            amount: securityAdjustEntry.amount,
            balance: securityAdjustEntry.balance,
            referenceId: securityAdjustEntry.referenceId,
            transactionId: securityAdjustEntry.transactionId,
            type: securityAdjustEntry.type,
            rentStartDate: securityAdjustEntry.rentStartDate,
            rentEndDate: securityAdjustEntry.rentEndDate,
            description: securityAdjustEntry.description,
            dueDate: securityAdjustEntry.dueDate,
          });
        }

        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], Due Id [${due.id}], User Type [${userType}], User Id [${userId}], Transaction has been recorded in ledger successfully`
        );
      }
      if (discountAmount > 0) {
        dueStats = { title: "Discount", amount: -discountAmount };
        duesStatsArr.push(dueStats);
      }
    }
    //creating receipt and adding a record to transaction table.
    // let settings = await settingsDB.getByClientIdAndPropId({
    //   clientId: client.id,
    //   propId: occupancy.propId,
    // });

    // let logo = settings?.logo 
    //   ? process.env.RS_LOGO_URI + client?.id + "/" + occupancy?.propId + "/" + settings?.logo
    //   : client?.logo
    //     ? process.env.RS_LOGO_URI + client?.id + "/" + client?.logo
    //     : String(process.env.RS_DEFAULT_LOGO_URI);;
    // log.info(`[${C}], [${F}], Logo [${logo}]`);
    // if (excessDueflag) {
    //   duesStatsArr.push(excessDueStats);
    // }

    // let flatName = "";

    // if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
    //   const { name } = await flatDB.getById({ id: occupancy.flatId });
    //   flatName = name;
    // } else {
    //   flatName =
    //     occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
    // }

    // let isGstEnabled = property?.isGstEnabled || 0;
    // let invoiceNo = property?.invoiceNo || null;
    // let invoiceNoPrefix = property?.invoiceNoPrefix || null;
    // let gstNo = property?.gstNo || null;
    // let businessName = property?.ownerName || ""
    // let transactionInvoiceNo = ""
    // let prefix = "";
    // if (0 != isGstEnabled && 0 != invoiceNo) {
    //   let getGstTransactions = await transactionDB.getByGstNo({ gstNo });
    //   if (false == getGstTransactions) {
    //     if (null != invoiceNoPrefix) {
    //       prefix = invoiceNoPrefix;
    //     }
    //     transactionInvoiceNo = `${prefix}${invoiceNo}`;
    //   } else {
    //     invoiceNo = getGstTransactions?.invoiceNo;
    //     if (null != invoiceNoPrefix) {
    //       prefix = invoiceNoPrefix;
    //       invoiceNo = invoiceNo.replace(prefix, "");
    //       transactionInvoiceNo = `${prefix}${String(Number(invoiceNo) + 1)}`;
    //     } else {
    //       transactionInvoiceNo = String(Number(invoiceNo) + 1);
    //     }
    //   }
    //   businessName = property?.businessName || "";
    // } else {
    //   isGstEnabled = 0;
    // }

    // const receipt = await createReceiptMultipleDues({
    //   title: `${transId}_${transGId}`,
    //   roomNum: room.roomNum,
    //   propName: property.name,
    //   dueDate: moment(allDues[0].dueDate).format("MMM, YYYY"),
    //   mode: getModeName(CONSTANTS.TRANSACTION_MODES.OFFLINE),
    //   transGId,
    //   paidDate: moment().format("DD MMM, YYYY"),
    //   month: moment(allDues[0].dueDate).format("MMM, YYYY"),
    //   tenantName: tenant?.name || "",
    //   amount: Number(amount || 0),
    //   address: `${property?.address}`,
    //   landlord: businessName || "",
    //   landlordNumber: property?.ownerMobile || "",
    //   logo: logo,
    //   dueStats: duesStatsArr,
    //   "landlord-pan": "",
    //   occupancy,
    //   flatName,
    //   propType: property.type,
    //   isGstEnabled,
    //   transactionInvoiceNo,
    //   gstNo,
    // });
    // if (0 != isGstEnabled && 0 != invoiceNo) {
    //   await transactionDB.updateReceiptWithInvoice({ id: transId, receipt, invoiceNo: transactionInvoiceNo, gstNo });
    // } else {
    //   await transactionDB.updateReceipt({
    //     id: transId,
    //     receipt,
    //   });
    // }
    // receipts.push({
    //   name: transTitle,
    //   receipt,
    //   paidDate: moment().format("DD MMM, YYYY"),
    //   amount: Number(amtPaid || 0),
    //   mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
    //   transGId,
    // });
    if (isTransactionRecorded && amtPaid > 0) {
      let isNotiSent = false;
      if (tenant.regId) {
        isNotiSent = await sendNotification({
          title: `Received ₹${amount} for due payments`,
          message: `Thank you for paying for your due payments timely.`,
          regId: tenant.regId,
          userId: tenant.id,
          userType: CONSTANTS.USER_TYPE.TENANT,
          notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.OFFLINE_PAYMENT_CONFIRM,
          clientId: occupancy.clientId,
        });
      } else {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], User Type [${userType}], User Id [${userId}], No Tenant's RegId Found`
        );
      }

      const msg = CONSTANTS.MSG.DUE_PAID.replace(
        "{#var#}",
        tenant.name
      ).replace("{#var2#}", amount.toString());

      const isSent = await sendSMS(
        tenant.mobile,
        msg,
        CONSTANTS.SMS_TEMPLATE_IDS.DUE_PAID
      );
    }

    if (!isTransactionRecorded) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceIds}], User Type [${userType}], User Id [${userId}], No Due Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let tenantDues = await duesDB.getByTenantIdAndClientId({
      tenantId,
      clientId,
    });
    if (Number(isEvicted) === 1) {
      tenantDues = await moveOutDuesDB.getByTenantIdAndClientId({
        tenantId,
        clientId,
      });
    }
    if (!tenantDues) {
      await tenantDB.updateLastReminded({
        id: tenantId,
        lastRemindedOn: null,
      });
      if (Number(isEvicted) === 1) {
        await moveOutDB.updateLastReminded({
          clientId,
          tenantId,
          lastRemindedOn: null,
        });
      } else {
        await occupancyDB.updateLastReminded({
          clientId,
          tenantId,
          lastRemindedOn: null,
        });
      }
    }

    return res.status(200).json({
      msg: "Transaction has been recorded successfully",
      // receipts,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.PaymentCollectorsListing = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "PaymentCollectorsListing";

  try {
    const tenantId = req.id;
    //Pallav handled multi tenant scenario
    let clientId = req.clientId;
    let isEvicted = req.isEvicted;
    log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], IsEvicted [${isEvicted}]`);

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId: tenantId,
    // });
    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenantId,
      clientId: clientId,
    });
    if (Number(isEvicted) === 1) {
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        tenantId: tenantId,
        clientId: clientId,
      });
    }

    const client = await clientDB.getById({ id: occupancy.clientId });
    if (!client) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Client Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let userData = [
      {
        name: client.name,
        userId: client.id,
        userType: CONSTANTS.USER_TYPE.CLIENT,
        role: 0,
      },
    ];

    const staffs = await staffDB.getByClientIdAndPropId({
      clientId: client.id,
      propId: occupancy.propId,
    });

    if (!staffs) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Staff Found`);
    } else {
      for (const staff of staffs) {
        if (staff.role > CONSTANTS.STAFF_ROLES.PARTNER) continue;
        userData.push({
          name: staff.name,
          userId: staff.id,
          userType: CONSTANTS.USER_TYPE.STAFF,
          role: staff.role,
        });
      }
    }

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], List sent successfully`);

    return res.status(200).json({
      msg: "List sent successfully",
      data: userData,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ViewProfile = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ViewProfile";

  try {
    const tenantId = req.id;
    let isEvicted = req.isEvicted;
    let clientId = req.clientId;

    log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}]`);

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenantId,
      clientId: clientId,
    });

    if (isEvicted) {
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        tenantId: tenant.id,
        clientId: clientId,
      });
    }

    const property = await propertyDB.getById({ id: occupancy?.propId });
    if (!property) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Property Found for Property Id [${occupancy?.propId}]`);
      return res.status(400).json(
        {
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false
        }
      );
    }

    let documents = await documentDB.getByTenantIdAndClientId({
      tenantId,
      clientId: occupancy.clientId,
      moveOut: 0,
    });
    if (documents && documents.length > 0) {
      for (let doc of documents) {
        const title = await getDocumentTitle(doc.type);
        doc.title = title;
      }

      documents = documents.filter((doc: any) => doc.type !== CONSTANTS.DOCUMENT_TYPES.SIGNATURE);
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Profile details sent successfully`
    );
    //data: { image: photo || null },
    let photo = null;
    if (occupancy?.verificationId != null) {
      const filePath = `uploads/tmp/img-buffer-${tenantId}.jpeg`;
      try {
        photo = await fsPromises.readFile(filePath);
        photo = photo.toString('base64');
      } catch (err) {
        photo = null;
        // log.info(
        //   `[${C}], [${F}], Tenant Id [${tenantId}], Error reading file [${err}]`
        // );
      }
    }
    const tenantGuardian = await tenantGuardianDB.getByTenantId({ tenantId });
    tenant.fatherName = tenantGuardian?.fatherName ? tenantGuardian?.fatherName : tenant.fatherName;
    tenant.fatherMobile = tenantGuardian?.fatherMobile;
    tenant.motherName = tenantGuardian?.motherName;
    tenant.motherMobile = tenantGuardian?.motherMobile;

    // log.info(
    //       `[${C}], [${F}], Tenant Id [${tenantId}], Resp [${JSON.stringify({ ...tenant, documents, image: photo || null })}]]`
    // );
    return res.status(200).json({
      msg: "Profile details sent successfully",
      data: { ...tenant, documents, image: photo || null, isAadhaarInputEnabled: property?.isAadhaarInputEnabled || 0 },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.EditProfile = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "EditProfile";

  try {
    const tenantId = req.id;

    const { alternateMobile, occupation, email } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Alternate Mobile [${alternateMobile}], Occupation [${occupation}], Email [${email}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    await tenantDB.updateProfile({
      alternateMobile,
      occupation,
      email,
      id: tenantId,
    });

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Profile details updated successfully`
    );

    return res.status(200).json({
      msg: "Profile details updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.MoveOutRequest = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "MoveOutRequest";

  try {
    const tenantId = req.id;
    const clientId = req.clientId;
    let { moveOutDate, reason } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}], No Tenant Found`
      );
      return res.status(400).json({
        msg: "Your are not allowed to send move out request",
        isSuccess: false,
      });
    }

    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      clientId,
      tenantId: tenant.id,
    })
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}], No Occupancy Found`
      );
      return res.status(400).json({
        msg: "Unable to send move out request because your are not occupied at any bed",
        isSuccess: false,
      });
    }


    if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}], You are already moving out`
      );
      return res.status(400).json({
        msg: "You are already moving out",
        isSuccess: false,
      });
    }

    if (occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.OCCUPIED) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}], Tenant is not Occupied`
      );
      return res.status(400).json({
        msg: "Unable to send move out request because your are not occupied at any bed",
        isSuccess: false,
      });
    }

    const isRequestAlreadySent = await requestDB.getByTenantId({
      tenantId,
      type: CONSTANTS.REQUEST_TYPE.MOVE_OUT,
      status: CONSTANTS.REQUEST_STATUS.PENDING,
    });

    if (isRequestAlreadySent) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}], Move Out Request Already Sent`
      );
      return res.status(400).json({
        msg: "Unable to send move out request because you already sent a move out request",
        isSuccess: false,
      });
    }

    // const occupancy = await occupancyDB.getByTenantId({ tenantId });
    // if (!occupancy) {
    //   log.info(
    //     `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}], No Occupancy Found`
    //   );
    //   return res.status(400).json({
    //     msg: "Unable to send move out request because your are not occupied at any bed",
    //     isSuccess: false,
    //   });
    // }

    // if (occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.OCCUPIED) {
    //   log.info(
    //     `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}], Occupancy is not Occupied`
    //   );
    //   return res.status(400).json({
    //     msg: "Unable to send move out request because your are not occupied at any bed",
    //     isSuccess: false,
    //   });
    // }

    const client = await clientDB.getById({ id: occupancy.clientId });

    const room = await roomDB.getById({ id: occupancy.roomId });
    const property = await propertyDB.getById({ id: occupancy.propId });

    const title = `Move out request received`;
    const description = `${tenant.name} is requesting for move out from Room (${room.roomNum}) of Property (${property.name})`;

    const requestId = await requestDB.create({
      clientId: occupancy.clientId,
      occupancyId: occupancy.id,
      tenantId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      floor: occupancy.floor,
      title,
      description,
      type: CONSTANTS.REQUEST_TYPE.MOVE_OUT,
    });

    let isNotiSent = false;
    if (client.regId) {
      // const regIds = await clientDB.getRegIds({
      //   id: clientId,
      // });

      // if (regIds && regIds.length > 0) {
      //   for (let regId of regIds) {
      //     isNotiSent = await sendNotification({
      //       title: title,
      //       message: description,
      //       regId: regId.regId,
      //       userId: client.id,
      //       userType: CONSTANTS.USER_TYPE.CLIENT,
      //       notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.MOVEOUT,
      //       clientId: occupancy.clientId,
      //     });
      //   }
      // } else {
      // }
      isNotiSent = await sendNotification({
        title: title,
        message: description,
        regId: client.regId,
        userId: client.id,
        userType: CONSTANTS.USER_TYPE.CLIENT,
        notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.MOVEOUT,
        clientId: occupancy.clientId,
      });
    } else {
      await notificationDB.add({
        userId: client.id,
        userType: CONSTANTS.USER_TYPE.CLIENT,
        title,
        message: description,
        notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.MOVEOUT,
      });
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}], No RegId found for client [${client.id}]`
      );
    }

    const msg = CONSTANTS.MSG.MOVE_OUT_REQ;

    const isSent = await sendSMS(
      client.mobile,
      msg,
      CONSTANTS.SMS_TEMPLATE_IDS.MOVE_OUT_REQ
    );

    await occupancyDB.updateMoveOutDate({
      id: occupancy.id,
      moveOutDate,
      moveOutReason: reason
    });

    await logTenantEvictionActivity(
      req.userType!,
      Number(req.id)!,
      0,
      Number(req.platform),
      CONSTANTS.ACTIVITY_TYPES.MOVEOUT_REQUEST,
      occupancy.propId,
      Number(tenant.id),
      tenant.name,
      occupancy.roomId,
      ""
    );
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Move Out Date [${moveOutDate}], Reason [${reason}], Is Notification Sent [${isNotiSent}], Is SMS Sent [${isSent}] Move Out Request sent successfully`
    );

    return res.status(200).json({
      msg: "Move out request sent successfully",
      data: { requestId },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.MoveOutRequestStatus = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "MoveOutRequestStatus";

  try {
    const { requestId } = req.params;
    const tenantId = req.id;
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const request = await requestDB.getStatusById({ id: requestId });

    if (!request) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], No Request Found`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let msg = "";

    if (request.status === CONSTANTS.REQUEST_STATUS.PENDING) {
      msg = "Your move out request is still pending";
    } else if (request.status === CONSTANTS.REQUEST_STATUS.REJECTED) {
      msg = "Your move out request was rejected by the owner";
    } else if (request.status === CONSTANTS.REQUEST_STATUS.APPROVED) {
      msg = "Your move out request was approved by the owner";
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], Move out request details sent successfully`
    );

    return res.status(200).json({
      msg: "Move out request details sent successfully",
      data: { ...request, msg },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.CancelMoveOutRequest = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "CancelMoveOutRequest";

  try {
    const tenantId = req.id;
    const { requestId } = req.body;
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const request = await requestDB.getById({ id: requestId });

    if (!request) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], No Request Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (request.status !== CONSTANTS.REQUEST_STATUS.PENDING) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], Request Already Approved Or Rejected`
      );
      return res.status(400).json({
        msg: "Request already approved or rejected",
        isSuccess: false,
      });
    }

    await requestDB.updateStatus({
      id: request?.id,
      status: CONSTANTS.REQUEST_STATUS.CANCELLED,
    });

    if (request?.occupancyId) {
      await occupancyDB.updateMoveOutDate({
        id: request.occupancyId,
        moveOutDate: null,
        moveOutReason: null
      });
    }

    await moveOutDB.removeByTenantId({
      clientId: request.clientId,
      tenantId: request.tenantId
    });

    let requestName = getRequestName(CONSTANTS.REQUEST_TYPE.MOVE_OUT);
    const client = await clientDB.getById({ id: request?.clientId });
    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      clientId: request?.clientId,
      tenantId: request?.tenantId,
    })
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${request?.tenantId}], Client Id [${request?.clientId}], No Occupancy Found`
      );
      return res.status(400).json({
        msg: "Unable to send Guest request because your are not occupied at any bed",
        isSuccess: false,
      });
    }
    const notiSettings = await settingsDB.getByClientIdAndPropId({
      clientId: request?.clientId,
      propId: request?.propId,
    });

    let footerText = notiSettings.footer || "Kipinn Team";
    const property = await propertyDB.getById({ id: request?.propId });
    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
    }
    const room = await roomDB.getById({ id: occupancy.roomId });
    let occupancyName = `${property?.name} (${flatName}, ${room.roomNum})`;
    if (Number(notiSettings?.whatsApp) === 1) {
      log.info(
        `[${C}], [${F}], Client Id [${request?.clientId}], Request Id [${requestId}], WhatsApp Enabled`
      );
      await sendWhatsappRequestUpdates(
        client?.mobile,
        client?.name,
        requestName,
        occupancyName,
        `cancelled`,
        footerText,
        request?.clientId
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${request?.clientId}], Request Id [${requestId}], WhatsApp Not Enabled`
      );
    }
    await logTenantEvictionActivity(
      req.userType!,
      Number(req.id)!,
      0,
      Number(req.platform),
      CONSTANTS.ACTIVITY_TYPES.TENANT_CANCELLED_MOVEOUT,
      occupancy.propId,
      Number(tenant.id),
      tenant.name,
      occupancy.roomId,
      ""
    );

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], Move Out Request Cancelled Successfully`
    );


    return res.status(200).json({
      isSuccess: true,
      msg: "Request Cancelled Successfully",
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.UpdateRegId = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "UpdateRegId";

  try {
    const tenantId = req.id;
    let { regId } = req.body;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Reg Id [${regId}]`);

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Reg Id [${regId}], No Tenant Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    await tenantDB.updateRegId({ id: tenantId, regId });

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Reg Id [${regId}], Reg Id updated successfully`
    );

    return res.status(200).json({
      msg: "Reg Id updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.AddBasicByClient = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddBasicByClient";

  try {
    let {
      bedId,
      propId,
      roomId,
      fullName,
      mobile,
      gender,
      occupation,
      allBedsOccupied,
      alternateMobile,
      entireFlatOccupied,
      isRentalBondAllowed,
      bondAllowed,
    } = req.body;

    if (!isRentalBondAllowed) {
      isRentalBondAllowed = bondAllowed || CONSTANTS.RENTAL_BOND.MANDATORY;
    }

    const userType = req.userType;
    let clientId = req.id;
    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], All Beds Occupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Staff Id [${req.id}], Entire Flat Occupied [${entireFlatOccupied}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], All Beds Occupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Staff Id [${req.id}], Entire Flat Occupied [${entireFlatOccupied}], Bond Allowed [${isRentalBondAllowed}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], All Beds Occupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Entire Flat Occupied [${entireFlatOccupied}], Bond Allowed [${isRentalBondAllowed}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });
    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], alternateMobile [${alternateMobile}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    // if (mobile === client.mobile) {
    //   log.info(
    //     `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], alternateMobile [${alternateMobile}], Tenant & client mobile are same, Client can't occupy himself.`
    //   );
    //   return res.status(400).json({
    //     msg: "You can't occupy yourself",
    //     isSuccess: false,
    //   });
    // }

    // 2025-02-01 3:10 pm Abhinav, Commenting below code to allow staff to become tenant

    // let isExistsWithSameMobile = false;
    // const isStaffExistsWithSameMobile = await staffDB.getByMobile({ mobile });
    // if (isStaffExistsWithSameMobile) isExistsWithSameMobile = true;

    // if (isExistsWithSameMobile) {
    //   log.info(
    //     `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], Mobile number is already linked with another account`
    //   );

    //   return res.status(400).json({
    //     msg: "Mobile number is already linked with another account",
    //     isSuccess: false,
    //   });
    // }

    const property = await propertyDB.getById({ id: propId });
    if (!property) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], alternateMobile [${alternateMobile}], No Property Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (property.status !== CONSTANTS.PROPERTY_STATUS.ACTIVE && client?.allowTenantOnInactiveProperty !== 1) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], alternateMobile [${alternateMobile}], Property is not active`
      );
      return res.status(400).json({
        msg: "Please make this property active to add a tenant",
        isSuccess: false,
      });
    }

    if (property.status !== CONSTANTS.PROPERTY_STATUS.ACTIVE && client?.allowTenantOnInactiveProperty === 1) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Adding Tenant In InActive Property`);
    }

    const room = await roomDB.getById({ id: roomId });
    if (!room) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], alternateMobile [${alternateMobile}], No Room Found`
      );
      return res.status(400).json({ msg: "No Room Found", isSuccess: false });
    }

    if (room.status === CONSTANTS.ROOM_STATUS.OCCUPIED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], alternateMobile [${alternateMobile}], Room is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Room is already occupied", isSuccess: false });
    }

    if (entireFlatOccupied) {
      const flatVacant = await flatDB.isFlatFullyVacant({ id: room.flatId });
      if (!flatVacant) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], alternateMobile [${alternateMobile}], Flat is not vacant to occupy fully`
        );
        return res.status(400).json({
          msg: "Flat is not not vacant to occupy fully",
          isSuccess: false,
        });
      }
    }

    if (allBedsOccupied && room.status !== CONSTANTS.ROOM_STATUS.VACANT) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], All Beds Occupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Room is not vacant to occupy all beds`
      );
      return res.status(400).json({
        msg: "Room is not vacant to occupy all beds",
        isSuccess: false,
      });
    }

    let tenantId = null;

    const tenant = await tenantDB.getByMobile({ mobile });

    let occupancy = null;

    if (!tenant) {
      tenantId = await tenantDB.createByOwner({
        mobile,
        name: fullName,
        gender,
        occupation,
        alternateMobile: alternateMobile || null,
      });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], Tenant Created Successfully`
      );
    } else {
      //TENANT EXISTS

      occupancy = await occupancyDB.getByClientIdAndTenantId({
        clientId,
        tenantId: tenant.id,
      });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], Tenant Id [${tenant.id}], Tenant already exists`
      );

      if (
        occupancy[0]?.status === CONSTANTS.OCCUPANCY_STATUS.OCCUPIED ||
        occupancy[0]?.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT
      ) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], Tenant is already occupied or moving out`
        );
        return res.status(400).json({
          msg: "Tenant is already occupied with another bed",
          isSuccess: false,
        });
      } else if (occupancy[0]?.status === CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], Tenant is already reserved with another bed`
        );
        return res.status(400).json({
          msg: "Tenant is already reserved with another bed",
          isSuccess: false,
        });
      } else {
        await tenantDB.updateBasicDetails({
          name: fullName,
          gender,
          occupation,
          id: tenant.id,
        });

        tenantId = tenant.id;

        await requestDB.removeByClientIdAndTenantId({
          clientId,
          tenantId: tenant.id,
        });

        // await documentDB.removeByTenantIdandClientId({
        //   tenantId: tenant.id,
        //   clientId,
        // });
        await documentDB.updateMoveOutStatus({
          tenantId: tenant.id,
          clientId,
          moveOut: 1,
        });

        await duesDB.removeByClientIdAndTenantId({
          clientId,
          tenantId: tenant.id,
        });

        await occupancyDB.removeByClinetIdAndTenantId({
          clientId,
          tenantId: tenant.id,
        });
      }
    }

    let beds: any = [];

    if (entireFlatOccupied) {
      beds = await bedDB.getVacantBedsForFlat({
        flatId: room.flatId,
      });
      if (!beds) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], All Beds Occupied [${allBedsOccupied}], No Vacant Bed Available`
        );
        return res.status(400).json({
          msg: "No Vacant Bed Available",
          isSuccess: false,
        });
      }
    } else if (allBedsOccupied) {
      beds = await bedDB.getVacantBeds({
        roomId: room.id,
        status: CONSTANTS.BED_STATUS.VACANT,
      });

      if (!beds) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], All Beds Occupied [${allBedsOccupied}], No Vacant Bed Available`
        );
        return res.status(400).json({
          msg: "No Vacant Bed Available",
          isSuccess: false,
        });
      }
    } else {
      const bed = await bedDB.getById({ id: bedId });

      if (!bed) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], All Beds Occupied [${allBedsOccupied}], No Bed Found with Bed Id [${bedId}]`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }

      if (bed.status !== CONSTANTS.BED_STATUS.VACANT) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], All Beds Occupied [${allBedsOccupied}], Bed is not vacant`
        );
        return res.status(400).json({
          msg: "Bed is not vacant to occupy another tenant",
          isSuccess: false,
        });
      }

      beds = [bed];
    }

    for (const bed of beds) {
      const getRequestedOccupancy = await occupancyDB.getRequestedOccupancy({
        roomId: bed.roomId,
        status: CONSTANTS.OCCUPANCY_STATUS.REQUESTED,
      });

      if (getRequestedOccupancy) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Bed Id [${bed.id}], Occupancy request found, rejecting the request`
        );

        const request = await requestDB.getByOccupancyAndType({
          occupancyId: getRequestedOccupancy.id,
          type: CONSTANTS.REQUEST_TYPE.ROOM_SELECTION,
        });

        const tenant = await tenantDB.getById({ id: request.tenantId });

        await requestDB.updateStatus({
          id: request?.id,
          status: CONSTANTS.REQUEST_STATUS.REJECTED,
        });

        await requestDB.updateOccupancyId({
          id: request?.id,
          occupancyId: null,
        });

        if (request?.occupancyId) {
          await occupancyDB.removeById({
            id: request.occupancyId,
          });
        }

        if (request?.tenantId) {
          await tenantDB.updateStatus({
            id: request.tenantId,
            status: CONSTANTS.TENANT_STATUS.VACANT,
          });
        }

        const msg = CONSTANTS.MSG.ROOM_REQUEST_REJECTED.replace(
          "{#var#}",
          tenant.name
        );

        let isNotiSent = false;
        if (tenant.regId) {
          isNotiSent = await sendNotification({
            title: "Room selection request rejected!",
            message:
              "Your room selection request has been rejected by the Owner/Manager. Tap to view details",
            regId: tenant.regId,
            userId: tenant.id,
            userType: CONSTANTS.USER_TYPE.TENANT,
            notiCategory:
              CONSTANTS.NOTIFICATION_CATEGORY.OFFLINE_PAYMENT_CONFIRM,
            clientId: Number(clientId),
          });
        } else {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Request Id [${request.id}], No RegId found for tenant [${tenant.id}]`
          );
        }

        const isSent = await sendSMS(
          tenant.mobile,
          msg,
          CONSTANTS.SMS_TEMPLATE_IDS.ROOM_REQUEST_REJECTED
        );
      }

      const pendingOccupancy = await occupancyDB.getByBedIdAndTenantIdAndStatus({
        clientId,
        tenantId: tenantId,
        bedId: bed.id,
        status: CONSTANTS.OCCUPANCY_STATUS.PENDING,
      });

      //To handle if client goes back to first step and selects occupy all beds or entire flat
      //and if any pending occupancy is there for that bed, then we will update that occupancy
      if (pendingOccupancy) {
        const occupancyId = await occupancyDB.updateBasicDetails({
          clientId,
          tenantId,
          propId,
          roomId: bed.roomId,
          flatId: Number(room.flatId) ? room.flatId : null,
          bedId: bed.id,
          floor: room.floor || null,
          isOnlinePaymentEnabled: property.isOnlinePaymentEnabled,
          rentalBond: isRentalBondAllowed,
          id: pendingOccupancy.id,
          isFoodOpted: property.isFoodEnabled || 0,
        });

        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], Bed Id [${bed.id}], OccupancyId [${occupancyId}],  Pending Occupancy Updated Successfully`
        );
      } else {
        const occupancyId = await occupancyDB.addBasicDetails({
          clientId,
          tenantId,
          propId,
          roomId: bed.roomId,
          flatId: Number(room.flatId) ? room.flatId : null,
          bedId: bed.id,
          floor: room.floor || null,
          isOnlinePaymentEnabled: property.isOnlinePaymentEnabled,
          rentalBond: isRentalBondAllowed,
          isFoodOpted: property.isFoodEnabled || 0,
        });
        //Pallav - created occupancy gId for new occupancy
        let ogId = await generateOccupancyGId();
        occupancyDB.updateGId({ gId: ogId, tenantId, clientId });
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], Bed Id [${bed.id}], OccupancyId [${occupancyId}],  Occupancy Created Successfully`
        );
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Occupation [${occupation}], Tenant Basic Details Added Successfully`
    );

    return res.status(200).json({
      msg: "Tenant Basic Details Added Successfully",
      tenantId,
      propId,
      roomId,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

/**
 * Not Getting used as of now
 */
tenants.AddAgreementByClient = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddAgreementByClient";

  try {
    const {
      monthlyRent,
      securityDeposit,
      agreementStartDate,
      rentalCycle,
      agreementPeriod,
      noticePeriod,
      lockInPeriod,
      moveInDate,
      tenantId,
      electricityReading,
      propId,
      roomId,
      entireFlatOccupied,
    } = req.body;

    const userType = req.userType;
    let clientId = req.id;
    let dueDescription = "";
    let totalDueAmount = 0;
    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Electricity Reading [${electricityReading}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Electricity Reading [${electricityReading}], Staff Id [${req.id}], EntireFlatOccupied [${entireFlatOccupied}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Electricity Reading [${electricityReading}], EntireFlatOccupied [${entireFlatOccupied}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancies = null;
    if (entireFlatOccupied) {
      const room = await roomDB.getById({ id: roomId });
      occupancies = await occupancyDB.getOccupanciesForFlat({
        clientId,
        tenantId,
        propId,
        flatId: room.flatId,
      });
    } else {
      occupancies = await occupancyDB.getParticularOccupancies({
        clientId,
        tenantId,
        propId,
        roomId,
      });
    }

    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Occupancy Count [${occupancies.length}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (
      occupancies[0].status === CONSTANTS.TENANT_STATUS.OCCUPIED ||
      occupancies[0].staus === CONSTANTS.TENANT_STATUS.RESERVED
    ) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Tenant is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Tenant is already occupied", isSuccess: false });
    }

    const property = await propertyDB.getById({ id: propId });
    const room = await roomDB.getById({ id: roomId });

    if (room.status === CONSTANTS.ROOM_STATUS.OCCUPIED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Room is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Room is already occupied", isSuccess: false });
    }

    const isFutureDate = moment(moveInDate).isAfter(moment(), "day");

    for (const occupancy of occupancies) {
      const bed = await bedDB.getById({ id: occupancy.bedId });

      if (bed.status === CONSTANTS.BED_STATUS.OCCUPIED) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], bed Id [${bed.id}], bed is already occupied`
        );
        return res
          .status(400)
          .json({ msg: "bed is already occupied", isSuccess: false });
      }

      await occupancyDB.updateAgreementDetails({
        id: occupancy.id,
        rent: monthlyRent,
        security: securityDeposit,
        agreementStartDate,
        rentalCycle,
        agreementPeriod,
        noticePeriod,
        lockInPeriod,
        moveInDate,
        electricityReading,
        fine: property.fine,
        fineType: property.fineType,
        gracePeriod: property.gracePeriod,
        status: isFutureDate
          ? CONSTANTS.OCCUPANCY_STATUS.RESERVED
          : CONSTANTS.OCCUPANCY_STATUS.OCCUPIED,
      });

      if (isFutureDate) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.VACANT_RESERVED,
        });
      } else {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.OCCUPIED,
        });
      }
    }

    // Sukhbir - MultiOccupancy
    // if (isFutureDate) {
    //   await tenantDB.updateStatus({
    //     id: tenantId,
    //     status: CONSTANTS.TENANT_STATUS.RESERVED,
    //   });
    // } else {
    //   await tenantDB.updateStatus({
    //     id: tenantId,
    //     status: CONSTANTS.TENANT_STATUS.OCCUPIED,
    //   });
    // }

    let referenceId = await generateLedgerReferenceId({ clientId });
    if (Number(securityDeposit)) {
      totalDueAmount = securityDeposit;
      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.SECURITY);
      await duesDB.add({
        tenantId,
        amount: securityDeposit,
        occupancyId: occupancies[0].id,
        roomId,
        propId,
        clientId,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.SECURITY,
        balance: securityDeposit,
        ledgerReferenceId: referenceId,
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: securityDeposit,
        balance: securityDeposit,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.SECURITY,
        rentStartDate: null,
        rentEndDate: null,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: dueDescription,
      });
    }

    if (Number(monthlyRent)) {
      const rents = calculateMonthlyRent(moveInDate, rentalCycle, monthlyRent);
      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.RENT);
      if (Array.isArray(rents) && rents.length > 0) {
        for (let rent of rents) {
          referenceId = await generateLedgerReferenceId({ clientId });
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Start Date [${rent.startDate}], End Date [${rent.endDate}], Rent [${rent.rent}]`
          );
          totalDueAmount = totalDueAmount + rent.rent;
          await duesDB.addWithStartEndDate({
            tenantId,
            amount: rent.rent,
            occupancyId: occupancies[0].id,
            roomId,
            propId,
            clientId,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            type: CONSTANTS.DUES_TYPES.RENT,
            balance: rent.rent,
            ledgerReferenceId: referenceId,
          });
          await ledgerDB.add({
            tenantId: tenantId,
            roomId: roomId,
            propId: propId,
            clientId,
            amount: rent.rent,
            balance: rent.rent,
            referenceId: referenceId,
            transactionId: null,
            type: CONSTANTS.DUES_TYPES.RENT,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
            //changed below description moment(---) at 17/12/2024 20:03:00
            description: `${dueDescription} for ${moment(rent.startDate).format(
              "MMM YYYY"
            )}`,
          });
        }
      }
    }

    const extraCharges = await extraChargeDB.getAllByClientIdAndPropId({
      clientId,
      propId,
    });

    if (extraCharges) {
      for (let extraCharge of extraCharges) {
        let type = 0;

        type = convertTypes({
          from: "extraChargeType",
          to: "dueType",
          type: extraCharge.type,
        });
        let rents = calculateMonthlyRent(
          moveInDate,
          rentalCycle,
          extraCharge.amount
        );
        if (extraCharge.repetitionType === 1) {
          rents = [
            {
              month: moment().format("MMMM"),
              year: moment().year(),
              rent: extraCharge.amount,
              startDate: moveInDate,
              endDate: "null",
            },
          ];
        }
        for (let rent of rents) {
          if (type === 0) continue;
          referenceId = await generateLedgerReferenceId({ clientId });
          dueDescription = getDueDescription(extraCharge.type);
          totalDueAmount = totalDueAmount + rent.rent;
          await duesDB.add({
            tenantId,
            amount: rent.rent,
            occupancyId: occupancies[0].id,
            roomId,
            propId,
            clientId,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            type,
            balance: rent.rent,
            ledgerReferenceId: referenceId,
          });
          await ledgerDB.add({
            tenantId: tenantId,
            roomId: roomId,
            propId: propId,
            clientId,
            amount: rent.rent,
            balance: rent.rent,
            referenceId: referenceId,
            transactionId: null,
            type: type,
            rentStartDate: null,
            rentEndDate: null,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            description: dueDescription,
          });
        }
      }
    }

    let roomStatus = null;

    if (occupancies.length > 1) {
      roomStatus = CONSTANTS.ROOM_STATUS.OCCUPIED;
    } else {
      const bed = await bedDB.getVacantBed({
        roomId,
        status: CONSTANTS.BED_STATUS.VACANT,
      });
      if (bed) roomStatus = CONSTANTS.ROOM_STATUS.SEMI_OCCUPIED;
      else roomStatus = CONSTANTS.ROOM_STATUS.OCCUPIED;
    }

    for (let occupancy of occupancies) {
      await roomDB.updateStatus({
        id: occupancy.roomId,
        status: roomStatus,
      });
    }

    let gId = await generateTenantGId();

    await tenantDB.updateGId({ gId, id: tenantId });

    const link = `${process.env.TENANT_CHECKIN_PATH}/${gId}`;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Link [${link}]`
    );

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId,
      propId,
    });

    if (propSettings) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Settings are there`
      );
      if (propSettings.sms === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], SMS Notification not enabled`
        );
        const msg = CONSTANTS.MSG.TENANT_ADDED.replace("{#var#}", room.roomNum)
          .replace("{#var2#}", property.name)
          .replace("{#var3#}", link);
        //sendSMS(tenant.mobile, msg, CONSTANTS.SMS_TEMPLATE_IDS.TENANT_ADDED);
      }
      if (propSettings.onboardWelcome === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], WhatsApp Notification not enabled`
        );
        let flatName = "";
        let footerText = propSettings.footer || "The Kipinn Team";
        let template =
          propSettings.whatsappOnboardingTemplate || "tenantonboardbyownernew";
        if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: room.flatId });
          flatName = name + "," + room.roomNum;
        } else {
          flatName = room.roomNum;
        }
        sendWhatsappTenantWelcome(
          tenant.mobile,
          tenant.name,
          property.name,
          String(monthlyRent),
          String(securityDeposit),
          flatName,
          moveInDate,
          footerText,
          template,
          Number(clientId)
        );
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Is Future Date [${isFutureDate}], Web check-in [${link}], Tenant Dues [${totalDueAmount}], Tenant Agreement Details Added & Occupancy Created Successfully`
    );

    return res.status(200).json({
      msg: "Tenant Agreement Details Added & Occupancy Created Successfully",
      data: {
        tenantName: tenant.name,
        roomNum: room.roomNum,
        floor: room.floor,
        allbedsOccupied: occupancies.length > 1,
        totalDueAmount: totalDueAmount,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.AddAgreementByClientX = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddAgreementByClientX";

  try {
    let {
      monthlyRent,
      securityDeposit,
      agreementStartDate,
      rentalCycle,
      rentalType,
      agreementPeriod,
      noticePeriod,
      lockInPeriod,
      moveInDate,
      tenantId,
      electricityReading,
      propId,
      roomId,
      entireFlatOccupied,
      bookingAmount = 0,
      bookingAdjustType = 0,
      sendNotiToTenant=1, //only done through Add booking
      addFromBooking=0,
    } = req.body;

    const userType = req.userType;
    let clientId = req.id;
    let dueDescription = "";
    let totalDueAmount = 0;

    if (rentalCycle === "00") {
      rentalCycle = moment(moveInDate).format("DD");
    }

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Electricity Reading [${electricityReading}], Booking Amount [${bookingAmount}], Booking Adjust Type [${bookingAdjustType}], Added From Booking [${addFromBooking}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Electricity Reading [${electricityReading}], Staff Id [${req.id}], EntireFlatOccupied [${entireFlatOccupied}], Booking Amount [${bookingAmount}], Booking Adjust Type [${bookingAdjustType}], Added From Booking [${addFromBooking}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Electricity Reading [${electricityReading}], EntireFlatOccupied [${entireFlatOccupied}], Booking Amount [${bookingAmount}], Booking Adjust Type [${bookingAdjustType}], Added From Booking [${addFromBooking}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancies = null;
    if (entireFlatOccupied) {
      const room = await roomDB.getById({ id: roomId });
      occupancies = await occupancyDB.getOccupanciesForFlat({
        clientId,
        tenantId,
        propId,
        flatId: room.flatId,
      });
    } else {
      occupancies = await occupancyDB.getParticularOccupancies({
        clientId,
        tenantId,
        propId,
        roomId,
      });
    }

    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Occupancy Count [${occupancies.length}]`
    );

    let discountEndDate = null;
    let discountStartDate = null;

    if (occupancies[0].discount > 0 && occupancies[0].discountPeriod > 0) {
      log.info(
        `[${C}], [${F}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Discount Direction [${occupancies[0].isDiscountFromFirstMonth}], Discount Period [${occupancies[0].discountPeriod}], Discount Available, Adding Start and End Date`
      );
      let nextRentCycle = moment(moveInDate).date(rentalCycle);
      if (
        Number(moment(moveInDate).date()) !== Number(rentalCycle) &&
        rentalType === CONSTANTS.RENTAL_TYPES.MONTHLY
      ) {
        nextRentCycle = moment(nextRentCycle).add(rentalType, "month");
      }

      if (Number(occupancies[0].isDiscountFromFirstMonth) === 1) {
        discountEndDate = nextRentCycle
          .clone()
          .add(occupancies[0].discountPeriod, "months")
          .subtract(1, "day")
          .format("YYYY-MM-DD");
        discountStartDate = moment(nextRentCycle).format("YYYY-MM-DD");
      } else {
        discountEndDate = moment(agreementStartDate)
          .add(agreementPeriod, "months")
          .subtract(1, "day")
          .format("YYYY-MM-DD");
        discountStartDate = moment(agreementStartDate)
          .add((agreementPeriod - occupancies[0].discountPeriod), "months")
          .format("YYYY-MM-DD");
      }

      await occupancyDB.updateDiscountStartEndDate({
        tenantId,
        clientId,
        discountStartDate,
        discountEndDate,
      });

      // log.info(
      //   `[${C}], [${F}], Start Date [${discountStartDate}], Discount End Date [${discountEndDate}]`
      // );
    }

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (
      occupancies[0].status === CONSTANTS.OCCUPANCY_STATUS.OCCUPIED ||
      occupancies[0].staus === CONSTANTS.OCCUPANCY_STATUS.RESERVED
    ) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Tenant is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Tenant is already occupied", isSuccess: false });
    }

    const property = await propertyDB.getById({ id: propId });
    const room = await roomDB.getById({ id: roomId });

    if (room.status === CONSTANTS.ROOM_STATUS.OCCUPIED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Room is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Room is already occupied", isSuccess: false });
    }

    const isFutureDate = moment(moveInDate).isAfter(moment(), "day");

    for (const occupancy of occupancies) {
      const bed = await bedDB.getById({ id: occupancy.bedId });

      if (bed.status === CONSTANTS.BED_STATUS.OCCUPIED) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], bed Id [${bed.id}], bed is already occupied`
        );
        return res
          .status(400)
          .json({ msg: "bed is already occupied", isSuccess: false });
      }

      let monthDiff = moment(moveInDate).diff(moment(), "month");
      if (Number(moment().format("DD")) < Number(rentalCycle)) {
        monthDiff = Math.abs(monthDiff) - 1;
      }

      let rentalMonths =
        rentalType - 1 - (Math.abs(Math.abs(monthDiff)) % rentalType);

      if (moment(moveInDate).isAfter(moment())) {
        rentalMonths = 0;
      }

      if (moment(moveInDate).date() < Number(rentalCycle)) {
        rentalMonths += 1;
      }

      await occupancyDB.updateAgreementDetailsX({
        id: occupancy.id,
        rent: monthlyRent,
        security: securityDeposit,
        agreementStartDate,
        rentalCycle,
        rentalType,
        rentalMonths: rentalMonths,
        agreementPeriod,
        noticePeriod,
        lockInPeriod,
        moveInDate,
        electricityReading,
        fine: property.fine,
        fineType: property.fineType,
        gracePeriod: property.gracePeriod,
        status: isFutureDate
          ? CONSTANTS.OCCUPANCY_STATUS.RESERVED
          : CONSTANTS.OCCUPANCY_STATUS.OCCUPIED,
        bookingAmt: Number(bookingAmount) || 0,
        bookingAdjustType: Number(bookingAdjustType) || 0,
      });

      if (userType === CONSTANTS.USER_TYPE.STAFF) {
        await occupancyDB.updateBookedBy({
          bookedBy: Number(req.id),
          id: occupancy.id
        });
      }

      // Create booking for tenants
      if (Number(addFromBooking) === 0) {
        try {
          let applicationNumber = `KIP${clientId}${tenant.id}${moment().format("HHmmss")}`;
          
          const bookingId = await bookingsDB.create({
            clientId: clientId,
            tenantId: tenant.id,
            propId,
            roomId,
            moveInDate,
            status: isFutureDate ? CONSTANTS.BOOKING_STATUS.CONFIRMED : CONSTANTS.BOOKING_STATUS.MOVED_IN,
            stayType: CONSTANTS.STAY_TYPE.NORMAL,
            applicationNumber,
          });
  
          if (userType === CONSTANTS.USER_TYPE.STAFF) {
            await bookingsDB.updateBookedBy({
              bookedBy: Number(req.id),
              id: bookingId,
            });
          }
        } catch(error: any) {
          log.info(`[${C}], [${F}], Error In Booking Creation, Error: ${error?.message || error}`);
        }
      }
      
      if (isFutureDate) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.VACANT_RESERVED,
        });
        // await tenantDB.updateStatus({
        //   status: CONSTANTS.TENANT_STATUS.RESERVED,
        //   id: tenantId, 
        // });
        
        // // Create booking for reserved tenants
        // if (Number(addFromBooking) === 0) {
        //   let applicationNumber = `KIP${clientId}${tenant.id}${moment().format("HHmmss")}`;
          
        //   const bookingId = await bookingsDB.create({
        //     clientId: clientId,
        //     tenantId: tenant.id,
        //     propId,
        //     roomId,
        //     moveInDate,
        //     status: CONSTANTS.BOOKING_STATUS.CONFIRMED,
        //     stayType: CONSTANTS.STAY_TYPE.NORMAL,
        //     applicationNumber,
        //   });

        //   if (userType === CONSTANTS.USER_TYPE.STAFF) {
        //     await bookingsDB.updateBookedBy({
        //       bookedBy: Number(req.id),
        //       id: bookingId,
        //     });
        //   }
        // }
      } else {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.OCCUPIED,
        });
        // await tenantDB.updateStatus({
        //   status: CONSTANTS.TENANT_STATUS.OCCUPIED,
        //   id: tenantId, 
        // });
      }
    }

    //Function to update tId in tenant
    await generateTenantTId(
      tenantId,
    );

    //Tally Handling
    await setOccupancyTallyStatus(
      Number(clientId),
      occupancies[0],
      CONSTANTS.TALLY_STATUS.PENDING,
    );

    // Sukhbir - MultiOccupancy
    // if (isFutureDate) {
    //   await tenantDB.updateStatus({
    //     id: tenantId,
    //     status: CONSTANTS.TENANT_STATUS.RESERVED,
    //   });
    // } else {
    //   await tenantDB.updateStatus({
    //     id: tenantId,
    //     status: CONSTANTS.TENANT_STATUS.OCCUPIED,
    //   });
    // }

    const securityAdjustEntry = await ledgerDB.getLastEntry({
      tenantId,
      clientId,
    });

    if (securityAdjustEntry !== false) {
      await ledgerDB.forfeitRefund ({
        id: securityAdjustEntry.id,
        amount: 0,
        balance: 0,
        description: `Refund of amount ₹${Math.abs(securityAdjustEntry?.balance)} has been forfeited during eviction.`
      });
    }

    let referenceId = await generateLedgerReferenceId({ clientId });
    if (Number(securityDeposit)) {
      totalDueAmount = Number(securityDeposit);
      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.SECURITY);
      const dueId = await duesDB.addX({
        tenantId,
        amount: securityDeposit,
        occupancyId: occupancies[0].id,
        roomId,
        propId,
        clientId,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.SECURITY,
        balance: securityDeposit,
        ledgerReferenceId: referenceId,
        title: "Security Deposit",
        description: "Due added while onboarding",
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: securityDeposit,
        balance: securityDeposit,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.SECURITY,
        rentStartDate: null,
        rentEndDate: null,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: dueDescription,
        title: "Security Deposit",
      });
      await setDueTallyStatus(
        Number(clientId),
        dueId,
        CONSTANTS.TALLY_STATUS.PENDING
      );
    }


    //Last 2 Month Rent Creation
    const agreementEndRent = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.AGREEMENT_END_RENT,
    });

    if ((agreementEndRent && Number(agreementEndRent.value) === 1)) {
      let rents = [];
      rents = calculateAgreementEndRent(
        moveInDate,
        rentalCycle,
        monthlyRent,
        rentalType,
        Number(agreementPeriod),
      );

      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.RENT);

      let discountFlag = 1;

      if (Array.isArray(rents) && rents.length > 0) {
        for (let rent of rents) {
          referenceId = await generateLedgerReferenceId({ clientId });
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Start Date [${rent.startDate}], End Date [${rent.endDate}], Rent [${rent.rent}]`
          );

          let rentAfterDiscount = rent.rent;

          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Dicount Start Date [${discountStartDate}], Discount End Date [${discountEndDate}], Rent Start Date [${rent.startDate}] Disocunt Added`
          );

          if (
            Number(occupancies[0].discount) &&
            Number(occupancies[0].discount) > 0 &&
            moment(discountStartDate).isSameOrBefore(rent.startDate) &&
            discountFlag <= Number(occupancies[0].discountPeriod)
          ) {
            if (occupancies[0].discountType === CONSTANTS.DISCOUNT_TYPE.FLAT) {
              rentAfterDiscount = Number(rent.rent) - Number(occupancies[0].discount);
              discountFlag += 1;
            } else if (occupancies[0].discountType === CONSTANTS.DISCOUNT_TYPE.PERCENTAGE) {
              rentAfterDiscount = rent.rent - ((rent.rent * occupancies[0].discount) / 100);
              discountFlag += 1;
            }

            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Discount Type [${occupancies[0].discountType}], Discount [${occupancies[0].discount}], Rent Amount [${rent.rent}], Rent After Discount [${rentAfterDiscount}], Disocunt Added`
            );
          }

          // totalDueAmount = totalDueAmount + rent.rent;
          totalDueAmount = totalDueAmount + Number(rentAfterDiscount);
          if (rentAfterDiscount > 0) {
            const dueId = await duesDB.addWithStartEndDateX({
              tenantId,
              // amount: rent.rent,
              amount: rentAfterDiscount,
              occupancyId: occupancies[0].id,
              roomId,
              propId,
              clientId,
              rentStartDate: rent.startDate,
              rentEndDate: rent.endDate,
              dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
              type: CONSTANTS.DUES_TYPES.RENT,
              // balance: rent.rent,
              balance: rentAfterDiscount,
              ledgerReferenceId: referenceId,
              title: "Rent",
              description: "Due added while onboarding",
              discount: rent.rent - rentAfterDiscount,
            });
            await ledgerDB.add({
              tenantId: tenantId,
              roomId: roomId,
              propId: propId,
              clientId,
              // amount: rent.rent,
              amount: rentAfterDiscount,
              // balance: rent.rent,
              balance: rentAfterDiscount,
              referenceId: referenceId,
              transactionId: null,
              type: CONSTANTS.DUES_TYPES.RENT,
              rentStartDate: rent.startDate,
              rentEndDate: rent.endDate,
              dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
              //changed below description moment(---) at 17/12/2024 20:03:00
              description: rent.rent - rentAfterDiscount > 0
                ? `${dueDescription} for ${moment(rent.startDate).format("DD MMM YY")} to ${moment(rent.endDate).format("DD MMM YY")}, Discount of ₹${rent.rent - rentAfterDiscount} given for this due`
                : `${dueDescription} for ${moment(rent.startDate).format("DD MMM YY")} to ${moment(rent.endDate).format("DD MMM YY")}`,
              discount: rent.rent - rentAfterDiscount,
              title: "Rent",
            });
            await setDueTallyStatus(
              Number(clientId),
              dueId,
              CONSTANTS.TALLY_STATUS.PENDING
            );
          }
        }
      }
    }

    if (Number(monthlyRent)) {
      let rents = [];
      if (isFutureDate) {
        if (Number(bookingAmount) > 0 && Number(bookingAdjustType) === CONSTANTS.BOOKING_ADJUST_TYPE.RENT) {
          rents = calculateRentAsPerRentalTypeForReservedRentBooking(
            moveInDate,
            rentalCycle,
            monthlyRent,
            rentalType
          );
        } else {
          rents = calculateRentAsPerRentalTypeForReserved(
            moveInDate,
            rentalCycle,
            monthlyRent,
            rentalType
          );
        }
      } else {
        rents = calculateRentAsPerRentalType(
          moveInDate,
          rentalCycle,
          monthlyRent,
          rentalType
        );
      }
      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.RENT);

      let discountFlag = 1;

      if (Array.isArray(rents) && rents.length > 0) {
        for (let rent of rents) {
          referenceId = await generateLedgerReferenceId({ clientId });
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Start Date [${rent.startDate}], End Date [${rent.endDate}], Rent [${rent.rent}]`
          );

          let rentAfterDiscount = rent.rent;

          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Dicount Start Date [${discountStartDate}], Discount End Date [${discountEndDate}], Rent Start Date [${rent.startDate}] Disocunt Added`
          );

          if (
            Number(occupancies[0].discount) &&
            Number(occupancies[0].discount) > 0 &&
            moment(discountStartDate).isSameOrBefore(rent.startDate) &&
            discountFlag <= Number(occupancies[0].discountPeriod)
          ) {
            if (occupancies[0].discountType === CONSTANTS.DISCOUNT_TYPE.FLAT) {
              rentAfterDiscount = Number(rent.rent) - Number(occupancies[0].discount);
              discountFlag += 1;
            } else if (occupancies[0].discountType === CONSTANTS.DISCOUNT_TYPE.PERCENTAGE) {
              rentAfterDiscount = rent.rent - ((rent.rent * occupancies[0].discount) / 100);
              discountFlag += 1;
            }

            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Discount Type [${occupancies[0].discountType}], Discount [${occupancies[0].discount}], Rent Amount [${rent.rent}], Rent After Discount [${rentAfterDiscount}], Disocunt Added`
            );
          }

          // totalDueAmount = totalDueAmount + rent.rent;
          totalDueAmount = totalDueAmount + Number(rentAfterDiscount);
          if (rentAfterDiscount > 0) {
            const dueId = await duesDB.addWithStartEndDateX({
              tenantId,
              // amount: rent.rent,
              amount: rentAfterDiscount,
              occupancyId: occupancies[0].id,
              roomId,
              propId,
              clientId,
              rentStartDate: rent.startDate,
              rentEndDate: rent.endDate,
              dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
              type: CONSTANTS.DUES_TYPES.RENT,
              // balance: rent.rent,
              balance: rentAfterDiscount,
              ledgerReferenceId: referenceId,
              title: "Rent",
              description: "Due added while onboarding",
              discount: rent.rent - rentAfterDiscount,
            });
            await ledgerDB.add({
              tenantId: tenantId,
              roomId: roomId,
              propId: propId,
              clientId,
              // amount: rent.rent,
              amount: rentAfterDiscount,
              // balance: rent.rent,
              balance: rentAfterDiscount,
              referenceId: referenceId,
              transactionId: null,
              type: CONSTANTS.DUES_TYPES.RENT,
              rentStartDate: rent.startDate,
              rentEndDate: rent.endDate,
              dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
              //changed below description moment(---) at 17/12/2024 20:03:00
              description: rent.rent - rentAfterDiscount > 0
                ? `${dueDescription} for ${moment(rent.startDate).format("DD MMM YY")} to ${moment(rent.endDate).format("DD MMM YY")}, Discount of ₹${rent.rent - rentAfterDiscount} given for this due`
                : `${dueDescription} for ${moment(rent.startDate).format("DD MMM YY")} to ${moment(rent.endDate).format("DD MMM YY")}`,
              discount: rent.rent - rentAfterDiscount,
              title: "Rent",
            });
            await setDueTallyStatus(
              Number(clientId),
              dueId,
              CONSTANTS.TALLY_STATUS.PENDING
            );
          }
        }
      }
    }

    const extraCharges = await extraChargeDB.getAllByClientIdAndPropId({
      clientId,
      propId,
    });

    if (extraCharges) {
      for (let extraCharge of extraCharges) {
        let type = 0;

        type = convertTypes({
          from: "extraChargeType",
          to: "dueType",
          type: extraCharge.type,
        });

        if (Number(type) === CONSTANTS.DUES_TYPES.MOVE_OUT_CHARGES) {
          continue;
        }

        let isFullCharge = false;
        if (
          Number(type) === CONSTANTS.DUES_TYPES.TECH_CHARGES || 
          Number(type) === CONSTANTS.DUES_TYPES.LAUNDRY_CHARGES ||
          Number(type) === CONSTANTS.DUES_TYPES.FOOD ||
          Number(type) === CONSTANTS.DUES_TYPES.BUS_CHARGES ||
          Number(type) === CONSTANTS.DUES_TYPES.OTHER
        ) {
          isFullCharge = true;
        }

        // let rents = calculateMonthlyRent(moveInDate, rentalCycle, extraCharge.amount);
        let rents = [];
        if (isFutureDate) {
          rents = calculateRentAsPerRentalTypeForReserved(
            moveInDate,
            rentalCycle,
            extraCharge.amount,
            CONSTANTS.RENTAL_TYPES.MONTHLY
          );
        } else {
          rents = calculateRentAsPerRentalType(
            moveInDate,
            rentalCycle,
            extraCharge.amount,
            CONSTANTS.RENTAL_TYPES.MONTHLY
          );
        }
        if (extraCharge.repetitionType === 1) {
          rents = [
            {
              month: moment(moveInDate).format("MMMM"),
              year: moment(moveInDate).year(),
              rent: extraCharge.amount,
              startDate: moveInDate,
              endDate: moment(moveInDate).add(1, "day").format("YYYY-MM-DD"),
            },
          ];
        }
        for (let rent of rents) {
          if (type === 0) continue;
          referenceId = await generateLedgerReferenceId({ clientId });
          dueDescription = getDueDescription(extraCharge.type);
          totalDueAmount = totalDueAmount + Number(rent.rent);
          const dueId = await duesDB.addWithStartEndDateX({
            tenantId,
            amount: isFullCharge ? extraCharge.amount : rent.rent,
            // amount: rent.rent,
            occupancyId: occupancies[0].id,
            roomId,
            propId,
            clientId,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            type,
            // balance: rent.rent,
            balance: isFullCharge ? extraCharge.amount : rent.rent,
            ledgerReferenceId: referenceId,
            title: dueDescription,
            description: "Due added while onboarding",
          });
          await ledgerDB.add({
            tenantId: tenantId,
            roomId: roomId,
            propId: propId,
            clientId,
            // amount: rent.rent,
            amount: isFullCharge ? extraCharge.amount : rent.rent,
            // balance: rent.rent,
            balance: isFullCharge ? extraCharge.amount : rent.rent,
            referenceId: referenceId,
            transactionId: null,
            type: type,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            description: `${dueDescription} for ${moment(rent.startDate).format(
              "DD MMM YY"
            )} to ${moment(rent.endDate).format("DD MMM YY")}`,
            title: dueDescription,
          });
          await setDueTallyStatus(
            Number(clientId),
            dueId,
            CONSTANTS.TALLY_STATUS.PENDING
          );
        }
      }
    }

    let roomStatus = null;

    if (occupancies.length > 1) {
      roomStatus = CONSTANTS.ROOM_STATUS.OCCUPIED;
    } else {
      const bed = await bedDB.getVacantBed({
        roomId,
        status: CONSTANTS.BED_STATUS.VACANT,
      });
      if (bed) roomStatus = CONSTANTS.ROOM_STATUS.SEMI_OCCUPIED;
      else roomStatus = CONSTANTS.ROOM_STATUS.OCCUPIED;
    }

    for (let occupancy of occupancies) {
      await roomDB.updateStatus({
        id: occupancy.roomId,
        status: roomStatus,
      });
    }

    let gId = await generateTenantGId();

    await tenantDB.updateGId({ gId, id: tenantId });

    const link = `${process.env.TENANT_CHECKIN_PATH}/${gId}`;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Link [${link}]`
    );

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId,
      propId,
    });

    if (propSettings && Number(sendNotiToTenant) === 1) { 
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Settings are there`
      );
      if (propSettings.sms === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], SMS Notification enabled`
        );
        const msg = CONSTANTS.MSG.TENANT_ADDED.replace("{#var#}", room.roomNum)
          .replace("{#var2#}", property.name)
          .replace("{#var3#}", link);
        //sendSMS(tenant.mobile, msg, CONSTANTS.SMS_TEMPLATE_IDS.TENANT_ADDED);
      }
      if (propSettings.onboardWelcome === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], WhatsApp Notification enabled`
        );
        let flatName = "";
        let footerText = propSettings.footer || "The Kipinn Team";
        let template =
          propSettings.whatsappOnboardingTemplate || "tenantonboardbyownernew";
        if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: room.flatId });
          flatName = name + "," + room.roomNum;
        } else {
          flatName = room.roomNum;
        }
        let whatsappAgreegrator = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.WHATSAPP_AGGREGATOR
        });
        let onboardingCred = await getClientWhatsappCredentialsMessageCentral(Number(clientId), property?.id, CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.ONBOARDING, CONSTANTS.USER_TYPE.TENANT);
        if(whatsappAgreegrator && Number(whatsappAgreegrator?.value)  === CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL && onboardingCred) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through Message Central`
          );
          const bodyValues: Record<string, string> = {
            body_1: `${tenant?.name}`,
            body_2: `${property.name}`,
            body_3: `${property.name}`,
            body_4: `${flatName}`,
            body_5: `${moveInDate}`,
            body_6: `${monthlyRent}`,
            body_7: `${securityDeposit}`,
            body_8: `${propSettings.ios || " "}`,
            //body_9: `${propSettings.android.replace("*Android*:", "") || " "}`,
            body_9: (propSettings.android || "").replace(/^\*Android:\*\s*/i, "").trim() || " ",
            body_10: `${footerText.replace("Team", "").trim()}`
          };
          sendWhatsappMC(
            tenant?.mobile,
            Number(clientId),
            Number(property.id),
            bodyValues,
            CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.ONBOARDING,
            CONSTANTS.USER_TYPE.TENANT,
            CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL
          );

        } else {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through intrakt`
          );
          if (propSettings.android || propSettings.ios) {
            sendWhatsappTenantWelcomeWithLink(
              tenant.mobile,
              tenant.name,
              property.name,
              String(monthlyRent),
              String(securityDeposit),
              flatName,
              moveInDate,
              footerText,
              template,
              propSettings.android || " ",
              propSettings.ios || " ",
              Number(clientId),
            );
          } else {
            sendWhatsappTenantWelcome(
              tenant.mobile,
              tenant.name,
              property.name,
              String(monthlyRent),
              String(securityDeposit),
              flatName,
              moveInDate,
              footerText,
              template,
              Number(clientId),
            );
          }
        }

        if (Number(bookingAmount) && Number(bookingAmount) > 0) {
          log.info(`[${C}], [${F}], CLient Id [${clientId}], Tenant Id [${tenantId}], Sending Booking Amount Payment Link`)
          let paymentLink = `${process.env.PAYU_WEB_CHECKOUT_URL}/${occupancies[0].gId}/b`;
          if (client?.paymentLinkBaseUrl && client?.paymentLinkBaseUrl.trim() !== "") {
            paymentLink = `${client?.paymentLinkBaseUrl.trim()}/${occupancies[0].gId}/b`;
          }
          await new Promise(resolve => setTimeout(resolve, 2000));
          let whatsappAgreegrator = await clientConfigDB.getClientConfig({
            clientId,
            provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
            type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.WHATSAPP_AGGREGATOR
          });
          let bookingCred = await getClientWhatsappCredentialsMessageCentral(Number(clientId), property?.id, CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.BOOKING_LINK, CONSTANTS.USER_TYPE.TENANT);
          if(whatsappAgreegrator && Number(whatsappAgreegrator?.value)  === CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL && bookingCred) {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending Booking Payment link through message central`
            );

            const bodyValues: Record<string, string> = {
              body_1: `${tenant?.name}`,
              body_2: `${property.name}`,
              body_3: `${flatName}`,
              body_4: `${moveInDate}`,
              body_5: `${bookingAmount}`,
              body_6: `${paymentLink.replace(/_/g, '%5F').replace(/-/g, '%2D')}`,
              body_7: `${footerText.replace("Team", "").trim()}`
            };
            
            sendWhatsappMC(
              tenant?.mobile,
              Number(clientId),
              Number(property.id),
              bodyValues,
              CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.BOOKING_LINK,
              CONSTANTS.USER_TYPE.TENANT,
              CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL
            );
          } else {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending Booking Payment Link through intrakt`
            );
            sendWhatsappTenantBookingAmountWithLink(
              tenant.mobile,
              tenant.name,
              property.name,
              flatName,
              moveInDate,
              bookingAmount,
              paymentLink,
              footerText,
              Number(clientId),
              Number(propId),
            );
          }
        }
      }
    }

    await hiddenDuesDB.removeByTenantIdAndClientId({
      clientId,
      tenantId,
    });

    // //Function to update tId in tenant
    // await generateTenantTId(
    //   tenantId,
    // );

    await logActivity(
      Number(req.userType),
      Number(req.id),
      Number(req.parentClientId!),
      Number(req.platform),
      Number(tenantId),
      CONSTANTS.ACTIVITY_TYPES.TENANT_ADDED,
      isFutureDate ? true : false,
      moveInDate,
      null,
      null,
      null
    );

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Is Future Date [${isFutureDate}], Web check-in [${link}], Tenant Dues [${totalDueAmount}], Tenant Agreement Details Added & Occupancy Created Successfully`
    );

    return res.status(200).json({
      msg: "Tenant Agreement Details Added & Occupancy Created Successfully",
      data: {
        tenantName: tenant.name,
        roomNum: room.roomNum,
        floor: room.floor,
        allbedsOccupied: occupancies.length > 1,
        totalDueAmount: totalDueAmount,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.AddAgreementByClientShortStay = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddAgreementByClientShortStay";

  try {
    const {
      dailyRent,
      moveInDate,
      moveOutDate,
      tenantId,
      propId,
      roomId,
      entireFlatOccupied,
      notifyTenant = 0,
      securityDeposit = 0,
      addFromBooking=0,
    } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Daily Rent [${dailyRent}], Security [${securityDeposit}], Move In Date [${moveInDate}], Move Out Date [${moveOutDate}], Entire Flat Occupied [${entireFlatOccupied}], Notify Tenant [${notifyTenant}]`
    );

    const userType = req.userType;
    let totalDueAmount = 0;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, Platform [${req.platform}], ${isPartner ? "Partner Requesting" : "Client Requesting"
        }....`
      );
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);

        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false
        });
      }

      if (staff.role !== CONSTANTS.STAFF_ROLES.ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.WARDEN && staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE && staff.role !== CONSTANTS.STAFF_ROLES.FINANCE_ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.SALES_HEAD && staff.role !== CONSTANTS.STAFF_ROLES.SUPER_ADMIN) {
        log.info(
          `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Only Admin, Warden and Back Office Allowed`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false
        });
      }

      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Platform [${req.platform}], Admin/Warden Requesting....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancies = null;
    if (entireFlatOccupied) {
      const room = await roomDB.getById({ id: roomId });
      occupancies = await occupancyDB.getOccupanciesForFlat({
        clientId,
        tenantId,
        propId,
        flatId: room.flatId,
      });
    } else {
      occupancies = await occupancyDB.getParticularOccupancies({
        clientId,
        tenantId,
        propId,
        roomId,
      });
    }

    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Occupancy Count [${occupancies.length}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (
      occupancies[0].status === CONSTANTS.OCCUPANCY_STATUS.OCCUPIED ||
      occupancies[0].staus === CONSTANTS.OCCUPANCY_STATUS.RESERVED
    ) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Tenant is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Tenant is already occupied", isSuccess: false });
    }

    const property = await propertyDB.getById({ id: propId });
    const room = await roomDB.getById({ id: roomId });

    if (room.status === CONSTANTS.ROOM_STATUS.OCCUPIED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Room is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Room is already occupied", isSuccess: false });
    }

    const isFutureDate = moment(moveInDate).isAfter(moment(), "day");

    if (Number(addFromBooking) === 0) {
      // Create booking for tenants
      try {
        let applicationNumber = `KIP${clientId}${tenant.id}${moment().format("HHmmss")}`;
        
        const bookingId = await bookingsDB.create({
          clientId: clientId,
          tenantId: tenant.id,
          propId,
          roomId,
          moveInDate,
          status: isFutureDate ? CONSTANTS.BOOKING_STATUS.CONFIRMED : CONSTANTS.BOOKING_STATUS.MOVED_IN,
          stayType: CONSTANTS.STAY_TYPE.NORMAL,
          applicationNumber,
        });
  
        if (userType === CONSTANTS.USER_TYPE.STAFF) {
          await bookingsDB.updateBookedBy({
            bookedBy: Number(req.id),
            id: bookingId,
          });
        }
      } catch(error: any) {
        log.info(`[${C}], [${F}], Error In Booking Creation, Error: ${error?.message || error}`);
      }
    }

    for (const occupancy of occupancies) {
      const bed = await bedDB.getById({ id: occupancy.bedId });

      if (bed.status === CONSTANTS.BED_STATUS.OCCUPIED) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], bed Id [${bed.id}], bed is already occupied`
        );
        return res
          .status(400)
          .json({ msg: "bed is already occupied", isSuccess: false });
      }

      await occupancyDB.updateAgreementDetailsShortStay({
        id: occupancy.id,
        rent: dailyRent,
        rentalCycle: 0,
        rentalType: 0,
        security: securityDeposit,
        agreementPeriod: moment(moveOutDate).diff(moment(moveInDate), "days"),
        moveInDate,
        moveOutDate,
        status: isFutureDate
          ? CONSTANTS.OCCUPANCY_STATUS.RESERVED
          : CONSTANTS.OCCUPANCY_STATUS.OCCUPIED,
        stayType: CONSTANTS.STAY_TYPE.SHORT,
      });

      if (userType === CONSTANTS.USER_TYPE.STAFF) {
        await occupancyDB.updateBookedBy({
          bookedBy: Number(req.id),
          id: occupancy.id
        });
      }

      if (isFutureDate) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.VACANT_RESERVED,
        });
        // await tenantDB.updateStatus({
        //   status: CONSTANTS.TENANT_STATUS.RESERVED,
        //   id: tenantId, 
        // })
      } else {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.OCCUPIED,
        });
        // await tenantDB.updateStatus({
        //   status: CONSTANTS.TENANT_STATUS.OCCUPIED,
        //   id: tenantId, 
        // })
      }
    }

    //Tally Handling
    await setOccupancyTallyStatus(
      Number(clientId),
      occupancies[0],
      CONSTANTS.TALLY_STATUS.PENDING,
    );

    // Sukhbir - MultiOccupancy
    // if (isFutureDate) {
    //   await tenantDB.updateStatus({
    //     id: tenantId,
    //     status: CONSTANTS.TENANT_STATUS.RESERVED,
    //   });
    // } else {
    //   await tenantDB.updateStatus({
    //     id: tenantId,
    //     status: CONSTANTS.TENANT_STATUS.OCCUPIED,
    //   });
    // }

    let roomStatus = null;

    if (occupancies.length > 1) {
      roomStatus = CONSTANTS.ROOM_STATUS.OCCUPIED;
    } else {
      const bed = await bedDB.getVacantBed({
        roomId,
        status: CONSTANTS.BED_STATUS.VACANT,
      });
      if (bed) roomStatus = CONSTANTS.ROOM_STATUS.SEMI_OCCUPIED;
      else roomStatus = CONSTANTS.ROOM_STATUS.OCCUPIED;
    }

    for (let occupancy of occupancies) {
      await roomDB.updateStatus({
        id: occupancy.roomId,
        status: roomStatus,
      });
    }

    const securityAdjustEntry = await ledgerDB.getLastEntry({
      tenantId,
      clientId,
    });

    if (securityAdjustEntry !== false) {
      await ledgerDB.forfeitRefund ({
        id: securityAdjustEntry.id,
        amount: 0,
        balance: 0,
        description: `Refund of amount ₹${Math.abs(securityAdjustEntry?.balance)} has been forfeited during eviction.`
      });
    }
    

    let gId = await generateTenantGId();
    await tenantDB.updateGId({ gId, id: tenantId });

    let referenceId = await generateLedgerReferenceId({ clientId });
    let totalRent = moment(moveOutDate).diff(moment(moveInDate), "days") * dailyRent;
    totalDueAmount += totalRent;

    if (totalRent > 0) {
      const dueId = await duesDB.addWithStartEndDateX({
        tenantId,
        amount: totalDueAmount,
        occupancyId: occupancies[0].id,
        roomId,
        propId,
        clientId,
        rentStartDate: moment(moveInDate).format("YYYY-MM-DD"),
        rentEndDate: moment(moveOutDate).format("YYYY-MM-DD"),
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.RENT,
        balance: totalDueAmount,
        ledgerReferenceId: referenceId,
        title: "Rent",
        description: "Due added while onboarding",
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: totalDueAmount,
        balance: totalDueAmount,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.RENT,
        rentStartDate: moment(moveInDate).format("YYYY-MM-DD"),
        rentEndDate: moment(moveOutDate).format("YYYY-MM-DD"),
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: `Rent for ${moment(moveInDate).format("YYYY-MM-DD")} to ${moment(moveOutDate).format("YYYY-MM-DD")}`,
        title: "Rent",
      });
      await setDueTallyStatus(
        Number(clientId),
        dueId,
        CONSTANTS.TALLY_STATUS.PENDING
      );
    }

    referenceId = await generateLedgerReferenceId({ clientId });
    totalDueAmount += Number(securityDeposit);

    if (securityDeposit > 0) {
      const dueId = await duesDB.addWithStartEndDateX({
        tenantId,
        amount: securityDeposit,
        occupancyId: occupancies[0].id,
        roomId,
        propId,
        clientId,
        rentStartDate: moment(moveInDate).format("YYYY-MM-DD"),
        rentEndDate: moment(moveOutDate).format("YYYY-MM-DD"),
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.SECURITY,
        balance: securityDeposit,
        ledgerReferenceId: referenceId,
        title: "Initial Security Deposit",
        description: "Due added while onboarding",
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: securityDeposit,
        balance: securityDeposit,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.SECURITY,
        rentStartDate: moment(moveInDate).format("YYYY-MM-DD"),
        rentEndDate: moment(moveOutDate).format("YYYY-MM-DD"),
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: `Initial Security Deposit for ${moment(moveInDate).format("YYYY-MM-DD")}`,
        title: "Initial Security Deposit",
      });

      await setDueTallyStatus(
        Number(clientId),
        dueId,
        CONSTANTS.TALLY_STATUS.PENDING
      );
    }

    const link = `${process.env.TENANT_CHECKIN_PATH}/${gId}`;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Link [${link}]`
    );

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId,
      propId,
    });

    if (propSettings && Number(notifyTenant) === 1) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Settings Are There And Client Asked To Notify Tenant`
      );
      if (propSettings.sms === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], SMS Notification not enabled`
        );
        const msg = CONSTANTS.MSG.TENANT_ADDED.replace("{#var#}", room.roomNum)
          .replace("{#var2#}", property.name)
          .replace("{#var3#}", link);
        //sendSMS(tenant.mobile, msg, CONSTANTS.SMS_TEMPLATE_IDS.TENANT_ADDED);
      }
      if (propSettings.onboardWelcome === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], WhatsApp Notification not enabled`
        );
        let flatName = "";
        let footerText = propSettings.footer || "The Kipinn Team";
        let template =
          propSettings.whatsappOnboardingTemplate || "tenantonboardbyownernew";
        if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: room.flatId });
          flatName = name + "," + room.roomNum;
        } else {
          flatName = room.roomNum;
        }
        let whatsappAgreegrator = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.WHATSAPP_AGGREGATOR
        });
        let onboardingCred = await getClientWhatsappCredentialsMessageCentral(Number(clientId), property?.id, CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.ONBOARDING, CONSTANTS.USER_TYPE.TENANT);
        if(whatsappAgreegrator && Number(whatsappAgreegrator?.value)  === CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL && onboardingCred) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through Message Central`
          );
          const bodyValues: Record<string, string> = {
            body_1: `${tenant?.name}`,
            body_2: `${property.name}`,
            body_3: `${property.name}`,
            body_4: `${flatName}`,
            body_5: `${moveInDate}`,
            body_6: `${dailyRent}`,
            body_7: `${securityDeposit}`,
            body_8: `${propSettings.ios || " "}`,
            //body_9: `${propSettings.android.replace("*Android*:", "") || " "}`,
            body_9: (propSettings.android || "").replace(/^\*Android:\*\s*/i, "").trim() || " ",
            body_10: `${footerText.replace("Team", "").trim()}`
          };
          sendWhatsappMC(
            tenant?.mobile,
            Number(clientId),
            Number(property.id),
            bodyValues,
            CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.ONBOARDING,
            CONSTANTS.USER_TYPE.TENANT,
            CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL
          );

        } else {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through intrakt`
          );
          if (template === CONSTANTS.WHATSAPP_TEMPLATES.ON_BOARDING_WITH_LINK) {
            sendWhatsappTenantWelcomeWithLink(
              tenant.mobile,
              tenant.name,
              property.name,
              String(dailyRent),
              String(securityDeposit),
              flatName,
              moveInDate,
              footerText,
              template,
              propSettings.android || "",
              propSettings.ios || "",
              Number(clientId),
            );
          } else {
            sendWhatsappTenantWelcome(
              tenant.mobile,
              tenant.name,
              property.name,
              String(dailyRent),
              String(securityDeposit),
              flatName,
              moveInDate,
              footerText,
              template,
              Number(clientId),
            );
          }
        }

      }
    }
    await generateTenantTId(
      tenantId,
    );
    await logActivity(
      Number(req.userType),
      Number(req.id),
      Number(req.parentClientId!),
      Number(req.platform),
      Number(tenantId),
      CONSTANTS.ACTIVITY_TYPES.TENANT_ADDED,
      isFutureDate ? true : false,
      moveInDate,
      null,
      null,
      null
    );

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Daily Rent [${dailyRent}], Security [${securityDeposit}], Move In Date [${moveInDate}], Move Out Date [${moveOutDate}], Entire Flat Occupied [${entireFlatOccupied}], Agreement Details Added For Short Stay Tenant`
    );

    return res.status(200).json({
      msg: "Agreement details added successfully",
      data: {
        tenantName: tenant.name,
        roomNum: room.roomNum,
        floor: room.floor,
        allbedsOccupied: occupancies.length > 1,
        totalDueAmount: totalDueAmount,
      },
      isSuccess: true,
    });

  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
}

tenants.BookAgain = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "BookAgain";

  try {
    let { tenantId, propId, roomId, moveInDate, rentalCycle, agreementPeriod, noticePeriod, lockInPeriod, carryForwardDues = 0, resetKyc = 1 } = req.body;

    if (rentalCycle === "00") {
      rentalCycle = moment(moveInDate).format("DD");
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Rental Cycle [${rentalCycle}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lock In Period [${lockInPeriod}], Dues Carried Forward [${carryForwardDues}], Reset Kyc [${resetKyc}]`
    );

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}], Partner` : `Client Id [${clientId}], Client`} Requesting....`
      );
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
      }

      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`
      )
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    let isOccupancyExist = await occupancyDB.getByClientIdAndTenantId({
      clientId,
      tenantId: tenantId,
    });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Tenant Found`
      );
    } else if (isOccupancyExist) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Is Occupied On Another Bed`
      );
      return res.status(400).json({
        msg: "Tenant is already occupied with another bed",
        isSuccess: false,
      });
    } else {
      await requestDB.removeByClientIdAndTenantId({
        clientId,
        tenantId: tenant.id,
      });

      await documentDB.updateMoveOutStatusByType({
        tenantId: tenant.id,
        clientId,
        moveOut: 1,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
      });

      await occupancyDB.removeByClinetIdAndTenantId({
        clientId,
        tenantId: tenant.id,
      });
    }

    const oldOccupancy = await moveOutDB.getByFullOccupancyTenantIdAndClientIdDesc({
      tenantId,
      clientId,
    });

    if (!oldOccupancy) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Old Occupancy Found`
      );

      return res.status(400).json({
        msg: "No old occupancy found.",
        isSuccess: false,
      })
    }

    const property = await propertyDB.getById({
      id: propId,
    });

    const room = await roomDB.getById({
      id: roomId,
    });

    const bed = await bedDB.getVacantBed({
      roomId: roomId,
      status: CONSTANTS.BED_STATUS.VACANT,
    });

    if (!bed) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Vacant Bed In The Room`
      );

      return res.status(400).json({
        msg: "No vacant bed in this room",
        isSuccess: false,
      });
    }

    let occupancyId = await occupancyDB.addBasicDetails({
      clientId,
      tenantId,
      propId,
      roomId: roomId,
      flatId: Number(room.flatId) ? room.flatId : null,
      bedId: bed.id,
      floor: room.floor || null,
      isOnlinePaymentEnabled: property.isOnlinePaymentEnabled,
      rentalBond: oldOccupancy.rentalBond,
      isFoodOpted: property.isFoodEnabled || 0,
    });
    //Pallav - created occupancy gId for new occupancy
    let ogId = await generateOccupancyGId();
    // if (oldOccupancy?.gId) {
    //   ogId = oldOccupancy.gId;
    // }
    await occupancyDB.updateGId({ gId: ogId, tenantId, clientId });
    let monthDiff = moment(moveInDate).diff(moment(), "month");
    if (Number(moment().format("DD")) < Number(rentalCycle)) {
      monthDiff = Math.abs(monthDiff) - 1;
    }

    if (Number(oldOccupancy.rentalType) === 0) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Rental Type [${oldOccupancy.rentalType}], Short Stay Tenants Cannot Be Re-assigned`);

      return res.status(400).json({
        msg: "Cannot re-assign short stay tenant",
        isSuccess: false,
      });
    }

    let rentalMonths =
      oldOccupancy.rentalType - 1 - (Math.abs(Math.abs(monthDiff)) % oldOccupancy.rentalType);

    if (moment(moveInDate).isAfter(moment())) {
      rentalMonths = 0;
    }

    if (moment(moveInDate).date() < Number(rentalCycle)) {
      rentalMonths += 1;
    }

    const isFutureDate = moment(moveInDate).isAfter(moment(), "day");

    const occupancies = await occupancyDB.getParticularOccupancies({
      clientId,
      tenantId,
      propId,
      roomId,
    });

    let occupancy = occupancies[0];

    let discountEndDate = null;
    let discountStartDate = null;

    if (occupancies[0].discount > 0 && occupancies[0].discountPeriod > 0) {
      log.info(
        `[${C}], [${F}], Rental Cycle [${rentalCycle}], Rental Type [${occupancies[0].rentalType}], Discount Direction [${occupancies[0].isDiscountFromFirstMonth}], Discount Period [${occupancies[0].discountPeriod}], Discount Available, Adding Start and End Date`
      );
      let nextRentCycle = moment(moveInDate).date(rentalCycle);
      if (
        Number(moment(moveInDate).date()) !== Number(rentalCycle) &&
        occupancies[0].rentalType === CONSTANTS.RENTAL_TYPES.MONTHLY
      ) {
        nextRentCycle = moment(nextRentCycle).add(occupancies[0].rentalType, "month");
      }

      if (Number(occupancies[0].isDiscountFromFirstMonth) === 1) {
        discountEndDate = nextRentCycle
          .clone()
          .add(occupancies[0].discountPeriod, "months")
          .subtract(1, "day")
          .format("YYYY-MM-DD");
        discountStartDate = moment(nextRentCycle).format("YYYY-MM-DD");
      } else {
        discountEndDate = moment(moveInDate)
          .add(agreementPeriod, "months")
          .subtract(1, "day")
          .format("YYYY-MM-DD");
        discountStartDate = moment(moveInDate)
          .add((agreementPeriod - occupancies[0].discountPeriod), "months")
          .format("YYYY-MM-DD");
      }

      await occupancyDB.updateDiscountStartEndDate({
        tenantId,
        clientId,
        discountStartDate,
        discountEndDate,
      });
    }

    await occupancyDB.updateAgreementDetailsX({
      id: occupancyId,
      rent: oldOccupancy.rent,
      security: oldOccupancy.security,
      agreementStartDate: moveInDate,
      rentalCycle: rentalCycle,
      rentalType: oldOccupancy.rentalType,
      rentalMonths: rentalMonths,
      agreementPeriod,
      noticePeriod,
      lockInPeriod,
      moveInDate,
      electricityReading: 0,
      fine: property.fine,
      fineType: property.fineType,
      gracePeriod: property.gracePeriod,
      status: isFutureDate
        ? CONSTANTS.OCCUPANCY_STATUS.RESERVED
        : CONSTANTS.OCCUPANCY_STATUS.OCCUPIED,
    });

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      await occupancyDB.updateBookedBy({
        bookedBy: Number(req.id),
        id: occupancyId,
      });
    }

    if (isFutureDate) {
      await bedDB.updateStatus({
        id: bed.id,
        status: CONSTANTS.BED_STATUS.VACANT_RESERVED,
      });
      // await tenantDB.updateStatus({
      //     status: CONSTANTS.TENANT_STATUS.RESERVED,
      //     id: tenantId, 
      // });
    } else {
      await bedDB.updateStatus({
        id: bed.id,
        status: CONSTANTS.BED_STATUS.OCCUPIED,
      });
      // await tenantDB.updateStatus({
      //     status: CONSTANTS.TENANT_STATUS.OCCUPIED,
      //     id: tenantId, 
      // });
    }

    //Pallav
    // Sukhbir - MultiOccupancy
    // if (isFutureDate) {
    //   await tenantDB.updateStatus({
    //     id: tenantId,
    //     status: CONSTANTS.TENANT_STATUS.RESERVED,
    //   });
    // } else {
    //   await tenantDB.updateStatus({
    //     id: tenantId,
    //     status: CONSTANTS.TENANT_STATUS.OCCUPIED,
    //   });
    // }

    const securityAdjustEntry = await ledgerDB.getLastEntry({
      tenantId,
      clientId,
    });

    if (securityAdjustEntry !== false) {
      await ledgerDB.forfeitRefund ({
        id: securityAdjustEntry.id,
        amount: 0,
        balance: 0,
        description: `Refund of amount ₹${Math.abs(securityAdjustEntry?.balance)} has been forfeited during eviction.`
      });
    }

    let totalDueAmount = 0;
    let dueDescription;

    let referenceId = await generateLedgerReferenceId({ clientId });
    if (Number(oldOccupancy.security)) {
      totalDueAmount = oldOccupancy.security;
      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.SECURITY);
      const dueId = await duesDB.addX({
        tenantId,
        amount: oldOccupancy.security,
        occupancyId: occupancyId,
        roomId,
        propId,
        clientId,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.SECURITY,
        balance: oldOccupancy.security,
        ledgerReferenceId: referenceId,
        title: "Security Deposit",
        description: "Due added while onboarding",
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: oldOccupancy.security,
        balance: oldOccupancy.security,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.SECURITY,
        rentStartDate: null,
        rentEndDate: null,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: dueDescription,
        title: "Security Deposit",
      });

      await setDueTallyStatus(
        Number(clientId),
        dueId,
        CONSTANTS.TALLY_STATUS.PENDING
      );
    }

    if (Number(oldOccupancy.rent)) {
      let rents = [];
      if (isFutureDate) {
        rents = calculateRentAsPerRentalTypeForReserved(
          moveInDate,
          rentalCycle,
          oldOccupancy.rent,
          oldOccupancy.rentalType
        );
      } else {
        rents = calculateRentAsPerRentalType(
          moveInDate,
          rentalCycle,
          oldOccupancy.rent,
          oldOccupancy.rentalType
        );
      }
      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.RENT);

      let discountFlag = 1;

      if (Array.isArray(rents) && rents.length > 0) {
        for (let rent of rents) {
          referenceId = await generateLedgerReferenceId({ clientId });
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Start Date [${rent.startDate}], End Date [${rent.endDate}], Rent [${rent.rent}]`
          );

          let rentAfterDiscount = rent.rent;

          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Dicount Start Date [${discountStartDate}], Discount End Date [${discountEndDate}], Rent Start Date [${rent.startDate}] Disocunt Added`
          );

          if (
            Number(occupancies[0].discount) &&
            Number(occupancies[0].discount) > 0 &&
            moment(discountStartDate).isSameOrBefore(rent.startDate) &&
            discountFlag <= Number(occupancies[0].discountPeriod)
          ) {
            if (occupancies[0].discountType === CONSTANTS.DISCOUNT_TYPE.FLAT) {
              rentAfterDiscount = Number(rent.rent) - Number(occupancies[0].discount);
              discountFlag += 1;
            } else if (occupancies[0].discountType === CONSTANTS.DISCOUNT_TYPE.PERCENTAGE) {
              rentAfterDiscount = rent.rent - ((rent.rent * occupancies[0].discount) / 100);
              discountFlag += 1;
            }

            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Discount Type [${occupancies[0].discountType}], Discount [${occupancies[0].discount}], Rent Amount [${rent.rent}], Rent After Discount [${rentAfterDiscount}], Disocunt Added`
            );
          }

          // totalDueAmount = totalDueAmount + rent.rent;
          totalDueAmount = totalDueAmount + rentAfterDiscount;
          if (rentAfterDiscount > 0) {
            const dueId = await duesDB.addWithStartEndDateX({
              tenantId,
              // amount: rent.rent,
              amount: rentAfterDiscount,
              occupancyId: occupancyId,
              roomId,
              propId,
              clientId,
              rentStartDate: rent.startDate,
              rentEndDate: rent.endDate,
              dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
              type: CONSTANTS.DUES_TYPES.RENT,
              // balance: rent.rent,
              balance: rentAfterDiscount,
              ledgerReferenceId: referenceId,
              title: "Rent",
              description: "Due added while onboarding",
              discount: rent.rent - rentAfterDiscount,
            });
            await ledgerDB.add({
              tenantId: tenantId,
              roomId: roomId,
              propId: propId,
              clientId,
              // amount: rent.rent,
              amount: rentAfterDiscount,
              // balance: rent.rent,
              balance: rentAfterDiscount,
              referenceId: referenceId,
              transactionId: null,
              type: CONSTANTS.DUES_TYPES.RENT,
              rentStartDate: rent.startDate,
              rentEndDate: rent.endDate,
              dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
              //changed below description moment(---) at 17/12/2024 20:03:00
              description: rent.rent - rentAfterDiscount > 0
                ? `${dueDescription} for ${moment(rent.startDate).format("DD MMM YY")} to ${moment(rent.endDate).format("DD MMM YY")}, Discount of ₹${rent.rent - rentAfterDiscount} given for this due`
                : `${dueDescription} for ${moment(rent.startDate).format("DD MMM YY")} to ${moment(rent.endDate).format("DD MMM YY")}`,
              discount: rent.rent - rentAfterDiscount,
              title: "Rent",
            });

            await setDueTallyStatus(
              Number(clientId),
              dueId,
              CONSTANTS.TALLY_STATUS.PENDING
            );
          }
        }
      }
    }

    const extraCharges = await extraChargeDB.getAllByClientIdAndPropId({
      clientId,
      propId,
    });

    if (extraCharges) {
      for (let extraCharge of extraCharges) {
        let type = 0;

        type = convertTypes({
          from: "extraChargeType",
          to: "dueType",
          type: extraCharge.type,
        });

        let isFullCharge = false;
        if (
          Number(type) === CONSTANTS.DUES_TYPES.TECH_CHARGES || 
          Number(type) === CONSTANTS.DUES_TYPES.LAUNDRY_CHARGES ||
          Number(type) === CONSTANTS.DUES_TYPES.FOOD ||
          Number(type) === CONSTANTS.DUES_TYPES.OTHER
        ) {
          isFullCharge = true;
        }

        if (Number(type) === CONSTANTS.DUES_TYPES.MOVE_OUT_CHARGES) {
          continue;
        }
        // let rents = calculateMonthlyRent(moveInDate, rentalCycle, extraCharge.amount);
        let rents = [];
        if (isFutureDate) {
          rents = calculateRentAsPerRentalTypeForReserved(
            moveInDate,
            rentalCycle,
            extraCharge.amount,
            CONSTANTS.RENTAL_TYPES.MONTHLY
          );
        } else {
          rents = calculateRentAsPerRentalType(
            moveInDate,
            rentalCycle,
            extraCharge.amount,
            CONSTANTS.RENTAL_TYPES.MONTHLY
          );
        }
        if (extraCharge.repetitionType === 1) {
          rents = [
            {
              month: moment(moveInDate).format("MMMM"),
              year: moment(moveInDate).year(),
              rent: extraCharge.amount,
              startDate: moveInDate,
              endDate: moment(moveInDate).add(1, "day").format("YYYY-MM-DD"),
            },
          ];
        }
        for (let rent of rents) {
          if (type === 0) continue;
          referenceId = await generateLedgerReferenceId({ clientId });
          dueDescription = getDueDescription(extraCharge.type);
          totalDueAmount = totalDueAmount + rent.rent;
          const dueId = await duesDB.addWithStartEndDateX({
            tenantId,
            // amount: rent.rent,
            amount: isFullCharge ? extraCharge.amount : rent.rent,
            occupancyId: occupancyId,
            roomId,
            propId,
            clientId,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            type,
            // balance: rent.rent,
            balance: isFullCharge ? extraCharge.amount : rent.rent,
            ledgerReferenceId: referenceId,
            title: dueDescription,
            description: "Due added while onboarding",
          });
          await ledgerDB.add({
            tenantId: tenantId,
            roomId: roomId,
            propId: propId,
            clientId,
            // amount: rent.rent,
            amount: isFullCharge ? extraCharge.amount : rent.rent,
            // balance: rent.rent,
            balance: isFullCharge ? extraCharge.amount : rent.rent,
            referenceId: referenceId,
            transactionId: null,
            type: type,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            description: `${dueDescription} for ${moment(rent.startDate).format(
              "DD MMM YY"
            )} to ${moment(rent.endDate).format("DD MMM YY")}`,
            title: dueDescription,
          });

          await setDueTallyStatus(
            Number(clientId),
            dueId,
            CONSTANTS.TALLY_STATUS.PENDING
          );
        }
      }
    }

    let roomStatus = null;

    const vacantBed = await bedDB.getVacantBed({
      roomId,
      status: CONSTANTS.BED_STATUS.VACANT,
    });
    if (vacantBed) roomStatus = CONSTANTS.ROOM_STATUS.SEMI_OCCUPIED;
    else roomStatus = CONSTANTS.ROOM_STATUS.OCCUPIED;

    await roomDB.updateStatus({
      id: roomId,
      status: roomStatus,
    });
    let gId = await generateTenantGId();

    await tenantDB.updateGId({ gId, id: tenantId });

    const link = `${process.env.TENANT_CHECKIN_PATH}/${gId}`;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Link [${link}]`
    );

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId,
      propId,
    });

    if (propSettings) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Settings are there`
      );
      if (propSettings.sms === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], SMS Notification not enabled`
        );
        const msg = CONSTANTS.MSG.TENANT_ADDED.replace("{#var#}", room.roomNum)
          .replace("{#var2#}", property.name)
          .replace("{#var3#}", link);
        //sendSMS(tenant.mobile, msg, CONSTANTS.SMS_TEMPLATE_IDS.TENANT_ADDED);
      }
      if (propSettings.onboardWelcome === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], WhatsApp Notification not enabled`
        );
        let flatName = "";
        let footerText = propSettings.footer || "The Kipinn Team";
        let template =
          propSettings.whatsappOnboardingTemplate || "tenantonboardbyownernew";
        if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: room.flatId });
          flatName = name + "," + room.roomNum;
        } else {
          flatName = room.roomNum;
        }
        let whatsappAgreegrator = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.WHATSAPP_AGGREGATOR
        });
        let onboardingCred = await getClientWhatsappCredentialsMessageCentral(Number(clientId), property?.id, CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.ONBOARDING, CONSTANTS.USER_TYPE.TENANT);
        if(whatsappAgreegrator && Number(whatsappAgreegrator?.value)  === CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL && onboardingCred) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through Message Central`
          );
          const bodyValues: Record<string, string> = {
            body_1: `${tenant?.name}`,
            body_2: `${property.name}`,
            body_3: `${property.name}`,
            body_4: `${flatName}`,
            body_5: `${moveInDate}`,
            body_6: `${oldOccupancy.rent}`,
            body_7: `${oldOccupancy.security}`,
            body_8: `${propSettings.ios || " "}`,
            //body_9: `${propSettings.android.replace("*Android*:", "") || " "}`,
            body_9: (propSettings.android || "").replace(/^\*Android:\*\s*/i, "").trim() || " ",
            body_10: `${footerText.replace("Team", "").trim()}`
          };
          sendWhatsappMC(
            tenant?.mobile,
            Number(clientId),
            Number(property.id),
            bodyValues,
            CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.ONBOARDING,
            CONSTANTS.USER_TYPE.TENANT,
            CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL
          );

        } else {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through intrakt`
          );
          if (template === CONSTANTS.WHATSAPP_TEMPLATES.ON_BOARDING_WITH_LINK) {
            sendWhatsappTenantWelcomeWithLink(
              tenant.mobile,
              tenant.name,
              property.name,
              String(oldOccupancy.rent),
              String(oldOccupancy.security),
              flatName,
              moveInDate,
              footerText,
              template,
              propSettings.android || " ",
              propSettings.ios || " ",
              Number(clientId),
            );
          } else {
            sendWhatsappTenantWelcome(
              tenant.mobile,
              tenant.name,
              property.name,
              String(oldOccupancy.rent),
              String(oldOccupancy.security),
              flatName,
              moveInDate,
              footerText,
              template,
              Number(clientId),
            );
          }
        }
      }
    }

    //Function to update tId in tenant
    await generateTenantTId(
      tenantId,
    );

    if (Number(carryForwardDues) === 0) {
      const oldDues = await moveOutDuesDB.getByTenantIdAndClientId({
        tenantId,
        clientId,
      });
      if (oldDues && oldDues.length > 0) {
        for (const due of oldDues) {
          if (Number(due.amount) === Number(due.balance)) {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ledger Reference Id [${due.ledgerReferenceId}], Delete Ledger Entry as Due is not paid`
            );
            await ledgerDB.remove({ referenceId: due.ledgerReferenceId });
          } else {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ledger Reference Id [${due.ledgerReferenceId}], Update Ledger Entry as Due is partially paid`
            );
            await ledgerDB.UpdateAmountAndBalanceByReferenceId({
              referenceId: due.ledgerReferenceId,
              amount: due.amount - due.balance,
              balance: due.amount - due.balance,
            });
            await ledgerDB.UpdateBalanceByReferenceIdForPaidEntry({
              referenceId: due.ledgerReferenceId,
              balance: due.amount - (due.amount - due.balance),
            });
          }
        }
      }
      await moveOutDuesDB.removeAllByTenantId({
        tenantId,
      });
    } else {
      //Change Dues details and make propId roomId and etc according to this new occupancy
      const oldDues = await moveOutDuesDB.getByTenantIdAndClientId({
        tenantId,
        clientId,
      });

      if (oldDues && oldDues.length > 0) {
        for (let due of oldDues) {
          let dueDescription = due.description + `, due carried forward from last occupancy`;
          const referenceId = await generateLedgerReferenceId({ clientId });
          await AddDues(
            tenantId,
            due.amount,
            due.type,
            occupancy,
            clientId!,
            due.rentStartDate,
            due.rentEndDate,
            moveInDate,
            0, //rentDuration
            due.description,
            due.title,
            dueDescription,
            referenceId
          );

          //Deleting/Adjusting old ledger entries
          if (Number(due.amount) === Number(due.balance)) {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ledger Reference Id [${due.ledgerReferenceId}], Delete Ledger Entry as Due is not paid`
            );
            await ledgerDB.remove({ referenceId: due.ledgerReferenceId });
          } else {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ledger Reference Id [${due.ledgerReferenceId}], Update Ledger Entry as Due is partially paid`
            );
            await ledgerDB.UpdateAmountAndBalanceByReferenceId({
              referenceId: due.ledgerReferenceId,
              amount: due.amount - due.balance,
              balance: due.amount - due.balance,
            });
            await ledgerDB.UpdateBalanceByReferenceIdForPaidEntry({
              referenceId: due.ledgerReferenceId,
              balance: due.amount - (due.amount - due.balance),
            });
          }
        }
      }
      await moveOutDuesDB.removeAllByTenantId({
        tenantId,
      });
    }

    await hiddenDuesDB.removeByTenantIdAndClientId({
      clientId,
      tenantId,
    });

    await moveOutDB.remove({
      clientId,
      tenantId,
      propId: oldOccupancy.propId,
      roomId: oldOccupancy.roomId,
      bedId: oldOccupancy.bedId,
    });

    if (Number(resetKyc) === 0) {
      // await occupancyDB.updateKycStatus({
      //   tenantId: tenantId,
      //   kycStatus: oldOccupancy.kycStatus,
      // });
      await occupancyDB.updateKycStatusWithClientId({
        tenantId: tenantId,
        clientId: oldOccupancy.clientId,
        kycStatus: oldOccupancy.kycStatus,
      });

      await tenantDB.updateKycStatus({
        id: tenantId,
        kycStatus: oldOccupancy.kycStatus,
      });
    }

    // await logActivity(
    //   Number(req.userType),
    //   Number(req.id),
    //   Number(req.platform),
    //   Number(tenantId),
    //   CONSTANTS.ACTIVITY_TYPES.TENANT_ADDED,
    //   isFutureDate ? true : false,
    //   moveInDate,
    //   null,
    //   null,
    //   null
    // );

    await logActivity(
      Number(req.userType),
      Number(req.id),
      Number(req.parentClientId!),
      Number(req.platform),
      Number(tenantId),
      CONSTANTS.ACTIVITY_TYPES.BOOKED_AGAIN,
      oldOccupancy.wasCancelled,
      null,
      null,
      null,
      null
    );

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Is Future Date [${isFutureDate}], Web check-in [${link}], Tenant Booked Again Successfully`
    );

    return res.status(200).json({
      msg: "Tenant booked successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
}

tenants.AddInitialSettlementByClient = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "AddInitialSettlementByClient";
  try {
    const {
      tenantId,
      ledgerReferenceId,
      type,
      amount,
      propId,
      roomId,
      mode = CONSTANTS.TRANSACTION_MODES.OFFLINE,
      notes = "",
      isonlinePayment = 0,
      bookedBy = 0,
    } = req.body;
    const userType = req.userType;
    let clientId = req.id;
    let recordedBy = "";

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;
      recordedBy = staff.name;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Client Requested....`);
    }
    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ledger Reference Id [${ledgerReferenceId}], Type [${type}], Amount [${amount}], Mode [${mode}], Prop Id [${propId}], Room Id [${roomId}], Notes [${notes}], Online Payment Allowed [${isonlinePayment}], Booked By [${bookedBy}]`
    );

    const client = await clientDB.getById({ id: clientId });
    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (req.userType === CONSTANTS.USER_TYPE.CLIENT) {
      recordedBy = client.name;
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    await occupancyDB.updateOnlinePaymentStatus({
      tenantId: tenantId,
      clientId: clientId,
      isOnlinePaymentEnabled: isonlinePayment,
    });

    if (type === CONSTANTS.INITIAL_SETTLEMENT_TYPE.NO_DUES) {
      //In case of no dues, we will assume tenant has paid the amount equals to the due balance
      const totalDueBalance = await duesDB.getTotalDuesByTenantId({
        tenantId: tenantId,
        propId: propId,
      });
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Total Due Balance [${totalDueBalance.totalDues}]`
      );
      await adjustInitialSettlement({
        tenantId: tenantId,
        clientId: clientId,
        ledgerReferenceId: ledgerReferenceId,
        amountPaid: Number(totalDueBalance.totalDues),
        propId: propId,
        type: type,
        mode: mode,
        recordedBy: recordedBy,
      });
    } else if (
      type === CONSTANTS.INITIAL_SETTLEMENT_TYPE.PENDING_DUES &&
      Number(ledgerReferenceId) !== 0
    ) {
      //In pending case we will get amount to be paid, so we will use that amount to get amount paid by tenant
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Type [PENDING DUES]`
      );
      let totalDueBalance = await duesDB.getTotalDuesByTenantId({
        tenantId: tenantId,
        propId: propId,
      });
      let amountPaid = Number(totalDueBalance.totalDues) - amount;
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Amount Paid [${amountPaid}]`
      );
      //process the payment, adding in ledger and dues table
      if (amountPaid > 0) {
        await adjustInitialSettlement({
          tenantId: tenantId,
          clientId: clientId,
          ledgerReferenceId: ledgerReferenceId,
          amountPaid: amountPaid,
          propId: propId,
          type: type,
          mode: mode,
          recordedBy: recordedBy,
        });
      }
    } else if (Number(ledgerReferenceId) === 0) {
      // No dues created
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No dues created for tenant`
      );
      if (type === CONSTANTS.INITIAL_SETTLEMENT_TYPE.PENDING_DUES) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Type [PENDING DUES], No Dues thus ignoring this type`
        );
      } else {
        const newLedgerReferenceId = await generateLedgerReferenceId({
          clientId,
        });
        await adjustInitialSettlement({
          tenantId: tenantId,
          clientId: clientId,
          ledgerReferenceId: newLedgerReferenceId,
          amountPaid: amount,
          propId: propId,
          type: type,
          mode: mode,
          recordedBy: recordedBy,
        });
      }
    } else if (type === CONSTANTS.INITIAL_SETTLEMENT_TYPE.EXCESS_AMOUNT) {
      //In excess case we will get amount that is extra, so we will add it to the totalBalance and treat it as the amount paid
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Type [EXCESS AMOUNT]`
      );
      let totalDueBalance = await duesDB.getTotalDuesByTenantId({
        tenantId: tenantId,
        propId: propId,
      });
      let amountPaid = Number(totalDueBalance.totalDues) + amount;
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Amount Paid [${amountPaid}]`
      );
      //process the payment, adding in ledger and dues table
      await adjustInitialSettlement({
        tenantId: tenantId,
        clientId: clientId,
        ledgerReferenceId: ledgerReferenceId,
        amountPaid: amountPaid,
        propId: propId,
        type: type,
        mode: mode,
        recordedBy: recordedBy,
      });
    } else if (type === CONSTANTS.INITIAL_SETTLEMENT_TYPE.TOKEN_AMOUNT) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Type [TOKEN]`
      );
      //In token amount case we will get amount paid by tenant directly, so no need to due calculation to get it
      if (amount > 0) {
        await adjustInitialSettlement({
          tenantId: tenantId,
          clientId: clientId,
          ledgerReferenceId: ledgerReferenceId,
          amountPaid: amount,
          propId: propId,
          type: type,
          mode: mode,
          recordedBy: recordedBy,
        });
      }
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ledger Reference Id [${ledgerReferenceId}], Type [${type}], Mode [${mode}], Amount [${amount}], Invalid Type`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    await occupancyDB.updateInitialSettlementNotes({
      clientId: clientId,
      tenantId: tenantId,
      notes: notes,
    });

    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      clientId,
      tenantId,
    });
    const property = await propertyDB.getById({ id: propId });
    const room = await roomDB.getById({ id: occupancy.roomId });

    if (Number(bookedBy) && Number(bookedBy) > 0) {
      await occupancyDB.updateBookedBy({
        id: occupancy.id,
        bookedBy,
      });
    }

    const isBooking = await bookingsDB.getByTenantAndClientIdAndStatus({
      tenantId,
      clientId,
      status: CONSTANTS.BOOKING_STATUS.CONFIRMED,
    });

    if (isBooking && Number(bookedBy) && Number(bookedBy) > 0) {
      await bookingsDB.updateBookedBy({
        bookedBy,
        id: isBooking?.id,
      });
    }

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId,
      propId,
    });

    if (propSettings) {
      let footerText = propSettings.footer || "The Kipinn Team";

      let { totalDues } = await duesDB.getTotalDuesByTenantId({
        tenantId,
        propId,
      });
      let whatsappAgreegrator = await clientConfigDB.getClientConfig({
        clientId,
        provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
        type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.WHATSAPP_AGGREGATOR
      });
      if (Number(isonlinePayment) === 1 && occupancy?.bookingAmt === 0 && Number(totalDues) > 0) {
        let paymentLink = `${process.env.PAYU_WEB_CHECKOUT_URL}/${occupancy.gId}`;
        if (client?.paymentLinkBaseUrl && client?.paymentLinkBaseUrl.trim() !== "") {
          paymentLink = `${client?.paymentLinkBaseUrl.trim()}/${occupancy.gId}`;
        }
        // log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Tenant Name [${tenant?.name || "" }], Tenant Mobile [${tenant?.mobile || ""}], Property Name [${property?.name || ""}], Room Number [${room?.roomNum || ""}], Total Dues [${totalDues}], Payment Link [${paymentLink}], Footer [${footerText}], Sending WhatsApp Notification`);

        const separateSecurityLink = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.SEPARATE_SECURITY_PAYMENT_LINK,
        });

        const securityDue = await duesDB.getByTenantIdAndClientIdAndType({
          tenantId,
          clientId,
          type: CONSTANTS.DUES_TYPES.SECURITY,
        });
        
        if (separateSecurityLink && Number(separateSecurityLink?.value) === 1 && securityDue && securityDue.length > 0) {

          paymentLink = `${paymentLink}/${securityDue[0].id}`;
          let securityLinkTemp = await getClientWhatsappCredentialsMessageCentral(Number(clientId), property?.id, CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.SECURITY_LINK, CONSTANTS.USER_TYPE.TENANT);
          if(whatsappAgreegrator && Number(whatsappAgreegrator?.value)  === CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL && securityLinkTemp) {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through message central`
            );
            const bodyValues: Record<string, string> = {
              body_1: `${tenant?.name}`,
              body_2: `${property.name}`,
              body_3: `${room?.roomNum}`,
              body_4: `${occupancy.security}`,
              body_5: `${paymentLink}`,
              body_6: `${footerText.replace("Team", "").trim()}`
            };
            sendWhatsappMC(
              tenant?.mobile,
              Number(clientId),
              Number(occupancy.propId),
              bodyValues,
              CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.SECURITY_LINK,
              CONSTANTS.USER_TYPE.TENANT,
              CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL
            );
          } else { 
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through intrakt`
            );
            sendWhatsappTenantSeparateSecurity(
              tenant.mobile,
              tenant.name,
              property?.name || "",
              room?.roomNum || "",
              occupancy.security,
              paymentLink,
              footerText,
              Number(clientId)
            );
          }
        } else {
          let dueLinkTemp = await getClientWhatsappCredentialsMessageCentral(Number(clientId), property?.id, CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.DUE_WITH_LINK, CONSTANTS.USER_TYPE.TENANT);
          if(whatsappAgreegrator && Number(whatsappAgreegrator?.value)  === CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL && dueLinkTemp) {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through message central`
            );
            const bodyValues: Record<string, string> = {
              body_1: `${tenant?.name}`,
              body_2: `${property.name}`,
              body_3: `${room?.roomNum}`,
              body_4: `${totalDues}`,
              body_5: `${paymentLink.replace(/_/g, '%5F').replace(/-/g, '%2D')}`,
              body_6: `${footerText.replace("Team", "").trim()}`
            };
            sendWhatsappMC(
              tenant?.mobile,
              Number(clientId),
              Number(occupancy.propId),
              bodyValues,
              CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.DUE_WITH_LINK,
              CONSTANTS.USER_TYPE.TENANT,
              CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL
            );
          } else {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through intrakt`
            );
            sendWhatsappTenantSingleDue(
              tenant.mobile,
              tenant.name,
              property?.name || "",
              room?.roomNum || "",
              totalDues,
              paymentLink,
              footerText,
              Number(clientId)
            );
          }
        }
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ledger Reference Id [${ledgerReferenceId}], Type [${type}], Mode [${mode}], Amount [${amount}], Initial Settlement Added Successfully`
    );
    return res.status(200).json({
      msg: "Initial Settlement Processed Successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.InitiateReserveTenant = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "InitiateReserveTenant";

  try {
    let {
      bedId,
      propId,
      flatId,
      roomId,
      fullName,
      mobile,
      gender,
      occupation,
      allBedsOccupied,
      alternateMobile,
      isRentalBondAllowed,
      bondAllowed,
    } = req.body;

    if (!isRentalBondAllowed) {
      isRentalBondAllowed = bondAllowed || CONSTANTS.RENTAL_BOND.MANDATORY;
    }

    const userType = req.userType;
    let clientId = req.id;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Staff Id [${req.id}], Bond Allowed [${isRentalBondAllowed}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Bond Allowed [${isRentalBondAllowed}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });
    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (mobile === client.mobile) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Tenant & client mobile are same, Client can't occupy himself.`
      );
      return res.status(400).json({
        msg: "You can't occupy yourself",
        isSuccess: false,
      });
    }

    const property = await propertyDB.getById({ id: propId });
    if (!property) {
      log.info(
        `Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], No Property Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (property.status !== CONSTANTS.PROPERTY_STATUS.ACTIVE) {
      log.info(
        `Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Property is not active`
      );
      return res.status(400).json({
        msg: "Please make this property active to add a tenant",
        isSuccess: false,
      });
    }

    const room = await roomDB.getById({ id: roomId });
    if (!room) {
      log.info(
        `Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], No Room Found`
      );
      return res.status(400).json({ msg: "No Room Found", isSuccess: false });
    }

    let tenantId = null;
    let occupancy = null;

    const tenant = await tenantDB.getByMobile({ mobile });

    if (!tenant) {
      tenantId = await tenantDB.createByOwner({
        mobile,
        name: fullName,
        gender,
        occupation,
        alternateMobile,
      });
      let gId = await generateTenantGId();
      await tenantDB.updateGId({ gId, id: tenantId });
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], Tenant Created Successfully`
      );
    } else {
      //TENANT EXISTS

      occupancy = await occupancyDB.getByClientIdAndTenantId({
        clientId,
        tenantId: tenant.id,
      });

      tenantId = tenant?.id;
      if (tenant?.gId === null) {
        let gId = await generateTenantGId();
        await tenantDB.updateGId({ gId, id: tenantId });
      }
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], Tenant already exists`
      );

      if (
        occupancy[0]?.status === CONSTANTS.OCCUPANCY_STATUS.OCCUPIED ||
        occupancy[0]?.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT
      ) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], Tenant is already occupied or moving out`
        );
        return res.status(400).json({
          msg: "Tenant is already occupied with another bed",
          isSuccess: false,
        });
      } else if (occupancy[0]?.status === CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], Tenant is already reserved with another bed`
        );
        return res.status(400).json({
          msg: "Tenant is already reserved with another bed",
          isSuccess: false,
        });
      } else {
        await tenantDB.updateBasicDetails({
          name: fullName,
          gender,
          occupation,
          id: tenant.id,
        });

        tenantId = tenant.id;

        await requestDB.removeByClientIdAndTenantId({
          clientId,
          tenantId: tenant.id,
        });

        // await documentDB.removeByTenantIdandClientId({
        //   tenantId: tenant.id,
        //   clientId,
        // });

        await documentDB.updateMoveOutStatus({
          tenantId: tenant.id,
          clientId,
          moveOut: 1,
        });

        await duesDB.removeByClientIdAndTenantId({
          clientId,
          tenantId: tenant.id,
        });

        await occupancyDB.removeByClinetIdAndTenantId({
          clientId,
          tenantId: tenant.id,
        });
      }
    }

    let beds: any = [];

    if (allBedsOccupied) {
      beds = await bedDB.getByRoomId({ roomId });

      if (!beds) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], No Bed Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      const isOtherThanMovingOut = beds.find(
        (bed: bedTypes) =>
          bed.status !== CONSTANTS.BED_STATUS.MOVING_OUT &&
          bed.status !== CONSTANTS.BED_STATUS.VACANT
      );

      if (isOtherThanMovingOut) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], No All Beds Status are Moving Out`
        );
        return res.status(400).json({
          msg: "You can reserve a tenant only for evicted beds",
          isSuccess: false,
        });
      }
    } else {
      const bed = await bedDB.getById({ id: bedId });

      if (!bed) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], No Bed Found with Bed Id [${bedId}]`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }

      if (bed.status !== CONSTANTS.BED_STATUS.MOVING_OUT) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], Bed is not with moving out status`
        );
        return res.status(400).json({
          msg: "You can reserve a tenant only for evicted beds",
          isSuccess: false,
        });
      }

      beds = [bed];
    }

    let minMoveInDate = null;
    const movingOutTenant = await occupancyDB.getMovingOutTenant({
      bedId,
      clientId,
      propId,
    });

    if (movingOutTenant) {
      minMoveInDate = moment(movingOutTenant.moveOutDate)
        .add(1, "day")
        .format("DD-MM-YYYY");
    }

    for (const bed of beds) {
      const occupancyId = await occupancyDB.addBasicDetails({
        clientId,
        tenantId,
        propId,
        roomId,
        flatId: room.flatId,
        bedId: bed.id,
        floor: room.floor,
        isOnlinePaymentEnabled: property.isOnlinePaymentEnabled,
        rentalBond: isRentalBondAllowed,
        isFoodOpted: property.isFoodEnabled || 0,
      });
      //Pallav - created occupancy gId for new occupancy
      let ogId = await generateOccupancyGId();
      occupancyDB.updateGId({ gId: ogId, tenantId, clientId });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], Occupancy Id [${occupancyId}] Occupancy Reserved Successfully`
      );
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], Tenant Basic Details Added Successfully`
    );

    return res.status(200).json({
      msg: "Tenant Basic Details Added Successfully",
      tenantId,
      propId,
      roomId,
      minMoveInDate,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ReserveTenant = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ReserveTenant";

  try {
    const {
      monthlyRent,
      securityDeposit,
      agreementStartDate,
      rentalCycle,
      agreementPeriod,
      noticePeriod,
      lockInPeriod,
      moveInDate,
      tenantId,
      propId,
      roomId,
    } = req.body;

    const userType = req.userType;
    let clientId = req.id;
    let dueDescription = "";
    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancies = await occupancyDB.getParticularOccupancies({
      clientId,
      tenantId,
      propId,
      roomId,
    });

    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Occupancy Count [${occupancies.length}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (tenant.status === CONSTANTS.TENANT_STATUS.OCCUPIED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Tenant is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Tenant is already occupied", isSuccess: false });
    }

    const property = await propertyDB.getById({ id: propId });
    const room = await roomDB.getById({ id: roomId });

    if (room.status === CONSTANTS.ROOM_STATUS.INACTIVE) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Room is InActive`
      );
      return res.status(400).json({
        msg: "Room is not available for reservation",
        isSuccess: false,
      });
    }

    let isReserved = false;

    for (const occupancy of occupancies) {
      const bed = await bedDB.getById({ id: occupancy.bedId });

      if (
        bed.status !== CONSTANTS.BED_STATUS.MOVING_OUT &&
        bed.status !== CONSTANTS.BED_STATUS.VACANT
      ) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], bed Id [${bed.id}], Bed status is not moving out`
        );

        continue;
      }

      await occupancyDB.updateAgreementDetails({
        id: occupancy.id,
        rent: monthlyRent,
        security: securityDeposit,
        agreementStartDate,
        rentalCycle,
        agreementPeriod,
        noticePeriod,
        lockInPeriod,
        moveInDate,
        electricityReading: 0,
        fine: property.fine,
        fineType: property.fineType,
        gracePeriod: property.gracePeriod,
        status: CONSTANTS.OCCUPANCY_STATUS.RESERVED,
      });

      if (bed.status === CONSTANTS.BED_STATUS.MOVING_OUT) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.RESERVED,
        });
      } else {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.VACANT_RESERVED,
        });
      }

      isReserved = true;
    }

    if (isReserved) {
      await tenantDB.updateStatus({
        id: tenantId,
        status: CONSTANTS.TENANT_STATUS.RESERVED,
      });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Tenant Agreement Details Added & Occupancy Reserved Successfully`
      );
    }

    const referenceId = await generateLedgerReferenceId({ clientId });
    if (Number(securityDeposit)) {
      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.SECURITY);
      await duesDB.add({
        tenantId,
        amount: securityDeposit,
        occupancyId: occupancies[0].id,
        roomId,
        propId,
        clientId,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.SECURITY,
        balance: securityDeposit,
        ledgerReferenceId: referenceId,
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: securityDeposit,
        balance: securityDeposit,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.SECURITY,
        rentStartDate: null,
        rentEndDate: null,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: dueDescription,
      });
    }

    if (Number(monthlyRent)) {
      const rents = calculateMonthlyRent(moveInDate, rentalCycle, monthlyRent);

      if (Array.isArray(rents) && rents.length > 0) {
        dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.RENT);
        for (let rent of rents) {
          // referenceId = await generateLedgerReferenceId({clientId});
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Start Date [${rent.startDate}], End Date [${rent.endDate}], Rent [${rent.rent}]`
          );
          await duesDB.addWithStartEndDate({
            tenantId,
            amount: rent.rent,
            occupancyId: occupancies[0].id,
            roomId,
            propId,
            clientId,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
            type: CONSTANTS.DUES_TYPES.RENT,
            balance: rent.rent,
            ledgerReferenceId: referenceId,
          });
          await ledgerDB.add({
            tenantId: tenantId,
            roomId: roomId,
            propId: propId,
            clientId,
            amount: rent.rent,
            balance: rent.rent,
            referenceId: referenceId,
            transactionId: null,
            type: CONSTANTS.DUES_TYPES.RENT,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
            description: `${dueDescription} for ${moment(moveInDate).format(
              "MMM YYYY"
            )}`,
          });
        }
      }
    }

    const extraCharges = await extraChargeDB.getAllByClientIdAndPropId({
      clientId,
      propId,
    });

    if (extraCharges) {
      for (let extraCharge of extraCharges) {
        let type = 0;
        dueDescription = getDueDescription(extraCharge.type);
        // referenceId = await generateLedgerReferenceId({clientId});
        type = convertTypes({
          from: "extraChargeType",
          to: "dueType",
          type: extraCharge.type,
        });

        if (type === 0) continue;

        await duesDB.add({
          tenantId,
          amount: extraCharge.amount,
          occupancyId: occupancies[0].id,
          roomId,
          propId,
          clientId,
          dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
          type,
          balance: extraCharge.amount,
          ledgerReferenceId: referenceId,
        });
        await ledgerDB.add({
          tenantId: tenantId,
          roomId: roomId,
          propId: propId,
          clientId,
          amount: extraCharge.amount,
          balance: extraCharge.amount,
          referenceId: referenceId,
          transactionId: null,
          type: type,
          rentStartDate: null,
          rentEndDate: null,
          dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
          description: dueDescription,
        });
      }
    }

    return res.status(200).json({
      msg: isReserved
        ? "Tenant Agreement Details Added & Occupancy Reserved Successfully"
        : "Bed is not available for reservation",
      data: {
        tenantName: tenant.name,
        roomNum: room.roomNum,
        floor: room.floor,
        allbedsOccupied: occupancies.length > 1,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ReserveTenantX = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ReserveTenantX";

  try {
    let {
      monthlyRent,
      securityDeposit,
      agreementStartDate,
      rentalCycle,
      rentalType,
      agreementPeriod,
      noticePeriod,
      lockInPeriod,
      moveInDate,
      tenantId,
      propId,
      roomId,
      bookingAmount = 0,
      bookingAdjustType = 0,
    } = req.body;

    const userType = req.userType;
    let clientId = req.id;
    let dueDescription = "";

    if (Number(rentalCycle) === 0) {
      const day = moment(moveInDate, "YYYY-MM-DD").format("DD");
      rentalCycle = day;
    }

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Booking Amount [${bookingAmount}], Booking Adjust Type [${bookingAdjustType}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Booking Amount [${bookingAmount}], Booking Adjust Type [${bookingAdjustType}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Booking Amount [${bookingAmount}], Booking Adjust Type [${bookingAdjustType}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancies = await occupancyDB.getParticularOccupancies({
      clientId,
      tenantId,
      propId,
      roomId,
    });

    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Occupancy Count [${occupancies.length}]`
    );

    let discountEndDate = null;
    let discountStartDate = null;

    if (occupancies[0].discount > 0 && occupancies[0].discountPeriod > 0) {
      log.info(
        `[${C}], [${F}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Discount Direction [${occupancies[0].isDiscountFromFirstMonth}], Discount Period [${occupancies[0].discountPeriod}], Discount Available, Adding Start and End Date`
      );
      let nextRentCycle = moment().date(rentalCycle);
      if (
        Number(moment(moveInDate).date()) !== Number(rentalCycle) &&
        rentalType === CONSTANTS.RENTAL_TYPES.MONTHLY
      ) {
        nextRentCycle = moment(nextRentCycle).add(rentalType, "month");
      }

      if (Number(occupancies[0].isDiscountFromFirstMonth) === 1) {
        discountEndDate = nextRentCycle
          .clone()
          .add(occupancies[0].discountPeriod, "months")
          .subtract(1, "day")
          .format("YYYY-MM-DD");
        discountStartDate = moment(nextRentCycle).format("YYYY-MM-DD");
      } else {
        discountEndDate = moment(agreementStartDate)
          .add(agreementPeriod, "months")
          .subtract(1, "day")
          .format("YYYY-MM-DD");
        discountStartDate = moment(agreementStartDate)
          .add((agreementPeriod - occupancies[0].discountPeriod), "months")
          .format("YYYY-MM-DD");
      }

      await occupancyDB.updateDiscountStartEndDate({
        tenantId,
        clientId,
        discountStartDate,
        discountEndDate,
      });

      // log.info(
      //   `[${C}], [${F}], Start Date [${discountStartDate}], Discount End Date [${discountEndDate}]`
      // );
    }

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (occupancies[0].status === CONSTANTS.OCCUPANCY_STATUS.OCCUPIED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Tenant is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Tenant is already occupied", isSuccess: false });
    }

    const property = await propertyDB.getById({ id: propId });
    const room = await roomDB.getById({ id: roomId });

    if (room.status === CONSTANTS.ROOM_STATUS.INACTIVE) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Room is InActive`
      );
      return res.status(400).json({
        msg: "Room is not available for reservation",
        isSuccess: false,
      });
    }

    let isReserved = false;

    for (const occupancy of occupancies) {
      const bed = await bedDB.getById({ id: occupancy.bedId });

      if (
        bed.status !== CONSTANTS.BED_STATUS.MOVING_OUT &&
        bed.status !== CONSTANTS.BED_STATUS.VACANT
      ) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], bed Id [${bed.id}], Bed status is not moving out`
        );

        continue;
      }

      let monthDiff = moment(moveInDate).diff(moment(), "month");
      if (Number(moment().format("DD")) < Number(rentalCycle)) {
        monthDiff -= 1;
      }

      let rentalMonths =
        rentalType - 1 - (Math.abs(Math.abs(monthDiff)) % rentalType);

      await occupancyDB.updateAgreementDetailsX({
        id: occupancy.id,
        rent: monthlyRent,
        security: securityDeposit,
        agreementStartDate,
        rentalCycle,
        rentalType,
        rentalMonths: rentalMonths,
        agreementPeriod,
        noticePeriod,
        lockInPeriod,
        moveInDate,
        electricityReading: 0,
        fine: property.fine,
        fineType: property.fineType,
        gracePeriod: property.gracePeriod,
        status: CONSTANTS.OCCUPANCY_STATUS.RESERVED,
        bookingAmt: Number(bookingAmount) || 0,
        bookingAdjustType: Number(bookingAdjustType) || 0,
      });

      if (userType === CONSTANTS.USER_TYPE.STAFF) {
        await occupancyDB.updateBookedBy({
          bookedBy: Number(req.id),
          id: occupancy.id
        });
      }

      if (bed.status === CONSTANTS.BED_STATUS.MOVING_OUT) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.RESERVED,
        });
      } else {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.VACANT_RESERVED,
        });
      }

      isReserved = true;
    }

    //Tally Handling
    await setOccupancyTallyStatus(
      Number(clientId),
      occupancies[0],
      CONSTANTS.TALLY_STATUS.PENDING,
    );

    if (isReserved) {
      await tenantDB.updateStatus({
        id: tenantId,
        status: CONSTANTS.TENANT_STATUS.RESERVED,
      });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Tenant Agreement Details Added & Occupancy Reserved Successfully`
      );
    }

    // Create booking for reserved tenants
    let applicationNumber = `KIP${clientId}${tenant.id}${moment().format("HHmmss")}`;
    
    await bookingsDB.create({
      clientId: clientId,
      tenantId: tenant.id,
      propId,
      roomId,
      moveInDate,
      status: CONSTANTS.BOOKING_STATUS.CONFIRMED,
      stayType: CONSTANTS.STAY_TYPE.NORMAL,
      applicationNumber,
    });

    const securityAdjustEntry = await ledgerDB.getLastEntry({
      tenantId,
      clientId,
    });

    if (securityAdjustEntry !== false) {
      await ledgerDB.forfeitRefund ({
        id: securityAdjustEntry.id,
        amount: 0,
        balance: 0,
        description: `Refund of amount ₹${Math.abs(securityAdjustEntry?.balance)} has been forfeited during eviction.`
      });
    }

    let referenceId = await generateLedgerReferenceId({ clientId });
    if (Number(securityDeposit)) {
      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.SECURITY);
      const dueId = await duesDB.add({
        tenantId,
        amount: securityDeposit,
        occupancyId: occupancies[0].id,
        roomId,
        propId,
        clientId,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.SECURITY,
        balance: securityDeposit,
        ledgerReferenceId: referenceId,
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: securityDeposit,
        balance: securityDeposit,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.SECURITY,
        rentStartDate: null,
        rentEndDate: null,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: dueDescription,
      });

      await setDueTallyStatus(
        Number(clientId),
        dueId,
        CONSTANTS.TALLY_STATUS.PENDING
      );
    }

    if (Number(monthlyRent)) {
      // const rents = calculateRentAsPerRentalType(
      //   moveInDate,
      //   rentalCycle,
      //   monthlyRent,
      //   rentalType
      // );
      let discountFlag = 1;
      // const rents = calculateRentAsPerRentalTypeForReserved(
      //   moveInDate,
      //   rentalCycle,
      //   monthlyRent,
      //   rentalType
      // );

      let rents = [];

      if (Number(bookingAmount) > 0 && Number(bookingAdjustType) === CONSTANTS.BOOKING_ADJUST_TYPE.RENT) {
        rents = calculateRentAsPerRentalTypeForReservedRentBooking(
          moveInDate,
          rentalCycle,
          monthlyRent,
          rentalType
        );
      } else {
        rents = calculateRentAsPerRentalTypeForReserved(
          moveInDate,
          rentalCycle,
          monthlyRent,
          rentalType
        );
      }

      if (Array.isArray(rents) && rents.length > 0) {
        dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.RENT);
        for (let rent of rents) {
          referenceId = await generateLedgerReferenceId({ clientId });
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Start Date [${rent.startDate}], End Date [${rent.endDate}], Rent [${rent.rent}]`
          );

          let rentAfterDiscount = rent.rent;

          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Dicount Start Date [${occupancies[0].discountStartDate}], Discount End Date [${occupancies[0].discountEndDate}], Rent Start Date [${rent.startDate}] Disocunt Added`
          );

          if (
            Number(occupancies[0].discount) &&
            Number(occupancies[0].discount) > 0 &&
            moment(occupancies[0].discountStartDate).isSameOrBefore(rent.startDate) &&
            discountFlag <= Number(occupancies[0].discountPeriod)
          ) {
            if (occupancies[0].discountType === CONSTANTS.DISCOUNT_TYPE.FLAT) {
              rentAfterDiscount = Number(rent.rent) - Number(occupancies[0].discount);
              discountFlag += 1;
            } else if (occupancies[0].discount === CONSTANTS.DISCOUNT_TYPE.PERCENTAGE) {
              rentAfterDiscount = rent.rent - ((rent.rent * occupancies[0].discount) / 100);
              discountFlag += 1;
            }

            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Discount Type [${occupancies[0].discountType}], Discount [${occupancies[0].discount}], Rent Amount [${rent.rent}], Rent After Discount [${rentAfterDiscount}], Disocunt Added`
            );
          }
          // await duesDB.addWithStartEndDate({
          //   tenantId,
          //   amount: rent.rent,
          //   occupancyId: occupancies[0].id,
          //   roomId,
          //   propId,
          //   clientId,
          //   rentStartDate: rent.startDate,
          //   rentEndDate: rent.endDate,
          //   dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
          //   type: CONSTANTS.DUES_TYPES.RENT,
          //   balance: rent.rent,
          //   ledgerReferenceId: referenceId,
          // });
          const dueId = await duesDB.addWithStartEndDateX({
            tenantId,
            amount: rentAfterDiscount,
            occupancyId: occupancies[0].id,
            roomId,
            propId,
            clientId,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            type: CONSTANTS.DUES_TYPES.RENT,
            balance: rentAfterDiscount,
            ledgerReferenceId: referenceId,
            title: "Rent",
            description: "Due added while onboarding",
          });
          await ledgerDB.add({
            tenantId: tenantId,
            roomId: roomId,
            propId: propId,
            clientId,
            amount: rentAfterDiscount,
            balance: rentAfterDiscount,
            referenceId: referenceId,
            transactionId: null,
            type: CONSTANTS.DUES_TYPES.RENT,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
            description: rent.rent - rentAfterDiscount > 0
              ? `${dueDescription} for ${moment(moveInDate).format("MMM YYYY")}, Discount of ₹${rent.rent - rentAfterDiscount} given for this due`
              : `${dueDescription} for ${moment(moveInDate).format("MMM YYYY")}`,
            discount: rent.rent - rentAfterDiscount,
            title: "Rent",
          });

          await setDueTallyStatus(
            Number(clientId),
            dueId,
            CONSTANTS.TALLY_STATUS.PENDING
          );
        }
      }
    }

    const extraCharges = await extraChargeDB.getAllByClientIdAndPropId({
      clientId,
      propId,
    });

    if (extraCharges) {
      for (let extraCharge of extraCharges) {
        let type = 0;

        type = convertTypes({
          from: "extraChargeType",
          to: "dueType",
          type: extraCharge.type,
        });

        let isFullCharge = false;
        if (
          Number(type) === CONSTANTS.DUES_TYPES.TECH_CHARGES || 
          Number(type) === CONSTANTS.DUES_TYPES.LAUNDRY_CHARGES ||
          Number(type) === CONSTANTS.DUES_TYPES.FOOD ||
          Number(type) === CONSTANTS.DUES_TYPES.BUS_CHARGES ||
          Number(type) === CONSTANTS.DUES_TYPES.OTHER
        ) {
          isFullCharge = true;
        }

        if (Number(type) === CONSTANTS.DUES_TYPES.MOVE_OUT_CHARGES) {
          continue;
        }
        // let rents = calculateMonthlyRent(
        //   moveInDate,
        //   rentalCycle,
        //   extraCharge.amount
        // );
        let rents = calculateRentAsPerRentalTypeForReserved(
          moveInDate,
          rentalCycle,
          extraCharge.amount,
          CONSTANTS.RENTAL_TYPES.MONTHLY
        );
        if (extraCharge.repetitionType === 1) {
          // rents = [
          //   {
          //     month: moment().format("MMMM"),
          //     year: moment().year(),
          //     rent: extraCharge.amount,
          //     startDate: moveInDate,
          //     endDate: "null",
          //   },
          // ];
          rents = [
            {
              month: moment(moveInDate).format("MMMM"),
              year: moment(moveInDate).year(),
              rent: extraCharge.amount,
              startDate: moveInDate,
              endDate: moment(moveInDate).add(1, "day").format("YYYY-MM-DD"),
            },
          ];
        }
        for (let rent of rents) {
          if (type === 0) continue;
          referenceId = await generateLedgerReferenceId({ clientId });
          dueDescription = getDueDescription(extraCharge.type);
          // await duesDB.add({
          //   tenantId,
          //   amount: rent.rent,
          //   occupancyId: occupancies[0].id,
          //   roomId,
          //   propId,
          //   clientId,
          //   dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
          //   type,
          //   balance: rent.rent,
          //   ledgerReferenceId: referenceId,
          // });
          const dueId = await duesDB.addWithStartEndDateX({
            tenantId,
            // amount: rent.rent,
            amount: isFullCharge ? extraCharge.amount : rent.rent,
            occupancyId: occupancies[0].id,
            roomId,
            propId,
            clientId,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            type,
            // balance: rent.rent,
            balance: isFullCharge ? extraCharge.amount : rent.rent,
            ledgerReferenceId: referenceId,
            title: dueDescription,
            description: "Due added while onboarding",
          });
          await ledgerDB.add({
            tenantId: tenantId,
            roomId: roomId,
            propId: propId,
            clientId,
            // amount: rent.rent,
            amount: isFullCharge ? extraCharge.amount : rent.rent,
            // balance: rent.rent,
            balance: isFullCharge ? extraCharge.amount : rent.rent,
            referenceId: referenceId,
            transactionId: null,
            type: type,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            description: dueDescription,
            title: dueDescription,
          });

          await setDueTallyStatus(
            Number(clientId),
            dueId,
            CONSTANTS.TALLY_STATUS.PENDING
          );
        }
      }
    }

    await generateTenantTId(
      tenantId,
    );

    await logActivity(
      Number(req.userType),
      Number(req.id),
      Number(req.parentClientId!),
      Number(req.platform),
      Number(tenantId),
      CONSTANTS.ACTIVITY_TYPES.TENANT_ADDED,
      true,
      moveInDate,
      null,
      null,
      null
    );

    return res.status(200).json({
      msg: isReserved
        ? "Tenant Agreement Details Added & Occupancy Reserved Successfully"
        : "Bed is not available for reservation",
      data: {
        tenantName: tenant.name,
        roomNum: room.roomNum,
        floor: room.floor,
        allbedsOccupied: occupancies.length > 1,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.InitiateReserveMultipleTenant = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "InitiateReserveMultipleTenant";

  try {
    let {
      bedId,
      propId,
      flatId,
      roomId,
      fullName,
      mobile,
      gender,
      occupation,
      allBedsOccupied,
      alternateMobile,
      isRentalBondAllowed,
      bondAllowed,
    } = req.body;

    if (!isRentalBondAllowed) {
      isRentalBondAllowed = bondAllowed || CONSTANTS.RENTAL_BOND.MANDATORY;
    }

    const userType = req.userType;
    let clientId = req.id;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Staff Id [${req.id}], Bond Allowed [${isRentalBondAllowed}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Bond Allowed [${isRentalBondAllowed}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });
    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (mobile === client.mobile) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Tenant & client mobile are same, Client can't occupy himself.`
      );
      return res.status(400).json({
        msg: "You can't occupy yourself",
        isSuccess: false,
      });
    }

    const property = await propertyDB.getById({ id: propId });
    if (!property) {
      log.info(
        `Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], No Property Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (property.status !== CONSTANTS.PROPERTY_STATUS.ACTIVE) {
      log.info(
        `Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], Property is not active`
      );
      return res.status(400).json({
        msg: "Please make this property active to add a tenant",
        isSuccess: false,
      });
    }

    const room = await roomDB.getById({ id: roomId });
    if (!room) {
      log.info(
        `Client Id [${clientId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], alternateMobile [${alternateMobile}], No Room Found`
      );
      return res.status(400).json({ msg: "No Room Found", isSuccess: false });
    }

    let tenantId = null;
    let occupancy = null;

    const tenant = await tenantDB.getByMobile({ mobile });

    if (!tenant) {
      tenantId = await tenantDB.createByOwner({
        mobile,
        name: fullName,
        gender,
        occupation,
        alternateMobile,
      });
      let gId = await generateTenantGId();
      await tenantDB.updateGId({ gId, id: tenantId });
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], Tenant Created Successfully`
      );
    } else {
      //TENANT EXISTS
      occupancy = await occupancyDB.getByClientIdAndTenantId({
        clientId,
        tenantId: tenant.id,
      });
      tenantId = tenant?.id;
      if (tenant?.gId === null) {
        let gId = await generateTenantGId();
        await tenantDB.updateGId({ gId, id: tenantId });
      }
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], Tenant already exists`
      );

      if (
        occupancy[0]?.status === CONSTANTS.OCCUPANCY_STATUS.OCCUPIED ||
        occupancy[0]?.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT
      ) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], Tenant is already occupied or moving out`
        );
        return res.status(400).json({
          msg: "Tenant is already occupied with another bed",
          isSuccess: false,
        });
      } else if (occupancy[0]?.status === CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], Tenant is already reserved with another bed`
        );
        return res.status(400).json({
          msg: "Tenant is already reserved with another bed",
          isSuccess: false,
        });
      } else {
        await tenantDB.updateBasicDetails({
          name: fullName,
          gender,
          occupation,
          id: tenant.id,
        });

        tenantId = tenant.id;

        await requestDB.removeByClientIdAndTenantId({
          clientId,
          tenantId: tenant.id,
        });

        await documentDB.updateMoveOutStatus({
          tenantId: tenant.id,
          clientId,
          moveOut: 1,
        });

        await duesDB.removeByClientIdAndTenantId({
          clientId,
          tenantId: tenant.id,
        });

        await occupancyDB.removeByClinetIdAndTenantId({
          clientId,
          tenantId: tenant.id,
        });
      }
    }

    let beds: any = [];

    if (allBedsOccupied) {
      beds = await bedDB.getByRoomId({ roomId });
      if (!beds) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], No Bed Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

    } else {
      const bed = await bedDB.getById({ id: bedId });
      if (!bed) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], No Bed Found with Bed Id [${bedId}]`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }

      beds = [bed];
    }

    let vacantSlots: any[] = [];

    for (let bed of beds) {
      const tenants = await occupancyDB.getAllByBedId({
        bedId: bed.id,
      });

      // const isOtherThanMovingOut = tenants.find(
      //   (tenant: any) =>
      //     tenant.moveOutDate === null
      // );

      // if (isOtherThanMovingOut) {
      //   log.info(
      //     `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], No All Tenants Have Moving Out Date`
      //   );
      //   return res.status(400).json({
      //     msg: "You can reserve only when all exsiting tenants are moving out",
      //     isSuccess: false,
      //   });
      // }

      let tempVacantSlots = await getVacantSlots(tenants);

      if (vacantSlots.length > 0) {
        let tempSlotLastEntry = tempVacantSlots[tempVacantSlots.length - 1];
        let vacantSlotLastEntry = vacantSlots[vacantSlots.length - 1];

        vacantSlots = [
          {
            startDate: moment(tempSlotLastEntry.startDate).isAfter(moment(vacantSlotLastEntry.startDate), "date")
              ? tempSlotLastEntry.startDate
              : vacantSlotLastEntry.startDate,
            endDate: null,
          }
        ]
      } else {
        vacantSlots = tempVacantSlots;
      }
    }

    for (const bed of beds) {
      const occupancyId = await occupancyDB.addBasicDetails({
        clientId,
        tenantId,
        propId,
        roomId,
        flatId: room.flatId,
        bedId: bed.id,
        floor: room.floor,
        isOnlinePaymentEnabled: property.isOnlinePaymentEnabled,
        rentalBond: isRentalBondAllowed,
        isFoodOpted: property.isFoodEnabled || 0,
      });
      let ogId = await generateOccupancyGId();
      occupancyDB.updateGId({ gId: ogId, tenantId, clientId });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], Occupancy Id [${occupancyId}] Occupancy Reserved Successfully`
      );
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bedId}] Prop Id [${propId}], Room Id [${roomId}], Tenant Name [${fullName}], Mobile [${mobile}], Gender [${gender}], Flat Id [${flatId}], Occupation [${occupation}], allBedsOccupied [${allBedsOccupied}], Tenant Basic Details Added Successfully`
    );

    return res.status(200).json({
      msg: "Tenant Basic Details Added Successfully",
      tenantId,
      propId,
      roomId,
      vacantSlots,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ReserveMultipleTenant = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ReserveMultipleTenant";

  try {
    let {
      monthlyRent,
      securityDeposit,
      agreementStartDate,
      rentalCycle,
      rentalType,
      agreementPeriod,
      noticePeriod,
      lockInPeriod,
      moveInDate,
      tenantId,
      propId,
      roomId,
      bookingAmount,
      bookingAdjustType,
    } = req.body;

    const userType = req.userType;
    let clientId = req.id;
    let dueDescription = "";

    if (Number(rentalCycle) === 0) {
      const day = moment(moveInDate, "YYYY-MM-DD").format("DD");
      rentalCycle = day;
    }

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Booking Amount [${bookingAmount}], Booking Adjust Type [${bookingAdjustType}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Booking Amount [${bookingAmount}], Booking Adjust Type [${bookingAdjustType}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Monthly Rent [${monthlyRent}], Security Deposit [${securityDeposit}], Agreement Start Date [${agreementStartDate}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Agreement Period [${agreementPeriod}], Notice Period [${noticePeriod}], Lockin Period [${lockInPeriod}], Move In Date [${moveInDate}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Booking Amount [${bookingAmount}], Booking Adjust Type [${bookingAdjustType}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancies = await occupancyDB.getParticularOccupancies({
      clientId,
      tenantId,
      propId,
      roomId,
    });

    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Occupancy Count [${occupancies.length}]`
    );

    let discountEndDate = null;
    let discountStartDate = null;

    if (occupancies[0].discount > 0 && occupancies[0].discountPeriod > 0) {
      log.info(
        `[${C}], [${F}], Rental Cycle [${rentalCycle}], Rental Type [${rentalType}], Discount Direction [${occupancies[0].isDiscountFromFirstMonth}], Discount Period [${occupancies[0].discountPeriod}], Discount Available, Adding Start and End Date`
      );
      let nextRentCycle = moment().date(rentalCycle);
      if (
        Number(moment(moveInDate).date()) !== Number(rentalCycle) &&
        rentalType === CONSTANTS.RENTAL_TYPES.MONTHLY
      ) {
        nextRentCycle = moment(nextRentCycle).add(rentalType, "month");
      }

      if (Number(occupancies[0].isDiscountFromFirstMonth) === 1) {
        discountEndDate = nextRentCycle
          .clone()
          .add(occupancies[0].discountPeriod, "months")
          .subtract(1, "day")
          .format("YYYY-MM-DD");
        discountStartDate = moment(nextRentCycle).format("YYYY-MM-DD");
      } else {
        discountEndDate = moment(agreementStartDate)
          .add(agreementPeriod, "months")
          .subtract(1, "day")
          .format("YYYY-MM-DD");
        discountStartDate = moment(agreementStartDate)
          .add((agreementPeriod - occupancies[0].discountPeriod), "months")
          .format("YYYY-MM-DD");
      }

      await occupancyDB.updateDiscountStartEndDate({
        tenantId,
        clientId,
        discountStartDate,
        discountEndDate,
      });

      // log.info(
      //   `[${C}], [${F}], Start Date [${discountStartDate}], Discount End Date [${discountEndDate}]`
      // );
    }

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (occupancies[0].status === CONSTANTS.OCCUPANCY_STATUS.OCCUPIED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Tenant is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Tenant is already occupied", isSuccess: false });
    }

    const property = await propertyDB.getById({ id: propId });
    const room = await roomDB.getById({ id: roomId });

    if (room.status === CONSTANTS.ROOM_STATUS.INACTIVE) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Room is InActive`
      );
      return res.status(400).json({
        msg: "Room is not available for reservation",
        isSuccess: false,
      });
    }

    for (let occupancy of occupancies) {
      const tenants = await occupancyDB.getAllByBedId({
        bedId: occupancy.bedId,
      });

      let vacantSlots = await getVacantSlots(tenants);

      let isValidMoveInDate = false;
      for (let slot of vacantSlots) {
        if (moment(slot.startDate).isSameOrBefore(moment(moveInDate), "date") && slot.endDate === null) {
          isValidMoveInDate = true;
        } else if (moment(moveInDate).isBetween(slot.startDate, slot.endDate, "day", "[]")) {
          isValidMoveInDate = true;
        }
      }

      if (!isValidMoveInDate) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${occupancy.bedId}], Move-In Date Is In Conflict With Exsiting Tenant Occupancy`
        );

        return res.status(400).json({
          msg: "Move-in date is in conflict with existing tenant",
          isSUccess: false,
        });
      }
    }

    let isReserved = false;

    for (const occupancy of occupancies) {
      const bed = await bedDB.getById({ id: occupancy.bedId });

      if (
        bed.status !== CONSTANTS.BED_STATUS.MOVING_OUT &&
        bed.status !== CONSTANTS.BED_STATUS.VACANT &&
        bed.status !== CONSTANTS.BED_STATUS.RESERVED &&
        bed.status !== CONSTANTS.BED_STATUS.VACANT_RESERVED
      ) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], bed Id [${bed.id}], Bed status is not moving out/reserved/vacant`
        );

        continue;
      }

      let monthDiff = moment(moveInDate).diff(moment(), "month");
      if (Number(moment().format("DD")) < Number(rentalCycle)) {
        monthDiff -= 1;
      }

      let rentalMonths =
        rentalType - 1 - (Math.abs(Math.abs(monthDiff)) % rentalType);

      await occupancyDB.updateAgreementDetailsX({
        id: occupancy.id,
        rent: monthlyRent,
        security: securityDeposit,
        agreementStartDate,
        rentalCycle,
        rentalType,
        rentalMonths: rentalMonths,
        agreementPeriod,
        noticePeriod,
        lockInPeriod,
        moveInDate,
        electricityReading: 0,
        fine: property.fine,
        fineType: property.fineType,
        gracePeriod: property.gracePeriod,
        status: CONSTANTS.OCCUPANCY_STATUS.RESERVED,
        bookingAmt: Number(bookingAmount) || 0,
        bookingAdjustType: Number(bookingAdjustType) || 0,
      });

      if (userType === CONSTANTS.USER_TYPE.STAFF) {
        await occupancyDB.updateBookedBy({
          bookedBy: Number(req.id),
          id: occupancy.id
        });
      }

      // if (bed.status === CONSTANTS.BED_STATUS.MOVING_OUT || bed.status === CONSTANTS.BED_STATUS.RESERVED || bed.status === CONSTANTS.BED_STATUS.VACANT_RESERVED) {
      if (bed.status === CONSTANTS.BED_STATUS.MOVING_OUT || bed.status === CONSTANTS.BED_STATUS.RESERVED) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.RESERVED,
        });
      } else if (moment(moveInDate).isSameOrBefore(moment(), "date")) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.RESERVED,
        });
      } else {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.VACANT_RESERVED,
        });
      }

      isReserved = true;
    }

    if (isReserved) {
      await tenantDB.updateStatus({
        id: tenantId,
        status: CONSTANTS.TENANT_STATUS.RESERVED,
      });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Move In Date [${moveInDate}], Tenant Agreement Details Added & Occupancy Reserved Successfully`
      );
    }

    // Create booking for reserved tenants
    let applicationNumber = `KIP${clientId}${tenant.id}${moment().format("HHmmss")}`;
    
    await bookingsDB.create({
      clientId: clientId,
      tenantId: tenant.id,
      propId,
      roomId,
      moveInDate,
      status: CONSTANTS.BOOKING_STATUS.CONFIRMED,
      stayType: CONSTANTS.STAY_TYPE.NORMAL,
      applicationNumber,
    });

    const securityAdjustEntry = await ledgerDB.getLastEntry({
      tenantId,
      clientId,
    });

    if (securityAdjustEntry !== false) {
      await ledgerDB.forfeitRefund ({
        id: securityAdjustEntry.id,
        amount: 0,
        balance: 0,
        description: `Refund of amount ₹${Math.abs(securityAdjustEntry?.balance)} has been forfeited during eviction.`
      });
    }

    //Tally Handling
    await setOccupancyTallyStatus(
      Number(clientId),
      occupancies[0],
      CONSTANTS.TALLY_STATUS.PENDING,
    );

    let referenceId = await generateLedgerReferenceId({ clientId });
    if (Number(securityDeposit)) {
      dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.SECURITY);
      const dueId = await duesDB.add({
        tenantId,
        amount: securityDeposit,
        occupancyId: occupancies[0].id,
        roomId,
        propId,
        clientId,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.SECURITY,
        balance: securityDeposit,
        ledgerReferenceId: referenceId,
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: securityDeposit,
        balance: securityDeposit,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.SECURITY,
        rentStartDate: null,
        rentEndDate: null,
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: dueDescription,
      });

      await setDueTallyStatus(
        Number(clientId),
        dueId,
        CONSTANTS.TALLY_STATUS.PENDING
      );
    }

    if (Number(monthlyRent)) {
      // const rents = calculateRentAsPerRentalType(
      //   moveInDate,
      //   rentalCycle,
      //   monthlyRent,
      //   rentalType
      // );
      let discountFlag = 1;
      // const rents = calculateRentAsPerRentalTypeForReserved(
      //   moveInDate,
      //   rentalCycle,
      //   monthlyRent,
      //   rentalType
      // );

      let rents = [];

      if (Number(bookingAmount) > 0 && Number(bookingAdjustType) === CONSTANTS.BOOKING_ADJUST_TYPE.RENT) {
        rents = calculateRentAsPerRentalTypeForReservedRentBooking(
          moveInDate,
          rentalCycle,
          monthlyRent,
          rentalType
        );
      } else {
        rents = calculateRentAsPerRentalTypeForReserved(
          moveInDate,
          rentalCycle,
          monthlyRent,
          rentalType
        );
      }

      if (Array.isArray(rents) && rents.length > 0) {
        dueDescription = getDueDescription(CONSTANTS.DUES_TYPES.RENT);
        for (let rent of rents) {
          referenceId = await generateLedgerReferenceId({ clientId });
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Start Date [${rent.startDate}], End Date [${rent.endDate}], Rent [${rent.rent}]`
          );

          let rentAfterDiscount = rent.rent;

          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Dicount Start Date [${occupancies[0].discountStartDate}], Discount End Date [${occupancies[0].discountEndDate}], Rent Start Date [${rent.startDate}] Disocunt Added`
          );

          if (
            Number(occupancies[0].discount) &&
            Number(occupancies[0].discount) > 0 &&
            moment(occupancies[0].discountStartDate).isSameOrBefore(rent.startDate) &&
            discountFlag <= Number(occupancies[0].discountPeriod)
          ) {
            if (occupancies[0].discountType === CONSTANTS.DISCOUNT_TYPE.FLAT) {
              rentAfterDiscount = Number(rent.rent) - Number(occupancies[0].discount);
              discountFlag += 1;
            } else if (occupancies[0].discount === CONSTANTS.DISCOUNT_TYPE.PERCENTAGE) {
              rentAfterDiscount = rent.rent - ((rent.rent * occupancies[0].discount) / 100);
              discountFlag += 1;
            }

            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Discount Type [${occupancies[0].discountType}], Discount [${occupancies[0].discount}], Rent Amount [${rent.rent}], Rent After Discount [${rentAfterDiscount}], Disocunt Added`
            );
          }
          // await duesDB.addWithStartEndDate({
          //   tenantId,
          //   amount: rent.rent,
          //   occupancyId: occupancies[0].id,
          //   roomId,
          //   propId,
          //   clientId,
          //   rentStartDate: rent.startDate,
          //   rentEndDate: rent.endDate,
          //   dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
          //   type: CONSTANTS.DUES_TYPES.RENT,
          //   balance: rent.rent,
          //   ledgerReferenceId: referenceId,
          // });
          const dueId = await duesDB.addWithStartEndDateX({
            tenantId,
            amount: rentAfterDiscount,
            occupancyId: occupancies[0].id,
            roomId,
            propId,
            clientId,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            type: CONSTANTS.DUES_TYPES.RENT,
            balance: rentAfterDiscount,
            ledgerReferenceId: referenceId,
            title: "Rent",
            description: "Due added while onboarding",
          });
          await ledgerDB.add({
            tenantId: tenantId,
            roomId: roomId,
            propId: propId,
            clientId,
            amount: rentAfterDiscount,
            balance: rentAfterDiscount,
            referenceId: referenceId,
            transactionId: null,
            type: CONSTANTS.DUES_TYPES.RENT,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
            description: rent.rent - rentAfterDiscount > 0
              ? `${dueDescription} for ${moment(moveInDate).format("MMM YYYY")}, Discount of ₹${rent.rent - rentAfterDiscount} given for this due`
              : `${dueDescription} for ${moment(moveInDate).format("MMM YYYY")}`,
            discount: rent.rent - rentAfterDiscount,
            title: "Rent",
          });

          await setDueTallyStatus(
            Number(clientId),
            dueId,
            CONSTANTS.TALLY_STATUS.PENDING
          );
        }
      }
    }

    const extraCharges = await extraChargeDB.getAllByClientIdAndPropId({
      clientId,
      propId,
    });

    if (extraCharges) {
      for (let extraCharge of extraCharges) {
        let type = 0;

        type = convertTypes({
          from: "extraChargeType",
          to: "dueType",
          type: extraCharge.type,
        });

        let isFullCharge = false;
        if (
          Number(type) === CONSTANTS.DUES_TYPES.TECH_CHARGES || 
          Number(type) === CONSTANTS.DUES_TYPES.LAUNDRY_CHARGES ||
          Number(type) === CONSTANTS.DUES_TYPES.FOOD ||
          Number(type) === CONSTANTS.DUES_TYPES.BUS_CHARGES ||
          Number(type) === CONSTANTS.DUES_TYPES.OTHER
        ) {
          isFullCharge = true;
        }

        if (Number(type) === CONSTANTS.DUES_TYPES.MOVE_OUT_CHARGES) {
          continue;
        }
        let rents = calculateRentAsPerRentalTypeForReserved(
          moveInDate,
          rentalCycle,
          extraCharge.amount,
          CONSTANTS.RENTAL_TYPES.MONTHLY
        );
        if (extraCharge.repetitionType === 1) {
          rents = [
            {
              month: moment(moveInDate).format("MMMM"),
              year: moment(moveInDate).year(),
              rent: extraCharge.amount,
              startDate: moveInDate,
              endDate: moment(moveInDate).add(1, "day").format("YYYY-MM-DD"),
            },
          ];
        }
        for (let rent of rents) {
          if (type === 0) continue;
          referenceId = await generateLedgerReferenceId({ clientId });
          dueDescription = getDueDescription(extraCharge.type);
          const dueId = await duesDB.addWithStartEndDateX({
            tenantId,
            // amount: rent.rent,
            amount: isFullCharge ? extraCharge.amount : rent.rent,
            occupancyId: occupancies[0].id,
            roomId,
            propId,
            clientId,
            rentStartDate: rent.startDate,
            rentEndDate: rent.endDate,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            type,
            // balance: rent.rent,
            balance: isFullCharge ? extraCharge.amount : rent.rent,
            ledgerReferenceId: referenceId,
            title: dueDescription,
            description: "Due added while onboarding",
          });
          await ledgerDB.add({
            tenantId: tenantId,
            roomId: roomId,
            propId: propId,
            clientId,
            // amount: rent.rent,
            amount: isFullCharge ? extraCharge.amount : rent.rent,
            // balance: rent.rent,
            balance: isFullCharge ? extraCharge.amount : rent.rent,
            referenceId: referenceId,
            transactionId: null,
            type: type,
            rentStartDate: null,
            rentEndDate: null,
            dueDate: moment(rent.startDate).format("YYYY-MM-DD HH:mm:ss"),
            description: dueDescription,
            title: dueDescription,
          });

          await setDueTallyStatus(
            Number(clientId),
            dueId,
            CONSTANTS.TALLY_STATUS.PENDING
          );
        }
      }
    }

    await generateTenantTId(
      tenantId,
    );

    await logActivity(
      Number(req.userType),
      Number(req.id),
      Number(req.parentClientId!),
      Number(req.platform),
      Number(tenantId),
      CONSTANTS.ACTIVITY_TYPES.TENANT_ADDED,
      true,
      moveInDate,
      null,
      null,
      null
    );

    return res.status(200).json({
      msg: isReserved
        ? "Tenant Agreement Details Added & Occupancy Reserved Successfully"
        : "Bed is not available for reservation",
      data: {
        tenantName: tenant.name,
        roomNum: room.roomNum,
        floor: room.floor,
        allbedsOccupied: occupancies.length > 1,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.AddAgreementByClientShortStayX = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddAgreementByClientShortStayX";

  try {
    const {
      dailyRent,
      moveInDate,
      moveOutDate,
      tenantId,
      propId,
      roomId,
      entireFlatOccupied,
      notifyTenant = 0,
      securityDeposit = 0,
      sendNotiToTenant=1, //only done from Add booking
      addFromBooking=0,
    } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Daily Rent [${dailyRent}], Security [${securityDeposit}], Move In Date [${moveInDate}], Move Out Date [${moveOutDate}], Entire Flat Occupied [${entireFlatOccupied}], Notify Tenant [${notifyTenant}], Send Noti to Tenant [${sendNotiToTenant}], Add From Booking [${addFromBooking}]`
    );

    const userType = req.userType;
    let totalDueAmount = 0;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, Platform [${req.platform}], ${isPartner ? "Partner Requesting" : "Client Requesting"
        }....`
      );
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);

        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false
        });
      }

      if (staff.role !== CONSTANTS.STAFF_ROLES.ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.WARDEN && staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE && staff.role !== CONSTANTS.STAFF_ROLES.FINANCE_ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.SALES_HEAD && staff.role !== CONSTANTS.STAFF_ROLES.SUPER_ADMIN) {
        log.info(
          `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Only Admin, Warden and Back Office Allowed`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false
        });
      }

      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Platform [${req.platform}], Admin/Warden Requesting....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancies = null;
    if (entireFlatOccupied) {
      const room = await roomDB.getById({ id: roomId });
      occupancies = await occupancyDB.getOccupanciesForFlat({
        clientId,
        tenantId,
        propId,
        flatId: room.flatId,
      });
    } else {
      occupancies = await occupancyDB.getParticularOccupancies({
        clientId,
        tenantId,
        propId,
        roomId,
      });
    }

    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Occupancy Count [${occupancies.length}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (
      occupancies[0].status === CONSTANTS.OCCUPANCY_STATUS.OCCUPIED ||
      occupancies[0].staus === CONSTANTS.OCCUPANCY_STATUS.RESERVED
    ) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Tenant is already occupied`
      );
      return res
        .status(400)
        .json({ msg: "Tenant is already occupied", isSuccess: false });
    }

    const property = await propertyDB.getById({ id: propId });
    const room = await roomDB.getById({ id: roomId });

    // if (room.status === CONSTANTS.ROOM_STATUS.OCCUPIED) {
    //   log.info(
    //     `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Room is already occupied`
    //   );
    //   return res
    //     .status(400)
    //     .json({ msg: "Room is already occupied", isSuccess: false });
    // }

    for (let occupancy of occupancies) {
      const tenants = await occupancyDB.getAllByBedId({
        bedId: occupancy.bedId,
      });

      let vacantSlots = await getVacantSlots(tenants);

      let isValidMoveInDate = false;
      for (let slot of vacantSlots) {
        if (moment(slot.startDate).isBefore(moment(moveInDate), "date") && slot.endDate === null) {
          isValidMoveInDate = true;
        } else if (moment(moveInDate).isBetween(slot.startDate, slot.endDate, "day", "[]")) {
          isValidMoveInDate = true;
        }
      }

      if (!isValidMoveInDate) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${occupancy.bedId}], Move-In Date Is In Conflict With Exsiting Tenant Occupancy`
        );

        return res.status(400).json({
          msg: "Move-in date is in conflict with existing tenant",
          isSUccess: false,
        });
      }
    }

    const securityAdjustEntry = await ledgerDB.getLastEntry({
      tenantId,
      clientId,
    });

    if (securityAdjustEntry !== false) {
      await ledgerDB.forfeitRefund ({
        id: securityAdjustEntry.id,
        amount: 0,
        balance: 0,
        description: `Refund of amount ₹${Math.abs(securityAdjustEntry?.balance)} has been forfeited during eviction.`
      });
    }

    const isFutureDate = moment(moveInDate).isAfter(moment(), "day");

    if (Number(addFromBooking) === 0) {
      // Create booking for tenants
      let applicationNumber = `KIP${clientId}${tenant.id}${moment().format("HHmmss")}`;
      try {
        const bookingId = await bookingsDB.create({
          clientId: clientId,
          tenantId: tenant.id,
          propId,
          roomId,
          moveInDate,
          status: isFutureDate ? CONSTANTS.BOOKING_STATUS.CONFIRMED : CONSTANTS.BOOKING_STATUS.MOVED_IN,
          stayType: CONSTANTS.STAY_TYPE.NORMAL,
          applicationNumber,
        });
  
        if (userType === CONSTANTS.USER_TYPE.STAFF) {
          await bookingsDB.updateBookedBy({
            bookedBy: Number(req.id),
            id: bookingId,
          });
        }
      } catch(error: any) {
        log.info(`[${C}], [${F}], Error In Booking Creation, Error: ${error?.message || error}`);
      }
    }

    for (const occupancy of occupancies) {
      const bed = await bedDB.getById({ id: occupancy.bedId });

      if (bed.status === CONSTANTS.BED_STATUS.OCCUPIED) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], bed Id [${bed.id}], bed is already occupied`
        );
        return res
          .status(400)
          .json({ msg: "bed is already occupied", isSuccess: false });
      }

      await occupancyDB.updateAgreementDetailsShortStay({
        id: occupancy.id,
        rent: dailyRent,
        rentalCycle: 0,
        rentalType: 0,
        security: securityDeposit,
        agreementPeriod: moment(moveOutDate).diff(moment(moveInDate), "days"),
        moveInDate,
        moveOutDate,
        status: isFutureDate
          ? CONSTANTS.OCCUPANCY_STATUS.RESERVED
          : CONSTANTS.OCCUPANCY_STATUS.OCCUPIED,
        stayType: CONSTANTS.STAY_TYPE.SHORT,
      });

      if (userType === CONSTANTS.USER_TYPE.STAFF) {
        await occupancyDB.updateBookedBy({
          bookedBy: Number(req.id),
          id: occupancy.id
        });
      }

      if (isFutureDate) {
        await bedDB.updateStatus({
          id: bed.id,
          status: bed.status === CONSTANTS.BED_STATUS.MOVING_OUT || bed.status === CONSTANTS.BED_STATUS.RESERVED
            ? CONSTANTS.BED_STATUS.RESERVED
            : CONSTANTS.BED_STATUS.VACANT_RESERVED,
        });
      } else {
        if (bed.status === CONSTANTS.BED_STATUS.RESERVED || bed.status === CONSTANTS.BED_STATUS.VACANT_RESERVED) {
          await bedDB.updateStatus({
            id: bed.id,
            status: CONSTANTS.BED_STATUS.RESERVED,
          });

          //Moving Out Logic
          const securityTransanction = await ledgerDB.getSecurityTransactionAmountX({
            tenantId,
            roomId: occupancy.roomId,
            propId: occupancy.propId,
            type: CONSTANTS.DUES_TYPES.SECURITY,
          });

          let securityAmt = securityTransanction
            ? Math.abs(Number(securityTransanction.amount))
            : 0;

          const securityUsed = await ledgerDB.getUsedSecurityAmtByTenantIdAndClientId({
            clientId,
            tenantId,
            createdAt: occupancy.createdAt,
          });

          securityAmt = Number(securityAmt) - (Number(securityUsed) || 0);

          // To handle previous excess balance
          let prevBalance = await ledgerDB.getPreviousBalance({
            tenantId,
            clientId,
          });
          if (prevBalance && prevBalance < 0) {
            securityAmt += Math.abs(prevBalance);
          }

          const { totalDues } = await duesDB.getTotalDuesByTenantIdForEviction({
            tenantId: tenantId,
            propId: occupancy.propId,
          });

          for (const occupancy of occupancies) {
            let moveOutId = await moveOutDB.add({
              clientId,
              tenantId,
              propId: occupancy.propId,
              roomId: occupancy.roomId,
              bedId: occupancy.bedId,
              floor: occupancy.floor,
              reason: occupancy.reason,
              clientDues: securityAmt,
              tenantDues: totalDues || 0,
              rent: dailyRent,
              rentalCycle: 0,
              rentalType: 0,
              rentalMonths: occupancy.rentalMonths,
              noticePeriod: occupancy.noticePeriod,
              lockInPeriod: occupancy.lockInPeriod,
              security: occupancy.security,
              agreementPeriod: moment(moveOutDate).diff(moment(moveInDate), "days"),
              agreementStartDate: moveInDate,
              moveInDate: moveInDate,
              moveOutDate: moveOutDate,
              flatId: occupancy.flatId,
              notes: occupancy.notes,
              rentalBond: occupancy.rentalBond,
              moveOutReason: occupancy.moveOutReason,
              isPoliceVerified: occupancy.isPoliceVerified,
              isRentAgreementSigned: occupancy.isRentAgreementSigned,
              stayType: CONSTANTS.STAY_TYPE.SHORT,
              kycStatus: occupancy.kycStatus,
              discountType: occupancy.discountType,
              discount: occupancy.discount,
              discountPeriod: occupancy.discountPeriod,
              discountStartDate: occupancy.discountStartDate,
              discountEndDate: occupancy.discountEndDate,
              isDiscountFromFirstMonth: occupancy.isDiscountFromFirstMonth,
              wasCancelled: 0,
              status: CONSTANTS.MOVE_OUT_STATUS.INITIATED,
              gId: occupancy?.gId || null,
              tallyStatus: occupancy.tallyStatus,
            });
            await moveOutDB.updateBookedBy({ bookedBy: occupancy.bookedBy, id: moveOutId });
            await occupancyDB.updateStatus({
              id: occupancy.id,
              status: CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT,
            });
          }

          const moveOutCharge = await extraChargeDB.getByPropAndType({
            clientId,
            propId: occupancy.propId,
            type: CONSTANTS.EXTRA_CHARGE_TYPES.MOVE_OUT_CHARGES,
          });

          if (moveOutCharge) {
            const referenceId = await generateLedgerReferenceId({ clientId });
            await duesDB.addWithStartEndDateX({
              tenantId,
              amount: moveOutCharge?.amount || 0,
              occupancyId: occupancy.id,
              roomId: occupancy.roomId,
              propId: occupancy.propId,
              clientId,
              dueDate: moment(moveOutDate).format("YYYY-MM-DD"),
              rentStartDate: moment(moveOutDate).format("YYYY-MM-DD"),
              rentEndDate: moment(moveOutDate).format("YYYY-MM-DD"),
              type: CONSTANTS.DUES_TYPES.MOVE_OUT_CHARGES,
              balance: moveOutCharge?.amount || 0,
              ledgerReferenceId: referenceId,
              title: "Move Out Charges",
              description: "Move Out Charges automatically added while moving out",
            });
          }

          if (securityAmt > 0) {
            let referenceId = await generateLedgerReferenceId({ clientId });
            ledgerDB.add({
              tenantId,
              roomId: occupancy.roomId,
              propId: occupancy.propId,
              clientId,
              amount: 0,
              balance: -securityAmt,
              referenceId: referenceId,
              transactionId: null,
              type: CONSTANTS.DUES_TYPES.SECURITY,
              rentStartDate: null,
              rentEndDate: null,
              dueDate: moveOutDate,
              description: "Security deposit and any excess amounts paid by tenant. Refund after eviction.",
            });
          }
        } else {
          await bedDB.updateStatus({
            id: bed.id,
            status: CONSTANTS.BED_STATUS.OCCUPIED
          });
        }
      }
    }

    let roomStatus = null;

    if (occupancies.length > 1) {
      roomStatus = CONSTANTS.ROOM_STATUS.OCCUPIED;
    } else {
      const bed = await bedDB.getVacantBed({
        roomId,
        status: CONSTANTS.BED_STATUS.VACANT,
      });
      if (bed) roomStatus = CONSTANTS.ROOM_STATUS.SEMI_OCCUPIED;
      else roomStatus = CONSTANTS.ROOM_STATUS.OCCUPIED;
    }

    for (let occupancy of occupancies) {
      await roomDB.updateStatus({
        id: occupancy.roomId,
        status: roomStatus,
      });
    }

    let gId = await generateTenantGId();
    await tenantDB.updateGId({ gId, id: tenantId });

    let referenceId = await generateLedgerReferenceId({ clientId });
    let totalRent = moment(moveOutDate).diff(moment(moveInDate), "days") * dailyRent;
    totalDueAmount += totalRent;

    if (totalRent > 0) {
      await duesDB.addWithStartEndDateX({
        tenantId,
        amount: totalDueAmount,
        occupancyId: occupancies[0].id,
        roomId,
        propId,
        clientId,
        rentStartDate: moment(moveInDate).format("YYYY-MM-DD"),
        rentEndDate: moment(moveOutDate).format("YYYY-MM-DD"),
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.RENT,
        balance: totalDueAmount,
        ledgerReferenceId: referenceId,
        title: "Rent",
        description: "Due added while onboarding",
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: totalDueAmount,
        balance: totalDueAmount,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.RENT,
        rentStartDate: moment(moveInDate).format("YYYY-MM-DD"),
        rentEndDate: moment(moveOutDate).format("YYYY-MM-DD"),
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: `Rent for ${moment(moveInDate).format("YYYY-MM-DD")} to ${moment(moveOutDate).format("YYYY-MM-DD")}`,
        title: "Rent",
      });
    }

    referenceId = await generateLedgerReferenceId({ clientId });
    totalDueAmount += Number(securityDeposit);

    if (securityDeposit > 0) {
      await duesDB.addWithStartEndDateX({
        tenantId,
        amount: securityDeposit,
        occupancyId: occupancies[0].id,
        roomId,
        propId,
        clientId,
        rentStartDate: moment(moveInDate).format("YYYY-MM-DD"),
        rentEndDate: moment(moveOutDate).format("YYYY-MM-DD"),
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        type: CONSTANTS.DUES_TYPES.SECURITY,
        balance: securityDeposit,
        ledgerReferenceId: referenceId,
        title: "Initial Security Deposit",
        description: "Due added while onboarding",
      });
      await ledgerDB.add({
        tenantId: tenantId,
        roomId: roomId,
        propId: propId,
        clientId,
        amount: securityDeposit,
        balance: securityDeposit,
        referenceId: referenceId,
        transactionId: null,
        type: CONSTANTS.DUES_TYPES.SECURITY,
        rentStartDate: moment(moveInDate).format("YYYY-MM-DD"),
        rentEndDate: moment(moveOutDate).format("YYYY-MM-DD"),
        dueDate: moment(moveInDate).format("YYYY-MM-DD HH:mm:ss"),
        description: `Initial Security Deposit for ${moment(moveInDate).format("YYYY-MM-DD")}`,
        title: "Initial Security Deposit",
      });
    }

    const link = `${process.env.TENANT_CHECKIN_PATH}/${gId}`;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Link [${link}]`
    );

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId,
      propId,
    });

    if (propSettings && Number(notifyTenant) === 1 && Number(sendNotiToTenant) === 1) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Settings Are There And Client Asked To Notify Tenant`
      );
      if (propSettings.sms === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], SMS Notification not enabled`
        );
        const msg = CONSTANTS.MSG.TENANT_ADDED.replace("{#var#}", room.roomNum)
          .replace("{#var2#}", property.name)
          .replace("{#var3#}", link);
        //sendSMS(tenant.mobile, msg, CONSTANTS.SMS_TEMPLATE_IDS.TENANT_ADDED);
      }
      if (propSettings.onboardWelcome === 1) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], WhatsApp Notification not enabled`
        );
        let flatName = "";
        let footerText = propSettings.footer || "The Kipinn Team";
        let template =
          propSettings.whatsappOnboardingTemplate || "tenantonboardbyownernew";
        if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: room.flatId });
          flatName = name + "," + room.roomNum;
        } else {
          flatName = room.roomNum;
        }
        let whatsappAgreegrator = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.WHATSAPP_AGGREGATOR
        });
        let onboardingCred = await getClientWhatsappCredentialsMessageCentral(Number(clientId), property?.id, CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.ONBOARDING, CONSTANTS.USER_TYPE.TENANT);
        if(whatsappAgreegrator && Number(whatsappAgreegrator?.value)  === CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL && onboardingCred) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through Message Central`
          );
          const bodyValues: Record<string, string> = {
            body_1: `${tenant?.name}`,
            body_2: `${property.name}`,
            body_3: `${property.name}`,
            body_4: `${flatName}`,
            body_5: `${moveInDate}`,
            body_6: `${dailyRent}`,
            body_7: `${securityDeposit}`,
            body_8: `${propSettings.ios || " "}`,
            //body_9: `${propSettings.android.replace("*Android*:", "") || " "}`,
            body_9: (propSettings.android || "").replace(/^\*Android:\*\s*/i, "").trim() || " ",
            body_10: `${footerText.replace("Team", "").trim()}`
          };
          sendWhatsappMC(
            tenant?.mobile,
            Number(clientId),
            Number(property.id),
            bodyValues,
            CONSTANTS.WHATSAPP_TEMPLATE_TYPES.TENANT.ONBOARDING,
            CONSTANTS.USER_TYPE.TENANT,
            CONSTANTS.CLIENT_CONFIG_PROVIDER.MESSAGE_CENTRAL
          );

        } else {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], TenantId [${tenantId}], Sending through intrakt`
          );
          if (template === CONSTANTS.WHATSAPP_TEMPLATES.ON_BOARDING_WITH_LINK) {
            sendWhatsappTenantWelcomeWithLink(
              tenant.mobile,
              tenant.name,
              property.name,
              String(dailyRent),
              String(securityDeposit),
              flatName,
              moveInDate,
              footerText,
              template,
              propSettings.android || "",
              propSettings.ios || "",
              Number(clientId),
            );
          } else {
            sendWhatsappTenantWelcome(
              tenant.mobile,
              tenant.name,
              property.name,
              String(dailyRent),
              String(securityDeposit),
              flatName,
              moveInDate,
              footerText,
              template,
              Number(clientId),
            );
          }
        }


      }
    }
    await generateTenantTId(
      tenantId,
    );
    await logActivity(
      Number(req.userType),
      Number(req.id),
      Number(req.parentClientId!),
      Number(req.platform),
      Number(tenantId),
      CONSTANTS.ACTIVITY_TYPES.TENANT_ADDED,
      isFutureDate ? true : false,
      moveInDate,
      null,
      null,
      null
    );

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${propId}], Room Id [${roomId}], Daily Rent [${dailyRent}], Security [${securityDeposit}], Move In Date [${moveInDate}], Move Out Date [${moveOutDate}], Entire Flat Occupied [${entireFlatOccupied}], Agreement Details Added For Short Stay Tenant`
    );

    return res.status(200).json({
      msg: "Agreement details added successfully",
      data: {
        tenantName: tenant.name,
        roomNum: room.roomNum,
        floor: room.floor,
        allbedsOccupied: occupancies.length > 1,
        totalDueAmount: totalDueAmount,
      },
      isSuccess: true,
    });

  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetTenantByBed = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetTenantByBed";

  try {
    let { bedId } = req.params;
    let { t: tenantStatus, tenantId } = req.query;

    const userType = req.userType;
    let clientId = req.id;
    let isMultipleOccupancy = false;
    let occupiedBedCount = 1;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Bed Id [${bedId}], Tenant Id [${tenantId}], Tenant Status [${tenantStatus}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}], Tenant Status [${tenantStatus}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}], Tenant Status [${tenantStatus}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}], Tenant Status [${tenantStatus}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const bed = await bedDB.getById({ id: bedId });
    if (!bed) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}], Tenant Status [${tenantStatus}], No Bed Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancyStatus = CONSTANTS.OCCUPANCY_STATUS.OCCUPIED;

    if (bed.status === CONSTANTS.BED_STATUS.MOVING_OUT) {
      occupancyStatus = CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT;
    } else if (bed.status === CONSTANTS.BED_STATUS.VACANT_RESERVED) {
      occupancyStatus = CONSTANTS.OCCUPANCY_STATUS.RESERVED;
    } else if (bed.status === CONSTANTS.BED_STATUS.RESERVED) {
      if (Number(tenantStatus) === CONSTANTS.TENANT_STATUS.RESERVED) {
        occupancyStatus = CONSTANTS.OCCUPANCY_STATUS.RESERVED;
      } else {
        occupancyStatus = CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT;
      }
    }

    let occupancy = await occupancyDB.getByBedId({
      bedId,
      status: occupancyStatus,
    });

    if (Number(tenantId) && Number(tenantId) > 0) {
      occupancy = await occupancyDB.getByBedIdAndTenantId({
        bedId: bedId,
        tenantId: tenantId,
      });
    }

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}], Tenant Status [${tenantStatus}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancyByTenantAndClientId = await occupancyDB.getByClientIdAndTenantId({
      clientId: clientId,
      tenantId: occupancy.tenantId
    });

    if (occupancyByTenantAndClientId && occupancyByTenantAndClientId.length > 1) {
      isMultipleOccupancy = occupancyByTenantAndClientId.every(
        (occupancy: occupanciesTypes) =>
          occupancy.roomId === occupancyByTenantAndClientId[0].roomId
      );
      // isMultipleOccupancy = true;
      occupiedBedCount = occupancyByTenantAndClientId.length || 1;
    }

    let canAssignFullRoom = false;
    const occupancyByRoom = await occupancyDB.getByRoomId({
      roomId: occupancy.roomId
    });

    const roomBedCount = await bedDB.getCountsByRoomId({
      id: occupancy.roomId,
    });

    if (occupancyByRoom && occupancyByRoom.length === 1 && roomBedCount.totalBeds > 1) canAssignFullRoom = true;

    const property = await propertyDB.getById({ id: occupancy.propId });
    if (!property) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}], Tenant Status [${tenantStatus}], No Property Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false
      });
    }

    const tenant = await tenantDB.getById({ id: occupancy.tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}], Tenant Status [${tenantStatus}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let docs = await documentDB.getByTenantIdAndClientId({
      tenantId: occupancy.tenantId,
      clientId,
      moveOut: 0,
    });
    if (docs && docs.length > 0) {
      for (let doc of docs) {
        const title = await getDocumentTitle(doc.type);
        doc.title = title;
      }
      docs = docs.filter((doc: any) => doc.type !== CONSTANTS.DOCUMENT_TYPES.SIGNATURE);
    }

    let totalDue = 0;

    // if (occupancyStatus === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
    //   const { totalDues } = await duesDB.getTotalDuesByTenantIdForEviction({
    //     tenantId: tenant.id,
    //     propId: occupancy.propId,
    //   });
    //   totalDue = totalDues;
    // } else {
    //   const { totalDues } = await duesDB.getTotalDuesByTenantId({
    //     tenantId: occupancy.tenantId,
    //     propId: occupancy.propId,
    //   });
    //   totalDue = totalDues;
    // }

    const { totalDues } = await duesDB.getTotalDuesByTenantId({
      tenantId: occupancy.tenantId,
      propId: occupancy.propId,
    });
    totalDue = totalDues;

    // Pallav - Multi Tenant handling
    //const dues = await duesDB.getByTenantId({ tenantId: occupancy.tenantId });
    const dues = await duesDB.getByTenantIdAndClientId({ tenantId: occupancy.tenantId, clientId });

    let moveInInspection = await moveInDB.getByTenantIdandClientId({
      tenantId: occupancy.tenantId,
      clientId: occupancy?.clientId,
    });

    if (moveInInspection) {
      let moveInImage = await imageDB.getByMoveInId({
        moveInInspectionId: moveInInspection.id,
      });

      moveInInspection.images = moveInImage;
    } else {
      moveInInspection = [];
    }

    const lastSavedElecReading = await electricityDB.getForPreviousMonth({
      clientId,
      propId: occupancy?.propId,
      roomId: occupancy?.roomId,
    });

    const rentAgreementRecords =
      await rentAgreementRecordDB.getByTenantIdandClientId({
        tenantId: occupancy?.tenantId,
        clientId: occupancy?.clientId,
      });

    let flat = null;
    const room = await roomDB.getById({ id: occupancy?.roomId });
    if (Number(room.flatId)) {
      flat = await flatDB.getById({ id: room.flatId });
    }

    if (occupancy.propertyType === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      occupancy.flatName = name;
    } else {
      occupancy.flatName = occupancy.floor;
    }

    let totalCollectedAmt =
      await transactionDB.getTotalCollectionByTenantIdAndClientId({
        tenantId: occupancy?.tenantId,
        clientId: occupancy?.clientId,
      });

    let transactions = await transactionDB.getByTenantIdAndClientId({
      tenantId: occupancy?.tenantId,
      clientId: occupancy?.clientId,
    });
    let secAmount = 0;
    let secTransAdjust = [];
    if (transactions && transactions.length > 0) {
      let secTrans = transactions.filter(
        (t: transactionsTypes) =>
          t.transactionFor === CONSTANTS.TRANSACTION_FOR.SECURITY
      );
      secTransAdjust = transactions.filter(
        (t: transactionsTypes) =>
          t.mode === CONSTANTS.TRANSACTION_MODES.ADJUSTED_FROM_SECURITY
      );

      secAmount = secTrans.reduce((acc: number, curr: transactionsTypes) => {
        return acc + Number(curr.amount);
      }, 0);

      for (let trans of transactions) {
        let canRestore = true;

        const ledgerRecordsByTxnId = await ledgerDB.getByTransactionId({
          transactionId: Number(trans.id),
        });

        let occupancy = await occupancyDB.getByTenantIdAndClientId({
          tenantId: trans.tenantId,
          clientId,
        });

        let securityUsed = false;

        if (occupancy) {
          securityUsed = await ledgerDB.getUsedSecurityAmtByTenantIdAndClientId({
            tenantId: trans.tenantId,
            clientId: clientId,
            createdAt: occupancy.createdAt,
          });
        }

        if (!occupancy) {
          canRestore = false;
        } else if (securityUsed && trans.transactionFor === CONSTANTS.TRANSACTION_FOR.SECURITY) {
          canRestore = false;
        } else if (!ledgerRecordsByTxnId || ledgerRecordsByTxnId.length === 0) {
          canRestore = false;
        } else if (ledgerRecordsByTxnId && ledgerRecordsByTxnId.length > 1) {
          canRestore = false;
        } else if (Math.abs(ledgerRecordsByTxnId[0].amount) !== Math.abs(trans.amount)) {
          //Excess Amount Check using transaction amount and ledger record amount
          canRestore = false;
        } else if (ledgerRecordsByTxnId[0].type === CONSTANTS.DUES_TYPES.SETTLEMENT) {
          canRestore = false;
        }
        trans.canRestore = canRestore;
      }
    }

    let securities = await transactionDB.getSecurityByTenantIdAndClientId({
      tenantId: occupancy?.tenantId,
      clientId: occupancy?.clientId,
    });
    if (securities && securities.length > 0) {
      securities.push(...secTransAdjust);
      securities.sort(
        (a: transactionsTypes, b: transactionsTypes) =>
          new Date(b.collectionDate).getTime() -
          new Date(a.collectionDate).getTime()
      );

    }
    const lastRentDue = await ledgerDB.getLastRent({
      tenantId: occupancy?.tenantId,
      clientId: occupancy?.clientId,
    });

    let rentStartDate = moment().date(occupancy.rentalCycle).format("YYYY-MM-DD");
    let rentEndDate = moment(rentStartDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");

    let firstRentStartDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");
    let firstRentEndDate = moment(occupancy.moveInDate).date(occupancy.rentalCycle).isAfter(moment(occupancy.moveInDate), "date")
      ? moment(firstRentStartDate).date(occupancy.rentalCycle).subtract(1, "day").format("YYYY-MM-DD")
      : moment(firstRentStartDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");

    if (moment().isBetween(firstRentStartDate, firstRentEndDate, "date", "[]")) {
      rentStartDate = firstRentStartDate;
      rentEndDate = firstRentEndDate;
    } else if (moment(occupancy.moveInDate).isAfter(moment(rentStartDate), "date")) {
      rentStartDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");

      rentEndDate = moment().date(occupancy.rentalCycle).isAfter(moment(occupancy.moveInDate), "date")
        ? moment().date(occupancy.rentalCycle).subtract(1, "day").format("YYYY-MM-DD")
        : moment().date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    } else if (moment().isBefore(moment().date(occupancy.rentalCycle), "date")) {

      rentStartDate = moment().date(occupancy.rentalCycle).subtract(1, "month").format("YYYY-MM-DD");
      rentEndDate = moment(rentStartDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    }

    if (lastRentDue && lastRentDue.rentEndDate) {
      rentStartDate = moment(lastRentDue?.rentEndDate)
        .add(1, "days")
        .format("YYYY-MM-DD");

      rentEndDate = moment(rentStartDate).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
      // } else if (tenant.status === CONSTANTS.TENANT_STATUS.RESERVED) {
    } else if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
      rentStartDate = moment(occupancy?.moveInDate).format("YYYY-MM-DD");

      rentEndDate = moment(occupancy.moveInDate).date(occupancy.rentalCycle).isAfter(moment(occupancy.moveInDate), "date")
        ? moment(occupancy.moveInDate).date(occupancy.rentalCycle).subtract(1, "day").format("YYYY-MM-DD")
        : moment(occupancy.moveInDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    }

    let advanceRentSlots = [];
    let rentDuration = 1;
    let rentalMonthToBeAdded = 0;
    const globalRentStartDate = rentStartDate;

    while (rentDuration < 13) {

      if (moment(rentStartDate).isAfter(moment(), "date")) {
        rentalMonthToBeAdded += 1;
      }

      let rentObj = {
        rentDuration: rentDuration,
        rentStartDate: globalRentStartDate,
        rentEndDate: rentEndDate,
        rentalMonthAdd: moment(rentStartDate).isAfter(moment(), "date")
          ? rentalMonthToBeAdded
          : 0,
      };

      advanceRentSlots.push(rentObj);

      rentStartDate = moment(rentEndDate).add(1, "days").format("YYYY-MM-DD");
      rentEndDate = moment(rentStartDate).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
      rentDuration += 1;
    }

    const tenantGuardian = await tenantGuardianDB.getByTenantId({ tenantId: occupancy?.tenantId });

    const settings = await settingsDB.getByClientIdAndPropId({
      clientId: clientId,
      propId: occupancy.propId
    });

    let footerText = settings.footer || "The Kipinn Team";

    const lastRentPaid = await transactionDB.getLastRentPaidByClientIdAndTenantId({
      tenantId: occupancy?.tenantId,
      clientId: occupancy?.clientId,
    });

    const bankAccounts = await bankDB.getByClientIdLimitedFields({ clientId });

    tenant.status = mapOccupancyStatusToTenantStatus(occupancy.status);
    tenant.kycStatus = occupancy.kycStatus;
    tenant.gId = occupancy?.gId;

    let paymentLink = `${process.env.PAYU_WEB_CHECKOUT_URL}/${occupancy.gId}`;
    if (client?.paymentLinkBaseUrl && client?.paymentLinkBaseUrl.trim() !== "") {
      paymentLink = `${client?.paymentLinkBaseUrl.trim()}/${occupancy.gId}`;
    }

    /* Due Reminder Message*/
    let dueReminderMessage = CONSTANTS.MSG.SHARE_DUE_PAYMENT_WITHOUT_LINK
          .replace("{#var1#}", tenant?.name || "")
          .replace("{#var2#}", totalDues.toString())
          .replace("{#var3#}", footerText || "");
    if (property.isOnlinePaymentEnabled === 1 &&  occupancy.isOnlinePaymentEnabled === 1
    ) {
      dueReminderMessage = CONSTANTS.MSG.SHARE_DUE_PAYMENT_WITH_LINK
          .replace("{#var1#}", tenant?.name || "")
          .replace("{#var2#}", totalDues.toString())
          .replace("{#var3#}", paymentLink)
          .replace("{#var4#}", footerText || "");
    }
    
    /* KYC Reminder Message */

    let kycReminderMessage = CONSTANTS.MSG.KYC_REMINDER
          .replace("{#var1#}", tenant?.name || "")
          .replace("{#var2#}", footerText || "");




    let bookingLink: string | null = `${process.env.PAYU_WEB_CHECKOUT_URL}/${occupancy.gId}/b`;
    if (client?.paymentLinkBaseUrl && client?.paymentLinkBaseUrl.trim() !== "") {
      bookingLink = `${client?.paymentLinkBaseUrl.trim()}/${occupancy.gId}/b`;
    }

    let bookingMessage: string | null = CONSTANTS.MSG.SHARE_BOOKING_PAYMENT_WITH_LINK
      .replace("{#var1#}", tenant?.name || "")
      .replace("{#var2#}", occupancy.bookingAmt.toString())
      .replace("{#var3#}", bookingLink)
      .replace("{#var4#}", footerText || "");

    const bookingAmtPaid = await ledgerDB.isBookingPaid({
      clientId: occupancy.clientId,
      tenantId: occupancy.tenantId,
      moveInDate: moment(occupancy.moveInDate).format("YYYY-MM-DD"),
    });

    if (bookingAmtPaid) {
      bookingMessage = null;
    }

    if (!Number(occupancy.bookingAmt) || Number(occupancy.bookingAmt) <= 0) {
      bookingMessage = null;
    }

    if (
      property.isOnlinePaymentEnabled !== 1 ||
      occupancy.isOnlinePaymentEnabled !== 1
    ) {
      bookingMessage = null;
    }

    const canToggleDue = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.CAN_HIDE_DUES,
    });

    const hiddenDues = await hiddenDuesDB.getByClientIdAndTenantId({ clientId, tenantId: occupancy?.tenantId });
    const totalHiddenDues = await hiddenDuesDB.getTotalByClientIdAndTenantId({ clientId, tenantId: occupancy?.tenantId });

    const securityRemaining = await ledgerDB.getUnUsedSecurityByTenantIdAndClientId({
      clientId,
      tenantId: occupancy.tenantId,
      createdAt: occupancy.createdAt,
    });

    const lastNote = await tenantNotesDB.getByTenantAndClientId({
      clientId,
      tenantId: occupancy.tenantId,
      pageNum: 1,
      limit: 1,
    });

    let tenants = await occupancyDB.getAllByBedId({
      bedId: bedId,
    });

    if (tenants) {
      tenants.sort((a: any, b: any) =>
        moment(a.moveInDate).diff(moment(b.moveInDate))
      );
    }

    let maxMoveInDate = null;
    let minMoveInDate = null;
    let maxMoveOutDate = null;
    let minMoveOutDate = null;

    if (occupancy.moveOutDate) {
      maxMoveInDate = occupancy.moveOutDate;
    }

    if (moment(occupancy.moveInDate).isBefore(moment(), "date")) {
      minMoveOutDate = moment().format("YYYY-MM-DD");
    }

    if (tenants) {
      const currentOccupancyIndex = tenants.findIndex(
        (t: any) => t.id === occupancy.id
      );

      const previousTenant = tenants[currentOccupancyIndex - 1];
      if (previousTenant && previousTenant.moveOutDate) {
        minMoveInDate = moment(previousTenant.moveOutDate)
          .add(1, "day")
          .format("YYYY-MM-DD");
      }

      const nextTenant = tenants[currentOccupancyIndex + 1];
      if (nextTenant && nextTenant.moveInDate) {
        maxMoveOutDate = moment(nextTenant.moveInDate)
          .subtract(1, "day")
          .format("YYYY-MM-DD");
      }
    }

    const advancePaidRents = await ledgerDB.getAdvancePaidRent({
      tenantId,
      clientId,
    });

    let advanceRentPaidAmount = 0;
    let advanceRentPaidMonths = 0;
    const uniqueRentMonths = new Set<string>();

    if (advancePaidRents && advancePaidRents.length > 0) {
      for (let rent of advancePaidRents) {
        advanceRentPaidAmount += Math.abs(rent.amount);
        uniqueRentMonths.add(rent.referenceId);
      }
    }

    advanceRentPaidMonths = uniqueRentMonths.size;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Bed Id [${bedId}] Tenant details sent successfully`
    );

    return res.status(200).json({
      msg: "Tenant details sent successfully",
      data: {
        ...tenant,
        isMultipleOccupancy,
        canAssignFullRoom,
        occupiedBedCount,
        securityRemaining: securityRemaining || 0,
        lastNote: lastNote ? lastNote[0] : null,
        flat,
        totalDues: Number(totalDue) || 0,
        totalCollection: Number(totalCollectedAmt) || 0,
        occupancy,
        docs: docs || [],
        dues: dues || [],
        transactions: transactions || [],
        securities: securities || [],
        securityPaid: secAmount,
        moveInInspection,
        rentAgreementRecords: rentAgreementRecords || [],
        lastSavedElecReading: Number(lastSavedElecReading) || 0,
        rentStartDate,
        advanceRentSlots: advanceRentSlots || [],
        tenantGuardian: tenantGuardian || {},
        tenantPreference: property?.tenantPreference || 1,
        footerText: footerText,
        canDoTenantEkyc: client.canDoTenantEkyc || 0,
        tenantAttendanceEnabled: client.tenantAttendanceEnabled || 0,
        lastRentPaid: lastRentPaid?.collectionDate ? lastRentPaid?.collectionDate : null,
        bankAccounts,
        paymentLink,
        dueReminderMessage,
        bookingMessage,
        kycReminderMessage,
        canHideDues: canToggleDue ? Number(canToggleDue.value) : 0,
        hiddenDues: hiddenDues || [],
        totalHiddenDues: totalHiddenDues || 0,
        maxMoveInDate,
        minMoveInDate,
        maxMoveOutDate,
        minMoveOutDate,
        advanceRentPaidAmount: advanceRentPaidAmount || 0,
        advanceRentPaidMonths: advanceRentPaidMonths || 0,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetDetailsByClient = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetDetailsByClient";

  try {
    let { tenantId } = req.params;
    const { e } = req.query;

    let isEvicted = e === "1";

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], isEvicted [${isEvicted}]`);

    const userType = req.userType;
    let clientId = req.id;
    let isMultipleOccupancy = false;
    let occupiedBedCount = 1;

    let payoutData = await getWalletBalanceAndLimits({
      clientId: Number(req.id),
      userType: Number(userType),
      staffId: Number(req.id),
    });

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      payoutData = await getWalletBalanceAndLimits({
        clientId: staff.clientId,
        userType: Number(userType),
        staffId: Number(req.id),
      });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Staff Id [${req.id}], isEvicted [${isEvicted}], Staff Requested....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], isEvicted [${isEvicted}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancy = null;
    let canBookAgain = false;

    if (isEvicted) {
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        tenantId,
        clientId,
      });

      canBookAgain = await occupancyDB.getByTenantIdAndClientId({
        tenantId,
        clientId,
      });

      if (canBookAgain) {
        canBookAgain = false;
      } else {
        canBookAgain = true;
      }
    } else {
      occupancy = await occupancyDB.getByTenantIdAndClientId({
        tenantId,
        clientId,
      });
      if (!occupancy) {
        occupancy = await moveOutDB.getByTenantIdAndClientId({
          tenantId,
          clientId,
        });
        isEvicted = true;
        if (!occupancy) {
          log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Occupancy Found In Either Occupancies or MoveOut`);

          return res.status(400).json({
            msg: CONSTANTS.MSG.INVALID_REQUEST,
            isSuccess: false,
          })
        }
      }

      const occupancyByTenantAndClientId = await occupancyDB.getByClientIdAndTenantId({
        clientId: clientId,
        tenantId: occupancy.tenantId
      });

      if (occupancyByTenantAndClientId && occupancyByTenantAndClientId.length > 1) {
        isMultipleOccupancy = occupancyByTenantAndClientId.every(
          (occupancy: occupanciesTypes) =>
            occupancy.roomId === occupancyByTenantAndClientId[0].roomId
        );
        // isMultipleOccupancy = true;
        occupiedBedCount = occupancyByTenantAndClientId.length || 1;
      }
    }

    if (!occupancy) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Occupancy Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let canAssignFullRoom = false;
    if (!isEvicted) {
      const occupancyByRoom = await occupancyDB.getByRoomId({
        roomId: occupancy.roomId
      });

      const roomBedCount = await bedDB.getCountsByRoomId({
        id: occupancy.roomId,
      });

      if (occupancyByRoom && occupancyByRoom.length === 1 && roomBedCount.totalBeds > 1) canAssignFullRoom = true;
    }

    const property = await propertyDB.getById({ id: occupancy.propId });
    if (!property) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], No Property Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let lastSavedElecReading = 0;
    let docs = [];
    let totalDuesAmt = 0;
    let totalCollectedAmt = 0;
    let dues = [];
    let moveInInspection = null;

    if (!isEvicted) {
      lastSavedElecReading = await electricityDB.getForPreviousMonth({
        clientId: occupancy.clientId,
        propId: occupancy?.propId,
        roomId: occupancy?.roomId,
      });

      docs = await documentDB.getByTenantIdAndClientId({ tenantId, clientId, moveOut: 0 });
      if (docs && docs.length > 0) {
        for (let doc of docs) {
          const title = await getDocumentTitle(doc.type);
          doc.title = title;
        }
        docs = docs.filter((doc: any) => doc.type !== CONSTANTS.DOCUMENT_TYPES.SIGNATURE && doc.type !== CONSTANTS.DOCUMENT_TYPES.PARENT_SIGNATURE);
      }

      if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
        const { totalDues } = await duesDB.getTotalDuesByTenantIdForEviction({
          tenantId,
          propId: occupancy.propId,
        });
        totalDuesAmt = Number(totalDues) || 0;
      } else {
        const { totalDues } = await duesDB.getTotalDuesByTenantId({
          tenantId,
          propId: occupancy.propId,
        });
        totalDuesAmt = Number(totalDues) || 0;
      }
      // Pallav - Multi Tenant Scenario
      //dues = await duesDB.getByTenantId({ tenantId });
      dues = await duesDB.getByTenantIdAndClientId({ tenantId, clientId });

      totalCollectedAmt =
        await transactionDB.getTotalCollectionByTenantIdAndClientId({
          tenantId: tenantId,
          clientId: occupancy?.clientId,
        });

      moveInInspection = await moveInDB.getByTenantIdandClientId({
        tenantId,
        clientId: occupancy?.clientId,
      });

      if (moveInInspection) {
        let moveInImage = await imageDB.getByMoveInId({
          moveInInspectionId: moveInInspection.id,
        });

        moveInInspection.images = moveInImage;
      } else {
        moveInInspection = [];
      }
    } else {
      // dues = await moveOutDuesDB.getByTenantId({
      //   tenantId,
      // });
      dues = await moveOutDuesDB.getByTenantIdAndClientId({
        tenantId,
        clientId
      });
      //totalDuesAmt = occupancy.tenantDues || 0;

      const { totalDues } = await moveOutDuesDB.getTotalDuesByTenantId({
        tenantId,
        propId: occupancy.propId,
      });
      totalDuesAmt = Number(totalDues) || 0;
      totalCollectedAmt =
        await transactionDB.getTotalCollectionByTenantIdAndClientId({
          tenantId: tenantId,
          clientId: occupancy?.clientId,
        });

      docs = await documentDB.getByTenantIdAndClientIdMovedOut({ tenantId, clientId, moveOut: 1 });
      if (docs && docs.length > 0) {
        for (let doc of docs) {
          const title = await getDocumentTitle(doc.type);
          doc.title = title;
        }
        //docs = docs.filter((doc: any) => doc.type !== CONSTANTS.DOCUMENT_TYPES.SIGNATURE);
        docs = docs.filter((doc: any) => doc.type !== CONSTANTS.DOCUMENT_TYPES.SIGNATURE && doc.type !== CONSTANTS.DOCUMENT_TYPES.PARENT_SIGNATURE);
      }
    }

    const hiddenDues = await hiddenDuesDB.getByClientIdAndTenantId({ clientId, tenantId: occupancy?.tenantId });

    const rentAgreementRecords =
      await rentAgreementRecordDB.getByTenantIdandClientId({
        tenantId,
        clientId: occupancy?.clientId,
      });

    let flatName = "";
    if (occupancy.propType === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      occupancy.flatName = name;
      flatName = name + ", " + occupancy.roomNum;
    } else {
      occupancy.flatName = occupancy.floor;
      flatName = occupancy.floor;
    }

    const lastRentDue = await ledgerDB.getLastRent({
      tenantId,
      clientId,
    });

    let rentStartDate = moment().date(occupancy.rentalCycle).format("YYYY-MM-DD");
    let rentEndDate = moment(rentStartDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");

    let firstRentStartDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");
    let firstRentEndDate = moment(occupancy.moveInDate).date(occupancy.rentalCycle).isAfter(moment(occupancy.moveInDate), "date")
      ? moment(firstRentStartDate).date(occupancy.rentalCycle).subtract(1, "day").format("YYYY-MM-DD")
      : moment(firstRentStartDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");

    if (moment().isBetween(firstRentStartDate, firstRentEndDate, "date", "[]")) {
      rentStartDate = firstRentStartDate;
      rentEndDate = firstRentEndDate;
    } else if (moment(occupancy.moveInDate).isAfter(moment(rentStartDate), "date")) {
      rentStartDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");

      rentEndDate = moment().date(occupancy.rentalCycle).isAfter(moment(occupancy.moveInDate), "date")
        ? moment().date(occupancy.rentalCycle).subtract(1, "day").format("YYYY-MM-DD")
        : moment().date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    } else if (moment().isBefore(moment().date(occupancy.rentalCycle), "date")) {

      rentStartDate = moment().date(occupancy.rentalCycle).subtract(1, "month").format("YYYY-MM-DD");
      rentEndDate = moment(rentStartDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    }

    if (lastRentDue && lastRentDue.rentEndDate) {
      rentStartDate = moment(lastRentDue?.rentEndDate)
        .add(1, "days")
        .format("YYYY-MM-DD");

      rentEndDate = moment(rentStartDate).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    } else if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
      rentStartDate = moment(occupancy?.moveInDate).format("YYYY-MM-DD");

      rentEndDate = moment(occupancy?.moveInDate).date(occupancy.rentalCycle).isAfter(moment(occupancy.moveInDate), "date")
        ? moment(occupancy?.moveInDate).date(occupancy.rentalCycle).subtract(1, "day").format("YYYY-MM-DD")
        : moment(occupancy?.moveInDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    }

    let advanceRentSlots = [];
    let rentDuration = 1;
    let rentalMonthToBeAdded = 0;
    const globalRentStartDate = rentStartDate;

    while (rentDuration < 13) {

      if (moment(rentStartDate).isAfter(moment(), "date")) {
        rentalMonthToBeAdded += 1;
      }

      let rentObj = {
        rentDuration: rentDuration,
        rentStartDate: globalRentStartDate,
        rentEndDate: rentEndDate,
        rentalMonthAdd: moment(rentStartDate).isAfter(moment(), "date")
          ? rentalMonthToBeAdded
          : 0,
      };

      advanceRentSlots.push(rentObj);

      rentStartDate = moment(rentEndDate).add(1, "days").format("YYYY-MM-DD");
      rentEndDate = moment(rentStartDate).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
      rentDuration += 1;
    }

    const tenantGuardian = await tenantGuardianDB.getByTenantId({ tenantId });

    const settings = await settingsDB.getByClientIdAndPropId({
      clientId: clientId,
      propId: occupancy.propId
    });

    let footerText = settings.footer || "The Kipinn Team";

    tenant.kycStatus = occupancy.kycStatus;
    tenant.status = mapOccupancyStatusToTenantStatus(occupancy.status);
    tenant.gId = occupancy?.gId;


    const lastRentPaid = await transactionDB.getLastRentPaidByClientIdAndTenantId({
      tenantId: occupancy?.tenantId,
      clientId: occupancy?.clientId,
    });

    let paymentLink = `${process.env.PAYU_WEB_CHECKOUT_URL}/${occupancy.gId}`;
    if (client?.paymentLinkBaseUrl && client?.paymentLinkBaseUrl.trim() !== "") {
      paymentLink = `${client?.paymentLinkBaseUrl.trim()}/${occupancy.gId}`;
    }

    /* Due Reminder Message*/
    let dueReminderMessage = CONSTANTS.MSG.SHARE_DUE_PAYMENT_WITHOUT_LINK
          .replace("{#var1#}", tenant?.name || "")
          .replace("{#var2#}", totalDuesAmt.toString())
          .replace("{#var3#}", footerText || "");
    if (property.isOnlinePaymentEnabled === 1 &&  occupancy.isOnlinePaymentEnabled === 1
    ) {
      dueReminderMessage = CONSTANTS.MSG.SHARE_DUE_PAYMENT_WITH_LINK
          .replace("{#var1#}", tenant?.name || "")
          .replace("{#var2#}", totalDuesAmt.toString())
          .replace("{#var3#}", paymentLink)
          .replace("{#var4#}", footerText || "");
    }
    
    /* KYC Reminder Message */

    let kycReminderMessage = CONSTANTS.MSG.KYC_REMINDER
          .replace("{#var1#}", tenant?.name || "")
          .replace("{#var2#}", footerText || "");
    if(settings?.android || settings?.ios ) {
      let appLink = null;
      if(settings?.android) {
        appLink = `\n${settings?.android}`;
      }
      if(settings?.ios) {
        if(settings?.android) {
          appLink += `\n\n${settings?.ios}`;
        } else {
          appLink = `\n${settings?.ios}`;
        }
      }
      if(appLink) {
        kycReminderMessage = CONSTANTS.MSG.KYC_REMINDER_WITH_APP_LINK
            .replace("{#var1#}", tenant?.name || "")
            .replace("{#var2#}", appLink || "")
            .replace("{#var3#}", footerText || "");
      }
    }

    let bookingLink: string | null = `${process.env.PAYU_WEB_CHECKOUT_URL}/${occupancy.gId}/b`;
    if (client?.paymentLinkBaseUrl && client?.paymentLinkBaseUrl.trim() !== "") {
      bookingLink = `${client?.paymentLinkBaseUrl.trim()}/${occupancy.gId}/b`;
    }

    let bookingMessage: string | null = CONSTANTS.MSG.SHARE_BOOKING_PAYMENT_WITH_LINK
      .replace("{#var1#}", tenant?.name || "")
      .replace("{#var2#}", occupancy.bookingAmt.toString())
      .replace("{#var3#}", bookingLink)
      .replace("{#var4#}", footerText || "");

    const bookingAmtPaid = await ledgerDB.isBookingPaid({
      clientId: occupancy.clientId,
      tenantId: occupancy.tenantId,
      moveInDate: moment(occupancy.moveInDate).format("YYYY-MM-DD"),
    });

    if (bookingAmtPaid) {
      bookingMessage = null;
    }

    if (!Number(occupancy.bookingAmt) || Number(occupancy.bookingAmt) <= 0) {
      bookingMessage = null;
    }

    if (isEvicted) {
      bookingMessage = null;
    }

    if (
      property.isOnlinePaymentEnabled !== 1 ||
      occupancy.isOnlinePaymentEnabled !== 1
    ) {
      bookingMessage = null;
    }

    let securityRemaining = await ledgerDB.getUnUsedSecurityByTenantIdAndClientId({
      clientId,
      tenantId: occupancy.tenantId,
      createdAt: occupancy.createdAt,
    });

    if (isEvicted) securityRemaining = 0;

    const lastNote = await tenantNotesDB.getByTenantAndClientId({
      clientId,
      tenantId: occupancy.tenantId,
      pageNum: 1,
      limit: 1,
    });

    let tenants = await occupancyDB.getAllByBedId({
      bedId: occupancy.bedId,
    });

    if (tenants) {
      tenants.sort((a: any, b: any) =>
        moment(a.moveInDate).diff(moment(b.moveInDate))
      );
    }

    let maxMoveInDate = null;
    let minMoveInDate = null;
    let maxMoveOutDate = null;
    let minMoveOutDate = null;

    if (occupancy.moveOutDate) {
      maxMoveInDate = occupancy.moveOutDate;
    }

    if (moment(occupancy.moveInDate).isBefore(moment(), "date")) {
      minMoveOutDate = moment().format("YYYY-MM-DD");
    }

    if (tenants) {
      const currentOccupancyIndex = tenants.findIndex(
        (t: any) => t.id === occupancy.id
      );

      const previousTenant = tenants[currentOccupancyIndex - 1];
      if (previousTenant && previousTenant.moveOutDate) {
        minMoveInDate = moment(previousTenant.moveOutDate)
          .add(1, "day")
          .format("YYYY-MM-DD");
      }

      const nextTenant = tenants[currentOccupancyIndex + 1];
      if (nextTenant && nextTenant.moveInDate) {
        maxMoveOutDate = moment(nextTenant.moveInDate)
          .subtract(1, "day")
          .format("YYYY-MM-DD");
      }
    }

    const advancePaidRents = await ledgerDB.getAdvancePaidRent({
      tenantId,
      clientId,
    });

    let advanceRentPaidAmount = 0;
    let advanceRentPaidMonths = 0;
    const uniqueRentMonths = new Set<string>();
    let combinedRentMonths = 0;

    if (advancePaidRents && advancePaidRents.length > 0 && Number(occupancy.rentalType) > 0) {
      for (let rent of advancePaidRents) {
        advanceRentPaidAmount += Math.abs(rent.amount);
        uniqueRentMonths.add(rent.referenceId);

        let startDate = moment(rent.rentStartDate).date(occupancy.rentalCycle);

        const diff = moment(rent.rentEndDate).diff(rent.rentStartDate, "days");

        if (Number(diff) > 31) {
          let loopCounter = 0;
          while (moment(startDate).isSameOrBefore(moment(rent.rentEndDate), "days")) {
            combinedRentMonths += 1;
            startDate = moment(startDate).add(occupancy.rentalType, "months");
            loopCounter++;
            if (loopCounter >= 50) {
              log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Error, Breaking Out Of While Loop`)
              break;
            }
          }
        }

        if (combinedRentMonths > 0) combinedRentMonths -= 1;
      }
    }

    advanceRentPaidMonths = uniqueRentMonths.size + combinedRentMonths;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant details sent successfully`
    );

    return res.status(200).json({
      msg: "Tenant details sent successfully",
      data: {
        ...tenant,
        isMultipleOccupancy,
        payoutData,
        canAssignFullRoom,
        occupiedBedCount,
        securityRemaining: securityRemaining || 0,
        lastNote: lastNote ? lastNote[0] : null,
        totalDues: totalDuesAmt,
        totalCollection: Number(totalCollectedAmt) || 0,
        occupancy,
        flatName, //Sukhbir Remove this
        docs: docs || [],
        dues: dues || [],
        hiddenDues: hiddenDues || [],
        moveInInspection: moveInInspection || null,
        rentAgreementRecords: rentAgreementRecords || [],
        lastSavedElecReading: Number(lastSavedElecReading) || 0,
        rentStartDate,
        advanceRentSlots: advanceRentSlots || [],
        tenantGuardian: tenantGuardian || null,
        tenantPreference: property?.tenantPreference || 1,
        footerText: footerText,
        canDoTenantEkyc: client.canDoTenantEkyc || 0,
        tenantAttendanceEnabled: client.tenantAttendanceEnabled || 0,
        lastRentPaid: lastRentPaid?.collectionDate ? lastRentPaid?.collectionDate : null,
        canBookAgain,
        paymentLink,
        dueReminderMessage,
        bookingMessage,
        kycReminderMessage,
        maxMoveInDate,
        minMoveInDate,
        maxMoveOutDate,
        minMoveOutDate,
        advanceRentPaidAmount: advanceRentPaidAmount || 0,
        advanceRentPaidMonths: advanceRentPaidMonths || 0,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.CancelReservation = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "CancelReservation";

  try {
    const { tenantId } = req.body;

    const userType = req.userType;
    let clientId = req.id;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancies = await occupancyDB.getAllByTenantIdAndClientId({
      clientId,
      tenantId,
    });

    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = occupancies[0];

    if (occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Occupancy is not reserved`
      );
      return res
        .status(400)
        .json({ msg: "Tenant is not reserved for any bed", isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    // if (tenant.status !== CONSTANTS.TENANT_STATUS.RESERVED) {
    //   log.info(
    //     `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Tenant is not reserved for any bed`
    //   );
    //   return res
    //     .status(400)
    //     .json({ msg: "Tenant is not reserved for any bed", isSuccess: false });
    // }

    for (const occupancy of occupancies) {
      const bed = await bedDB.getById({ id: occupancy.bedId });

      if (
        bed.status !== CONSTANTS.BED_STATUS.RESERVED &&
        bed.status !== CONSTANTS.BED_STATUS.VACANT_RESERVED
      ) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], bed Id [${bed.id}], Bed is not reserved for any tenant`
        );
        return res.status(400).json({
          msg: "Bed is not reserved for any tenant",
          isSuccess: false,
        });
      }

      await documentDB.removeByTenantIdandClientId({
        tenantId: occupancy?.tenantId,
        clientId: occupancy?.clientId,
      });


      let dues = await duesDB.getByOccupancyId({ occupancyId: occupancy?.id });
      if (dues && dues.length > 0) {
        for (const due of dues) {
          if (Number(due.amount) === Number(due.balance)) {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ledger Reference Id [${due.ledgerReferenceId}], Delete Ledger Entry as Due is not paid`
            );
            await ledgerDB.remove({ referenceId: due.ledgerReferenceId });
          } else {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ledger Reference Id [${due.ledgerReferenceId}], Update Ledger Entry as Due is partially paid`
            );
            await ledgerDB.UpdateAmountAndBalanceByReferenceId({
              referenceId: due.ledgerReferenceId,
              amount: due.amount - due.balance,
              balance: due.amount - due.balance,
            });
            await ledgerDB.UpdateBalanceByReferenceIdForPaidEntry({
              referenceId: due.ledgerReferenceId,
              balance: due.amount - (due.amount - due.balance),
            });
          }
        }
      }

      await duesDB.removeByOccupancyId({
        occupancyId: occupancy?.id,
      });

      await requestDB.removeByOccupancyId({
        occupancyId: occupancy?.id,
      });

      await occupancyDB.removeById({
        id: occupancy?.id,
      });

      const bedTenants = await occupancyDB.getAllByBedId({
        bedId: bed.id,
      });

      if (bedTenants && bedTenants.length > 1) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.RESERVED,
        });
      } else if (bedTenants && bedTenants[0].status === CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.VACANT_RESERVED,
        });
      } else if (bedTenants && bedTenants[0].status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.MOVING_OUT,
        });
      } else {
        await bedDB.updateStatus({
          id: bed.id,
          status: CONSTANTS.BED_STATUS.VACANT,
        });
      }

      // if (bed.status === CONSTANTS.BED_STATUS.VACANT_RESERVED) {
      //   await bedDB.updateStatus({
      //     id: bed.id,
      //     status: CONSTANTS.BED_STATUS.VACANT,
      //   });
      // } else {
      //   await bedDB.updateStatus({
      //     id: bed.id,
      //     status: CONSTANTS.BED_STATUS.MOVING_OUT,
      //   });
      // }
    }

    for (const occupancy of occupancies) {
      const room = await roomDB.getById({ id: occupancy.roomId });

      const vacantBeds = await bedDB.getVacantBeds({
        roomId: occupancy.roomId,
        status: CONSTANTS.BED_STATUS.VACANT,
      });

      const bedCount = await bedDB.getCountsByRoomId({
        id: occupancy.roomId,
      });
      const totalBeds = bedCount.totalBeds;

      if (vacantBeds) {
        if (totalBeds === vacantBeds.length) {
          await roomDB.updateStatus({
            id: room.id,
            status: CONSTANTS.ROOM_STATUS.VACANT,
          });
        } else {
          await roomDB.updateStatus({
            id: room.id,
            status: CONSTANTS.ROOM_STATUS.SEMI_OCCUPIED,
          });
        }
      }

      let moveOutId = await moveOutDB.add({
        clientId,
        tenantId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        bedId: occupancy.bedId,
        floor: occupancy.floor,
        reason: occupancy.reason,
        clientDues: 0,
        tenantDues: 0,
        rent: occupancy.rent,
        rentalCycle: occupancy.rentalCycle,
        rentalType: occupancy.rentalType,
        rentalMonths: occupancy.rentalMonths,
        noticePeriod: occupancy.noticePeriod,
        lockInPeriod: occupancy.lockInPeriod,
        security: occupancy.security,
        agreementPeriod: occupancy.agreementPeriod,
        agreementStartDate: occupancy.agreementStartDate,
        moveInDate: occupancy.moveInDate,
        moveOutDate: moment().format("YYYY-MM-DD"),
        flatId: occupancy.flatId,
        notes: occupancy.notes,
        rentalBond: occupancy.rentalBond,
        moveOutReason: occupancy.moveOutReason,
        isPoliceVerified: occupancy.isPoliceVerified,
        isRentAgreementSigned: occupancy.isRentAgreementSigned,
        stayType: occupancy.stayType,
        kycStatus: occupancy.kycStatus,
        discountType: occupancy.discountType,
        discount: occupancy.discount,
        discountPeriod: occupancy.discountPeriod,
        discountStartDate: occupancy.discountStartDate,
        discountEndDate: occupancy.discountEndDate,
        isDiscountFromFirstMonth: occupancy.isDiscountFromFirstMonth,
        wasCancelled: 1,
        status: CONSTANTS.MOVE_OUT_STATUS.MOVEOUT,
        gId: occupancy?.gId || null,
        tallyStatus: occupancy.tallyStatus,
      });

      await moveOutDB.updateBookedBy({ bookedBy: occupancy.bookedBy, id: moveOutId });

      await moveOutDB.updateRefundStatus({ id: moveOutId, refundStatus: CONSTANTS.REFUND_STATUS.FORFEITED });
    }

    await bookingsDB.updateStatusByTenantIdAndClientIdAndDetails({
      tenantId,
      clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      moveInDate: occupancy.moveInDate,
      status: CONSTANTS.BOOKING_STATUS.CANCELLED,
    });

    await tenantDB.updateStatus({
      id: tenantId,
      status: CONSTANTS.TENANT_STATUS.VACANT,
    });

    await logActivity(
      Number(req.userType),
      Number(req.id),
      Number(req.parentClientId!),
      Number(req.platform),
      Number(tenantId),
      CONSTANTS.ACTIVITY_TYPES.CANCEL_RESERVATION,
      null,
      null,
      null,
      null,
      null
    );

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Reservation cancelled successfully`
    );

    return res.status(200).json({
      msg: "Reservation cancelled successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListForClient = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ListForClient";

  try {
    // let clientId = req.id;
    const userType = req.userType;
    let { pageNum, filter, searchVal, propId, flatId, platform } = req.query;

    const limit = 10;
    let tenantCount = 0;
    //let occupancies: occupanciesTypes[] = [];
    let occupancies: any[] = [];
    let summary: any[] = [];

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, Page Num [${pageNum}], Filter [${filter}], Search Val [${searchVal}], Property Id [${propId}], Flat Id [${flatId}], Platform [${platform}], Req Platform [${req.platform}], ${isPartner ? "Partner Requesting" : "Client Requesting"
        }....`
      );

      const client = await clientDB.getById({ id: clientId });

      if (!client) {
        log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      if (Number(flatId)) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Flat Id [${flatId}]`
        );
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsOfSelectedFlat(
            Number(clientId),
            Number(flatId),
            Number(pageNum),
            String(filter),
            String(searchVal),
            String(platform),
            limit
          );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;
      } else if (Number(propId)) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}]`
        );
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsOfSelectedProperty(
            Number(clientId),
            Number(propId),
            Number(pageNum),
            String(filter),
            String(searchVal),
            String(platform),
            limit
          );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;
        summary = await occupancyDB.getOccupancyStatsByPropId(clientId, propId);
      } else {
        const { tenantTotalCount, tenantOccupancies } = await tenantsForClient(
          Number(clientId),
          Number(propId),
          Number(pageNum),
          String(filter),
          "", //FilterVal placeholder value
          String(searchVal),
          String(platform),
          limit,
          "", //StartDate placeholder value
          "", //endDate placeholder value
        );
        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;
        summary = await occupancyDB.getOccupancyStatsForClient(clientId);
      }

      if (occupancies) {
        for (let occupancy of occupancies) {
          if (occupancy.propertyType === CONSTANTS.PROPERTY_TYPE.FLAT) {
            const { name } = await flatDB.getById({ id: occupancy.flatId });
            occupancy.flatName = name;
          } else {
            occupancy.flatName = occupancy.floor;
          }

          occupancy.iseKycDone = Number(occupancy.kycStatus) >= CONSTANTS.KYC_STATUS.SELFI_UPLOADED ? 1 : 0;

          let collection = 0;
          let advance = 0;
          let total = 0;
          if (filter === "E" || occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
            const { totalCollection, advancePaid } =
              await ledgerDB.getTotalByTenantIdForMovingOut({
                tenantId: occupancy.tenantId,
                clientId,
              });
            collection = totalCollection;
            advance = advancePaid;

            collection = collection || 0;
            advance = advance || 0;

            if (advance < 0 && collection >= 0) {
              total = Number(collection) + Number(advance);
            } else if (advance < 0 && collection < 0) {
              total = Number(advance);
            } else {
              total = Number(collection);
            }
          }
          occupancy.excessAmount = total < 0 ? Math.abs(total) : 0;
          occupancy.refundAmount = total < 0 ? Math.abs(total) : 0;
          const paidBookingAmt = await ledgerDB.isBookingPaid({
            clientId,
            tenantId: occupancy.tenantId,
            moveInDate: occupancy.moveInDate,
          });

          occupancy.bookingAmt = paidBookingAmt ? Math.abs(paidBookingAmt[0].amount) : 0;
        }
      }
    } else {
      //Staff
      const staffId = req.id;
      const staff = await staffDB.getById({ id: staffId });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${staffId}], No Staff Found`);
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Staff Id [${staffId}], Page Num [${pageNum}], Filter [${filter}], Platform [${platform}], Search Val [${searchVal}], Property Id [${propId}], Staff Requesting....`
      );

      if (Number(flatId)) {
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsOfSelectedFlat(
            Number(clientId),
            Number(flatId),
            Number(pageNum),
            String(filter),
            String(platform),
            String(searchVal),
            limit
          );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;
      } else if (Number(propId)) {
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsOfSelectedProperty(
            Number(clientId),
            Number(propId),
            Number(pageNum),
            String(filter),
            String(platform),
            String(searchVal),
            limit
          );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;
      } else {
        const { tenantTotalCount, tenantOccupancies } = await tenantsForStaff(
          Number(clientId),
          Number(staffId),
          Number(propId),
          Number(pageNum),
          String(filter),
          "", //FilterVal placeholder
          String(searchVal),
          limit,
          "", //Start and end date placeholders
          "",
        );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;
        summary = await occupancyDB.getOccupancyStatsForClient(clientId);
      }

      if (occupancies) {
        for (let occupancy of occupancies) {
          if (occupancy.propertyType === CONSTANTS.PROPERTY_TYPE.FLAT) {
            const { name } = await flatDB.getById({ id: occupancy.flatId });
            occupancy.flatName = name;
          } else {
            occupancy.flatName = occupancy.floor;
          }
          occupancy.iseKycDone = Number(occupancy.kycStatus) >= CONSTANTS.KYC_STATUS.SELFI_UPLOADED ? 1 : 0;

          let collection = 0;
          let advance = 0;
          let total = 0;
          if (filter === "E" || occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
            const { totalCollection, advancePaid } =
              await ledgerDB.getTotalByTenantIdForMovingOut({
                tenantId: occupancy.tenantId,
                clientId,
              });
            collection = totalCollection;
            advance = advancePaid;

            collection = collection || 0;
            advance = advance || 0;

            if (advance < 0 && collection >= 0) {
              total = Number(collection) + Number(advance);
            } else if (advance < 0 && collection < 0) {
              total = Number(advance);
            } else {
              total = Number(collection);
            }
          }
          occupancy.excessAmount = total < 0 ? Math.abs(total) : 0;
          occupancy.refundAmount = total < 0 ? Math.abs(total) : 0;

          const paidBookingAmt = await ledgerDB.isBookingPaid({
            clientId,
            tenantId: occupancy.tenantId,
            moveInDate: occupancy.moveInDate,
          });

          occupancy.bookingAmt = paidBookingAmt ? Math.abs(paidBookingAmt[0].amount) : 0;
        }
      }
    }

    const isManualMoveInEnabled = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.IS_MANUAL_MOVEIN,
    });

    const propList = await propertyDB.getAllActiveByClientId({
      clientId: clientId,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Filter [${filter}], Search Val [${searchVal}], Tenant list sent successfully`
    );
    // log.info(
    //   `[${C}], [${F}], Client Id [${clientId}], occupancies [${JSON.stringify(occupancies)}]`
    // );
    return res.status(200).json({
      msg: "Tenant list sent successfully",
      data: {
        total: tenantCount || 0,
        list: occupancies || [],
        summary,
        isManualMoveInEnabled: isManualMoveInEnabled ? isManualMoveInEnabled?.value : 0,
        propList: propList || [],
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListForClientX = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ListForClientX";

  try {
    // let clientId = req.id;
    const userType = req.userType;
    let { pageNum, filter, filterVal, searchVal, platform, isEvicted } = req.query;
    let propId: any = req.query.propId;
    let flatId: any = req.query.flatId;

    if (propId === "" || String(propId).toLowerCase() === "null" || String(propId).toLowerCase() === "undefined") {
      propId = null;
    }

    if (flatId === "" || String(flatId).toLowerCase() === "null" || String(flatId).toLowerCase() === "undefined") {
      flatId = null;
    }

    log.info(
      `[${C}], [${F}], Page Num [${pageNum}], Filter [${filter}], Filter Val [${filterVal}], Search Val [${searchVal}], Property Id [${propId}], Flat Id [${flatId}], Platform [${platform}], Req Platform [${req.platform}], Is Evicted [${isEvicted}], Type of Filter [${typeof filter}]`
    );

    if (filter && typeof filter === 'string') {
      filter = filter.split(',').map((v: any) => v.trim()).filter(Boolean);
    }

    //log.info(`Filter [${filter}], Type of Filter [${typeof filter}]`);

    const limit = 10;
    let tenantCount = 0;
    //let occupancies: occupanciesTypes[] = [];
    let occupancies: any[] = [];
    let summary: any[] = [];

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    let propertiesIds: string | null = null;

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, Page Num [${pageNum}], Filter [${filter}], Filter Val [${filterVal}], Search Val [${searchVal}], Property Id [${propId}], Flat Id [${flatId}], Platform [${platform}], Req Platform [${req.platform}], Is Evicted [${isEvicted}], ${isPartner ? "Partner Requesting" : "Client Requesting"
        }....`
      );


    } else {
      const staffId = req.id;
      const staff = await staffDB.getById({ id: staffId });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${staffId}], No Staff Found`);
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Staff Id [${staffId}], Page Num [${pageNum}], Filter [${filter}], Filter Val [${filterVal}], Search Val [${searchVal}], Property Id [${propId}], Flat Id [${flatId}], Platform [${platform}], Req Platform [${req.platform}], Is Evicted [${isEvicted}], Staff Requesting....`
      );

      const staffLinkedProps = await propertyDB.getPropsByStaffId({
        staffId,
      });

      if (staffLinkedProps) {
        propertiesIds = staffLinkedProps
          .map((prop: propertiesTypes) => prop.id)
          .join(",");
      } else {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Staff Id [${staffId}], No Property Linked`
        );

        return res.status(400).json({
          msg: "No property linked",
          isSuccess: false,
        });
      }
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (isEvicted) {
      tenantCount = await moveOutDB.getCountByClientIdAndFilters({
        clientId,
        propIds: propId,
        flatIds: flatId,
        filterArr: filter,
        filterVal: filterVal,
        propertiesIds: propertiesIds,
      });
      occupancies = await moveOutDB.getByClientIdAndFilters({
        clientId,
        propIds: propId,
        flatIds: flatId,
        filterArr: filter,
        filterVal: filterVal,
        propertiesIds: propertiesIds,
        pageNum,
        limit,
      });
    } else {
      tenantCount = await occupancyDB.getCountByClientIdAndFilters({
        clientId,
        propIds: propId,
        flatIds: flatId,
        filterArr: filter,
        filterVal: filterVal,
        propertiesIds: propertiesIds,
        searchVal: String(searchVal) ? String(searchVal).trim() : null,
      });
      occupancies = await occupancyDB.getByClientIdAndFilters({
        clientId,
        propIds: propId,
        flatIds: flatId,
        filterArr: filter,
        filterVal: filterVal,
        propertiesIds: propertiesIds,
        pageNum,
        limit,
        searchVal: String(searchVal) ? String(searchVal).trim() : null,
      });
      summary = await occupancyDB.getOccupancyStatsForClient(clientId);
    }

    if (occupancies) {
      for (let occupancy of occupancies) {
        if (occupancy.propertyType === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: occupancy.flatId });
          occupancy.flatName = name;
        } else {
          occupancy.flatName = occupancy.floor;
        }

        occupancy.iseKycDone = Number(occupancy.kycStatus) >= CONSTANTS.KYC_STATUS.SELFI_UPLOADED ? 1 : 0;

        let collection = 0;
        let advance = 0;
        let total = 0;
        if (filter === "E" || occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
          const { totalCollection, advancePaid } =
            await ledgerDB.getTotalByTenantIdForMovingOut({
              tenantId: occupancy.tenantId,
              clientId,
            });
          collection = totalCollection;
          advance = advancePaid;

          collection = collection || 0;
          advance = advance || 0;

          if (advance < 0 && collection >= 0) {
            total = Number(collection) + Number(advance);
          } else if (advance < 0 && collection < 0) {
            total = Number(advance);
          } else {
            total = Number(collection);
          }
        }
        occupancy.excessAmount = total < 0 ? Math.abs(total) : 0;
        occupancy.refundAmount = total < 0 ? Math.abs(total) : 0;
        const paidBookingAmt = await ledgerDB.isBookingPaid({
          clientId,
          tenantId: occupancy.tenantId,
          moveInDate: occupancy.moveInDate,
        });

        occupancy.bookingAmt = paidBookingAmt ? Math.abs(paidBookingAmt[0].amount) : 0;
      }
    }

    const isManualMoveInEnabled = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.IS_MANUAL_MOVEIN,
    });

    const propList = await propertyDB.getAllActiveByClientId({
      clientId: clientId,
    });

    //For Booked by filter
    let staffs = await staffDB.getActiveByClientId({
      clientId,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Filter [${filter}], Search Val [${searchVal}], Tenant list sent successfully`
    );
    return res.status(200).json({
      msg: "Tenant list sent successfully",
      data: {
        total: tenantCount || 0,
        list: occupancies || [],
        summary,
        isManualMoveInEnabled: isManualMoveInEnabled ? isManualMoveInEnabled?.value : 0,
        propList: propList || [],
        staffList: staffs || [],
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

//Not Being Used -- 2026-01-14
tenants.MarkAsPaidByClient = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "MarkAsPaidByClient";

  try {
    let {
      id: tenantId,
      mode,
      collectionDate,
      amount,
      ledgerReferenceIds,
    } = req.body;

    const userType = req.userType;
    let clientId = req.id;
    let dueDescription = "";
    let receiptDescription = "";
    let recordedBy = "";
    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;
      recordedBy = staff.name;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Mode [${mode}], Collection Date [${collectionDate}], Amount [${amount}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Mode [${mode}], Collection Date [${collectionDate}], Amount [${amount}], Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Mode [${mode}], Collection Date [${collectionDate}], Amount [${amount}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (req.userType === CONSTANTS.USER_TYPE.CLIENT) {
      recordedBy = client.name;
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Mode [${mode}], Collection Date [${collectionDate}], Amount [${amount}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantId({ tenantId });
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Mode [${mode}], Collection Date [${collectionDate}], Amount [${amount}], No Occupancy Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }
    let amtPaid = amount;
    const property = await propertyDB.getById({ id: occupancy.propId });
    const room = await roomDB.getById({ id: occupancy.roomId });
    let isAllDuePaid = 0;
    let totalAmountPaid = 0;
    for (const ledgerReferenceId of ledgerReferenceIds) {
      let allDues = await duesDB.getByTenantIdAndLedgerReferenceId({
        tenantId,
        ledgerReferenceId,
      });
      if (!allDues) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceId}], User Type [${userType}], User Id [${req.id}], No Dues Found`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      let dueTotal = 0;
      let count = 1;
      let lengthOfDues = allDues.length;
      let security = await allDues.find(
        (due: any) => due.type === CONSTANTS.DUES_TYPES.SECURITY
      );

      let transTitle: string = "Payment for ";
      for (let i = 0; i < allDues.length; i++) {
        const due = allDues[i];
        if (due.title === null) {
          transTitle += await getDueDescription(due.type);
        } else {
          transTitle += `${due.title}`;
        }
        if (i !== allDues.length - 1) {
          transTitle += ", ";
        }
      }

      const transGId: string = await generateTransId();
      const transId = await transactionDB.add({
        gId: transGId,
        clientId: client.id,
        tenantId: tenantId,
        roomId: room.roomId,
        propId: room.propId,
        amount: dueTotal,
        name: transTitle,
        type: CONSTANTS.TRANSACTION_TYPES.TENANT_PAID,
        transactionFor: 0,
        status: CONSTANTS.TRANSACTION_STATUS.SUCCESS,
        dueDate: allDues[0].dueDate,
        receipt: "",
        mode: Number(mode),
        collectionDate: moment().format("YYYY-MM-DD HH:mm:ss"),
        propName: property.name,
        roomNum: room.roomNum,
        ledgerReferenceId: ledgerReferenceId,
        recordedBy: recordedBy,
        title: allDues[0].title || null,
      });

      if (security && security.balance > 0) {
        let due = security;
        dueDescription = getDueDescription(due.type);
        dueTotal += Number(due.balance);
        if (security.balance <= amtPaid) {
          if (count == lengthOfDues) {
            await ledgerDB.add({
              tenantId,
              roomId: occupancy.roomId,
              propId: occupancy.propId,
              clientId: occupancy.clientId,
              amount: -due.balance,
              balance: -(amtPaid - due.balance),
              referenceId: ledgerReferenceId,
              transactionId: null,
              type: due.type,
              rentStartDate: null,
              rentEndDate: null,
              dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
              description: dueDescription,
            });
            receiptDescription = dueDescription;
            amtPaid = 0;
          } else {
            await ledgerDB.add({
              tenantId,
              roomId: occupancy.roomId,
              propId: occupancy.propId,
              clientId: occupancy.clientId,
              amount: -due.balance,
              balance: 0,
              referenceId: ledgerReferenceId,
              transactionId: null,
              type: due.type,
              rentStartDate: null,
              rentEndDate: null,
              dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
              description: dueDescription,
            });
            receiptDescription = dueDescription;
            amtPaid -= due.balance;
          }
          await duesDB.removeDue({ id: due.id });
          log.info(
            `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceId}], Due Id [${due.id}], User Type [${userType}], User Id [${req.id}], Transaction has been recorded successfully`
          );
        } else if (security.balance > amtPaid) {
          await ledgerDB.add({
            tenantId,
            roomId: occupancy.roomId,
            propId: occupancy.propId,
            clientId: occupancy.clientId,
            amount: -amtPaid,
            balance: due.balance - amtPaid,
            referenceId: ledgerReferenceId,
            transactionId: null,
            type: due.type,
            rentStartDate: null,
            rentEndDate: null,
            dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
            description: dueDescription,
          });
          receiptDescription = dueDescription;
          await duesDB.updateBalance({
            id: due.id,
            balance: due.balance - amtPaid,
          });
          amtPaid = 0;
          log.info(
            `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceId}], Due Id [${due.id}], User Type [${userType}], User Id [${req.id}], Transaction has been recorded successfully`
          );
        }
        count++;
      }

      if (amtPaid != 0) {
        for (const due of allDues) {
          dueTotal += Number(due.balance);
          if (amtPaid == 0) break;
          if (due.type === CONSTANTS.DUES_TYPES.SECURITY) {
            continue;
          }
          dueDescription = getDueDescription(due.type);
          if (due.balance != amtPaid) {
            dueDescription = "Partial payment for " + dueDescription;
          } else if (due.balance < due.amount) {
            dueDescription = "Final payment for " + dueDescription;
          }
          // if (!due) {
          //   log.info(
          //     `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceId}], Due Id [${due.id}], User Type [${userType}], User Id [${userId}], No Due Found`
          //   );
          //   continue;
          // }
          if (amtPaid <= due.balance) {
            await ledgerDB.add({
              tenantId,
              roomId: occupancy.roomId,
              propId: occupancy.propId,
              clientId: occupancy.clientId,
              amount: -amtPaid,
              balance: due.balance - amtPaid,
              referenceId: ledgerReferenceId,
              transactionId: null,
              type: due.type,
              rentStartDate: null,
              rentEndDate: null,
              dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
              description: dueDescription,
              discount: due.discount,
            });
            receiptDescription = dueDescription;
            if (amtPaid == due.balance) {
              await duesDB.removeDue({ id: due.id });
            } else {
              await duesDB.updateBalance({
                id: due.id,
                balance: due.balance - amtPaid,
              });
            }
            amtPaid = 0;
          } else {
            if (count == lengthOfDues) {
              amtPaid -= due.balance;
              await ledgerDB.add({
                tenantId,
                roomId: occupancy.roomId,
                propId: occupancy.propId,
                clientId: occupancy.clientId,
                amount: -due.balance,
                balance: -(amtPaid - due.balance),
                referenceId: ledgerReferenceId,
                transactionId: null,
                type: due.type,
                rentStartDate: null,
                rentEndDate: null,
                dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
                description: dueDescription,
                discount: due.discount,
              });
              receiptDescription = dueDescription;
              //break;
            } else {
              await ledgerDB.add({
                tenantId,
                roomId: occupancy.roomId,
                propId: occupancy.propId,
                clientId: occupancy.clientId,
                amount: -due.balance,
                balance: 0,
                referenceId: ledgerReferenceId,
                transactionId: null,
                type: due.type,
                rentStartDate: null,
                rentEndDate: null,
                dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
                description: dueDescription,
                discount: due.discount,
              });
              receiptDescription = dueDescription;
              amtPaid -= due.balance;
            }
            await duesDB.removeDue({ id: due.id });
          }
          count++;
          log.info(
            `[${C}], [${F}], Tenant Id [${tenantId}], LedgerReferenceId [${ledgerReferenceId}], Due Id [${due.id}], User Type [${userType}], User Id [${req.id}], Transaction has been recorded successfully`
          );
        }
        totalAmountPaid += dueTotal;
      }
      let receipts = [];

      let settings = await settingsDB.getByClientIdAndPropId({
        clientId,
        propId: occupancy.propId,
      });

      let logo = settings?.logo
        ? process.env.RS_LOGO_URI + client?.id + "/" + occupancy?.propId + "/" + settings?.logo
        : client?.logo
          ? process.env.RS_LOGO_URI + client?.id + "/" + client?.logo
          : String(process.env.RS_DEFAULT_LOGO_URI);
      log.info(`[${C}], [${F}], Logo [${logo}]`);
      let flatName = "";

      if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
        const { name } = await flatDB.getById({ id: occupancy.flatId });
        flatName = name;
      } else {
        flatName =
          occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
      }
      const receipt = await createReceipt({
        // title: receiptDescription,
        title: `${transId}_${transGId}`,
        roomNum: room.roomNum,
        propName: property.name,
        dueDate: moment(allDues[0].dueDate).format("MMM, YYYY"),
        mode: getModeName(CONSTANTS.TRANSACTION_MODES.OFFLINE),
        transGId,
        paidDate: moment(collectionDate).format("DD MMM, YYYY"),
        month: moment(allDues[0].dueDate).format("MMM, YYYY"),
        tenantName: tenant?.name || "",
        amount: Number(dueTotal || 0),
        address: `${property?.address}`,
        landlord: client?.name || "",
        landlordNumber: client?.mobile || "",
        logo: logo,
        "landlord-pan": "",
        occupancy,
        flatName,
        propType: property.type,
      });
      await transactionDB.updateReceipt({
        id: transId,
        receipt,
      });
      receipts.push({
        name: transTitle,
        receipt,
        paidDate: moment().format("DD MMM, YYYY"),
        amount: Number(amtPaid || 0),
        mode: CONSTANTS.TRANSACTION_MODES.OFFLINE,
        transGId,
      });
    }

    isAllDuePaid = totalAmountPaid <= amount ? 1 : 0;
    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Mode [${mode}], Collection Date [${collectionDate}], Amount [${amount}], ${isAllDuePaid
        ? "Tenant marked as paid successfully"
        : "Some tenant'dues paid successfully"
      }`
    );

    return res.status(200).json({
      msg: "Tenant marked as paid successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.RejectMoveOutRequest = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "RejectMoveOutRequest";

  try {
    const { requestId, reason = null } = req.body;

    const userType = req.userType;
    let clientId = req.id;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Request Id [${requestId}] Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}] Staff Id [${req.id}], Reason [${reason}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], Reason [${reason}] Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const request = await requestDB.getById({ id: requestId });

    if (!request) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], No Request Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (request.status !== CONSTANTS.REQUEST_STATUS.PENDING) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], Request Already Approved Or Rejected`
      );
      return res.status(400).json({
        msg: "Request already approved or rejected",
        isSuccess: false,
      });
    }
    let requestReason = "Your request has been rejected";
    if (!reason) {
      await requestDB.updateStatus({
        id: request?.id,
        status: CONSTANTS.REQUEST_STATUS.REJECTED,
      });
    } else {
      requestReason = reason;
      await requestDB.updateStatusWithReason({
        id: request?.id,
        status: CONSTANTS.REQUEST_STATUS.REJECTED,
        reason,
      });
    }
    let tenant = await tenantDB.getById({ id: request?.tenantId });
    let property = await propertyDB.getById({ id: request?.propId });
    const notiSettings = await settingsDB.getByClientIdAndPropId({
      clientId,
      propId: request?.propId,
    });
    let requestName = getRequestName(CONSTANTS.REQUEST_TYPE.MOVE_OUT);
    let footerText = notiSettings.footer || "Kipinn Team";
    let tenantMobile = tenant?.mobile || "";
    //tenantMobile = "9599054423";
    if (Number(notiSettings?.whatsApp) === 1) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], WhatsApp Enabled`
      );
      await sendWhatsappRequestRejectionWithReason(
        tenantMobile,
        tenant?.name,
        requestName,
        property?.name,
        requestReason,
        footerText,
        Number(clientId)
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], WhatsApp Not Enabled`
      );
    }

    if (request?.occupancyId) {
      await occupancyDB.updateMoveOutDate({
        id: request.occupancyId,
        moveOutDate: null,
        moveOutReason: null,
      });
    }

    await logTenantEvictionActivity(
      req.userType!,
      Number(req.id)!,
      Number(req.parentClientId),
      Number(req.platform),
      CONSTANTS.ACTIVITY_TYPES.MOVEOUT_REQUEST_REJECTED,
      property?.id,
      Number(tenant.id),
      tenant.name,
      request?.roomId,
      ""
    );

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], Move Out Request Rejected Successfully`
    );

    return res.status(200).json({
      isSuccess: true,
      msg: "Request Rejected Successfully",
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetOwnerByPAN = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetOwnerByPAN";

  try {
    let { pan } = req.params;
    let tenantId = req.id;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], PAN [${pan}]`);

    if (!pan || pan.length !== 10 || pan === "undefined" || pan === "null") {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], PAN [${pan}], Invalid PAN`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], PAN [${pan}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const client = await clientDB.getByPAN({ panNumber: pan });
    if (!client) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], PAN [${pan}], No Client Found`
      );
      return res.status(400).json({
        msg: "No owner found associated with this PAN",
        isSuccess: false,
      });
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], PAN [${pan}], Owner details sent successfully`
    );

    return res.status(200).json({
      msg: "Owner details sent successfully",
      data: {
        ...client,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.AddOccupationDetails = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddOccupationDetails";

  const file = req.file as Express.Multer.File;
  let fileFlag = false;

  try {
    let { institutionName, title, institutionEmail, occupation } = req.body;
    const tenantId = req.id;
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Tenant Institution Name [${institutionName}], Institution Email [${institutionEmail}], Title [${title}], Occupation [${occupation}], Request`
    );

    if (file) {
      fileFlag = true;
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Tenant Institution Name [${institutionName}], Institution Email [${institutionEmail}], Title [${title}], Occupation [${occupation}], File [${fileFlag}], Request`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      if (true == fileFlag) {
        await fsPromises.unlink(file.path);
      }
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenant?.id,
    });
    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      if (true == fileFlag) {
        await fsPromises.unlink(file.path);
      }
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    if (occupation != tenant.occupation) {
      const preFile = await documentDB.getIDByType({
        tenantId,
        clientId: occupancy?.clientId,
        type: CONSTANTS.DOCUMENT_TYPES.INSTITUTION_ID,
        moveOut: 0,
      });
      if (preFile) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], New Occupation [${occupation}], Old Occupation [${tenant.occupation}], Removing Institution ID`
        );
        await documentDB.removeByIdAndTenantId({
          tenantId,
          id: preFile.id,
        });
      }
    }

    await tenantDB.updateTenantOccupationDetails({
      id: tenantId,
      institutionName,
      institutionEmail,
      title,
      occupation,
    });
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Tenant Occupation Details Added Successfully`
    );

    if (fileFlag) {
      const folderName = `tenant_${occupancy.tenantId}`;
      const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;
      const filename = `InstitutionId.${file.mimetype.split("/")[1]}`;

      if (!fs.existsSync(folderPath)) {
        await fsPromises.mkdir(folderPath, { recursive: true });
      }

      const oldPath = `uploads/tmp/${file.filename}`;
      const newPath = `${folderPath}/${filename}`;
      const url = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId
        }/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId
        }/${folderName}/${filename}?v=${moment().format("YYYY-MM-DD-HH-mm-ss")}`;

      await fsPromises.copyFile(oldPath, newPath);

      const isExists = await documentDB.getIDByType({
        tenantId,
        clientId: occupancy?.clientId,
        type: CONSTANTS.DOCUMENT_TYPES.INSTITUTION_ID,
        moveOut: 0,
      });
      if (isExists) {
        await documentDB.updateDoc({
          tenantId,
          clientId: occupancy.clientId,
          propId: occupancy.propId,
          roomId: occupancy.roomId,
          type: CONSTANTS.DOCUMENT_TYPES.INSTITUTION_ID,
          value: url,
          id: isExists.id,
        });
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Institution Id Re-uploaded Sucessfully`
        );
      } else {
        await documentDB.addDocWithStatus({
          tenantId,
          clientId: occupancy.clientId,
          propId: occupancy.propId,
          roomId: occupancy.roomId,
          type: CONSTANTS.DOCUMENT_TYPES.INSTITUTION_ID,
          value: url,
          status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
        });

        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Institution Id Uploaded Sucessfully`
        );
      }

      await fsPromises.unlink(oldPath);
    }

    return res.status(200).json({
      msg: "Institution details and id uploaded sucessfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    if (fileFlag) {
      await fsPromises.unlink(file.path);
    }

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.AddGuardianDetails = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddGuardianDetails";
  try {
    let { fatherMobile, motherName, motherMobile } = req.body;
    fatherMobile = fatherMobile || null;
    motherName = motherName || null;
    motherMobile = motherMobile || null;
    const tenantId = req.id;
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Father Mobile [${fatherMobile}], Mother Name [${motherName}], Mother Mobile [${motherMobile}]`
    );
    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }
    await tenantDB.updateTenantGuardianDetails({
      id: tenantId,
      fatherMobile,
      motherName,
      motherMobile,
    });

    const tenantGuardian = await tenantGuardianDB.getByTenantId({ tenantId });
    if (!tenantGuardian) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Creating New Guardian Record`);

      await tenantGuardianDB.createNew({
        tenantId,
        fatherMobile,
        motherName,
        motherMobile,
      });
    } else {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Updating Guardian Record`);

      await tenantGuardianDB.updateNew({
        tenantId,
        fatherMobile,
        motherName,
        motherMobile,
      });
    }
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Tenant Guardian Details Added Successfully`
    );
    return res.status(200).json({
      msg: "Guardian details updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetList = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetList";
  try {
    // let clientId = req.id;
    const userType = req.userType;
    const { pageNum, filter, searchVal } = req.body;
    const limit = 10;

    let tenants = [];

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    log.info(
      `[${C}], [${F}], UserType [${userType}], Req Id [${req.id}], Filter [${filter}], Search Value [${searchVal}], Page Number [${pageNum}]`
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? "Partner Id" : "Client Id"}, [${req.id
        }] Filter [${filter}], ${isPartner ? "Partner Requesting" : "Client Requesting"
        }....`
      );

      if (filter === CONSTANTS.TENANT_LIST_FILTERS.NOT_RESERVED) {
        if (searchVal !== "") {
          tenants = await occupancyDB.getTenantsWithoutReservedClient({
            clientId,
            pageNum,
            limit,
          });
        } else {
          tenants = await occupancyDB.getSearchTenantsWithoutReservedClient({
            clientId,
            pageNum,
            limit,
          });
        }
      } else {
        log.info(
          `[${C}], [${F}], ${isPartner ? "Partner Id" : "Client Id"}, [${req.id
          }] Filter [${filter}], Invalid Filter`
        );
      }
    } else {
      const staffId = req.id;

      log.info(
        `[${C}], [${F}], Staff Id [${staffId}], Filter [${filter}], Search Value [${searchVal}], Page Number [${pageNum}] Staff Requesting...`
      );

      const staff = await staffDB.getById({ id: staffId });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${staffId}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }

      clientId = staff.clientId;

      const staffLinkedProps = await propertyDB.getPropsByStaffId({
        staffId,
      });

      if (staffLinkedProps) {
        const propertiesIds = staffLinkedProps
          .map((prop: propertiesTypes) => prop.id)
          .join(",");

        if (filter === CONSTANTS.TENANT_LIST_FILTERS.NOT_RESERVED) {
          if (searchVal !== "") {
            tenants = await occupancyDB.getTenantsWithoutReservedStaff({
              clientId,
              pageNum,
              limit,
            });
          } else {
            tenants = await occupancyDB.getSearchTenantsWithoutReservedStaff({
              clientId,
              pageNum,
              limit,
            });
          }
        } else {
          log.info(
            `[${C}], [${F}], ${isPartner ? "Partner Id" : "Client Id"}, [${req.id
            }] Filter [${filter}], Invalid Filter`
          );
        }
      } else {
        log.info(`[${C}], [${F}], Staff Id [${staffId}], No Properties Linked`);
        return res.status(400).json({
          msg: "No Properties Linked",
          isSuccess: false,
        });
      }
    }
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListForWeb = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ListForWeb";

  try {
    // let clientId = req.id;
    const userType = req.userType;
    let { filter, filterVal, searchVal, propId, flatId, platform, startDate, endDate } = req.query;

    const limit = 1e9;
    const pageNum = 1;
    let tenantCount = 0;
    //let occupancies: occupanciesTypes[] = [];
    let occupancies: any[] = [];
    let summary: any = [];
    let evictedStats: any[] = [];
    let totalDues = 0;
    let dueTenantCount = 0;
    let totalMovingOutRefund = 0;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, Page Num [${pageNum}], Filter [${filter}], Filter Val [${filterVal}], Search Val [${searchVal}], Property Id [${propId}], Flat Id [${flatId}], Platform [${platform}], Start Date [${startDate}], End Date [${endDate}], ${isPartner ? "Partner Requesting" : "Client Requesting"
        }....`
      );

      const client = await clientDB.getById({ id: clientId });

      if (!client) {
        log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      if (Number(flatId)) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Flat Id [${flatId}]`
        );
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsOfSelectedFlat(
            Number(clientId),
            Number(flatId),
            Number(pageNum),
            String(filter),
            String(searchVal),
            String(platform),
            limit
          );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;
      } else if (Number(propId) && Number(propId) !== 0) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}]`
        );
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsOfSelectedPropertyForWeb(
            Number(clientId),
            Number(propId),
            Number(pageNum),
            String(filter),
            String(filterVal),
            String(searchVal),
            String(platform),
            limit,
            String(startDate),
            String(endDate),
          );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;

        if (filter === "E" || filter === "ED" || filter === "ECM" || filter === "ERD" || filter === "EPR" || filter === "CB" || filter === "MO" || filter === "WP") {
          if (startDate && endDate && String(startDate).trim() !== "" && String(endDate).trim() !== "") {
            summary = await moveOutDB.getEvictedStatsByDateRange({ clientId, propId, startDate, endDate });
          } else {
            summary = await moveOutDB.getEvictedStats({ clientId, propId });
          }
        } else {
          summary = await occupancyDB.getOccupancyStatsByPropId(
            clientId,
            propId
          );
        }
      } else {
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsForClient(
            Number(clientId),
            Number(propId),
            Number(pageNum),
            String(filter),
            String(filterVal),
            String(searchVal),
            String(platform),
            limit,
            String(startDate),
            String(endDate),
          );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;

        if (filter === "E" || filter === "ED" || filter === "ECM" || filter === "ERD" || filter === "EPR" || filter === "CB" || filter === "MO" || filter === "WP" || filter === "FNFG" || filter === "FNFNG") {
          if (startDate && endDate && String(startDate).trim() !== "" && String(endDate).trim() !== "") {
            summary = await moveOutDB.getEvictedStatsByClientIdAndDateRange({
              clientId,
              startDate,
              endDate,
            });
          } else {
            summary = await moveOutDB.getEvictedStatsByClientId({ clientId });
          }
        } else {
          summary = await occupancyDB.getOccupancyStatsForClient(
            clientId,
          );
        }
      }

      if (occupancies) {
        for (let occupancy of occupancies) {
          if (occupancy.propertyType === CONSTANTS.PROPERTY_TYPE.FLAT) {
            const { name } = await flatDB.getById({ id: occupancy.flatId });
            occupancy.flatName = name;
          } else {
            occupancy.flatName = occupancy.floor;
          }

          totalDues += Number(occupancy.totalDues);

          if (Number(occupancy.totalDues) > 0) {
            dueTenantCount += 1;
          }

          occupancy.iseKycDone = Number(occupancy.kycStatus) >= CONSTANTS.KYC_STATUS.SELFI_UPLOADED ? 1 : 0;

          let collection = 0;
          let advance = 0;
          let total = 0;
          //if (filter === "E" || occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
          // if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
          //   // log.info(
          //   //   `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${occupancy.tenantId}], Ledger Data 1`
          //   // );
          //   const { totalCollection, advancePaid } =
          //     await ledgerDB.getTotalByTenantIdForMovingOut({
          //       tenantId: occupancy.tenantId,
          //       clientId,
          //     });
          //   // let totalCollection = 0;
          //   // let advancePaid = 0;
          //   // log.info(
          //   //   `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${occupancy.tenantId}], Ledger Data 2`
          //   // );
          //   collection = totalCollection;
          //   advance = advancePaid;

          //   collection = collection || 0;
          //   advance = advance || 0;

          //   if (advance < 0 && collection >= 0) {
          //     total = Number(collection) + Number(advance);
          //   } else if (advance < 0 && collection < 0) {
          //     total = Number(advance);
          //   } else {
          //     total = Number(collection);
          //   }
          // }
          // occupancy.excessAmount = total < 0 ? Math.abs(total) : 0;
          // occupancy.refundAmount = total < 0 ? Math.abs(total) : 0;
          // let refundAmount = total < 0 ? Math.abs(total) : 0;

          // if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) totalMovingOutRefund += refundAmount;
        }
      }
    } else {
      //Staff
      const staffId = req.id;
      const staff = await staffDB.getById({ id: staffId });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${staffId}], No Staff Found`);
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Staff Id [${staffId}] Page Num [${pageNum}], Filter [${filter}], Platform [${platform}], Search Val [${searchVal}], Property Id [${propId}], Filter Val [${filterVal}], Flat Id [${flatId}], Platform [${platform}], Start Date [${startDate}], End Date [${endDate}], Staff Requesting....`
      );

      const staffLinkedProps = await propertyDB.getPropsByStaffId({
        staffId,
      });

      let propertiesIds = "";

      if (staffLinkedProps) {
        propertiesIds = staffLinkedProps
          .map((prop: propertiesTypes) => prop.id)
          .join(",");
      }

      if (Number(flatId)) {
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsOfSelectedFlat(
            Number(clientId),
            Number(flatId),
            Number(pageNum),
            String(filter),
            String(platform),
            String(searchVal),
            limit
          );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;
      } else if (Number(propId) && Number(propId) !== 0) {
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsOfSelectedPropertyForWeb(
            Number(clientId),
            Number(propId),
            Number(pageNum),
            String(filter),
            String(filterVal),
            String(searchVal),
            String(platform),
            limit,
            String(startDate),
            String(endDate),
          );
        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;
        if (filter === "E" || filter === "ED" || filter === "ECM" || filter === "ERD" || filter === "EPR" || filter === "CB" || filter === "MO" || filter === "WP") {
          if (startDate && endDate && String(startDate).trim() !== "" && String(endDate).trim() !== "") {
            summary = await moveOutDB.getEvictedStatsByDateRange({ clientId, propId, startDate, endDate });
          } else {
            summary = await moveOutDB.getEvictedStats({ clientId, propId });
          }
        } else {
          summary = await occupancyDB.getOccupancyStatsByPropId(
            clientId,
            propId
          );
        }
      } else {
        const { tenantTotalCount, tenantOccupancies } =
          await tenantsForStaff(
            Number(clientId),
            Number(staff.id),
            Number(propId),
            Number(pageNum),
            String(filter),
            String(filterVal),
            String(searchVal),
            limit,
            String(startDate),
            String(endDate),
          );

        tenantCount = tenantTotalCount;
        occupancies = tenantOccupancies;

        if (filter === "E" || filter === "ED" || filter === "ECM" || filter === "ERD" || filter === "EPR" || filter === "CB" || filter === "MO" || filter === "WP") {
          if (startDate && endDate && String(startDate).trim() !== "" && String(endDate).trim() !== "") {
            summary = await moveOutDB.getEvictedStatsByClientIdForStaffAndDateRange({ clientId, propertiesIds, startDate, endDate });
          } else {
            summary = await moveOutDB.getEvictedStatsByClientIdForStaff({ clientId, propertiesIds });
          }
        } else {
          summary = await occupancyDB.getOccupancyStatsForStaff({
            clientId,
            propertiesIds: propertiesIds,
          });
        }
      }

      if (occupancies) {
        for (let occupancy of occupancies) {
          if (occupancy.propertyType === CONSTANTS.PROPERTY_TYPE.FLAT) {
            const { name } = await flatDB.getById({ id: occupancy.flatId });
            occupancy.flatName = name;
          } else {
            occupancy.flatName = occupancy.floor;
          }

          totalDues += Number(occupancy.totalDues);

          if (Number(occupancy.totalDues) > 0) {
            dueTenantCount += 1;
          }

          occupancy.iseKycDone = Number(occupancy.kycStatus) >= CONSTANTS.KYC_STATUS.SELFI_UPLOADED ? 1 : 0;

          let collection = 0;
          let advance = 0;
          let total = 0;
          //if (filter === "E" || occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
          // if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
          //   const { totalCollection, advancePaid } =
          //     await ledgerDB.getTotalByTenantIdForMovingOut({
          //       tenantId: occupancy.tenantId,
          //       clientId,
          //     });
          //   collection = totalCollection;
          //   advance = advancePaid;

          //   collection = collection || 0;
          //   advance = advance || 0;

          //   if (advance < 0 && collection >= 0) {
          //     total = Number(collection) + Number(advance);
          //   } else if (advance < 0 && collection < 0) {
          //     total = Number(advance);
          //   } else {
          //     total = Number(collection);
          //   }
          // }
          // occupancy.excessAmount = total < 0 ? Math.abs(total) : 0;
          // occupancy.refundAmount = total < 0 ? Math.abs(total) : 0;
          // let refundAmount = total < 0 ? Math.abs(total) : 0;

          // if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) totalMovingOutRefund += refundAmount;
        }
      }
    }

    summary.movingOutRefund = totalMovingOutRefund;

    const appInstalledCount = await tenantDB.getAppInstalledCountByPropId({
      propId,
    });

    summary.appInstalledCount = appInstalledCount ? appInstalledCount.count : 0;

    //For Booked by filter
    let staffs = await staffDB.getActiveByClientId({
      clientId,
    });
    if (Number(propId) && Number(propId) !== 0) {
      staffs = await staffDB.getByPropId({
        propId,
      });
    }

    //CAN PUSH EKYC BULK
    let isEkycBulkEnabled = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.EKYC_BULK_REMINDER,
    });
    if (!isEkycBulkEnabled) {
      isEkycBulkEnabled = 0;
    } else {
      isEkycBulkEnabled = Number(isEkycBulkEnabled.value) || 0;
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Page Num [${pageNum}], Filter [${filter}], Search Val [${searchVal}], Tenant list sent successfully`
    );

    return res.status(200).json({
      msg: "Tenant list sent successfully",
      data: {
        isEkycBulkEnabled: isEkycBulkEnabled,
        total: tenantCount || 0,
        list: occupancies || [],
        summary,
        totalDues: totalDues || 0,
        dueTenantCount,
      },
      staffList: staffs || [],
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.UpdateRentDetailsForWeb = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "UpdateRentDetailsForWeb";

  try {
    let {
      tenantId,
      rent,
      security,
      moveInDate,
      agreementStartDate = null,
      rentalCycle,
      agreementPeriod,
      lockInPeriod,
      noticePeriod,
      moveOutDate,
      rentalType,
      isOnlinePaymentEnabled = 0,
      bookedBy = null,
    } = req.body;
    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Rent [${rent}], Security [${security}], Move In Date [${moveInDate}], Agreement Start Date [${agreementStartDate}], Rental Type [${rentalType}], Rental Cycle [${rentalCycle}], Agreement Period [${agreementPeriod}], Lock In Period [${lockInPeriod}], Notice Period [${noticePeriod}], Move Out Date [${moveOutDate}], Online Payment Enabled [${isOnlinePaymentEnabled}], Booked By [${bookedBy}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Tenant Not Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}],  ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, Tenant Id [${tenantId}], ${isPartner ? `Partner` : `Client`
        } Requesting...`
      );
    } else {

      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Staff Id [${req.id}], No Staff Found`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      // if (staff.role !== CONSTANTS.STAFF_ROLES.ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.WARDEN && staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE) {
      //   log.info(
      //     `[${C}], [${F}], User Type [${userType}], User Id [${req.id}], Staff Not Authorized`
      //   );

      //   return res.status(400).json({
      //     msg: "Unauthorized Access",
      //     isSuccess: false,
      //   });
      // }

      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
    }

    let occupancy = await occupancyDB.getByTenantIdAndClientId({ clientId: clientId, tenantId: tenantId });
    if (false == occupancy) {
      log.info(
        `[${C}], [${F}], User Type [${userType}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, Tenant Id [${tenantId}], Occupancy Not Found, `
      );

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }
    // moveOutDate = null;
    // if(CONSTANTS.STAY_TYPE.SHORT === Number(occupancy?.stayType))
    // {
    //   moveOutDate = occupancy?.moveOutDate;
    // }
    await occupancyDB.updateTenantRentDetails({
      tenantId,
      clientId,
      rent,
      security,
      moveInDate,
      rentalCycle,
      agreementPeriod,
      lockInPeriod,
      noticePeriod,
      moveOutDate: moveOutDate ? moveOutDate : occupancy?.moveOutDate,
      isOnlinePaymentEnabled,
      agreementStartDate,
    });

    if (bookedBy && Number(bookedBy)) {
      await occupancyDB.updateBookedByClientIdAndTenantId({
        bookedBy,
        clientId,
        tenantId,
      });
    }

    log.info(
      `[${C}], [${F}],  ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
      }, Tenant Id [${tenantId}], Rent Details Updated Successfully...`
    );

    return res.status(200).json({
      msg: "Rent details updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.UpdatePersonalDetails = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "UpdatePersonalDetails";

  try {
    let {
      tenantId,
      name,
      mobile,
      email,
      gender,
      dob = "",
      address,
      nationality,
      alternateMobile,
      monthlyIncome,
      bloodGroup,
    } = req.body;
    const userType = req.userType;

    if (dob === null) dob = "";

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Name [${name}], Mobile [${mobile}], Email [${email}], Gender [${gender}], DOB [${dob}], Address [${address}], Nationality [${nationality}], Alternate Mobile [${alternateMobile}], Monthly Income [${monthlyIncome}], Blood Group [${bloodGroup}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Tenant Not Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        }, Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Staff Id [${req.id}], No Staff Found`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (staff.role !== CONSTANTS.STAFF_ROLES.ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.WARDEN && staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE && staff.role !== CONSTANTS.STAFF_ROLES.FINANCE_ADMIN) {
        log.info(
          `[${C}], [${F}], User Type [${userType}], User Id [${req.id}], Staff Not Authorized`
        );

        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"} Requesting....`
      );
    }

    const tenantByMobile = await tenantDB.getByMobile({
      mobile,
    });
    if (tenantByMobile && tenantByMobile.id !== tenantId) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Mobile [${mobile}], Mobile already exists for another tenant`
      );
      return res.status(400).json({
        msg: "Mobile number already exists for another tenant",
        isSuccess: false,
      });
    }

    await tenantDB.UpdatePersonalDetailsForWeb({
      id: tenantId,
      name,
      mobile,
      email,
      gender,
      dob: dob === "" ? null : moment(dob).format("YYYY-MM-DD"),
      address,
      alternateMobile,
      bloodGroup,
    });

    log.info(
      `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
      }, Tenant Id [${tenantId}], Name [${name}], Mobile [${mobile}], Email [${email}], Gender [${gender}], DOB [${dob}], Address [${address}], Nationality [${nationality}], Alternate Mobile [${alternateMobile}], Monthly Income [${monthlyIncome}], Blood Group [${bloodGroup}], Personal details updated successfully`
    );

    return res.status(200).json({
      msg: "Personal details updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.UpdateOccupationDetails = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "UpdateOccupationDetails";

  try {
    let { tenantId, institutionName, title, institutionEmail, occupation, courseEndYear=null, courseEndMonth=null } =
      req.body;
    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Institution Name [${institutionName}], Title [${title}], Institution Email [${institutionEmail}], Occupation [${occupation}], Course End Year [${courseEndYear}], Course End Month [${courseEndMonth}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Tenant Not Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        }, Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Staff Id [${req.id}], No Staff Found`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      // if (staff.role !== CONSTANTS.STAFF_ROLES.ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.WARDEN && staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE && staff.role !== CONSTANTS.STAFF_ROLES.FINANCE_ADMIN) {
      //   log.info(
      //     `[${C}], [${F}], User Type [${userType}], User Id [${req.id}], Staff Not Admin, Warden or Back Office`
      //   );

      //   return res.status(400).json({
      //     msg: "Unauthorized Access",
      //     isSuccess: false,
      //   });
      // }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"} Requesting....`
      );
    }

    await tenantDB.updateTenantOccupationDetails({
      id: tenantId,
      institutionName,
      institutionEmail,
      title,
      occupation,
    });
    
    if(courseEndMonth === 0) {
      courseEndMonth = null;
    }
    if(courseEndYear === 0) {
      courseEndYear = null;
    }

    await tenantDB.updateTenantOccupationEndyearAndDate({
      id: tenantId,
      courseEndYear,
      courseEndMonth
    });

    log.info(
      `[${C}], [${F}],  ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
      }, Tenant Id [${tenantId}], Occupation [${occupation}], Institution Name [${institutionName}], Instituion Email [${institutionEmail}], Title [${title}], Personal Details Updated Successfully...`
    );

    return res.status(200).json({
      msg: "Personal details updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.UpdateGuardianDetails = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "UpdateGuardianDetails";

  try {
    const {
      tenantId,
      fatherName,
      fatherMobile,
      fatherOccupation,
      fatherAnnualIncome,
      motherName,
      motherMobile,
      localGuardianName,
      localGuardianMobile,
      localGuardianRelation,
    } = req.body;

    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Father Name [${fatherName}], Father Mobile [${fatherMobile}], Father Occupation [${fatherOccupation}], Father Annual Income [${fatherAnnualIncome}], Mother Name [${motherName}], Mother Mobile [${motherMobile}], Local Guardian Name [${localGuardianName}], Local Guardian Mobile [${localGuardianMobile}], Local Guardian Relation [${localGuardianRelation}]`
    );

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Staff Id [${req.id}], No Staff Found`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (staff.role !== CONSTANTS.STAFF_ROLES.ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.WARDEN && staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE && staff.role !== CONSTANTS.STAFF_ROLES.FINANCE_ADMIN) {
        log.info(
          `[${C}], [${F}], User Type [${userType}], User Id [${req.id}], Staff Not Admin, Warden, Back Office or Finance Admin`
        );

        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"} Requesting....`
      );
    }

    const tenantGuardian = await tenantGuardianDB.getByTenantId({ tenantId });
    if (!tenantGuardian) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Creating New Record`);

      await tenantGuardianDB.create({
        tenantId,
        fatherName,
        fatherMobile,
        fatherOccupation,
        fatherAnnualIncome,
        motherName,
        motherMobile,
        localGuardianName,
        localGuardianMobile,
        localGuardianRelation,
      });
    } else {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Updating Record`);

      await tenantGuardianDB.update({
        tenantId,
        fatherName,
        fatherMobile,
        fatherEmail: tenantGuardian.fatherEmail,
        fatherOccupation,
        fatherAnnualIncome,
        motherName,
        motherMobile,
        motherEmail: tenantGuardian.motherEmail,
        localGuardianName,
        localGuardianMobile,
        localGuardianEmail: tenantGuardian.localGuardianEmail,
        localGuardianRelation,
      });
    }

    if (fatherName != 'undefined' && fatherName != null && fatherName.trim() !== "") {
      log.info(`[${C}], [${F}], Father Name [${fatherName}] is not empty`);
      await tenantDB.updateTenantFatherName({ id: tenantId, fatherName });
    }

    if (fatherMobile != 'undefined' && fatherMobile != null && fatherMobile.trim() !== "") {
      log.info(`[${C}], [${F}], Father Mobile [${fatherMobile}] is not empty`);
      await tenantDB.updateTenantFatherMobile({ id: tenantId, fatherMobile });
    }

    if (motherName != 'undefined' && motherName != null && motherName.trim() !== "") {
      log.info(`[${C}], [${F}], Mother Name [${motherName}] is not empty`);
      await tenantDB.updateTenantMotherName({ id: tenantId, motherName });
    }

    if (motherMobile != 'undefined' && motherMobile != null && motherMobile.trim() !== "") {
      log.info(`[${C}], [${F}], Mother Mobile [${motherMobile}] is not empty`);
      await tenantDB.updateTenantMotherMobile({ id: tenantId, motherMobile });
    }

    return res.status(200).json({
      msg: "Guardian details updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetDetailForWeb = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetDetailForWeb";

  try {
    let { tenantId } = req.params;
    const { e } = req.query;

    let isEvicted = e === "1";
    let isMultipleOccupancy = false;
    let occupiedBedCount = 1;
    let isPayoutEnabled = 0;
    let payoutBalance = 0; 

    const userType = req.userType;
    let clientId = req.id;
    
    let payoutData = await getWalletBalanceAndLimits({
      clientId: Number(req.id),
      userType: Number(userType),
      staffId: null,
    });

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      payoutData = await getWalletBalanceAndLimits({
        clientId: Number(clientId),
        userType: Number(userType),
        staffId: Number(req.id),
      });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Staff Id [${req.id}], isEvicted [${isEvicted}], Staff Requested....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], isEvicted [${isEvicted}], Client Requested....`
      );
        const isPayoutEnabledConfig = await clientConfigDB.getClientConfig({
          clientId,
          provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
          type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.IS_PAYOUT_ENABLED,
        });
        if (isPayoutEnabledConfig && Number(isPayoutEnabledConfig.value)) {
          isPayoutEnabled = Number(isPayoutEnabledConfig.value);
        }
        if(isPayoutEnabled === 1) {
          let balance = await cashfreePayout.getWalletBalance(C, F, clientId);
          if (!balance.error) {
            payoutBalance = Number(balance?.availableBalance);
          } else {
            payoutBalance = 0;
          }
          payoutBalance = payoutData?.walletBalance || payoutBalance;
        }
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Client Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancy = null;

    if (isEvicted) {
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        tenantId,
        clientId,
      });
    } else {
      occupancy = await occupancyDB.getByTenantIdAndClientId({
        tenantId,
        clientId,
      });
      if (!occupancy) {
        occupancy = await moveOutDB.getByTenantIdAndClientId({
          tenantId,
          clientId,
        });
        isEvicted = true;
        if (!occupancy) {
          log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Occupancy Found In Either Occupancies or MoveOut`);

          return res.status(400).json({
            msg: CONSTANTS.MSG.INVALID_REQUEST,
            isSuccess: false,
          })
        }
      }

      const occupancyByTenantAndClientId = await occupancyDB.getByClientIdAndTenantId({
        clientId: clientId,
        tenantId: occupancy.tenantId
      });

      if (occupancyByTenantAndClientId && occupancyByTenantAndClientId.length > 1) {
        isMultipleOccupancy = occupancyByTenantAndClientId.every(
          (occupancy: occupanciesTypes) =>
            occupancy.roomId === occupancyByTenantAndClientId[0].roomId
        );
        // isMultipleOccupancy = true;
        occupiedBedCount = occupancyByTenantAndClientId.length || 1;
      }
    }

    if (!occupancy) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], No Occupancy Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let canAssignFullRoom = false;
    if (!isEvicted) {
      const occupancyByRoom = await occupancyDB.getByRoomId({
        roomId: occupancy.roomId
      });

      const roomBedCount = await bedDB.getCountsByRoomId({
        id: occupancy.roomId,
      });

      if (occupancyByRoom && occupancyByRoom.length === 1 && roomBedCount.totalBeds > 1) canAssignFullRoom = true;
    }

    const property = await propertyDB.getById({
      id: occupancy.propId,
    });
    if (!property) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Property Id [${occupancy.propId}], No Property Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let lastSavedElecReading = 0;
    let docs = [];
    let totalDuesAmt = 0;
    let totalCollectedAmt = 0;
    let dues = [];
    let moveInInspection = null;

    if (!isEvicted) {
      lastSavedElecReading = await electricityDB.getForPreviousMonth({
        clientId: occupancy.clientId,
        propId: occupancy?.propId,
        roomId: occupancy?.roomId,
      });

      docs = await documentDB.getByTenantIdAndClientId({ tenantId, clientId, moveOut: 0 });
      if (docs && docs.length > 0) {
        for (let doc of docs) {
          const title = await getDocumentTitle(doc.type);
          doc.title = title;
        }
      }

      // if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
      //   const { totalDues } = await duesDB.getTotalDuesByTenantIdForEviction({
      //     tenantId,
      //     propId: occupancy.propId,
      //   });
      //   totalDuesAmt = Number(totalDues) || 0;
      // } else {
      //   const { totalDues } = await duesDB.getTotalDuesByTenantId({
      //     tenantId,
      //     propId: occupancy.propId,
      //   });
      //   totalDuesAmt = Number(totalDues) || 0;
      // }

      const { totalDues } = await duesDB.getTotalDuesByTenantId({
        tenantId,
        propId: occupancy.propId,
      });
      totalDuesAmt = Number(totalDues) || 0;

      //dues = await duesDB.getByTenantId({ tenantId });
      //Pallav - Multi tenant Senerio
      dues = await duesDB.getByTenantIdAndClientId({ tenantId, clientId: occupancy?.clientId });

      // totalCollectedAmt = await transactionDB.getTotalCollectionByTenantIdAndClientId({
      //   tenantId: tenantId,
      //   clientId: occupancy?.clientId,
      // });

      moveInInspection = await moveInDB.getByTenantIdandClientId({
        tenantId,
        clientId: occupancy?.clientId,
      });

      if (moveInInspection) {
        let moveInImage = await imageDB.getByMoveInId({
          moveInInspectionId: moveInInspection.id,
        });

        moveInInspection.images = moveInImage;
      } else {
        moveInInspection = [];
      }
    } else {
      // dues = await moveOutDuesDB.getByTenantId({
      //   tenantId,
      // });
      dues = await moveOutDuesDB.getByTenantIdAndClientId({
        tenantId,
        clientId
      });
      const { totalDues } = await moveOutDuesDB.getTotalDuesByTenantId({
        tenantId,
        propId: occupancy.propId,
      });
      totalDuesAmt = Number(totalDues) || 0;
      //totalDuesAmt = occupancy.tenantDues || 0;
      docs = await documentDB.getByTenantIdAndClientIdMovedOut({ tenantId, clientId, moveOut: 1 });
      if (docs && docs.length > 0) {
        for (let doc of docs) {
          const title = await getDocumentTitle(doc.type);
          doc.title = title;
        }
      }
    }

    totalCollectedAmt =
      await transactionDB.getTotalCollectionByTenantIdAndClientId({
        tenantId: tenantId,
        clientId: occupancy?.clientId,
      });

    const rentAgreementRecords =
      await rentAgreementRecordDB.getByTenantIdandClientId({
        tenantId,
        clientId: occupancy?.clientId,
      });

    // log.info(
    //   `[${C}], [${F}], Client Id [${clientId}], Tenant details sent successfully`
    // );

    let flatName = "";
    if (occupancy.propType === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      occupancy.flatName = name;
      flatName = name + ", " + occupancy.roomNum;
    } else {
      occupancy.flatName = occupancy.floor;
      flatName = occupancy.floor;
    }

    if (dues) {
      for (let due of dues) {
        due.flatName = flatName;

        let dueImages = await documentDB.getByLedgerReferenceId({
          ledgerReferenceId: due.ledgerReferenceId,
        });

        due.dueImages = dueImages 
        ? dueImages.map((img:any) => ({ id: img.id, value: img.value }))
        : [];
      }
    }

    let transactions = await transactionDB.getByTenantIdAndClientId({
      tenantId,
      clientId: occupancy?.clientId,
    });

    let secAmount = 0;
    let secTransAdjust = [];
    if (transactions && transactions.length > 0) {
      let secTrans = transactions.filter(
        (t: transactionsTypes) =>
          t.transactionFor === CONSTANTS.TRANSACTION_FOR.SECURITY
      );

      secTransAdjust = transactions.filter(
        (t: transactionsTypes) =>
          t.mode === CONSTANTS.TRANSACTION_MODES.ADJUSTED_FROM_SECURITY
      );


      secAmount = secTrans.reduce((acc: number, curr: transactionsTypes) => {
        return acc + Number(curr.amount);
      }, 0);

      for (let trans of transactions) {
        let canRestore = true;

        const ledgerRecordsByTxnId = await ledgerDB.getByTransactionId({
          transactionId: Number(trans.id),
        });

        let occupancy = await occupancyDB.getByTenantIdAndClientId({
          tenantId: trans.tenantId,
          clientId,
        });

        let securityUsed = false;

        if (occupancy) {
          securityUsed = await ledgerDB.getUsedSecurityAmtByTenantIdAndClientId({
            tenantId: trans.tenantId,
            clientId: clientId,
            createdAt: occupancy.createdAt,
          });
        }

        if (!occupancy) {
          canRestore = false;
        } else if (securityUsed && trans.transactionFor === CONSTANTS.TRANSACTION_FOR.SECURITY) {
          canRestore = false;
        } else if (!ledgerRecordsByTxnId || ledgerRecordsByTxnId.length === 0) {
          canRestore = false;
        } else if (ledgerRecordsByTxnId && ledgerRecordsByTxnId.length > 1) {
          canRestore = false;
        } else if (Math.abs(ledgerRecordsByTxnId[0].amount) !== Math.abs(trans.amount)) {
          //Excess Amount Check using transaction amount and ledger record amount
          canRestore = false;
        } else if (ledgerRecordsByTxnId[0].type === CONSTANTS.DUES_TYPES.SETTLEMENT) {
          canRestore = false;
        }
        // else if(ledgerRecordsByTxnId[0].balance !== 0) {
        //   // log.info(
        //   //   `[${C}], [${F}], Client Id [${req.id}], Transaction Id [${trans.id}], Due Has Been Partially Paid, Cannot Restore Transaction`
        //   // );
        //   canRestore = false;
        // }

        trans.canRestore = canRestore;
      }
    }

    let securities = await transactionDB.getSecurityByTenantIdAndClientId({
      tenantId,
      clientId: occupancy?.clientId,
    });

    if (securities && securities.length > 0) {
      securities.push(...secTransAdjust);
      securities.sort(
        (a: transactionsTypes, b: transactionsTypes) =>
          new Date(b.collectionDate).getTime() -
          new Date(a.collectionDate).getTime()
      );
    }

    const tenantGuardian = await tenantGuardianDB.getByTenantId({ tenantId });

    const lastRentDue = await ledgerDB.getLastRent({
      tenantId,
      clientId,
    });

    let rentStartDate = moment().date(occupancy.rentalCycle).format("YYYY-MM-DD");
    let rentEndDate = moment(rentStartDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");

    let firstRentStartDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");
    let firstRentEndDate = moment(occupancy.moveInDate).date(occupancy.rentalCycle).isAfter(moment(occupancy.moveInDate), "date")
      ? moment(firstRentStartDate).date(occupancy.rentalCycle).subtract(1, "day").format("YYYY-MM-DD")
      : moment(firstRentStartDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");

    if (moment().isBetween(firstRentStartDate, firstRentEndDate, "date", "[]")) {
      rentStartDate = firstRentStartDate;
      rentEndDate = firstRentEndDate;
    } else if (moment(occupancy.moveInDate).isAfter(moment(rentStartDate), "date")) {
      rentStartDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");

      rentEndDate = moment().date(occupancy.rentalCycle).isAfter(moment(occupancy.moveInDate), "date")
        ? moment().date(occupancy.rentalCycle).subtract(1, "day").format("YYYY-MM-DD")
        : moment().date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    } else if (moment().isBefore(moment().date(occupancy.rentalCycle), "date")) {

      rentStartDate = moment().date(occupancy.rentalCycle).subtract(1, "month").format("YYYY-MM-DD");
      rentEndDate = moment(rentStartDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    }

    if (lastRentDue && lastRentDue.rentEndDate) {
      rentStartDate = moment(lastRentDue?.rentEndDate)
        .add(1, "days")
        .format("YYYY-MM-DD");

      rentEndDate = moment(rentStartDate).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
      // } else if (tenant.status === CONSTANTS.TENANT_STATUS.RESERVED) {
    } else if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
      rentStartDate = moment(occupancy?.moveInDate).format("YYYY-MM-DD");

      rentEndDate = moment(occupancy?.moveInDate).date(occupancy.rentalCycle).isAfter(moment(occupancy.moveInDate), "date")
        ? moment(occupancy?.moveInDate).date(occupancy.rentalCycle).subtract(1, "day").format("YYYY-MM-DD")
        : moment(occupancy?.moveInDate).date(occupancy.rentalCycle).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
    }

    let advanceRentSlots = [];
    let rentDuration = 1;
    let rentalMonthToBeAdded = 0;
    const globalRentStartDate = rentStartDate;

    while (rentDuration < 13) {

      if (moment(rentStartDate).isAfter(moment(), "date")) {
        rentalMonthToBeAdded += 1;
      }

      let rentObj = {
        rentDuration: rentDuration,
        rentStartDate: globalRentStartDate,
        rentEndDate: rentEndDate,
        rentalMonthAdd: moment(rentStartDate).isAfter(moment(), "date")
          ? rentalMonthToBeAdded
          : 0,
      };

      advanceRentSlots.push(rentObj);

      rentStartDate = moment(rentEndDate).add(1, "days").format("YYYY-MM-DD");
      rentEndDate = moment(rentStartDate).add(1, "month").subtract(1, "day").format("YYYY-MM-DD");
      rentDuration += 1;
    }

    let excessAmount = 0;

    let collection = 0;
    let advance = 0;
    let total = 0;
    if (isEvicted || (!isEvicted && occupancy.status === CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT)) {
      const { totalCollection, advancePaid } =
        await ledgerDB.getTotalByTenantIdForMovingOut({
          tenantId: occupancy.tenantId,
          clientId,
        });
      collection = totalCollection;
      advance = advancePaid;

      collection = collection || 0;
      advance = advance || 0;

      if (advance < 0 && collection >= 0) {
        total = Number(collection) + Number(advance);
      } else if (advance < 0 && collection < 0) {
        total = Number(advance);
      } else {
        total = Number(collection);
      }
    }
    excessAmount = total < 0 ? Math.abs(total) : 0;

    const excessBalance = await ledgerDB.getPreviousBalance({
      tenantId,
      clientId,
    });

    const lastRentPaid = await transactionDB.getLastRentPaidByClientIdAndTenantId({
      tenantId: occupancy?.tenantId,
      clientId: occupancy?.clientId,
    });

    const bankAccounts = await bankDB.getByClientIdLimitedFields({ clientId });

    tenant.status = mapOccupancyStatusToTenantStatus(occupancy.status);
    tenant.statusx = occupancy.status;
    tenant.kycStatus = occupancy.kycStatus;

    let isRefunded = 0;

    const settlementSummaryDoc = await documentDB.getIDByTypeAndStatus({
      tenantId,
      clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SETTLEMENT_SUMMARY,
      moveOut: 1,
      status: CONSTANTS.DOCUMENT_STATUS.UNVERIFIED,
    });


    if (settlementSummaryDoc) {
      isRefunded = 2;
    }
    let tenantRefundedAmount = 0;
    if (isEvicted) {
      // const isRefundedEntry = await ledgerDB.getLastRefundEntry({
      //   tenantId,
      //   clientId,
      //   moveInDate: occupancy.moveInDate,
      // });
      // if (isRefundedEntry) {
      if (occupancy.refundStatus === CONSTANTS.REFUND_STATUS.PROCESSED) {
        tenantRefundedAmount = await transactionDB.getRefundedAmountByClientIdAndTenantId({
          tenantId,
          clientId,
          createdAt: occupancy.moveInDate,
        })
        isRefunded = 1;
      }

      // const refundForfeitEntry = await ledgerDB.getLastRefundForfeitEntry({
      //   tenantId,
      //   clientId,
      //   moveInDate: occupancy.moveInDate,
      // });
      if (occupancy.refundStatus === CONSTANTS.REFUND_STATUS.FORFEITED) {
        isRefunded = 3; //Refund Forfeited
      }
    }

    const canToggleDue = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.CAN_HIDE_DUES,
    });

    let bookingLink: string | null = `${process.env.PAYU_WEB_CHECKOUT_URL}/${occupancy.gId}/b`;
    if (client?.paymentLinkBaseUrl && client?.paymentLinkBaseUrl.trim() !== "") {
      bookingLink = `${client?.paymentLinkBaseUrl.trim()}/${occupancy.gId}/b`;
    }

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId: client.id,
      propId: property.id,
    });

    let footerText = propSettings.footer || "The Kipinn Team";

    let bookingMessage: string | null = CONSTANTS.MSG.SHARE_BOOKING_PAYMENT_WITH_LINK
      .replace("{#var1#}", tenant?.name || "")
      .replace("{#var2#}", occupancy.bookingAmt.toString())
      .replace("{#var3#}", bookingLink)
      .replace("{#var4#}", footerText || "");

    const bookingAmtPaid = await ledgerDB.isBookingPaid({
      clientId: occupancy.clientId,
      tenantId: occupancy.tenantId,
      moveInDate: moment(occupancy.moveInDate).format("YYYY-MM-DD"),
    });

    if (bookingAmtPaid) {
      bookingLink = null;
      bookingMessage = null;
    }

    if (!Number(occupancy.bookingAmt) || Number(occupancy.bookingAmt) <= 0) {
      bookingLink = null;
      bookingMessage = null;
    }

    if (isEvicted) {
      bookingLink = null;
      bookingMessage = null;
    }

    if (
      property.isOnlinePaymentEnabled !== 1 ||
      occupancy.isOnlinePaymentEnabled !== 1
    ) {
      bookingLink = null;
      bookingMessage = null;
    }

    let securityRemaining = await ledgerDB.getUnUsedSecurityByTenantIdAndClientId({
      clientId,
      tenantId: occupancy.tenantId,
      createdAt: occupancy.createdAt,
    });

    if (isEvicted) securityRemaining = 0;

    const hiddenDues = await hiddenDuesDB.getByClientIdAndTenantId({ clientId, tenantId });
    const totalHiddenDues = await hiddenDuesDB.getTotalByClientIdAndTenantId({ clientId, tenantId });

    let tenants = await occupancyDB.getAllByBedId({
      bedId: occupancy.bedId,
    });

    if (tenants) {
      tenants.sort((a: any, b: any) =>
        moment(a.moveInDate).diff(moment(b.moveInDate))
      );
    }

    let maxMoveInDate = null;
    let minMoveInDate = null;
    let maxMoveOutDate = null;
    let minMoveOutDate = null;

    if (occupancy.moveOutDate) {
      maxMoveInDate = occupancy.moveOutDate;
    }

    if (moment(occupancy.moveInDate).isBefore(moment(), "date")) {
      minMoveOutDate = moment().format("YYYY-MM-DD");
    }

    if (tenants) {
      const currentOccupancyIndex = tenants.findIndex(
        (t: any) => t.id === occupancy.id
      );

      const previousTenant = tenants[currentOccupancyIndex - 1];
      if (previousTenant && previousTenant.moveOutDate) {
        minMoveInDate = moment(previousTenant.moveOutDate)
          .add(1, "day")
          .format("YYYY-MM-DD");
      }

      const nextTenant = tenants[currentOccupancyIndex + 1];
      if (nextTenant && nextTenant.moveInDate) {
        maxMoveOutDate = moment(nextTenant.moveInDate)
          .subtract(1, "day")
          .format("YYYY-MM-DD");
      }
    }

    const commission = await staffCommissionDB.getByClientIdAndTenantId({
      clientId,
      tenantId,
      occupancyId: occupancy.id,
    });

    const staffs = await staffDB.getActiveByClientId({ clientId });

    const advancePaidRents = await ledgerDB.getAdvancePaidRent({
      tenantId,
      clientId,
    });

    let advanceRentPaidAmount = 0;
    let advanceRentPaidMonths = 0;
    const uniqueRentMonths = new Set<string>();
    let combinedRentMonths = 0;

    if (advancePaidRents && advancePaidRents.length > 0 && Number(occupancy.rentalType) > 0) {
      for (let rent of advancePaidRents) {
        advanceRentPaidAmount += Math.abs(rent.amount);
        uniqueRentMonths.add(rent.referenceId);

        let startDate = moment(rent.rentStartDate).date(occupancy.rentalCycle);

        const diff = moment(rent.rentEndDate).diff(rent.rentStartDate, "days");

        if (Number(diff) > 31) {
          let loopCounter = 0;
          while (moment(startDate).isSameOrBefore(moment(rent.rentEndDate), "days")) {
            combinedRentMonths += 1;
            startDate = moment(startDate).add(occupancy.rentalType, "months");
            loopCounter++;
            if (loopCounter >= 50) {
              log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Error, Breaking Out Of While Loop`)
              break;
            }
          }
        }

        if (combinedRentMonths > 0) combinedRentMonths -= 1;
      }
    }

    advanceRentPaidMonths = uniqueRentMonths.size + combinedRentMonths;

    let account = await tenantBankDB.getByClientIdAndTenantId ({clientId, tenantId});

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Tenant details sent successfully`
    );
    let refundAmount = excessAmount > 0 ? Math.abs(excessAmount) : 0;
    if (1 === isRefunded) {
      refundAmount = tenantRefundedAmount;
    }
    return res.status(200).json({
      msg: "Tenant details sent successfully",
      data: {
        ...tenant,
        payoutBalance,
        payoutData,
        isMultipleOccupancy,
        canAssignFullRoom,
        occupiedBedCount,
        securityRemaining: securityRemaining || 0,
        totalDues: totalDuesAmt,
        totalCollection: Number(totalCollectedAmt) || 0,
        occupancy,
        flatName, //Sukhbir Remove this
        docs: docs || [],
        dues: dues || [],
        tenantGuardian: tenantGuardian || null,
        transactions: transactions || [],
        securities: securities || [],
        securityPaid: secAmount,
        moveInInspection: moveInInspection || null,
        rentAgreementRecords: rentAgreementRecords || [],
        lastSavedElecReading: Number(lastSavedElecReading) || 0,
        rentStartDate,
        advanceRentSlots: advanceRentSlots || [],
        tenantPreference: property?.tenantPreference || 1,
        excessBalance: excessBalance > 0 ? 0 : Math.abs(excessBalance),
        refundAmount: refundAmount,
        lastRentPaid: lastRentPaid?.collectionDate ? lastRentPaid?.collectionDate : null,
        bankAccounts,
        isRefunded,
        settlementSummaryDoc: settlementSummaryDoc?.value || null,
        canHideDues: canToggleDue ? Number(canToggleDue.value) : 0,
        bookingLink,
        bookingMessage,
        hiddenDues: hiddenDues || [],
        totalHiddenDues: totalHiddenDues || 0,
        maxMoveInDate,
        minMoveInDate,
        maxMoveOutDate,
        minMoveOutDate,
        staffs: staffs || [],
        commission: commission || [],
        advanceRentPaidAmount: advanceRentPaidAmount || 0,
        advanceRentPaidMonths: advanceRentPaidMonths || 0,
        account: account || [],
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.Search = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "Search";

  try {
    const { searchVal } = req.query;
    const userType = req.userType;

    log.info(`[${C}], [${F}], Search Value [${searchVal}]`);

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {

      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Staff Id [${req.id}], No Staff Found`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (staff.role !== CONSTANTS.STAFF_ROLES.ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.WARDEN && staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE && staff.role !== CONSTANTS.STAFF_ROLES.FINANCE_ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.SALES_HEAD && staff.role !== CONSTANTS.STAFF_ROLES.SUPER_ADMIN) {
        log.info(
          `[${C}], [${F}], User Type [${userType}], User Id [${req.id}], Staff Not Authorized`
        );

        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }

      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
    }

    let occupancies = await occupancyDB.getBySearchValAndClientId({
      clientId: clientId,
      searchVal: searchVal,
    });

    if (occupancies) {
      for (let occupancy of occupancies) {
        const room = await roomDB.getById({ id: occupancy.roomId });
        occupancy.roomNum = room.roomNum;

        const property = await propertyDB.getById({ id: occupancy.propId });
        occupancy.propName = property.name;
        if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: room.flatId });
          occupancy.flatName = name;
        } else {
          occupancy.flatName = room.floor;
        }
      }
    }

    log.info(`[${C}], [${F}], Client Id [${clientId}], Search Value [${searchVal}], Tenants details sent successfully`);

    return res.status(200).json({
      msg: "Tenants details sent successfully",
      list: occupancies || [],
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetExpiringAgreementsForWeb = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "GetExpiringAgreementsForWeb";

  try {
    const { propId, filter } = req.query;
    const userType = req.userType;

    //let clientId = req.id;
    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    let list = [];
    let totalCount = { count: 0 };
    let curMonthExpiringCount = { count: 0 };
    let expiredCount = { count: 0 };
    let nextMonthExpiringCount = { count: 0 };


    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      //log.info(
      //  `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Filter [${filter}], Client Requested....`
      //);
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );

      const client = await clientDB.getById({ id: clientId });
      if (!client) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], Filter [${filter}], No Client Found`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (Number(propId) && Number(propId) !== 0) {
        if (filter === "ECM") {
          //expiring current month
          list = await occupancyDB.getTenantsForCurMonthExpiringAgreements({
            propId,
          });
        } else if (filter === "E") {
          //expired
          list = await occupancyDB.getTenantsExpiredAgreements({
            propId,
          });
        } else if (filter === "ENM") {
          //expiring next month
          list = await occupancyDB.getTenantsWithNextMonthExpiringAgreements({
            propId,
          });
        } else {
          list = await occupancyDB.getTenantsForExpiringAgreements({
            propId,
          });
        }
        // totalCount = await occupancyDB.getTenantsForExpiringAgreementsCount({
        //   propId,
        // });
        totalCount = list.length > 0 ? { count: list.length } : { count: 0 };
        curMonthExpiringCount =
          await occupancyDB.getTenantsForCurMonthExpiringAgreementsCount({
            propId,
          });
        expiredCount = await occupancyDB.getTenantsExpiredAgreementsCount({
          propId,
        });
        nextMonthExpiringCount = await occupancyDB.getTenantsForNextMonthExpiringAgreementsCount({
          propId,
        });
      } else {
        if (filter === "ECM") {
          //expiring current month
          list = await occupancyDB.getTenantsForCurMonthExpiringAgreementsByClientId({
            clientId,
          });
        } else if (filter === "E") {
          //expired
          list = await occupancyDB.getTenantsExpiredAgreementsByClientId({
            clientId,
          });
        } else if (filter === "ENM") {
          //expiring next month
          list = await occupancyDB.getTenantsWithNextMonthExpiringAgreementsByClientId({
            clientId,
          });
        } else {
          list = await occupancyDB.getTenantsForExpiringAgreementsByClientId({
            clientId,
          });
        }
        // totalCount = await occupancyDB.getTenantsForExpiringAgreementsCountByClientId({
        //   clientId,
        // });
        totalCount = list.length > 0 ? { count: list.length } : { count: 0 };
        curMonthExpiringCount =
          await occupancyDB.getTenantsForCurMonthExpiringAgreementsCountByClientId({
            clientId,
          });
        expiredCount = await occupancyDB.getTenantsExpiredAgreementsCountByClientId({
          clientId,
        });
        nextMonthExpiringCount = await occupancyDB.getTenantsForNextMonthExpiringAgreementsCountClientId({
          clientId,
        });
      }
    } else {
      const staffId = req.id;
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Staff Id [${staffId}], Prop Id [${propId}], Filter [${filter}], Staff Requested....`
      );

      const staff = await staffDB.getById({ id: staffId });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Staff Id [${staffId}], Prop Id [${propId}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      const staffLinkedProps = await propertyDB.getPropsByStaffId({
        staffId,
      });

      if (staffLinkedProps) {
        const propertiesIds = staffLinkedProps
          .map((prop: propertiesTypes) => prop.id)
          .join(",");

        if (Number(propId) && Number(propId) !== 0) {
          if (filter === "ECM") {
            //expiring current month
            list = await occupancyDB.getTenantsForCurMonthExpiringAgreements({
              propId,
            });
          } else if (filter === "E") {
            //expired
            list = await occupancyDB.getTenantsExpiredAgreements({
              propId,
            });
          } else if (filter === "ENM") {
            //expiring next month
            list = await occupancyDB.getTenantsWithNextMonthExpiringAgreements({
              propId,
            });
          } else {
            list = await occupancyDB.getTenantsForExpiringAgreements({
              propId,
            });
          }
          // totalCount = await occupancyDB.getTenantsForExpiringAgreementsCount({
          //   propId,
          // });
          totalCount = list.length > 0 ? { count: list.length } : { count: 0 };
          curMonthExpiringCount =
            await occupancyDB.getTenantsForCurMonthExpiringAgreementsCount({
              propId,
            });
          expiredCount = await occupancyDB.getTenantsExpiredAgreementsCount({
            propId,
          });
          nextMonthExpiringCount = await occupancyDB.getTenantsForNextMonthExpiringAgreementsCount({
            propId,
          });
        } else {
          if (filter === "ECM") {
            //expiring current month
            list = await occupancyDB.getTenantsForCurMonthExpiringAgreementsByClientIdForStaff({
              clientId,
              propertiesIds,
            });
          } else if (filter === "E") {
            //expired
            list = await occupancyDB.getTenantsExpiredAgreementsByClientIdForStaff({
              clientId,
              propertiesIds,
            });
          } else if (filter === "ENM") {
            //expiring next month
            list = await occupancyDB.getTenantsWithNextMonthExpiringAgreementsByClientIdForStaff({
              clientId,
              propertiesIds,
            });
          } else {
            list = await occupancyDB.getTenantsForExpiringAgreementsByClientIdForStaff({
              clientId,
              propertiesIds,
            });
          }
          // totalCount = await occupancyDB.getTenantsForExpiringAgreementsCountByClientId({
          //   clientId,
          // });
          totalCount = list.length > 0 ? { count: list.length } : { count: 0 };
          curMonthExpiringCount =
            await occupancyDB.getTenantsForCurMonthExpiringAgreementsCountByClientIdForStaff({
              clientId,
              propertiesIds,
            });
          expiredCount = await occupancyDB.getTenantsExpiredAgreementsCountByClientIdForStaff({
            clientId,
            propertiesIds,
          });
          nextMonthExpiringCount = await occupancyDB.getTenantsForNextMonthExpiringAgreementsCountClientIdForStaff({
            clientId,
            propertiesIds,
          });
        }
      }
    }

    if (list && list.length > 0) {
      for (let item of list) {
        if (item.propType === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: item.flatId });
          item.flatName = name;
        } else {
          item.flatName = item.floor;
        }

        const agreementSetting = await argeementRenewConfigDB.getByClientIdAndPropId({
          clientId: clientId,
          propId: item.propId,
        });

        item.agreementSetting = agreementSetting ? agreementSetting : [];
      }
    }

    const stats = {
      total: totalCount?.count || 0,
      curMonthExpiring: curMonthExpiringCount?.count || 0,
      expired: expiredCount?.count || 0,
      nextMonthExpiring: nextMonthExpiringCount?.count || 0,
    };

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Prop Id [${propId}], List sent successfully`
    );

    return res.status(200).json({
      msg: "List sent successfully",
      list: list || [],
      stats,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.WebPaymentCheckout = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "WebPaymentCheckout";

  try {
    const { encryptedId } = req.params;

    log.info(`[${C}], [${F}], Encrypted Id [${encryptedId}]`);

    const tenant = await occupancyDB.getByGId({ gId: encryptedId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Encrypted Id [${encryptedId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    // const occupancy = await occupancyDB.getByTenantIdForWeb({
    //   tenantId: tenant.id,
    // });
    //Pallav - Multi tenant senerio
    const occupancy = await occupancyDB.getByTenantIdAndClientIdForWeb({
      tenantId: tenant.tenantId,
      clientId: tenant.clientId,
    });

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Encrypted Id [${encryptedId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (occupancy.flatId !== null) {
      const flat = await flatDB.getById({ id: occupancy.flatId });
      occupancy.flatName = flat?.name || "";
    }

    const client = await clientDB.getById({
      id: occupancy.clientId,
    });
    if (!client) {
      log.info(
        `[${C}], [${F}], Encrypted Id [${encryptedId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const dues = await duesDB.getOldestRentDue({
      tenantId: tenant.id,
      clientId: occupancy.clientId,
    });

    const paymentMethods = [
      {
        upi: client.upiEnabled,
        charges: (dues.balance * (client.upiCharges / 100)).toFixed(2),
      },
      {
        card: client.creditCardEnabled,
        charges: (dues.balance * (client.creditCardCharges / 100)).toFixed(2),
      },
      {
        netbanking: client.netBankingEnabled,
        charges: (dues.balance * (client.otherCharges / 100)).toFixed(2),
      },
    ];

    const token = jwt.sign(
      {
        id: tenant?.id,
        type: CONSTANTS.USER_TYPE.TENANT,
        clientId: tenant?.clientId,
      },
      process.env.TOKEN_SECRET!,
      { expiresIn: process.env.TOKEN_EXPIRY }
    );

    log.info(
      `[${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${occupancy.tenantId}], Encrypted Id [${encryptedId}], Tenant details sent successfully`
    );

    return res.status(200).json({
      msg: "Tenant details sent successfully",
      data: {
        token,
        ...tenant,
        totalDues: dues.balance || 0,
        occupancy,
        dues: dues || [],
        paymentMethods: paymentMethods || [],
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.WebPaymentCheckoutX = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "WebPaymentCheckoutX";
  try {
    const { encryptedId, dueId } = req.params;

    let isBookingPayment = false;
    let isEvicted = 0;

    log.info(
      `[${C}], [${F}], Encrypted Id [${encryptedId}], Due Id [${dueId}], Payment Initiated Via Payment Web Link`
    );
    let tenant = await occupancyDB.getByGIdX({ gId: encryptedId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Encrypted Id [${encryptedId}], No GID Found In occupancy, Checking MoveOut Table`
      );

      tenant = await moveOutDB.getByGIdX({ gId: encryptedId });
      if (!tenant) {
        log.info(
          `[${C}], [${F}], Encrypted Id [${encryptedId}], No GID Found In Occupancy Or MoveOut`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      isEvicted = 1;
    }

    let occupancy: any = false;
    //Pallav - Multi tenant senerio
    if (isEvicted === 1) {
      occupancy = await moveOutDB.getByTenantIdAndClientIdForWeb({
        tenantId: tenant.tenantId,
        clientId: tenant.clientId,
      });
    } else {
      occupancy = await occupancyDB.getByTenantIdAndClientIdForWeb({
        tenantId: tenant.tenantId,
        clientId: tenant.clientId,
      });
    }

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Encrypted Id [${encryptedId}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }
    if (occupancy.flatId !== null) {
      const flat = await flatDB.getById({ id: occupancy.flatId });
      occupancy.flatName = flat?.name || "";
    }
    const client = await clientDB.getById({
      id: occupancy.clientId,
    });
    if (!client) {
      log.info(
        `[${C}], [${F}], Encrypted Id [${encryptedId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }
    //let paymentGateway = client?.paymentGateway || 0;
    // const rentDue = await duesDB.getOldestRentDue({
    //   tenantId: tenant.id,
    //   clientId: occupancy.clientId,
    // });
    let tenantDues = [];
    if (Number(dueId) && dueId !== "") {
      let due = await duesDB.getById({
        id: dueId,
      });
      if (isEvicted === 1) {
        due = await moveOutDuesDB.getById({
          id: dueId,
        });
      }
      tenantDues.push(due);
      if (due.type === CONSTANTS.DUES_TYPES.RENT) {
        const ledgerReferenceId = `LEDGF${due.tenantId}${CONSTANTS.DUES_TYPES.FINE}${due.propId}${due.id}`;
        const fineDue = await duesDB.getByTenantIdAndLedgerReferenceId({
          tenantId: due.tenantId,
          ledgerReferenceId: ledgerReferenceId,
        });
        if (fineDue) {
          tenantDues.push(fineDue[0]);
        }

      }
    } else if (dueId === "b") {
      //booking amt payment
      if (!Number(occupancy.bookingAmt) || Number(occupancy.bookingAmt) <= 0) {
        log.info(
          `[${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${occupancy.tenantId}], No Booking Amount Is Request By Client, Blocking Request`
        );
        return res.status(200).json({
          msg: "No booking due found",
          data: {
            ...tenant,
            occupancy,
            dues: [],
          },
          isSuccess: true,
        });
      }

      const bookingAmtPaid = await ledgerDB.isBookingPaid({
        clientId: occupancy.clientId,
        tenantId: occupancy.tenantId,
        moveInDate: moment(occupancy.moveInDate).format("YYYY-MM-DD"),
      });
      if (bookingAmtPaid) {
        log.info(
          `[${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${occupancy.tenantId}], Booking Amt Already Paid, Blocking Request`
        );
        return res.status(200).json({
          msg: "Booking amount already paid.",
          data: {
            ...tenant,
            occupancy,
            dues: [],
            isBookingPayment: true,
          },
          isSuccess: true,
        });

      }

      if (occupancy.bookingAdjustType === CONSTANTS.BOOKING_ADJUST_TYPE.RENT) {
        const rentDue = await duesDB.getByTenantIdAndClientIdAndType({
          clientId: occupancy.clientId,
          tenantId: occupancy.tenantId,
          type: CONSTANTS.DUES_TYPES.RENT,
        });
        if (!rentDue) {
          log.info(
            `[${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${occupancy.tenantId}], Rent Already Fully Paid, Blocking Request`
          );
          return res.status(200).json({
            msg: "Booking amount already paid.",
            data: {
              ...tenant,
              occupancy,
              dues: [],
              isBookingPayment: true,
            },
            isSuccess: true,
          });

        } else if (rentDue && rentDue[0].balance < occupancy.bookingAmt) {
          // log.info(
          //   `[${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${occupancy.tenantId}], Security Already Paritally Paid, Blocking Request`
          // );
          // return res.status(200).json({
          //   msg: "Booking amount already paid.",
          //   data: {
          //     ...tenant,
          //     occupancy,
          //     dues: [],
          //     isBookingPayment: true,
          //   },
          //   isSuccess: true,
          // });

          log.info(`
            [${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${occupancy.tenantId}], Booking Amount Greater Than Rent, Excess Payment`
          );

        }
        tenantDues = rentDue;
      } else {
        const securityDue = await duesDB.getByTenantIdAndClientIdAndType({
          clientId: occupancy.clientId,
          tenantId: occupancy.tenantId,
          type: CONSTANTS.DUES_TYPES.SECURITY,
        });
        if (!securityDue) {
          log.info(
            `[${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${occupancy.tenantId}], Security Already Fully Paid, Blocking Request`
          );
          return res.status(200).json({
            msg: "Booking amount already paid.",
            data: {
              ...tenant,
              occupancy,
              dues: [],
              isBookingPayment: true,
            },
            isSuccess: true,
          });

        } else if (securityDue && securityDue[0].balance < occupancy.bookingAmt) {
          log.info(
            `[${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${occupancy.tenantId}], Security Already Paritally Paid, Blocking Request`
          );
          return res.status(200).json({
            msg: "Booking amount already paid.",
            data: {
              ...tenant,
              occupancy,
              dues: [],
              isBookingPayment: true,
            },
            isSuccess: true,
          });

        }

        tenantDues = securityDue;
      }
      isBookingPayment = true;
    } else {
      // tenantDues = await duesDB.getByTenantId({
      //   tenantId: tenant.id,
      // });

      //Pallav - Multi tenant senerio
      if (isEvicted === 1) {
        tenantDues = await moveOutDuesDB.getByTenantIdAndClientId({
          tenantId: tenant.tenantId,
          clientId: tenant.clientId,
        });
      } else {
        tenantDues = await duesDB.getByTenantIdAndClientId({
          tenantId: tenant.tenantId,
          clientId: tenant.clientId,
        });
      }
    }
    let total = 0;
    let applyCharges = true;
    if (tenantDues && tenantDues.length > 0) {
      tenantDues.forEach((due: { balance: any; type: any }): any => {
        if (Number(due.type) === CONSTANTS.DUES_TYPES.RENT) {
          applyCharges = false;
        }
        total += Number(due.balance) || 0;
      });
    }

    if (dueId === 'b') {
      total = occupancy.bookingAmt;
    }

    const property = await propertyDB.getById({
      id: occupancy.propId,
    });
    if (!property) {
      log.info(
        `[${C}], [${F}], Client Id [${occupancy.clientId}], Property Id [${occupancy.propId}], No Property Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let gstCharges = 0;
    // if (Number(property.isGstEnabled) === 1 && Number(occupancy.isGstEnabled) === 1) {
    if (Number(property.isGstEnabled) === 1) {
      gstCharges = Number((total * (property?.gstCharges / 100)).toFixed(2)) || 0;
    }

    let upiCharges: number = 0;
    let nbCharges: number = 0;
    let ccCharges: number = 0;
    let getChargesType = await clientConfigDB.getClientConfig({
      clientId: client.id,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.TRANSACTION_CHARGES,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.TRANSACTION_CHARGES.TYPE
    });
    let getChargesThrashHold = await clientConfigDB.getClientConfig({
      clientId: client.id,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.TRANSACTION_CHARGES,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.TRANSACTION_CHARGES.THRASHOLD
    });
    log.info(`[${C}], [${F}], Tenant Id [${occupancy.tenantId}], Client Id [${client.id}], Total Amount [${total}], Charge Type [${getChargesType?.value}], Thrashhold Amount [${getChargesThrashHold?.value}]`);
    if (false == getChargesType || getChargesType?.value == null || getChargesType?.value == "" || Number(getChargesType?.value) === CONSTANTS.CLIENT_CONFIG_VALUE.TRANSACTION_CHARGES.PERCENTAGE) {
      if (getChargesThrashHold && Number(total) > Number(getChargesThrashHold?.value)) {
        ccCharges = 0;
        nbCharges = 0;
        upiCharges = 0;
      } else {
        ccCharges = Number(((total + gstCharges) * (client.creditCardCharges / 100)).toFixed(2));
        nbCharges = Number(((total + gstCharges) * (client.otherCharges / 100)).toFixed(2));
        upiCharges = Number((((total + gstCharges) * client.upiCharges) / 100).toFixed(2));
      }
    } else {
      if (getChargesThrashHold && Number(total) > Number(getChargesThrashHold?.value)) {
        ccCharges = 0;
        nbCharges = 0;
        upiCharges = 0;
      } else {
        ccCharges = Number(client.creditCardCharges);
        nbCharges = Number(client.otherCharges);
        upiCharges = Number(client.upiCharges);
      }
    }

    const paymentMethods = [
      {
        upi: client.upiEnabled,
        charges: upiCharges,
      },
      {
        card: client.creditCardEnabled,
        charges: ((total + gstCharges) * (client.creditCardCharges / 100)).toFixed(2),
        //charges: ccCharges,
      },
      {
        netbanking: client.netBankingEnabled,
        // charges: ((total + gstCharges) * (client.otherCharges / 100)).toFixed(2),
        charges: nbCharges,
      },
    ];
    //     const paymentMethods = [
    //     {
    //       upi: client.upiEnabled,
    //       charges: ((total + gstCharges) * (client.upiCharges / 100)).toFixed(2),
    //     },
    //     {
    //       card: client.creditCardEnabled,
    //       charges: ((total + gstCharges) * (client.creditCardCharges / 100)).toFixed(2),
    //     },
    //     {
    //       netbanking: client.netBankingEnabled,
    //       charges: ((total + gstCharges) * (client.otherCharges / 100)).toFixed(2),
    //     },
    // ];

    const token = jwt.sign(
      {
        id: tenant?.tenantId,
        type: CONSTANTS.USER_TYPE.TENANT,
        clientId: tenant?.clientId,
        isEvicted: isEvicted === 1 ? true : false,
      },
      process.env.TOKEN_SECRET!,
      { expiresIn: process.env.TOKEN_EXPIRY }
    );
    log.info(
      `[${C}], [${F}], Client Id [${occupancy.clientId}], Tenant Id [${occupancy.tenantId}], Encrypted Id [${encryptedId}], Is Evicted Tenant [${isEvicted}], Tenant details sent successfully`
    );

    let easebuzzCharges = 0;
    if (Number(total) < 2000 && applyCharges) {
      easebuzzCharges = 5.90;
    }


    let dueSelection = await clientConfigDB.getClientConfig({
      clientId: client.id,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.ENABLE_DUE_SELECTION
    });
    let enableDueSelection = false;
    if (dueSelection && Number(dueSelection.value) === 1) {
      enableDueSelection = true;
    }

    let offlinePayment = await clientConfigDB.getClientConfig({
      clientId: client.id,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.ENABLE_LINK_CASH_PAYMENT
    });
    let offlineEnabled = false;
    if (offlinePayment && Number(offlinePayment.value) === 1) {
      offlineEnabled = true;
    }

    let emailRequiredConfig = await clientConfigDB.getClientConfig({
      clientId: client.id,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.PAYMENT_EMAIL,
    });

    let emailRequired = true;

    if (emailRequiredConfig && Number(emailRequiredConfig?.value) === 0) emailRequired = false;

    let paymentGateway = client.paymentGateway || 0;
    if (property.paymentGateway && Number(property.paymentGateway) > 0) {
      paymentGateway = property.paymentGateway;
    }
    //delete property.paymentGateway;
    let paymentEnabled = true
    if (!property?.isOnlinePaymentEnabled || !occupancy?.isOnlinePaymentEnabledTenant) {
      paymentEnabled = false
    }
    return res.status(200).json({
      msg: "Tenant details sent successfully",
      data: {
        token,
        gateway: paymentGateway,
        paymentEnabled,
        enableDueSelection,
        propGId: property?.gId || '',
        ...tenant,
        totalDues: total || 0,
        occupancy,
        dues: tenantDues || [],
        paymentMethods: paymentMethods || [],
        gstAmount: gstCharges || 0,
        isGSTInvoice: property.isGstEnabled || 0,
        //bookingAmt: dueId === "b" ? occupancy.bookingAmt : 0,
        isPartialPaymentEnabled: property?.isPartialPaymentEnabled ?? 0,
        isBookingPayment,
        name: tenant?.tenantName || "",
        easebuzzCharges,
        emailRequired,
        offlineEnabled,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListParticularAttendance = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "ListParticularAttendance";

  try {
    const { m, y } = req.query;
    const { tenantId } = req.params;

    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Month [${m}], Year [${y}]`
    );

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else if (userType === CONSTANTS.USER_TYPE.TENANT) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${req.id}], Tenant Requesting`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"
        } Requested....`
      );
    }
    const tenant = await tenantDB.getById({ id: tenantId });

    const date = moment(`${y}-${m}-01`).format("YYYY-MM-DD");
    let dateArray = [];
    let month = moment(date).month() + 1;
    let year = moment(date).year();
    let endDate = moment(date).endOf("month").format("YYYY-MM-DD");
    if (moment(date).month() === moment().month()) {
      endDate = moment().format("YYYY-MM-DD");
    }
    let startDate = moment(`${year}-${month}-01`).format("YYYY-MM-DD");

    while (startDate <= endDate) {
      dateArray.push(endDate);
      endDate = moment(endDate).subtract(1, "days").format("YYYY-MM-DD");
    }

    let tenantAttendances = await tenantAttendanceDB.getByTenantIdForMonth({
      tenantId,
      date: date,
    });

    const attendanceMap = tenantAttendances.reduce((acc: any, cur: any) => {
      acc[cur.attendanceDate] = cur;
      return acc;
    }, {});

    tenantAttendances = dateArray.map((date) => ({
      // sno: dateArray.indexOf(date) + 1,
      id: attendanceMap[date]?.id || null,
      tenantId: tenant.id,
      tenantName: tenant.name,
      clientId: clientId,
      propId: attendanceMap[date]?.propId || null,
      roomId: attendanceMap[date]?.roomId || null,
      propName: attendanceMap[date]?.propName || null,
      attendance: attendanceMap[date]?.status || 0,
      attendanceDate: date,
      attendanceTime: attendanceMap[date]
        ? attendanceMap[date]?.attendanceTime
        : "",
      isAttendanceMarked: attendanceMap[date] ? 1 : 0,
    }));

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Month [${m}], Year [${y}], Particular Tenant Attendance List Fetched Successfully`
    );

    return res.status(200).json({
      msg: "Staff attendance list fetched successfully",
      isSuccess: true,
      data: tenantAttendances || [],
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListAttendanceStatsGroupByProp = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "ListAttendanceStatsGroupByProp";

  try {
    const { date, pageNum, filter, s } = req.query;
    const userType = req.userType;

    log.info(`[${C}], [${F}], Date [${date}], Page Num [${pageNum}], Filter [${filter}], Search Val [${s}]`);

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    let data = [];
    const LIMIT = 10;

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
      if (s && s !== "" && s !== "undefined" && s !== " ") {
        data = await tenantAttendanceDB.getAttendanceStatsForClientAndDateAndSearchVal({
          clientId,
          date,
          pageNum: Number(pageNum) || 1,
          limit: LIMIT,
          searchVal: s,
        });
      } else {
        data = await tenantAttendanceDB.getAttendanceStatsForClientAndDate({
          clientId,
          date,
          pageNum: Number(pageNum) || 1,
          limit: LIMIT,
        });
      }
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"
        } Requested....`
      );
      if (s && s !== "" && s !== "undefined" && s !== " ") {
        data = await tenantAttendanceDB.getAttendanceStatsForStaffAndDateAndSearchVal({
          staffId: staff.id,
          clientId: clientId,
          date,
          pageNum: Number(pageNum) || 1,
          limit: LIMIT,
          searchVal: s,
        });
      } else {
        data = await tenantAttendanceDB.getAttendanceStatsForStaffAndDate({
          staffId: staff.id,
          clientId: clientId,
          date,
          pageNum: Number(pageNum) || 1,
          limit: LIMIT,
        });
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Date [${date}], Attendance Stats Grouped By Property Fetched Successfully`
    );
    return res.status(200).json({
      msg: "Attendance stats fetched successfully",
      isSuccess: true,
      data: data || [],
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListAttendanceStatsGroupByPropForWeb = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "ListAttendanceStatsGroupByPropForWeb";

  try {
    const { date, filter, s } = req.query;
    const userType = req.userType;

    log.info(`[${C}], [${F}], Date [${date}], Filter [${filter}], Search Val [${s}]`);

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    let data = [];

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
      if (s && s !== "" && s !== "undefined" && s !== " ") {
        data = await tenantAttendanceDB.getAttendanceStatsForClientAndDateAndSearchValForWeb({
          clientId,
          date,
          searchVal: s,
        });
      } else {
        data = await tenantAttendanceDB.getAttendanceStatsForClientAndDateForWeb({
          clientId,
          date,
        });
      }
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"
        } Requested....`
      );
      if (s && s !== "" && s !== "undefined" && s !== " ") {
        data = await tenantAttendanceDB.getAttendanceStatsForStaffAndDateAndSearchValForWeb({
          staffId: staff.id,
          clientId: clientId,
          date,
          searchVal: s,
        });
      } else {
        data = await tenantAttendanceDB.getAttendanceStatsForStaffAndDateForWeb({
          staffId: staff.id,
          clientId: clientId,
          date,
        });
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Date [${date}], Attendance Stats Grouped By Property Fetched Successfully`
    );
    return res.status(200).json({
      msg: "Attendance stats fetched successfully",
      isSuccess: true,
      data: data || [],
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListAttendanceForProperty = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "ListAttendanceForProperty";

  try {
    const { propId, date, s, pageNum } = req.query;
    const userType = req.userType;
    log.info(`[${C}], [${F}], Property Id [${propId}], Date [${date}], Search Val [${s}], Page Num [${pageNum}]`);

    const LIMIT = 10;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"
        } Requested....`
      );
    }

    let data = [];

    if (s && s !== "" && s !== "undefined" && s !== " ") {
      data = await tenantAttendanceDB.ListAttendanceForPropertyWithSearch({
        propId: Number(propId),
        clientId: clientId,
        date: date,
        searchVal: s,
        pageNum: Number(pageNum) || 1,
        limit: LIMIT,
      });
    } else {
      data = await tenantAttendanceDB.ListAttendanceForProperty({
        propId: Number(propId),
        clientId: clientId,
        date: date,
        pageNum: Number(pageNum) || 1,
        limit: LIMIT,
      });
    }

    const attendanceCount = await tenantAttendanceDB.getAttendanceCountForProperty({
      propId: Number(propId),
      clientId: clientId,
      date: date,
    });

    if (data && data.length > 0) {
      for (let item of data) {
        if (item.propType === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: item.flatId });
          item.flatName = name;
        } else {
          item.flatName = item.floor;
        }
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}], Date [${date}], Attendance List Fetched Successfully`
    );
    return res.status(200).json({
      msg: "Attendance list fetched successfully",
      isSuccess: true,
      data: data || [],
      present: attendanceCount?.present || 0,
      absent: attendanceCount?.absent || 0,
      lateNight: attendanceCount?.lateNight || 0,
      outOfStation: attendanceCount?.outOfStation || 0,
      nightOut: attendanceCount?.nightOut || 0,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListAttendanceForPropertyForWeb = async (
  req: CustomRequest,
  res: Response
) => {
  const C = "Tenant Controller";
  const F = "ListAttendanceForPropertyForWeb";

  try {
    const { propId, date, s } = req.query;
    const userType = req.userType;
    log.info(`[${C}], [${F}], Property Id [${propId}], Date [${date}], Search Val [${s}]`);

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"
        } Requested....`
      );
    }

    let data = [];

    if (s && s !== "" && s !== "undefined" && s !== " ") {
      data = await tenantAttendanceDB.ListAttendanceForPropertyWithSearchForWeb({
        propId: Number(propId),
        clientId: clientId,
        date: date,
        searchVal: s,
      });
    } else {
      data = await tenantAttendanceDB.ListAttendanceForPropertyForWeb({
        propId: Number(propId),
        clientId: clientId,
        date: date,
      });
    }

    const attendanceCount = await tenantAttendanceDB.getAttendanceCountForProperty({
      propId: Number(propId),
      clientId: clientId,
      date: date,
    });

    if (data && data.length > 0) {
      for (let item of data) {
        if (item.propType === CONSTANTS.PROPERTY_TYPE.FLAT) {
          const { name } = await flatDB.getById({ id: item.flatId });
          item.flatName = name;
        } else {
          item.flatName = item.floor;
        }
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Property Id [${propId}], Date [${date}], Attendance List Fetched Successfully`
    );
    return res.status(200).json({
      msg: "Attendance list fetched successfully",
      isSuccess: true,
      data: data || [],
      present: attendanceCount?.present || 0,
      absent: attendanceCount?.absent || 0,
      lateNight: attendanceCount?.lateNight || 0,
      outOfStation: attendanceCount?.outOfStation || 0,
      nightOut: attendanceCount?.mightOut || 0,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.MarkAttendance = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "MarkAttendance";

  try {
    let { tenantId, attendance = 0, attendanceDate = moment().format("YYYY-MM-DD") } = req.body;
    const userType = req.userType;

    attendanceDate = `${attendanceDate} ${moment().format("HH:mm:ss")}`;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Attendance [${attendance}], Attendance Date [${attendanceDate}],`
    );

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"
        } Requested....`
      );
    }

    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: Number(tenantId),
      clientId: clientId,
    });

    const property = await propertyDB.getById({id: occupancy?.propId});

    const tenant = await tenantDB.getById({id: tenantId});

    let tenantGuardian = await tenantGuardianDB.getByTenantId({
      tenantId: Number(tenantId),
    });

    let isParentDetailExist = false;
    let parentName = null;
    let parentMobile = null;

    if (tenantGuardian) {
      const fatherName = tenantGuardian.fatherName?.trim();
      const fatherMobile = tenantGuardian.fatherMobile?.trim();

      const motherName = tenantGuardian.motherName?.trim();
      const motherMobile = tenantGuardian.motherMobile?.trim();

      if (fatherMobile) {
        isParentDetailExist = true;
        parentName = fatherName;
        parentMobile = fatherMobile;
      } else if (motherMobile) {
        isParentDetailExist = true;
        parentName = motherName;
        parentMobile = motherMobile;
      }
      if(!parentName) {
        if(fatherName) {
          parentName = fatherName;
        } else if (motherName){
          parentName = motherName;
        } else {
          parentName = tenant.name;
        }
      }
    }
    const notiSettings = await settingsDB.getByClientIdAndPropId({
      clientId,
      propId: occupancy?.propId,
    });

    let footerText = notiSettings.footer || "Kipinn Team";
    const isMarkedToday = await tenantAttendanceDB.isMarkedToday({
      tenantId: Number(tenantId),
      attendanceDate: moment(attendanceDate).format("YYYY-MM-DD"),
    });

    const attendanceConfig = await clientConfigDB.getClientConfig({
      clientId,
      provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
      type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.NOTIFY_PARENT_ON_ATTENDANCE,
    });
    let isAttendanceEnabled = 0;
    if(attendanceConfig) {
      isAttendanceEnabled = Number(attendanceConfig?.value);
    }

    const attendanceText = {
      0: "absent",
      1: "present",
      2: "late night",
      3: "out of station",
      4: "night out"
      }[Number(attendance)];

    if (isMarkedToday) {
      await tenantAttendanceDB.updateAttendance ({
        attendance,
        id:isMarkedToday?.id || 0,
      });
      if(Number(isMarkedToday?.attendance) != Number(attendance)) {
        if(notiSettings?.whatsApp === 1) {
          if(isAttendanceEnabled) {
            if(isParentDetailExist) {
              if(Number(attendance) === Number(CONSTANTS.ATTENDANCE.PRESENT) && (1 === isAttendanceEnabled || 3 === isAttendanceEnabled)) {
                // sendWhatsappToParentOnIN(
                //   parentMobile,
                //   parentName,
                //   tenant.name,
                //   property.name,
                //   moment(attendanceDate).format("DD MMMM YYYY"),
                //   moment(attendanceDate).format("hh:mm A"),
                //   footerText,
                //   clientId,
                // );
                sendWhatsappToParentOnAttendance (
                  parentMobile,
                  parentName,
                  tenant.name,
                  String(attendanceText),
                  property.name,
                  moment(attendanceDate).format("DD MMMM YYYY"),
                  moment(attendanceDate).format("hh:mm A"),
                  footerText,
                  clientId,
                );
              } else if (Number(attendance) != Number(CONSTANTS.ATTENDANCE.PRESENT) && (2 === isAttendanceEnabled || 3 === isAttendanceEnabled)){
                
                // sendWhatsappToParentOnOut(
                //   parentMobile,
                //   parentName,
                //   tenant.name,
                //   property.name,
                //   moment(attendanceDate).format("DD MMMM YYYY"),
                //   moment(attendanceDate).format("hh:mm A"),
                //   footerText,
                //   clientId,
                // );
                sendWhatsappToParentOnAttendance (
                  parentMobile,
                  parentName,
                  tenant.name,
                  String(attendanceText),
                  property.name,
                  moment(attendanceDate).format("DD MMMM YYYY"),
                  moment(attendanceDate).format("hh:mm A"),
                  footerText,
                  clientId,
                );      
              }
            } else {
              log.info(
                `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Cant notify parent detail missing`
              );  
            }
          }
        } else {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${property?.id}], Prop Name [${property?.name}], Whatsapp not enabled`
          );
        }
      }
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Updating attendance marked for the day`
      );

      return res.status(200).json({
        msg: "Attendance marked successfully",
        isSuccess: true,
      });
    }
    if(notiSettings?.whatsApp === 1) {
      if(isAttendanceEnabled) {
        if(isParentDetailExist) {
          if(Number(attendance) === Number(CONSTANTS.ATTENDANCE.PRESENT && (1 === isAttendanceEnabled || 3 === isAttendanceEnabled))) {
            sendWhatsappToParentOnAttendance (
              parentMobile,
              parentName,
              tenant.name,
              String(attendanceText),
              property.name,
              moment(attendanceDate).format("DD MMMM YYYY"),
              moment(attendanceDate).format("hh:mm A"),
              footerText,
              clientId,
            );
          } else if (Number(attendance) != Number(CONSTANTS.ATTENDANCE.PRESENT) && (2 === isAttendanceEnabled || 3 === isAttendanceEnabled)) {
            // sendWhatsappToParentOnOut(
            //   parentMobile,
            //   parentName,
            //   tenant.name,
            //   property.name,
            //   moment(attendanceDate).format("DD MMMM YYYY"),
            //   moment(attendanceDate).format("hh:mm A"),
            //   footerText,
            //   clientId,
            // );
            sendWhatsappToParentOnAttendance (
              parentMobile,
              parentName,
              tenant.name,
              String(attendanceText),
              property.name,
              moment(attendanceDate).format("DD MMMM YYYY"),
              moment(attendanceDate).format("hh:mm A"),
              footerText,
              clientId,
            );
          }
        } else {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Cant notify parent detail missing`
          );  
        }
      }
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Prop Id [${property?.id}], Prop Name [${property?.name}], Whatsapp not enabled`
      );
    }

    await tenantAttendanceDB.markAttendance({
      tenantId: Number(tenantId),
      clientId: clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      attendance: Number(attendance),
      attendanceDate: attendanceDate,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Attendance [${attendance}], Attendance Date [${attendanceDate}], Attendance Marked Successfully`
    );

    return res.status(200).json({
      msg: "Attendance marked successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.DownloadTenantProfile = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "DownloadTenantProfile";


  try {
    const { tenantId } = req.params;

    const userType = req.userType;

    let clientId = req.id;
    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Client Requested....`
      );
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      clientId: clientId,
      tenantId: tenantId,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Client Id [${clientId}],, Tenant Id [${tenantId}], No Occupancy Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const room = await roomDB.getById({ id: occupancy.roomId });

    const profilePic = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    const property = await propertyDB.getById({ id: occupancy.propId });

    if (!property) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Property Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    //let templatePath = "http://54.196.139.145:3001/uploads/documents/defaults/tenant_profile.html";
    let templatePath = process.env.TENANT_APPLICATION_FORM!;
    let { data: html } = await axios.get(templatePath);

    if (!html) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] Template found without content`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    const folderName = `tenant_${occupancy.tenantId}`;

    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const urlBasePath = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    html = html.toString();

    let logo = process.env.RS_DEFAULT_LOGO_URI;

    let flatName = "";
    let fieldValue = "Room No.";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
      fieldValue = "Flat No.";
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
    }
    flatName += ` (${room.roomNum})`;
    html = html.replace(/{{logo}}/g, logo);
    html = html.replace(/{{propertyName}}/g, property.name);
    html = html.replace(/{{propertyAddress}}/g, property.address);

    /**
     * Rent Details 
    **/
    if (false == profilePic) {
      html = html.replace(/{{tenantPhoto}}/g, `https://cms.kipinn.com/images/no-profile-image.jpg`);
    } else {
      html = html.replace(/{{tenantPhoto}}/g, `${profilePic?.value}`);
    }
    html = html.replace(/{{propertyType}}/g, fieldValue);
    html = html.replace(/{{flatName}}/g, flatName);
    html = html.replace(
      /{{moveInDate}}/g,
      moment(occupancy.moveInDate).format("YYYY-MM-DD")
    );
    html = html.replace(/{{monthlyRent}}/g, occupancy.rent);
    html = html.replace(/{{securityDeposit}}/g, occupancy.security);
    /**
     * Personal Details 
    **/
    html = html.replace(/{{tenantName}}/g, tenant.name);
    html = html.replace(/{{tenantGender}}/g, tenant.gender === CONSTANTS.GENDER.MALE ? "Male" : tenant.gender === CONSTANTS.GENDER.FEMALE ? "Female" : "Other");
    html = html.replace(/{{tenantMobile}}/g, tenant.mobile);
    html = html.replace(/{{tenantAlternateMobile}}/g, tenant?.alternateMobile || "-");
    html = html.replace(/{{tenantEmail}}/g, tenant?.email || "-");
    html = html.replace(/{{tenantDob}}/g, tenant?.dob || "-");
    let bloodGroup = getBloodGroupNames(tenant?.bloodGroup);
    html = html.replace(/{{bloodGroup}}/g, bloodGroup || "-");
    html = html.replace(/{{tenantAddress}}/g, tenant?.address || '-');

    /**
     * Parent's and Guardian's Details
    **/

    let tenantGuardianDetail = await tenantGuardianDB.getByTenantId({ tenantId });
    html = html.replace(/{{fatherName}}/g, tenantGuardianDetail?.fatherName || '-');
    html = html.replace(/{{fatherMobile}}/g, tenantGuardianDetail?.fatherMobile || '-');
    html = html.replace(/{{fatherOccupation}}/g, tenantGuardianDetail?.fatherOccupation || '-');
    html = html.replace(/{{motherName}}/g, tenantGuardianDetail?.motherName || '-');
    html = html.replace(/{{motherMobile}}/g, tenantGuardianDetail?.motherMobile || '-');
    html = html.replace(/{{localGuardianName}}/g, tenantGuardianDetail?.localGuardianName || '-');
    html = html.replace(/{{localGuardianMobile}}/g, tenantGuardianDetail?.localGuardianMobile || '-');
    let relation = "";
    if (Number(tenantGuardianDetail?.localGuardianRelation) === 1)
      relation = "Uncle";
    else if (Number(tenantGuardianDetail?.localGuardianRelation) === 2)
      relation = "Aunt";
    else if (Number(tenantGuardianDetail?.localGuardianRelation) === 3)
      relation = "Brother";
    else if (Number(tenantGuardianDetail?.localGuardianRelation) === 4)
      relation = "Sister";
    else
      relation = "Friend";

    if (tenantGuardianDetail?.localGuardianName == "" || tenantGuardianDetail?.localGuardianName == null)
      relation = "-";

    html = html.replace(/{{localGuardianRelation}}/g, relation || '-');

    /**
     * Occupation Detail
    **/

    html = html.replace(/{{occupation}}/g, tenant?.occupation || '-');
    if (Number(tenant?.occupation) === 1 || tenant?.occupation === "Student") {
      html = html.replace(/{{companyNameTitle}}/g, 'Institute Name');
      html = html.replace(/{{institutionName}}/g, tenant?.institutionName || '-');
      html = html.replace(/{{workEmailTitle}}/g, 'Institute Email');
      html = html.replace(/{{institutionEmail}}/g, tenant?.institutionEmail || '-');
      html = html.replace(/{{jobDescriptionTitle}}/g, 'Course Name');
      html = html.replace(/{{jobDescription}}/g, tenant?.title || '-');
    } else {
      html = html.replace(/{{companyNameTitle}}/g, 'Company Name');
      html = html.replace(/{{institutionName}}/g, tenant?.institutionName || '-');
      html = html.replace(/{{workEmailTitle}}/g, 'Work Email');
      html = html.replace(/{{institutionEmail}}/g, tenant?.institutionEmail || '-');
      html = html.replace(/{{jobDescriptionTitle}}/g, 'Job Description');
      html = html.replace(/{{jobDescription}}/g, tenant?.title || '-');
    }

    /**
     * Documents
    **/

    let aadharFront = await documentDB.getIDByType({clientId, tenantId, type: CONSTANTS.DOCUMENT_TYPES.AADHAAR, moveOut: 0, });
    let aadharData = `<tr><td colspan="2">Govt. ID Front (Aadhaar)</td></tr>
            <tr><td colspan="2">No image uploaded</td></tr>
            <tr>
            <td>Govt. ID Back (Aadhaar)</td>
            <td>No image uploaded</td></tr>`;
    if (false != aadharFront) {
      if (aadharFront.value.length === 12) {
        aadharData = `<tr><td colspan="2">Govt. ID Front (Aadhaar)</td></tr>
            <tr><td colspan="2">${aadharFront?.value}</td></tr>`;
        html = html.replace(/{{aadharData}}/g, aadharData || '-');
      } else {
        //html = html.replace(/{{tenantPhoto}}/g,`<img src="${profilePic?.value}" style="width: 120px;"/>`);  
        aadharData = `<tr><td colspan="2">Govt. ID (Aadhaar)</td></tr><tr>
            <td><img src="${aadharFront?.value}" width = '225px' height='150px'/></td>`;
        let aadharBack = await documentDB.getIDByType({ clientId, tenantId, type: CONSTANTS.DOCUMENT_TYPES.AADHAAR_BACK, moveOut: 0, });

        if (false != aadharBack) {
          aadharData += `<td><img src="${aadharBack?.value}" width = '225px' height='150px '/></td>`;
        }
        aadharData += `</tr>`;
      }
    }
    html = html.replace(/{{aadharData}}/g, aadharData || '-');

    let panCardData = `<tr>
            <td>Govt. ID (Pan Card)</td>
            <td>No image uploaded</td>
        </tr>`;

    let panCard = await documentDB.getIDByType({clientId, tenantId, type: CONSTANTS.DOCUMENT_TYPES.PAN, moveOut: 0, });

    if (false != panCard) {
      panCardData = `<tr><td colspan="2">Govt. ID (Pan Card)</td></tr>
            <tr><td colspan="2"><img src="${panCard?.value}" height='150'/></td></tr>`;
    }

    html = html.replace(/{{panCardData}}/g, panCardData || '-');

    let instituteData = `<tr>
            <td>Student/Employer ID</td>
            <td>No image uploaded</td>
        </tr>`;
    let instituionId = await documentDB.getIDByType({clientId, tenantId, type: CONSTANTS.DOCUMENT_TYPES.INSTITUTION_ID, moveOut: 0, });

    if (false != instituionId) {
      instituteData = `<tr><td colspan="2">Student/Employer ID</td></tr>
            <tr><td colspan="2"><img src="${instituionId?.value}" height='150'/></td></tr>`;
    }
    html = html.replace(/{{instituteData}}/g, instituteData || '-');

    let policeVerification = await documentDB.getIDByType({ clientId, tenantId, type: CONSTANTS.DOCUMENT_TYPES.POLICE_VERIFICATION, moveOut: 0, });

    let pvData = `<tr>
            <td>Police Verification</td>
            <td>No image uploaded</td>
        </tr>`;

    if (false != policeVerification) {
      pvData = `<tr><td colspan="2">Police Verification</td></tr>
            <tr><td colspan="2"><img src="${policeVerification?.value}" height='150'/></td></tr>`;
    }
    html = html.replace(/{{pvData}}/g, pvData || '-');

    let rentAgreement = await documentDB.getIDByType({ clientId, tenantId, type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT, moveOut: 0, });
    let isPdf = false;
    let agrementData = "";
    if (false != rentAgreement) {
      isPdf = rentAgreement?.value.toLowerCase().endsWith(".pdf")
      if (!isPdf) {
        agrementData = `<tr><td colspan="2">Rent Agreement</td></tr>
            <tr><td colspan="2"><img src="${rentAgreement?.value}" width='75%'/></td></tr>`;
      }
      html = html.replace(/{{agreementData}}/g, agrementData || '-');
    }


    html = html.replace(/\n/g, "");

    const options = {
      format: "A4",
      orientation: "portrait",
      border: "10mm",
      childProcessOptions: {
        env: {
          OPENSSL_CONF: "/dev/null",
        },
      },
    };

    const filename = "tenantProfile1.pdf";
    let url = `${urlBasePath}/${filename}`;

    var document = {
      html: html,
      path: `${folderPath}/${filename}`,
      data: {},
      type: "",
    };

    await pdf.create(document, options);

    if (false != rentAgreement && isPdf) {
      let path = new URL(rentAgreement?.value).pathname;
      path = path.replace(/^\//, "");
      let finalPath = `tenantProfile.pdf`;
      url = `${urlBasePath}/${finalPath}`;
      await mergePdf(`${folderPath}/${filename}`, path, `${folderPath}/${finalPath}`);
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], URL [${url}]`
    );
    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Tenant profile document created Successfully`
    );
    return res.status(200).json({
      msg: "Tenant profile document created Successfully",
      link: url,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.SendEKYCReminder = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "SendEKYCReminder";

  try {
    const { tenantId } = req.body;
    const userType = req.userType;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], User Type [${userType}]`);

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"
        } Requested....`
      );
    }

    const tenant = await tenantDB.getById({
      id: tenantId,
    });

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenant.id,
    });

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId: clientId,
      propId: occupancy.propId,
    });


    let footerText = propSettings.footer || "The Kipinn Team";
    if (propSettings.android || propSettings.ios) {

      await sendWhatsappTenantKycReminderWithAppLink(
        tenant.mobile,
        tenant.name,
        footerText,
        propSettings.android || " ",
        propSettings.ios || " ",
        Number(clientId),
      );

    } else {

      await sendWhatsappTenantKycReminder(
        tenant.mobile,
        tenant.name,
        footerText,
        Number(clientId),
      );

    }

    log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Ekyc Reminder Sent Successfully`);

    return res.status(200).json({
      msg: "E-KYC Reminder sent to tenant",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.SendOnboardingReminder = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "SendOnboardingReminder"

  try {
    const { tenantId } = req.body;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}]`);

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"
        } Requested....`
      );
    }

    if (Number(clientId) !== 113 && Number(clientId) !== 110 && Number(clientId) !== 57 && Number(clientId) !== 116) {
      log.info(`[${C}], [${F}], ClientId [${clientId}], Feature only available to Tushar(Ghar)`);
      return res.status(400).json({
        msg: "Feature not available for you",
        isSuccess: false,
      });
    }

    const tenant = await tenantDB.getById({
      id: tenantId,
    });

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenantId,
    });

    const property = await propertyDB.getById({
      id: occupancy.propId,
    });

    const room = await roomDB.getById({
      id: occupancy.roomId,
    });

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId,
      propId: occupancy.propId,
    });

    let flatName = "";
    let footerText = propSettings.footer || "The Kipinn Team";
    let template =
      propSettings.whatsappOnboardingTemplate || "tenantonboardbyownernew";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: room.flatId });
      flatName = name + "," + room.roomNum;
    } else {
      flatName = room.roomNum;
    }

    if (template === CONSTANTS.WHATSAPP_TEMPLATES.ON_BOARDING_WITH_LINK) {
      sendWhatsappTenantWelcomeWithLink(
        tenant.mobile,
        tenant.name,
        property.name,
        String(occupancy.rent),
        String(occupancy.security),
        flatName,
        occupancy.moveInDate,
        footerText,
        template,
        propSettings.android || "",
        propSettings.ios || "",
        Number(clientId),
      );
    } else {
      sendWhatsappTenantWelcome(
        tenant.mobile,
        tenant.name,
        property.name,
        String(occupancy.rent),
        String(occupancy.security),
        flatName,
        occupancy.moveInDate,
        footerText,
        template,
        Number(clientId),
      );
    }

    log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Reminder Sent Successfully`);

    return res.status(200).json({
      msg: "Reminder sent successfully",
      isSuccess: true,
    });

  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ChangeRentalCycle = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ChangeRentalCycle";

  try {
    const { tenantId, rentalCycle } = req.body;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], New Rental Cycle [${rentalCycle}]`);


    async function handleNoLastRentDue(occupancy: any, rentalCycle: number, oldRentalCycle: number, tenantId: number, clientId: any) {
      try {
        log.info(`[${C}], [${F}], [handleNoLastRentDue], Occupancy [${JSON.stringify(occupancy)}], Rental Cycle [${rentalCycle}], Old Rental Cycle [${oldRentalCycle}], Tenant Id [${tenantId}], Client Id [${clientId}]`);

        if (moment(occupancy.moveInDate).date() >= Number(rentalCycle)) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Move In Date [${occupancy.moveInDate}], New Rental Cycle is smaller than moveInDate`
          );
          const startDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");
          const referenceDate = moment(occupancy.moveInDate).date(rentalCycle).format("YYYY-MM-DD");
          const endDate = moment(referenceDate).add(occupancy.rentalType, "months").subtract(1, "day").format("YYYY-MM-DD");
          const daysDiff = moment(endDate).diff(moment(startDate), "days");
          const dueDate = moment(startDate).format("YYYY-MM-DD");
          const rentDuration = occupancy.rentalMonths;
          const description = `Rent added by Kipinn while changing rental cycle for ${startDate} to ${endDate}`;
          const title = `Rent`;
          const dueDescription = `Rent added by Kipinn while changing rental cycle from ${oldRentalCycle} to ${rentalCycle}`;
          const referenceId = await generateLedgerReferenceId({ clientId });

          let gapRent = Math.ceil((Number(occupancy.rent) / moment(startDate).daysInMonth()) * (daysDiff + 1));
          if (moment(occupancy.moveInDate).date() === Number(rentalCycle)) {
            gapRent = occupancy.rent;
          }
          await AddDues(
            tenantId,
            gapRent,
            CONSTANTS.DUES_TYPES.RENT,
            occupancy,
            clientId!,
            startDate,
            endDate,
            dueDate,
            rentDuration,
            description,
            title,
            dueDescription,
            referenceId
          );
        } else {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Move In Date [${occupancy.moveInDate}], New Rental Cycle is greater than moveInDate`
          );
          const startDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");
          const referenceDate = moment(occupancy.moveInDate).date(rentalCycle).format("YYYY-MM-DD");
          const endDate = moment(referenceDate).subtract(1, "day").format("YYYY-MM-DD");
          const daysDiff = moment(endDate).diff(moment(startDate), "days");
          const dueDate = moment(startDate).format("YYYY-MM-DD");
          const rentDuration = occupancy.rentalMonths;
          const description = `Rent added by Kipinn while changing rental cycle for ${startDate} to ${endDate}`;
          const title = `Rent`;
          const dueDescription = `Rent added by Kipinn while changing rental cycle from ${oldRentalCycle} to ${rentalCycle}`;
          const referenceId = await generateLedgerReferenceId({ clientId });

          let gapRent = Math.ceil((Number(occupancy.rent) / moment(startDate).daysInMonth()) * (daysDiff + 1));
          if (moment(occupancy.moveInDate).date() === Number(rentalCycle)) {
            gapRent = occupancy.rent;
          }

          log.info(JSON.stringify({
            tenantId,
            daysDiff,
            rent: gapRent,
            dueType: CONSTANTS.DUES_TYPES.RENT,
            occupancy,
            clientId,
            startDate,
            endDate,
            dueDate,
            rentDuration,
            description,
            title,
            dueDescription,
            referenceId
          }));

          await AddDues(
            tenantId,
            gapRent,
            CONSTANTS.DUES_TYPES.RENT,
            occupancy,
            clientId!,
            startDate,
            endDate,
            dueDate,
            rentDuration,
            description,
            title,
            dueDescription,
            referenceId
          );
        }

        return true;
      } catch (error: any) {
        log.info(`[${C}], [${F}], [handleNoLastRentDue], Error in handling not last rent due [${error?.message || error}]`);
        return false;
      }
    }

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : "Warden"
        } Requested....`
      );
    }

    const tenant = await tenantDB.getById({
      id: tenantId,
    });
    if (!tenant) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Tenant Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenant.id,
      clientId: clientId,
    });
    if (!occupancy) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Occupancy Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const oldRentalCycle = occupancy.rentalCycle;

    if (Number(oldRentalCycle) === Number(rentalCycle)) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], No Change in Rental Cycle`);
      return res.status(200).json({
        msg: "No change in rental cycle",
        isSuccess: false,
      });
    }

    let rentDiff = 0;

    let lastRentDue = await ledgerDB.getLastRent({
      tenantId: tenantId,
      clientId: clientId,
    });

    log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Last Rent Due: ${JSON.stringify(lastRentDue)}`);

    if (lastRentDue) {
      if (Number(rentalCycle) > Number(oldRentalCycle)) {
        if (moment().date() < Number(oldRentalCycle)) {
          //add gap due
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Current Date [${moment().format("YYYY-MM-DD")}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], New Rental Cycle Is Greater Than Old And Current Date Is Before Old Rental Cycle Or After New Rental Cycle, Creating Due For Gap`
          );

          let daysDiff = Number(rentalCycle) - Number(oldRentalCycle) - 1;
          const startDate = moment(lastRentDue.rentEndDate).add(1, "day").format("YYYY-MM-DD");
          daysDiff -= (Number(moment(lastRentDue.rentEndDate).date()) - Number(oldRentalCycle));
          const endDate = moment(startDate).add(daysDiff, 'days').format("YYYY-MM-DD");
          const dueDate = moment(startDate).format("YYYY-MM-DD");
          const rentDuration = occupancy.rentalMonths;
          const description = `Rent added by Kipinn while changing rental cycle for ${startDate} to ${endDate}`;
          const title = `Rent`;
          const dueDescription = `Rent added by Kipinn while changing rental cycle from ${oldRentalCycle} to ${rentalCycle}`;
          const referenceId = await generateLedgerReferenceId({ clientId });

          const gapRent = Math.ceil((Number(occupancy.rent) / moment(lastRentDue.rentEndDate).daysInMonth()) * (daysDiff + 1));

          log.info(JSON.stringify({
            tenantId,
            daysDiff,
            rent: gapRent,
            dueType: CONSTANTS.DUES_TYPES.RENT,
            occupancy,
            clientId,
            startDate,
            endDate,
            dueDate,
            rentDuration,
            description,
            title,
            dueDescription,
            referenceId
          }));

          await AddDues(
            tenantId,
            gapRent,
            CONSTANTS.DUES_TYPES.RENT,
            occupancy,
            clientId!,
            startDate,
            endDate,
            dueDate,
            rentDuration,
            description,
            title,
            dueDescription,
            referenceId
          );
        } else {
          //if (moment().date() >= Number(oldRentalCycle) && moment().date() < rentalCycle)
          //adjust/edit
          lastRentDue = await ledgerDB.getLastRent({
            tenantId: tenantId,
            clientId: clientId,
          });

          log.info(`[${C}], [${F}], Client Id [${tenantId}], Tenant Id [${tenantId}], Last Rent Due [${JSON.stringify(lastRentDue)}]`);

          const rentDue = await duesDB.getByLedgerReferenceId({
            ledgerReferenceId: lastRentDue.referenceId,
            tenantId: tenantId,
            clientId: clientId,
          });

          log.info(`[${C}], [${F}], Client Id [${tenantId}], Tenant Id [${tenantId}], Rent Due [${JSON.stringify(rentDue)}]`);

          if (rentDue && (rentDue.amount !== rentDue.balance)) {
            log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Rent Due is not fully paid, cannot change rental cycle`);
            return res.status(400).json({
              msg: "Please fully pay the rent before changing the rental cycle.",
              isSuccess: false,
            });
          }

          if (moment().date() >= Number(rentalCycle)) {
            const startDate = moment(lastRentDue.rentStartDate).date(rentalCycle).format("YYYY-MM-DD");
            const endDate = moment(startDate).add(Number(occupancy.rentalType), "month").subtract(1, "day").format("YYYY-MM-DD");
            const dueDescription = `Rent added by Kipinn while changing rental cycle from ${oldRentalCycle} to ${rentalCycle}`;
            const referenceId = await generateLedgerReferenceId({ clientId });
            await AddDues(
              tenantId,
              occupancy.rent,
              CONSTANTS.DUES_TYPES.RENT,
              occupancy,
              clientId!,
              startDate,
              endDate,
              startDate, //dueDate
              occupancy.rentalMonths,
              `Rent added by Kipinn while changing rental cycle for ${startDate} to ${endDate}`,
              "Rent",
              dueDescription,
              referenceId
            );
            if (rentDue) {
              if (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
                await duesDB.removeDue({
                  id: rentDue.id,
                });
                await ledgerDB.remove({
                  referenceId: rentDue.ledgerReferenceId,
                });
              } else {

                let startDate = rentDue.rentStartDate;
                let endDate = moment(rentDue.rentStartDate).date(rentalCycle - 1).format("YYYY-MM-DD");

                let oldRent = Math.ceil((Number(occupancy.rent) / moment(rentDue.rentStartDate).daysInMonth()) * (moment(endDate).diff(moment(startDate), "days") + 1));

                await duesDB.updateEntry({
                  id: rentDue.id,
                  amount: oldRent,
                  balance: oldRent,
                  rentStartDate: startDate,
                  rentEndDate: endDate,
                  dueDate: startDate,
                  description: `Rent`,
                });

                await ledgerDB.updateEntry({
                  clientId,
                  tenantId,
                  referenceId: rentDue.ledgerReferenceId,
                  amount: oldRent,
                  balance: oldRent,
                  rentStartDate: startDate,
                  rentEndDate: endDate,
                  dueDate: startDate,
                  description: `Rent for ${moment(startDate).format("DD MMM, YY")} to ${moment(endDate).format("DD MMM, YY")}`,
                });
              }
            }
          }

          if (rentDue && (moment().date() < Number(rentalCycle))) {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Current Date [${moment().format("YYYY-MM-DD")}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], New Rental Cycle Is Greater Than Old And Current Date Is Between New Rental Cycle And Old Rental Cycle And Rent Due Is Not Paid, Updating The Due`
            );

            if (rentDue.amount !== rentDue.balance) {
              log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Rent Due is not fully paid, cannot change rental cycle`);
              return res.status(400).json({
                msg: "Please fully pay the rent before changing the rental cycle.",
                isSuccess: false,
              });
            }

            let calStartDate = moment().date(oldRentalCycle).isBefore(moment(occupancy.moveInDate)) ? moment(occupancy.moveInDate).date() - 1 : oldRentalCycle;

            rentDiff = Math.ceil((Number(occupancy.rent) / moment(rentDue.rentStartDate).daysInMonth()) * (Number(rentalCycle) - Number(calStartDate)));

            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Rent Difference [${rentDiff}], Rent Due is Not Paid, Updating Rent Due Amount`
            );


            let startDate = moment(rentDue.rentStartDate).format("YYYY-MM-DD");
            let dayDiff = Number(rentalCycle) - Number(oldRentalCycle) - 1;
            dayDiff -= (Number(moment(rentDue.rentStartDate).date()) - Number(oldRentalCycle));
            let endDate = moment(startDate).add(dayDiff, 'days').format("YYYY-MM-DD");

            // log.info(`Due Id [${rentDue.id}], Start Date [${startDate}], End Date [${endDate}], reference Id [${rentDue.ledgerReferenceId}], `)

            if ((moment(occupancy.moveInDate).date() === Number(rentalCycle)) && (occupancy.status === CONSTANTS.OCCUPANCY_STATUS.RESERVED)) {
              await duesDB.removeDue({ id: rentDue.id });
              await ledgerDB.remove({
                referenceId: rentDue.ledgerReferenceId,
              });

              await editLogsDB.add({
                tenantId: rentDue.tenantId,
                clientId: clientId,
                propId: rentDue.propId,
                roomId: rentDue.roomId,
                oldAmount: rentDue.amount,
                newAmount: 0,
                oldBalance: rentDue.balance,
                newBalance: 0,
                rentStartDate: rentDue.rentStartDate,
                rentEndDate: rentDue.rentEndDate,
                dueId: rentDue.id,
                ledgerReferenceId: rentDue.ledgerReferenceId,
                dueDate: rentDue.dueDate,
                dueType: rentDue.type,
                doneByUserType: req.userType,
                doneBy: req.id,
              });
            } else {
              await duesDB.updateEntry({
                id: rentDue.id,
                amount: Math.abs(rentDiff),
                balance: Math.abs(rentDiff),
                // amount: Math.abs(rentDue.amount - rentDiff),
                // balance: Math.abs(rentDue.amount - rentDiff),
                rentStartDate: startDate,
                rentEndDate: endDate,
                dueDate: startDate,
                description: `Rent`,
              });

              await ledgerDB.updateEntry({
                clientId,
                tenantId,
                referenceId: rentDue.ledgerReferenceId,
                amount: Math.abs(rentDiff),
                balance: Math.abs(rentDiff),
                // amount: Math.abs(rentDue.amount - rentDiff),
                // balance: Math.abs(rentDue.amount - rentDiff),
                rentStartDate: startDate,
                rentEndDate: endDate,
                dueDate: startDate,
                description: `Rent for ${moment(startDate).format("DD MMM, YY")} to ${moment(endDate).format("DD MMM, YY")}`,
              });

              await editLogsDB.add({
                tenantId: tenantId,
                clientId: clientId,
                propId: rentDue.propId,
                roomId: rentDue.roomId,
                oldAmount: rentDue.amount,
                // newAmount: Math.abs(rentDue.amount - rentDiff),
                newAmount: Math.abs(rentDiff),
                oldBalance: rentDue.balance,
                newBalance: Math.abs(rentDiff),
                // newBalance: Math.abs(rentDue.amount - rentDiff),
                dueId: rentDue.id,
                ledgerReferenceId: rentDue.ledgerReferenceId,
                rentStartDate: rentDue.rentStartDate,
                rentEndDate: rentDue.rentEndDate,
                dueDate: rentDue.dueDate,
                dueType: rentDue.type,
                doneByUserType: null,
                doneBy: null,
              });
            }

          } else if (!rentDue) {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Current Date [${moment().format("YYYY-MM-DD")}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], New Rental Cycle Is Greater Than Old And Current Date Is Between/After New Rental Cycle And Old Rental Cycle And Rent Due Is Paid, Adjusting Excess Payment`
            );

            let allExsistingDues = await duesDB.getByTenantIdAndPropId({
              tenantId,
              propId: occupancy.propId,
            });

            // rentDiff = Math.ceil((Number(lastRentDue.amount)/30) * (Number(oldRentalCycle) - Number(rentalCycle)));
            rentDiff = Number(lastRentDue.amount) - Math.ceil((Number(occupancy.rent) / moment(rentDue.rentStartDate).daysInMonth()) * (Number(rentalCycle) - Number(oldRentalCycle)));

            let amtPaid = rentDiff;

            let count = 1;

            if (allExsistingDues && allExsistingDues.length > 0) {
              log.info(
                `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Dues Exists Adjusting Excess Amount From Dues`
              );
              for (let due of allExsistingDues) {
                if (amtPaid <= 0) {
                  break;
                }
                if (count === allExsistingDues.length && due.balance < amtPaid) {
                  await duesDB.removeDue({ id: due.id });

                  await ledgerDB.add({
                    tenantId,
                    roomId: occupancy.roomId,
                    propId: occupancy.propId,
                    clientId: occupancy.clientId,
                    amount: -due.balance,
                    balance: due.balance - amtPaid,
                    referenceId: due.ledgerReferenceId,
                    transactionId: null,
                    type: due.type,
                    rentStartDate: due.rentStartDate,
                    rentEndDate: due.rentEndDate,
                    dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
                    description: "Adjustment while changing rental cycle",
                    title: due?.title || null,
                  });
                  amtPaid = 0;
                } else {
                  if (amtPaid >= due.balance) {

                    await duesDB.removeDue({ id: due.id });

                    await ledgerDB.add({
                      tenantId,
                      roomId: occupancy.roomId,
                      propId: occupancy.propId,
                      clientId: occupancy.clientId,
                      amount: -due.balance,
                      balance: 0,
                      referenceId: due.ledgerReferenceId,
                      transactionId: null,
                      type: due.type,
                      rentStartDate: due.rentStartDate,
                      rentEndDate: due.rentEndDate,
                      dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
                      description: "Adjustment while changing rental cycle",
                      title: due?.title || null,
                    });

                    amtPaid = amtPaid - due.balance;
                  } else {
                    await duesDB.updateBalance({
                      id: due.id,
                      balance: due.balance - amtPaid,
                    });

                    await ledgerDB.add({
                      tenantId,
                      roomId: occupancy.roomId,
                      propId: occupancy.propId,
                      clientId: occupancy.clientId,
                      amount: -amtPaid,
                      balance: due.balance - amtPaid,
                      referenceId: due.ledgerReferenceId,
                      transactionId: null,
                      type: due.type,
                      rentStartDate: due.rentStartDate,
                      rentEndDate: due.rentEndDate,
                      dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
                      description: "Adjustment while changing rental cycle",
                      title: due?.title || null,
                    });

                    amtPaid = 0;
                  }
                }
              }
            } else {
              log.info(
                `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], No Dues Exists, Adding Excess Amount To Last Ledger Entry`
              );

              await ledgerDB.updateLastEntryBalance({
                tenantId: tenantId,
                clientId: clientId,
                amount: -rentDiff,
              });
            }
          }
        }
      } else {
        log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Decreasing Rental Cycle`);

        let oldRentStartDate = moment().date() >= oldRentalCycle ? moment().date(oldRentalCycle).format("YYYY-MM-DD") : moment().date(oldRentalCycle).subtract(1, "month").format("YYYY-MM-DD");
        let oldRentEndDate = moment(oldRentStartDate).add(occupancy.rentalType, "months").subtract(1, "day").format("YYYY-MM-DD");

        //Note -> below code is to remove extra rents created when changing rental cycle multiple times
        await ledgerDB.deleteFutureCycleChangeRent({
          tenantId: tenantId,
          clientId: clientId,
          rentStartDate: oldRentStartDate,
          rentEndDate: oldRentEndDate,
        });

        await duesDB.deleteFutureCycleChangeRent({
          tenantId: tenantId,
          clientId: clientId,
          rentStartDate: oldRentStartDate,
          rentEndDate: oldRentEndDate,
        });

        lastRentDue = await ledgerDB.getLastRent({
          tenantId: tenantId,
          clientId: clientId,
        });

        // TODO: if lastRentDue is false ---> handling is missing will throw error
        log.info(`Last Rent Due [${JSON.stringify(lastRentDue)}]`);

        if (!lastRentDue) {
          await handleNoLastRentDue(occupancy, Number(rentalCycle), Number(oldRentalCycle), tenantId, clientId);
        } else {
          const rentDue = await duesDB.getByLedgerReferenceId({
            ledgerReferenceId: lastRentDue.referenceId,
            tenantId: tenantId,
            clientId: clientId,
          });

          log.info(`Rent Due: ${JSON.stringify(rentDue)}`);

          if ((moment().date() >= Number(rentalCycle)) && (moment().date() < Number(oldRentalCycle))) {
            const startDate = moment(lastRentDue.rentEndDate).date(rentalCycle).format("YYYY-MM-DD");
            const endDate = moment(startDate).add(Number(occupancy.rentalType), "month").subtract(1, "day").format("YYYY-MM-DD");
            const dueDescription = `Rent added by Kipinn while changing rental cycle from ${oldRentalCycle} to ${rentalCycle}`;
            const referenceId = await generateLedgerReferenceId({ clientId });
            await AddDues(
              tenantId,
              occupancy.rent,
              CONSTANTS.DUES_TYPES.RENT,
              occupancy,
              clientId!,
              startDate,
              endDate,
              startDate, //dueDate
              occupancy.rentalMonths,
              `Rent added by Kipinn while changing rental cycle for ${startDate} to ${endDate}`,
              "Rent",
              dueDescription,
              referenceId
            );
          }

          if (rentDue) {

            if (rentDue.amount !== rentDue.balance) {
              log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Rent Due is not fully paid, cannot change rental cycle`);
              return res.status(400).json({
                msg: "Please fully pay the rent before changing the rental cycle.",
                isSuccess: false,
              });
            }

            if (moment().date() === Number(rentalCycle) && moment().date() === 1) {
              await duesDB.removeDue({
                id: rentDue.id
              });
              await ledgerDB.remove({
                referenceId: lastRentDue.referenceId,
              });

            } else {
              rentDiff = Math.ceil((Number(occupancy.rent) / moment(rentDue.rentStartDate).daysInMonth()) * (Number(oldRentalCycle) - Number(rentalCycle)));

              log.info(
                `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Rent Difference [${rentDiff}], Rent Due is Not Paid, Updating Rent Due Amount`
              );

              if (rentDiff === lastRentDue.amount) {
                await duesDB.removeDue({
                  id: rentDue.id
                });
                await ledgerDB.remove({
                  referenceId: lastRentDue.referenceId,
                });
              } else {
                // const diff = Number(occupancy.rent) - rentDiff;

                let startDate = moment(rentDue.rentStartDate).format("YYYY-MM-DD");
                let endDate = moment(rentDue.rentEndDate).date(rentalCycle).subtract(1, "day").format("YYYY-MM-DD");

                const diff = Math.ceil((Number(occupancy.rent) / moment(rentDue.rentStartDate).daysInMonth()) * (moment(endDate).diff(moment(startDate), "days") + 1));

                await duesDB.updateEntry({
                  id: rentDue.id,
                  amount: diff,
                  balance: diff,
                  rentStartDate: startDate,
                  rentEndDate: endDate,
                  dueDate: startDate,
                  description: `Rent`,
                });

                await ledgerDB.updateEntry({
                  clientId,
                  tenantId,
                  referenceId: rentDue.ledgerReferenceId,
                  amount: diff,
                  balance: diff,
                  rentStartDate: startDate,
                  rentEndDate: endDate,
                  dueDate: startDate,
                  description: `Rent for ${moment(startDate).format("DD MMM, YY")} to ${moment(endDate).format("DD MMM, YY")}`,
                });

                await editLogsDB.add({
                  tenantId: tenantId,
                  clientId: clientId,
                  propId: rentDue.propId,
                  roomId: rentDue.roomId,
                  oldAmount: rentDue.amount,
                  newAmount: diff,
                  oldBalance: rentDue.balance,
                  newBalance: diff,
                  dueId: rentDue.id,
                  ledgerReferenceId: rentDue.ledgerReferenceId,
                  rentStartDate: rentDue.rentStartDate,
                  rentEndDate: rentDue.rentEndDate,
                  dueDate: rentDue.dueDate,
                  dueType: rentDue.type,
                  doneByUserType: null,
                  doneBy: null,
                });
              }
            }
          } else {
            log.info(
              `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Rent Due Paid, Ajusting Excess Rent Amount`
            );

            let allExsistingDues = await duesDB.getByTenantIdAndPropId({
              tenantId,
              propId: occupancy.propId,
            });

            rentDiff = Math.ceil((Number(lastRentDue.amount) / moment(rentDue.rentStartDate).daysInMonth()) * (Number(oldRentalCycle) - Number(rentalCycle)));

            let amtPaid = rentDiff;

            let count = 1;

            if (allExsistingDues && allExsistingDues.length > 0) {
              log.info(
                `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Dues Exists Adjusting Excess Amount From Dues`
              );
              for (let due of allExsistingDues) {
                if (amtPaid <= 0) {
                  break;
                }
                if (count === allExsistingDues.length && due.balance < amtPaid) {
                  await duesDB.removeDue({ id: due.id });

                  await ledgerDB.add({
                    tenantId,
                    roomId: occupancy.roomId,
                    propId: occupancy.propId,
                    clientId: occupancy.clientId,
                    amount: -due.balance,
                    balance: due.balance - amtPaid,
                    referenceId: due.ledgerReferenceId,
                    transactionId: null,
                    type: due.type,
                    rentStartDate: due.rentStartDate,
                    rentEndDate: due.rentEndDate,
                    dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
                    description: "Adjustment while changing rental cycle",
                    title: due?.title || null,
                  });
                  amtPaid = 0;
                } else {
                  if (amtPaid >= due.balance) {

                    await duesDB.removeDue({ id: due.id });

                    await ledgerDB.add({
                      tenantId,
                      roomId: occupancy.roomId,
                      propId: occupancy.propId,
                      clientId: occupancy.clientId,
                      amount: -due.balance,
                      balance: 0,
                      referenceId: due.ledgerReferenceId,
                      transactionId: null,
                      type: due.type,
                      rentStartDate: due.rentStartDate,
                      rentEndDate: due.rentEndDate,
                      dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
                      description: "Adjustment while changing rental cycle",
                      title: due?.title || null,
                    });

                    amtPaid = amtPaid - due.balance;
                  } else {
                    await duesDB.updateBalance({
                      id: due.id,
                      balance: due.balance - amtPaid,
                    });

                    await ledgerDB.add({
                      tenantId,
                      roomId: occupancy.roomId,
                      propId: occupancy.propId,
                      clientId: occupancy.clientId,
                      amount: -amtPaid,
                      balance: due.balance - amtPaid,
                      referenceId: due.ledgerReferenceId,
                      transactionId: null,
                      type: due.type,
                      rentStartDate: due.rentStartDate,
                      rentEndDate: due.rentEndDate,
                      dueDate: due.dueDate || moment().format("YYYY-MM-DD HH:mm:ss"),
                      description: "Adjustment while changing rental cycle",
                      title: due?.title || null,
                    });

                    amtPaid = 0;
                  }
                }
              }
            } else {
              log.info(
                `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], No Dues Exists, Adding Excess Amount To Last Ledger Entry`
              );

              await ledgerDB.updateLastEntryBalance({
                tenantId: tenantId,
                clientId: clientId,
                amount: -rentDiff,
              });
            }
          }
        }
      }
    } else {
      // if (Number(occupancy.status) !== CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
      //   if (moment().date() === Number(rentalCycle)) {
      //     const startDate = moment().format("YYYY-MM-DD");
      //     const endDate = moment(startDate).add(Number(occupancy.rentalType), 'month').subtract(1, "day").format("YYYY-MM-DD");
      //     const dueDate = moment(startDate).format("YYYY-MM-DD");
      //     const rentDuration = occupancy.rentalMonths;
      //     const description = `Rent added by Kipinn while changing rental cycle for ${startDate} to ${endDate}`;
      //     const title = `Rent`;
      //     const dueDescription = `Rent added by Kipinn while changing rental cycle from ${oldRentalCycle} to ${rentalCycle}`;
      //     const referenceId = await generateLedgerReferenceId({ clientId });

      //     await AddDues(
      //       tenantId,
      //       occupancy.rent,
      //       CONSTANTS.DUES_TYPES.RENT,
      //       occupancy,
      //       clientId!,
      //       startDate,
      //       endDate,
      //       dueDate,
      //       rentDuration,
      //       description,
      //       title,
      //       dueDescription,
      //       referenceId
      //     );
      //   }
      // } else {
      //   if (moment(occupancy.moveInDate).date() >= Number(rentalCycle)) {
      //     log.info(
      //       `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Move In Date [${occupancy.moveInDate}], New Rental Cycle is smaller than moveInDate`
      //     );
      //     const startDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");
      //     const referenceDate = moment(occupancy.moveInDate).date(rentalCycle).format("YYYY-MM-DD");
      //     const endDate = moment(referenceDate).add(occupancy.rentalType, "months").subtract(1, "day").format("YYYY-MM-DD");
      //     const daysDiff = moment(endDate).diff(moment(startDate), "days");
      //     const dueDate = moment(startDate).format("YYYY-MM-DD");
      //     const rentDuration = occupancy.rentalMonths;
      //     const description = `Rent added by Kipinn while changing rental cycle for ${startDate} to ${endDate}`;
      //     const title = `Rent`;
      //     const dueDescription = `Rent added by Kipinn while changing rental cycle from ${oldRentalCycle} to ${rentalCycle}`;
      //     const referenceId = await generateLedgerReferenceId({ clientId });

      //     let gapRent = Math.ceil((Number(occupancy.rent) / moment(startDate).daysInMonth()) * (daysDiff + 1));
      //     if (moment(occupancy.moveInDate).date() === Number(rentalCycle)) {
      //       gapRent = occupancy.rent;
      //     }
      //     await AddDues(
      //       tenantId,
      //       gapRent,
      //       CONSTANTS.DUES_TYPES.RENT,
      //       occupancy,
      //       clientId!,
      //       startDate,
      //       endDate,
      //       dueDate,
      //       rentDuration,
      //       description,
      //       title,
      //       dueDescription,
      //       referenceId
      //     );
      //   } else {
      //     log.info(
      //       `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Move In Date [${occupancy.moveInDate}], New Rental Cycle is greater than moveInDate`
      //     );
      //     const startDate = moment(occupancy.moveInDate).format("YYYY-MM-DD");
      //     const referenceDate = moment(occupancy.moveInDate).date(rentalCycle).format("YYYY-MM-DD");
      //     const endDate = moment(referenceDate).subtract(1, "day").format("YYYY-MM-DD");
      //     const daysDiff = moment(endDate).diff(moment(startDate), "days");
      //     const dueDate = moment(startDate).format("YYYY-MM-DD");
      //     const rentDuration = occupancy.rentalMonths;
      //     const description = `Rent added by Kipinn while changing rental cycle for ${startDate} to ${endDate}`;
      //     const title = `Rent`;
      //     const dueDescription = `Rent added by Kipinn while changing rental cycle from ${oldRentalCycle} to ${rentalCycle}`;
      //     const referenceId = await generateLedgerReferenceId({ clientId });

      //     let gapRent = Math.ceil((Number(occupancy.rent) / moment(startDate).daysInMonth()) * (daysDiff + 1));
      //     if (moment(occupancy.moveInDate).date() === Number(rentalCycle)) {
      //       gapRent = occupancy.rent;
      //     }

      //     log.info(JSON.stringify({
      //       tenantId,
      //       daysDiff,
      //       rent: gapRent,
      //       dueType: CONSTANTS.DUES_TYPES.RENT,
      //       occupancy,
      //       clientId,
      //       startDate,
      //       endDate,
      //       dueDate,
      //       rentDuration,
      //       description,
      //       title,
      //       dueDescription,
      //       referenceId
      //     }));

      //     await AddDues(
      //       tenantId,
      //       gapRent,
      //       CONSTANTS.DUES_TYPES.RENT,
      //       occupancy,
      //       clientId!,
      //       startDate,
      //       endDate,
      //       dueDate,
      //       rentDuration,
      //       description,
      //       title,
      //       dueDescription,
      //       referenceId
      //     );
      //   }
      // }

      await handleNoLastRentDue(occupancy, Number(rentalCycle), Number(oldRentalCycle), tenantId, clientId);
    }

    await occupancyDB.updateRentalCycle({
      tenantId: tenant.id,
      clientId: clientId,
      rentalCycle: rentalCycle,
    });

    const zeroAmountDues = await duesDB.getZeroAmountDueByTenantIdAndClientId({
      clientId,
      tenantId,
    });

    if (zeroAmountDues) {
      for (let due of zeroAmountDues) {
        await duesDB.removeDue({
          id: due.id,
        });
        await ledgerDB.remove({
          referenceId: due.ledgerReferenceId,
        });
      }
    }

    await logRentalCycleActivity(
      Number(req.userType),
      Number(req.id),
      Number(req.parentClientId),
      Number(req.platform),
      Number(CONSTANTS.ACTIVITY_TYPES.RENTAL_CYCLE_CHANGE),
      Number(tenantId),
      oldRentalCycle,
      rentalCycle,
    );

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Rental Cycle [${oldRentalCycle}], New Rental Cycle [${rentalCycle}], Rent Difference [${rentDiff}], Rental Cycle Updated Successfully`
    );

    return res.status(200).json({
      msg: "Rental Cycle Updated Successfully",
      isSuccess: true,
    });

  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.InitiateAadharVerifyDigiLocker = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "InitiateAadharVerifyDigiLocker";

  try {
    //Pallav - Multi Tenant Senerio
    let requestedBy = req.id;
    let userType = req.userType;
    let initiatedBy = "Tenant";
    let tenantId = requestedBy;
    let clientId = req.clientId || 0;
    if (Number(userType) === CONSTANTS.USER_TYPE.CLIENT) {
      tenantId = req.body.tenantId;
      clientId = Number(req.id);
      initiatedBy = "CLIENT";
    } else if (Number(userType) === CONSTANTS.USER_TYPE.STAFF) {
      tenantId = req.body.tenantId;
      let Staff = await staffDB.getById({ id: Number(req.id) });
      clientId = Staff?.clientId || 0;
      initiatedBy = "STAFF";
    }

    log.info(
      `[${C}], [${F}], Requested By [${requestedBy}], User Type [${initiatedBy}], Tenant Id [${tenantId}], Client Id [${clientId}], Verify Aadhar using Digilocker`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
        isDown: false,
      });
    }
    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId,
    // });
    //Pallav - Multi Tenant Senerio
    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId,
      clientId,
    });

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}]. Client Id [${clientId}], No Occupancy Found`
      );
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
      moveOut: 0,
    });

    if (isExists && isExists.status === CONSTANTS.DOCUMENT_STATUS.VERIFIED) {
      log.info(
        `[${C}], [${F}], Requested By [${requestedBy}], User Type [${initiatedBy}], Tenant Id [${tenantId}], ID Number Already Exists`
      );
      return res.status(400).json({
        msg: "ID Number Already Exists",
        isDown: false,
        isSuccess: false,
      });
    }
    let linkResponse;
    if (Number(userType) === CONSTANTS.USER_TYPE.TENANT) {
      linkResponse = await createDigiLockerCashfreeLink({
        C,
        F,
        tenantId: Number(tenantId),
        clientId: Number(clientId)
      });
    } else {
      linkResponse = await createDigiLockerCashfreeLinkViaClient({
        C,
        F,
        userId: Number(tenantId),
        userType: CONSTANTS.USER_TYPE.TENANT,
        redirectionUrl: process.env.CASHFREE_DIGILOCKER_TENANT_REDIRECTION_URL || "",
        clientId: Number(clientId)
      });
    }

    let { isSuccess, isServerError, msg, isDown, url } = linkResponse;
    // await createDigiLockerCashfreeLink({
    //   C,
    //   F,
    //   tenantId: Number(tenantId),
    // });


    if (!isSuccess) {
      log.info(
        `[${C}], [${F}], Requested By [${requestedBy}], User Type [${initiatedBy}], Tenant Id [${tenantId}], Failed to generate verification link`
      );
      if (!isServerError) {
        return res.status(400).json({
          msg: msg,
          isSuccess: false,
          isDown,
        });
      } else {
        return res.status(500).json({
          msg: CONSTANTS.MSG.ERROR_MESSAGE,
          isSuccess: false,
          isDown,
        });
      }
    }
    else {
      log.info(
        `[${C}], [${F}], Requested By [${requestedBy}], User Type [${initiatedBy}], Tenant Id [${tenantId}], Verification Link generated successfully`
      );
      return res.status(200).json({
        msg: "Link generated successfully",
        url,
        isSuccess: true,
      });
    }
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isDown: true,
      isSuccess: false,
    });
  }
};

tenants.VerifyDigilockerRequest = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "VerifyDigilockerRequest";

  try {
    let verificationId = req.query.verificationId || "";

    log.info(
      `[${C}], [${F}], Verification Id [${verificationId}], Verify aadhar Digilocker request`
    );
    let tenant = [];

    if (null != verificationId || undefined != verificationId || "" != verificationId) {

      //tenant = await tenantDB.getByVerificationId({ verificationId });
      //Pallav - Multi Tenant Senerio
      tenant = await occupancyDB.getByVerificationId({ verificationId });
    }

    if (null === tenant || false === tenant) {
      log.info(
        `[${C}], [${F}], Verification Id [${verificationId}], Type [AADHAAR] Verification ID not found `
      );
      return res.status(400).json({
        isServerError: false,
        isSuccess: false,
        msg: "Unable to create aadhar link. Please try again later.",
        isDown: true,
      });
    }

    let tenantId = tenant?.tenantId;
    let referenceId = tenant?.referenceId;


    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId,
    // });
    //Pallav - Multi Tenant Senerio
    const occupancy = tenant;

    // if (false == occupancy) {
    //   log.info(
    //     `[${C}], [${F}], Tenant Id [${tenantId}], Verification Id [${verificationId}], Occupancy Not found `
    //   );

    //   return res.status(400).json({
    //     isServerError: false,
    //     isSuccess: false,
    //     msg: "Unable to create aadhar link. Please try again later.",
    //     isDown: true,
    //   });
    // }

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
      moveOut: 0,
    });

    if (isExists && isExists.status === CONSTANTS.DOCUMENT_STATUS.VERIFIED) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number Already Exists`
      );
      return res.status(400).json({
        msg: "ID Number Already Exists",
        isDown: false,
        isSuccess: false,
      });
    }

    const { isServerError, isSuccess, msg, isDown, photo, result, completeAddress, frontUrl, backUrl } =
      await createDigiLockerVerifyRequest({
        C,
        F,
        verificationId: tenant?.verificationId,
        tenantId: Number(tenantId),
        referenceId: Number(referenceId),
        clientId: occupancy?.clientId,
      });


    if (!isSuccess) {
      if (!isServerError) {
        return res.status(400).json({
          msg: msg,
          isSuccess: false,
          isDown,
        });
      } else {
        return res.status(500).json({
          msg: CONSTANTS.MSG.ERROR_MESSAGE,
          isSuccess: false,
          isDown,
        });
      }
    }

    const { name, dob, gender, address, care_of, zip, photo_link, uid } = result;
    const idNumberDB = "XXXX-XXXX-" + uid.slice(-4);
    await tenantDB.updateAadhaarNumber({
      id: tenantId,
      aadharNumber: idNumberDB,
    });

    let genderVal = 0;
    if (gender && gender.toLowerCase() === "m") {
      genderVal = CONSTANTS.GENDER.MALE;
    } else if (gender && gender.toLowerCase() === "f") {
      genderVal = CONSTANTS.GENDER.FEMALE;
    } else {
      genderVal = CONSTANTS.GENDER.OTHER;
    }
    let careOf = care_of || "";
    if (care_of.includes("/")) {
      careOf = care_of?.slice(4).trim() || "";
    }
    await tenantDB.updatePersonalInfo({
      name: name,
      fatherName: careOf,
      dob: (dob && moment(dob, "DD-MM-YYYY").format("YYYY-MM-DD")) || null,
      gender: genderVal,
      address: completeAddress,
      id: tenantId,
      kycStatus: CONSTANTS.KYC_STATUS.ID_UPLOADED,
    });

    let guardianDetail = await tenantGuardianDB.getByTenantId({ tenantId });

    if (false == guardianDetail) {
      await tenantGuardianDB.addFatherName({ tenantId, fatherName: careOf });
    } else {
      await tenantGuardianDB.updateFatherName({ tenantId, fatherName: careOf });
    }

    const isDocExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
      moveOut: 0,
    });

    if (isDocExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
        value: frontUrl,
        id: isDocExists.id,
      });

    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
        value: frontUrl,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });
    }


    await documentDB.addDocWithStatus({
      tenantId,
      clientId: occupancy.clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR_BACK,
      value: backUrl,
      status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
    });
    /********************* End *********************/

    const updatedTenant = await tenantDB.getById({ id: tenantId });
    const imageBuffer = Buffer.from(photo, 'base64');

    await fsPromises.writeFile(`uploads/tmp/img-buffer-${tenantId}.jpeg`, imageBuffer as Uint8Array);

    //Adding Selfi to documents
    const folderName = `tenant_${occupancy.tenantId}`;
    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;
    const filename = `Selfi.jpeg`;

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const selfiUrl = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}/${filename}`;

    await fsPromises.writeFile(`${folderPath}/${filename}`, imageBuffer as Uint8Array);

    const isSelfiExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    if (isSelfiExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.SELFI,
        value: selfiUrl,
        id: isSelfiExists.id,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Selfi re-uploaded sucessfully`
      );
    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.SELFI,
        value: selfiUrl,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Selfi uploaded sucessfully`
      );
    }

    await occupancyDB.updateKycStatusWithClientId({
      tenantId: tenantId,
      clientId: occupancy.clientId,
      kycStatus: CONSTANTS.KYC_STATUS.ID_UPLOADED
    });

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Details fetched & sent sucessfully`
    );

    return res.status(200).json({
      msg: "Addhar verified successfully",
      data: { image: photo || null },
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isDown: true,
      isSuccess: false,
    });
  }
};

tenants.VerifyDigilockerRequestViaClient = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "VerifyDigilockerRequestViaClient";

  try {
    let verificationId = req.query.verificationId || "";

    log.info(
      `[${C}], [${F}], Verification Id [${verificationId}], Verify aadhar Digilocker request`
    );
    let tenant = [];

    if (null != verificationId || undefined != verificationId || "" != verificationId) {
      //tenant = await tenantDB.getByVerificationId({ verificationId });
      //Pallav - Multi Tenant Senerio
      tenant = await occupancyDB.getByVerificationId({ verificationId });
    }

    if (null === tenant || false === tenant) {
      log.info(
        `[${C}], [${F}], Verification Id [${verificationId}], Type [AADHAAR] Verification ID not found `
      );
      return res.status(400).json({
        isServerError: false,
        isSuccess: false,
        msg: "Unable to create aadhar link. Please try again later.",
        isDown: true,
      });
    }

    let tenantId = tenant?.tenantId;
    let referenceId = tenant?.referenceId;


    // const occupancy = await occupancyDB.getByTenantId({
    //   tenantId,
    // });
    //Pallav - Multi Tenant Senerio
    const occupancy = tenant;

    if (false == occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Verification Id [${verificationId}], Occupancy Not found `
      );

      return res.status(400).json({
        isServerError: false,
        isSuccess: false,
        msg: "Unable to create aadhar link. Please try again later.",
        isDown: true,
      });
    }

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
      moveOut: 0,
    });

    if (isExists && isExists.status === CONSTANTS.DOCUMENT_STATUS.VERIFIED) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], ID Number Already Exists`
      );
      return res.status(400).json({
        msg: "ID Number Already Exists",
        isDown: false,
        isSuccess: false,
      });
    }

    const { isServerError, isSuccess, msg, isDown, photo, completeAddress, result, frontUrl, backUrl } =
      await createDigiLockerVerifyRequest({
        C,
        F,
        verificationId: occupancy?.verificationId,
        tenantId: Number(tenantId),
        referenceId: Number(referenceId),
        clientId: occupancy?.clientId,
      });


    if (!isSuccess) {
      if (!isServerError) {
        return res.status(400).json({
          msg: msg,
          isSuccess: false,
          isDown,
        });
      } else {
        return res.status(500).json({
          msg: CONSTANTS.MSG.ERROR_MESSAGE,
          isSuccess: false,
          isDown,
        });
      }
    }

    const { name, dob, gender, address, care_of, zip, photo_link, uid } = result;
    const idNumberDB = "XXXX-XXXX-" + uid.slice(-4);
    await tenantDB.updateAadhaarNumber({
      id: tenantId,
      aadharNumber: idNumberDB,
    });

    let genderVal = 0;
    if (gender && gender.toLowerCase() === "m") {
      genderVal = CONSTANTS.GENDER.MALE;
    } else if (gender && gender.toLowerCase() === "f") {
      genderVal = CONSTANTS.GENDER.FEMALE;
    } else {
      genderVal = CONSTANTS.GENDER.OTHER;
    }
    let careOf = care_of || "";
    if (care_of.includes("/")) {
      careOf = care_of?.slice(4).trim() || "";
    }

    await tenantDB.updatePersonalInfo({
      name: name,
      fatherName: careOf,
      dob: (dob && moment(dob, "DD-MM-YYYY").format("YYYY-MM-DD")) || null,
      gender: genderVal,
      address: completeAddress,
      id: tenantId,
      kycStatus: CONSTANTS.KYC_STATUS.ID_UPLOADED,
    });

    let guardianDetail = await tenantGuardianDB.getByTenantId({ tenantId });

    if (false == guardianDetail) {
      await tenantGuardianDB.addFatherName({ tenantId, fatherName: careOf });
    } else {
      await tenantGuardianDB.updateFatherName({ tenantId, fatherName: careOf });
    }

    const isDocExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
      moveOut: 0,
    });

    if (isDocExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
        value: frontUrl,
        id: isDocExists.id,
      });

    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
        value: frontUrl,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });
    }


    await documentDB.addDocWithStatus({
      tenantId,
      clientId: occupancy.clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR_BACK,
      value: backUrl,
      status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
    });
    /********************* End *********************/

    const updatedTenant = await tenantDB.getById({ id: tenantId });
    const imageBuffer = Buffer.from(photo, 'base64');

    const folderName = `tenant_${occupancy.tenantId}`;
    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;
    const filename = `Selfi.jpeg`;

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    //const oldPath = `uploads/tmp/${file.filename}`;
    const newPath = `${folderPath}/${filename}`;
    const url = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}/${filename}`;

    await fsPromises.writeFile(`${folderPath}/${filename}`, imageBuffer as Uint8Array);

    const isSelfiExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    if (isSelfiExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.SELFI,
        value: url,
        id: isSelfiExists.id,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Selfi re-uploaded sucessfully`
      );
    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.SELFI,
        value: url,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Selfi uploaded sucessfully`
      );
    }
    //await fsPromises.unlink(oldPath);
    await tenantDB.updateKycStatus({
      id: tenantId,
      kycStatus: CONSTANTS.KYC_STATUS.SELFI_UPLOADED,
    });

    await occupancyDB.updateKycStatusWithClientId({
      tenantId: tenantId,
      clientId: occupancy.clientId,
      kycStatus: CONSTANTS.KYC_STATUS.SELFI_UPLOADED
    });

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Tenant aadhar verified sucessfully`
    );

    return res.status(200).json({
      msg: "Addhar verified successfully",
      data: { image: photo || null },
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isDown: true,
      isSuccess: false,
    });
  }
};

tenants.ManuallyCreateRentAgreement = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ManuallyCreateRentAgreement";

  //const file = req.file as Express.Multer.File;

  try {
    let key = req.query.key || "";
    let tenantId = req.query.tenantId || "";

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Key [${key}]`);
    if (!key || !tenantId) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Key [${key}], Parameter missing`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }
    else if (key != "XSAEDRTGDFRR154DERT") {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Key [${key}], Invalid key`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenant?.id,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const profilePic = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    const property = await propertyDB.getById({ id: occupancy.propId });

    if (!property) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Property Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const template = await propertyDB.getRentAgreementTemplate({
      clientId: occupancy.clientId,
      propId: property.id,
    });

    if (!template) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] No Template Found`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${template.path}]`
    );
    let { data: html } = await axios.get(template.path);

    if (!html) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] Template found without content`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    const folderName = `tenant_${occupancy.tenantId}`;



    // if (!fs.existsSync(folderPath)) {
    //   await fsPromises.mkdir(folderPath, { recursive: true });
    // }

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
      moveOut: 0,
    });

    if (!isExists) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Agreement Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let dbAgreementUrl = isExists.value;
    const afterUploads = dbAgreementUrl.split("uploads/")[1];
    const myActualPath = "uploads/" + afterUploads;
    const pathWithRAfile = myActualPath.split(`${folderName}/`)[0];
    const folderPath = `${pathWithRAfile}${folderName}`;


    // const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    const urlBasePath = folderPath.replace("uploads", `${process.env.UPLOAD_PATH}`);

    //const urlBasePath = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    //IMAGE Buffer Convert
    // const imageBuffer = await fsPromises.readFile(`${folderPath}/tenant_signature.${file.mimetype.split("/")[1]}`);
    // const base64String = imageBuffer.toString("base64");
    // const tenantSignatureBase64 = `data:image/jpeg;base64,${base64String}`;
    let signaturePath = `${folderPath}/tenant_signature.jpeg`;
    let signatureUrl = `${urlBasePath}/tenant_signature.jpeg`;
    if (!fs.existsSync(signaturePath)) {
      signaturePath = `${folderPath}/tenant_signature.png`;
      signatureUrl = `${urlBasePath}/tenant_signature.png`;
    }

    //const signatureUrl = `${urlBasePath}/tenant_signature.jpeg`;
    // log.info(
    //   `[${C}], [${F}], Tenant Id [${tenantId}],  Tenant Signature [${signatureUrl}]`
    // );

    html = html.toString();

    //New Added
    let client = await clientDB.getById({ id: occupancy.clientId });
    let settings = await settingsDB.getByClientIdAndPropId({
      clientId: client.id,
      propId: occupancy.propId,
    });

    let logo = settings?.logo
      ? process.env.RS_LOGO_URI + client?.id + "/" + occupancy?.propId + "/" + settings?.logo
      : client?.logo
        ? process.env.RS_LOGO_URI + client?.id + "/" + client?.logo
        : String(process.env.RS_DEFAULT_LOGO_URI);
    const room = await roomDB.getById({ id: occupancy.roomId });
    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
      flatName += `, Room No. ${room.roomNum}`;
    }

    html = html.replace(/{{letterSpacing}}/g, "1");
    html = html.replace(/{{lineHeight}}/g, "12");
    html = html.replace(/{{stampImage}}/g, "https://cms.kipinn.com/backend/uploads/dummyEstamp.jpg");

    if (false == profilePic) {
      html = html.replace(/{{tenantPhoto}}/g, "");
    } else {
      html = html.replace(/{{tenantPhoto}}/g, `<img src="${profilePic?.value}" style="width: 100px;"/>`);
    }
    html = html.replace(/{{tenantMobile}}/g, tenant.mobile);
    if (null != tenant.aadharNumber) {
      html = html.replace(/{{tenantAadhar}}/g, `, ID Proof:Aadhar No. ${tenant.aadharNumber}`);
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, `${tenant.aadharNumber}`);
    }
    else {
      html = html.replace(/{{tenantAadhar}}/g, "");
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, "");
    }

    html = html.replace(/{{logo}}/g, logo);
    // html = html.replace(/{{noticePeriod}}/g, occupancy.noticePeriod);
    html = html.replace(/{{unitNum}}/g, flatName);
    html = html.replace(
      /{{ownerSignature}}/g,
      `<img src="${template.signaturePath}" style="width: 150px;"/>`
    );
    html = html.replace(/{{propertyName}}/g, property.name);
    html = html.replace(/{{institutionName}}/g, tenant.institutionName);
    html = html.replace(/{{alternateMobile}}/g, tenant.alternateMobile);
    html = html.replace(/{{email}}/g, tenant.email);
    // html = html.replace(/{{ownerMobile}}/g, property.ownerMobile);

    html = html.replace(/{{address}}/g, property.address);
    html = html.replace(/{{streetAddress}}/g, property.streetAddress);
    if (occupancy.clientId === 201 || occupancy.clientId === 660) {
      html = html.replace(
        /{{agreementDate}}/g,
        moment(occupancy.agreementStartDate).format("MMMM YYYY")
      );
    } else {
      html = html.replace(
        /{{agreementDate}}/g,
        moment(occupancy.agreementStartDate).format("YYYY-MM-DD")
      );
    }
    html = html.replace(/{{ownerName}}/g, property.ownerName);
    html = html.replace(/{{ownerAddress}}/g, property.address);
    html = html.replace(/{{tenantName}}/g, tenant.name);
    html = html.replace(/{{tenantAddress}}/g, tenant.address);
    if (occupancy.clientId === 201 || occupancy.clientId === 660) {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("MMMM YYYY")
      )
    } else {
      html = html.replace(
        /{{moveInDate}}/g,
        moment(occupancy.moveInDate).format("YYYY-MM-DD")
      );
    }
    if (occupancy.clientId === 201 || occupancy.clientId === 660) {
      html = html.replace(
        /{{agreementEndDate}}/g,
        moment(occupancy.agreementStartDate)
          .add(occupancy.agreementPeriod, "months")
          .subtract(1, "day")
          .format("MMMM YYYY")
      );
    } else {
      html = html.replace(
        /{{agreementEndDate}}/g,
        moment(occupancy.agreementStartDate)
          .add(occupancy.agreementPeriod, "months")
          .subtract(1, "day")
          .format("YYYY-MM-DD")
      );
    }
    html = html.replace(/{{monthlyRent}}/g, occupancy.rent);
    html = html.replace(/{{securityDeposit}}/g, occupancy.security);
    html = html.replace(/{{lockInPeriod}}/g, occupancy.lockInPeriod);

    html = html.replace(
      /{{tenantSignature}}/g,
      `<img src='${signatureUrl}' width="150"  />`
    );
    let eqaroTenant = null;
    // let bondAvailable = property?.isBondAvailable || 0;
    let bondAvailable =
      property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
        ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
          ? CONSTANTS.RENTAL_BOND.MANDATORY
          : 0
        : 0;

    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      eqaroTenant = await eqaroTenantsDB.getByTenantId({
        tenantId: tenant?.id,
      });
      if (eqaroTenant?.status === CONSTANTS.EQARO_TENANT_STATUS.INELIGIBLE) {
        bondAvailable = 0;
      }
    }
    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      html = html.replace(
        /{{rentalBondTerms}}/g,
        `<li>That the Landlord has agreed to accept and acknowledge the Rent Bond issued on behalf of Tenant by Eqaro Surety Private Limited for a value upto INR ${eqaroTenant?.bondAmount
        }, dated ${moment(eqaroTenant?.bondEffectiveDate).format(
          "YYYY-MM-DD"
        )}, as consideration in lieu of interest free security deposit, towards letting out the demised premises under this agreement.</li><li>That the Landlord would be liable to handover the possession of the premises to the Tenant only after the duly signing of the present lease agreement and clearance of the security cheque or the submission of Eqaro Rental Bond. </li><li>That the Tenant shall permit the Landlord and/or his representatives and agents or Eqaro Surety Private Limited and/or its representative at all reasonable times to enter upon the demised premises or any portion therefore for the inspection of the premises or to leave notice of defects to be repaired if any.  Upon invocation of claim against Rent Bond, The Landlord shall permit Eqaro Surety Private Limited and/or its representative at all reasonable times to enter the demised premises or any portion therefore for the inspection and claim investigation</li>`
      );
    } else {
      html = html.replace(/{{rentalBondTerms}}/g, "");
    }
    html = html.replace(/\n/g, "");

    // const options = {
    //   format: "A4",
    //   orientation: "portrait",
    //   border: "10mm",
    //   header: {
    //     height: "60px",
    //     contents: `
    //   <div style="text-align:center; opacity:0.06;">
    //     <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSC4tAahBBN-14dqNlv6B9XTZ_DDgt8zKg99g&s" style="height:50px;" />
    //   </div>
    // `,
    //   },

    //   footer: {
    //     height: "40px",
    //     contents: {
    //       default: `
    //     <div style="text-align:center; font-size:10px; color:#999;">
    //       Page {{page}} of {{pages}}
    //     </div>
    //   `,
    //     },
    //   },
    //   childProcessOptions: {
    //     env: {
    //       OPENSSL_CONF: "/dev/null",
    //     },
    //   },
    // };

    //     const options1 = {
    //       format: "A4",
    //       orientation: "portrait",
    // border: {
    //     top: "20mm",
    //     right: "20mm",
    //     bottom: "20mm",
    //     left: "25mm"
    //   },

    //       footer: {
    //         height: "25px",
    //         contents: `
    //     <div style="text-align:center; font-size:10px; color:#999;"> Page {{page}} of {{pages}} </div>
    //   `,
    //       },
    //       childProcessOptions: {
    //         env: {
    //           OPENSSL_CONF: "/dev/null",
    //         },
    //       },
    //     };


    const options = {
      format: "A4",
      orientation: "portrait",
      border: "10mm",
      //border: "0",
      childProcessOptions: {
        env: {
          OPENSSL_CONF: "/dev/null",
        },
      },
    };

    const filename = "RentAgreement.pdf";
    const url = `${urlBasePath}/${filename}`;

    const coverImagePath = 'uploads/tmp/stamp.jpeg'; // your local cover image

    const coverImageBase64 = fs.existsSync(coverImagePath)
      ? `data:image/jpeg;base64,${fs.readFileSync(coverImagePath).toString('base64')}`
      : logo;

    // Mask aadhar - show only last 4 digits
    const maskedAadhar = tenant.aadharNumber
      ? `XXXX-XXXX-${String(tenant.aadharNumber).slice(-4)}`
      : null;

    // Build raw base64 only — no data URI prefix here
    const coverImageRawBase64 = fs.existsSync(coverImagePath)
      ? fs.readFileSync(coverImagePath).toString('base64')
      : null;

    const ext = coverImagePath.split('.').pop() || 'jpeg';

    // Then in the HTML use it correctly
    const imgSrc = coverImageRawBase64
      ? `data:image/${ext};base64,${coverImageRawBase64}`
      : logo; // fallback to logo URL if file not found

    const coverImageHtml = `
  <div style="page-break-after:always; margin:0; padding:0; overflow:hidden;">
    
    <img 
      src="${imgSrc}"
      style="
        display:block;
        width:230mm;
        height:305mm;
        object-fit:cover;
      "
    />

    <div style="
      margin-top:-160mm;
      margin-left:10mm;
      margin-right:10mm;
      padding:15px 20px;
    ">
      <p style="font-family:'Courier New',Courier,monospace; color:#0000000; font-size:9.5px; line-height:1.6; margin:0 0 8px 0; text-align:justify;">
        This Leave and License Agreement (&quot;Agreement&quot;) is executed at 
        ${property.address}, on ${moment(occupancy.agreementStartDate).format("YYYY-MM-DD")}, 
        under the provisions of Section 52 of the Indian Easements Act, 1882, 
        and shall remain valid until 
        ${moment(occupancy.agreementStartDate).add(occupancy.agreementPeriod, "months").format("YYYY-MM-DD")}.
      </p>
      <p style="font-family:'Courier New',Courier,monospace; color:#0000000; font-size:9.5px; line-height:1.6; margin:0 0 8px 0;">
        This Agreement is made and entered into by and between:
      </p>
      <p style="font-family:'Courier New',Courier,monospace; color:#0000000; font-size:9.5px; line-height:1.6; margin:0 0 8px 0; text-align:justify;">
        <strong>${property.ownerName}</strong>, representing the property known as 
        <strong>${flatName}</strong>, situated at ${property.address}, 
        hereinafter referred to as the &quot;Licensor / Management&quot;.
      </p>
      <p style="font-family:'Courier New',Courier,monospace; color:#0000000; font-size:9.5px; line-height:1.6; margin:0; text-align:justify;">
        <strong>${tenant.name}</strong> 
        (Mobile No: ${tenant.mobile}, ID Proof:${maskedAadhar ? ` Aadhar No. ${maskedAadhar}` : ''}), 
        residing at ${tenant.address}, 
        hereinafter referred to as the &quot;Licensee / Tenant&quot;.
      </p>
    </div>

  </div>
`;
    // // All your existing html.replace() calls first...
    // html = html.replace(/{{tenantName}}/g, tenant.name);

    // // Inject cover page as first page
    // if (html.includes('<body>')) {
    //   html = html.replace('<body>', `<body>${coverImageHtml}`);
    // } else {
    //   html = coverImageHtml + html;
    // }

    // This must stay LAST
    html = html.replace(/\n/g, "");

    var document = {
      html: html,
      path: `${folderPath}/${filename}`,
      data: {},
      type: "",
    };


    await pdf.create(document, options);

    // const isExists = await documentDB.getIDByType({
    //   tenantId,
    //   clientId: occupancy?.clientId,
    //   type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
    //   moveOut: 0,
    // });

    if (isExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        id: isExists.id,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], URL [${url}], Rent Agreement Updated Successfully`
      );
    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], URL [${url}], Rent Agreement Created Successfully`
      );
    }

    return res.status(200).json({
      msg: "Rent Agreement Re-created Successfully",
      url: url,
      isSuccess: true,
    });

  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ManuallyCreateRentAgreementMergePdf = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ManuallyCreateRentAgreementMergePdf";

  //const file = req.file as Express.Multer.File;

  try {
    let key = req.query.key || "";
    let tenantId = req.query.tenantId || "";

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Key [${key}]`);
    if (!key || !tenantId) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Key [${key}], Parameter missing`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }
    else if (key != "XSAEDRTGDFRR154DERT") {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Key [${key}], Invalid key`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenant?.id,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Occupancy Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const profilePic = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.SELFI,
      moveOut: 0,
    });

    const property = await propertyDB.getById({ id: occupancy.propId });

    if (!property) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Property Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const template = await propertyDB.getRentAgreementTemplate({
      clientId: occupancy.clientId,
      propId: property.id,
    });

    if (!template) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] No Template Found`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${template.path}]`
    );
    let { data: html } = await axios.get(template.path);

    if (!html) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] Template found without content`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    const folderName = `tenant_${occupancy.tenantId}`;



    // if (!fs.existsSync(folderPath)) {
    //   await fsPromises.mkdir(folderPath, { recursive: true });
    // }

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
      moveOut: 0,
    });

    if (!isExists) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], No Agreement Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    let dbAgreementUrl = isExists.value;
    const afterUploads = dbAgreementUrl.split("uploads/")[1];
    const myActualPath = "uploads/" + afterUploads;
    const pathWithRAfile = myActualPath.split(`${folderName}/`)[0];
    const folderPath = `${pathWithRAfile}${folderName}`;


    // const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    const urlBasePath = folderPath.replace("uploads", `${process.env.UPLOAD_PATH}`);

    //const urlBasePath = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    //IMAGE Buffer Convert
    // const imageBuffer = await fsPromises.readFile(`${folderPath}/tenant_signature.${file.mimetype.split("/")[1]}`);
    // const base64String = imageBuffer.toString("base64");
    // const tenantSignatureBase64 = `data:image/jpeg;base64,${base64String}`;
    let signaturePath = `${folderPath}/tenant_signature.jpeg`;
    let signatureUrl = `${urlBasePath}/tenant_signature.jpeg`;
    if (!fs.existsSync(signaturePath)) {
      signaturePath = `${folderPath}/tenant_signature.png`;
      signatureUrl = `${urlBasePath}/tenant_signature.png`;
    }

    //const signatureUrl = `${urlBasePath}/tenant_signature.jpeg`;
    // log.info(
    //   `[${C}], [${F}], Tenant Id [${tenantId}],  Tenant Signature [${signatureUrl}]`
    // );

    html = html.toString();

    //New Added
    let client = await clientDB.getById({ id: occupancy.clientId });
    let settings = await settingsDB.getByClientIdAndPropId({
      clientId: client.id,
      propId: occupancy.propId,
    });

    let logo = settings?.logo
      ? process.env.RS_LOGO_URI + client?.id + "/" + occupancy?.propId + "/" + settings?.logo
      : client?.logo
        ? process.env.RS_LOGO_URI + client?.id + "/" + client?.logo
        : String(process.env.RS_DEFAULT_LOGO_URI);
    const room = await roomDB.getById({ id: occupancy.roomId });
    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
      flatName += `, Room No. ${room.roomNum}`;
    }


    if (false == profilePic) {
      html = html.replace(/{{tenantPhoto}}/g, "");
    } else {
      html = html.replace(/{{tenantPhoto}}/g, `<img src="${profilePic?.value}" style="width: 100px;"/>`);
    }
    html = html.replace(/{{tenantMobile}}/g, tenant.mobile);
    if (null != tenant.aadharNumber) {
      html = html.replace(/{{tenantAadhar}}/g, `, ID Proof:Aadhar No. ${tenant.aadharNumber}`);
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, `${tenant.aadharNumber}`);
    }
    else {
      html = html.replace(/{{tenantAadhar}}/g, "");
      html = html.replace(/{{tenantAadharWithoutPrefix}}/g, "");
    }

    html = html.replace(/{{logo}}/g, logo);
    // html = html.replace(/{{noticePeriod}}/g, occupancy.noticePeriod);
    html = html.replace(/{{unitNum}}/g, flatName);
    html = html.replace(
      /{{ownerSignature}}/g,
      `<img src="${template.signaturePath}" style="width: 150px;"/>`
    );
    html = html.replace(/{{propertyName}}/g, property.name);
    html = html.replace(/{{institutionName}}/g, tenant.institutionName);
    html = html.replace(/{{alternateMobile}}/g, tenant.alternateMobile);
    html = html.replace(/{{email}}/g, tenant.email);
    // html = html.replace(/{{ownerMobile}}/g, property.ownerMobile);

    html = html.replace(/{{address}}/g, property.address);
    html = html.replace(/{{streetAddress}}/g, property.streetAddress);
    html = html.replace(
      /{{agreementDate}}/g,
      moment(occupancy.agreementStartDate).format("YYYY-MM-DD")
    );
    html = html.replace(/{{ownerName}}/g, property.ownerName);
    html = html.replace(/{{ownerAddress}}/g, property.address);
    html = html.replace(/{{tenantName}}/g, tenant.name);
    html = html.replace(/{{tenantAddress}}/g, tenant.address);
    html = html.replace(
      /{{moveInDate}}/g,
      moment(occupancy.moveInDate).format("YYYY-MM-DD")
    );
    html = html.replace(
      /{{agreementEndDate}}/g,
      moment(occupancy.agreementStartDate)
        .add(occupancy.agreementPeriod, "months")
        .subtract(1, "day")
        .format("YYYY-MM-DD")
    );
    html = html.replace(/{{monthlyRent}}/g, occupancy.rent);
    html = html.replace(/{{securityDeposit}}/g, occupancy.security);
    html = html.replace(/{{lockInPeriod}}/g, occupancy.lockInPeriod);

    html = html.replace(
      /{{tenantSignature}}/g,
      `<img src='${signatureUrl}' width="150"  />`
    );
    let eqaroTenant = null;
    // let bondAvailable = property?.isBondAvailable || 0;
    let bondAvailable =
      property?.isBondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY
        ? occupancy?.rentalBond === CONSTANTS.RENTAL_BOND.MANDATORY
          ? CONSTANTS.RENTAL_BOND.MANDATORY
          : 0
        : 0;

    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      eqaroTenant = await eqaroTenantsDB.getByTenantId({
        tenantId: tenant?.id,
      });
      if (eqaroTenant?.status === CONSTANTS.EQARO_TENANT_STATUS.INELIGIBLE) {
        bondAvailable = 0;
      }
    }
    if (bondAvailable === CONSTANTS.RENTAL_BOND.MANDATORY) {
      html = html.replace(
        /{{rentalBondTerms}}/g,
        `<li>That the Landlord has agreed to accept and acknowledge the Rent Bond issued on behalf of Tenant by Eqaro Surety Private Limited for a value upto INR ${eqaroTenant?.bondAmount
        }, dated ${moment(eqaroTenant?.bondEffectiveDate).format(
          "YYYY-MM-DD"
        )}, as consideration in lieu of interest free security deposit, towards letting out the demised premises under this agreement.</li><li>That the Landlord would be liable to handover the possession of the premises to the Tenant only after the duly signing of the present lease agreement and clearance of the security cheque or the submission of Eqaro Rental Bond. </li><li>That the Tenant shall permit the Landlord and/or his representatives and agents or Eqaro Surety Private Limited and/or its representative at all reasonable times to enter upon the demised premises or any portion therefore for the inspection of the premises or to leave notice of defects to be repaired if any.  Upon invocation of claim against Rent Bond, The Landlord shall permit Eqaro Surety Private Limited and/or its representative at all reasonable times to enter the demised premises or any portion therefore for the inspection and claim investigation</li>`
      );
    } else {
      html = html.replace(/{{rentalBondTerms}}/g, "");
    }
    html = html.replace(/\n/g, "");

    const options = {
      format: "A4",
      orientation: "portrait",
      border: "10mm",
      //border: "0",
      childProcessOptions: {
        env: {
          OPENSSL_CONF: "/dev/null",
        },
      },
    };

    const filename = "RentAgreement.pdf";
    const url = `${urlBasePath}/${filename}`;

    const coverImagePath = 'uploads/tmp/stamp.jpeg'; // your local cover image

    const coverImageBase64 = fs.existsSync(coverImagePath)
      ? `data:image/jpeg;base64,${fs.readFileSync(coverImagePath).toString('base64')}`
      : logo;

    // This must stay LAST
    html = html.replace(/\n/g, "");

    var document = {
      html: html,
      path: `${folderPath}/${filename}`,
      data: {},
      type: "",
    };


    await pdf.create(document, options);

    // const isExists = await documentDB.getIDByType({
    //   tenantId,
    //   clientId: occupancy?.clientId,
    //   type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
    //   moveOut: 0,
    // });

    if (isExists) {
      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        id: isExists.id,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], URL [${url}], Rent Agreement Updated Successfully`
      );
    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.RENT_AGREEMENT,
        value: url,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], URL [${url}], Rent Agreement Created Successfully`
      );
    }

    return res.status(200).json({
      msg: "Rent Agreement Re-created Successfully",
      url: url,
      isSuccess: true,
    });

  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.RefundRequest = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "RefundRequest";

  //YET TO CHANGE THE SMS

  const file = req.file as Express.Multer.File;
  const removeTmpImages = async () => {
    try {
      await fsPromises.unlink(file.path);
    } catch (err) {
      log.info(
        `[${C}], [${F}], Error in Removing Temp Image, Error [${JSON.stringify(
          err
        )}]`
      );
    }
  };
  //console.log(req);
  try {
    const tenantId = req.id;
    let { clientId } = req.body;
    let { accountNum, ifscCode, bankAddress, bankName, holderName, upiId = "" } = req.body;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Is File Uploaded ${file ? `[Yes], File ${JSON.stringify(file)}` : "[No]"}, Account No. [${accountNum}], IFSC Code [${ifscCode}], Bank Name [${bankName}], Account Holder Name [${holderName}], Bank Address [${bankAddress}], UPI [${upiId}]`
    );

    const clean = (val?: string) => val && val.trim() !== "" ? val.trim() : null;
    let isAccountDetailExist = false;
    if (clean(accountNum) && clean(ifscCode) && clean(bankName) && clean(holderName)) {
      isAccountDetailExist = true;
    }
    if (clean(upiId)) {
      isAccountDetailExist = true;
    }
    
    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`
      );
      if (file) {
        await fsPromises.unlink(file.path);
      }
      return res.status(400).json({
        msg: "Your are not allowed to send refund request",
        isSuccess: false,
      });
    }

    const isRequestAlreadySent = await requestDB.getByTenantId({
      tenantId,
      type: CONSTANTS.REQUEST_TYPE.REFUND,
      status: CONSTANTS.REQUEST_STATUS.PENDING,
    });

    if (isRequestAlreadySent) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Refund Request Already Sent`
      );
      if (file) {
        await fsPromises.unlink(file.path);
      }
      return res.status(400).json({
        msg: "Refund request already sent",
        isSuccess: false,
      });
    }

    // const isRequestReopenAvailable = await requestDB.getByTenantIdAndClientId({
    //   clientId,
    //   tenantId,
    //   type: CONSTANTS.REQUEST_TYPE.REFUND,
    //   status: CONSTANTS.REQUEST_STATUS.REOPEN_ALLOWED,
    // });

    // if (isRequestReopenAvailable) {
    //   log.info(
    //     `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Refund Request Id [${isRequestReopenAvailable?.id}], Refund Request Reopen Available`
    //   );
    //   await requestDB.updateStatus({
    //     id: isRequestReopenAvailable?.id,
    //     status: CONSTANTS.REQUEST_STATUS.PENDING,
    //   });
    // }


    const occupancy = await moveOutDB.getByTenantIdAndClientId({ tenantId, clientId });
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], No Moved Out Record Found`
      );
      if (file) {
        await fsPromises.unlink(file.path);
      }
      return res.status(400).json({
        msg: "Unable to send refund request because of lack of moved out record.",
        isSuccess: false,
      });
    }

    if (occupancy.status !== CONSTANTS.MOVE_OUT_STATUS.MOVEOUT) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Occupancy status is not Moved out`
      );
      if (file) {
        await fsPromises.unlink(file.path);
      }
      return res.status(400).json({
        msg: "Unable to send move out request because your are not moved out",
        isSuccess: false,
      });
    }

    const client = await clientDB.getById({ id: occupancy.clientId });

    const room = await roomDB.getById({ id: occupancy.roomId });
    const property = await propertyDB.getById({ id: occupancy.propId });

    const title = `Refund request received`;
    const description = `${tenant.name} is requesting for refund for Room (${room.roomNum}) of Property (${property.name})`;

    let requestId = null;

    const isRequestReopenAvailable = await requestDB.getByTenantIdAndClientId({
      clientId,
      tenantId,
      type: CONSTANTS.REQUEST_TYPE.REFUND,
      status: CONSTANTS.REQUEST_STATUS.REOPEN_ALLOWED,
    });

    if (isRequestReopenAvailable) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Refund Request Id [${isRequestReopenAvailable?.id}], Refund Request Reopen Available`
      );
      await requestDB.updateStatus({
        id: isRequestReopenAvailable?.id,
        status: CONSTANTS.REQUEST_STATUS.PENDING,
      });
      requestId = isRequestReopenAvailable?.id;
    } else {
      requestId = await requestDB.create({
        clientId: occupancy.clientId,
        occupancyId: occupancy.id,
        tenantId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        floor: occupancy.floor,
        title,
        description,
        type: CONSTANTS.REQUEST_TYPE.REFUND,
      });
  
      await requestDB.updateIsMoveOutId({
        id: requestId,
        isMoveOutId: 1,
      });
    }

    if (isAccountDetailExist) {
      const isBank = await tenantBankDB.getByClientIdAndTenantId({
        clientId,
        tenantId,
      });
      if (isBank && isBank.length > 0) {
        await tenantBankDB.updateBank({
          holderName: holderName && holderName.trim() !== "" ? holderName.trim() : null,
          accountNum,
          ifsc: ifscCode && ifscCode.trim() !== "" ? ifscCode.trim() : null,
          bankAddress: bankAddress && bankAddress.trim() !== "" ? bankAddress.trim() : null,
          bankName: bankName && bankName.trim() !== "" ? bankName.trim() : null,
          upiId: upiId && upiId.trim() !== "" ? upiId.trim() : null,
          id: isBank.id,
        })
      } else {
        await tenantBankDB.addBank({
          clientId,
          tenantId,
          holderName: holderName && holderName.trim() !== "" ? holderName.trim() : null,
          accountNum,
          ifsc: ifscCode && ifscCode.trim() !== "" ? ifscCode.trim() : null,
          bankAddress: bankAddress && bankAddress.trim() !== "" ? bankAddress.trim() : null,
          bankName: bankName && bankName.trim() !== "" ? bankName.trim() : null,
          upiId: upiId && upiId.trim() !== "" ? upiId.trim() : null,
        });
      }
    }

    let isNotiSent = false;
    if (client.regId) {
      // const regIds = await clientDB.getRegIds({
      //   id: clientId,
      // });

      // if (regIds && regIds.length > 0) {
      //   for (let regId of regIds) {
      //     isNotiSent = await sendNotification({
      //       title: title,
      //       message: description,
      //       regId: regId.regId,
      //       userId: client.id,
      //       userType: CONSTANTS.USER_TYPE.CLIENT,
      //       notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.REFUND,
      //       clientId: occupancy.clientId,
      //     });
      //   }
      // } else {
      // }
      isNotiSent = await sendNotification({
        title: title,
        message: description,
        regId: client.regId,
        userId: client.id,
        userType: CONSTANTS.USER_TYPE.CLIENT,
        notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.REFUND,
        clientId: occupancy.clientId,
      });
    } else {
      await notificationDB.add({
        userId: client.id,
        userType: CONSTANTS.USER_TYPE.CLIENT,
        title,
        message: description,
        notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.REFUND,
      });
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], No RegId found for client [${client.id}]`
      );
    }

    const msg = CONSTANTS.MSG.REFUND_REQ;

    const isSent = await sendSMS(
      client.mobile,
      msg,
      CONSTANTS.SMS_TEMPLATE_IDS.MOVE_OUT_REQ
    );

    if (file) {
      const folderName = `Tenant_${tenantId}`;
      const folderPath = `uploads/documents/client_${client.id}/${folderName}`;
      const fileExtension = file.mimetype.split("/")[1];
      const filename = `cancelledCheque.${fileExtension}`;

      if (!fs.existsSync(folderPath)) {
        await fsPromises.mkdir(folderPath, { recursive: true });
      }

      const oldPath = `uploads/tmp/${file.filename}`;
      const newPath = `${folderPath}/${filename}`;
      const url = `${process.env.UPLOAD_PATH}/documents/client_${client.id}/${folderName}/${filename}`;

      await fsPromises.copyFile(oldPath, newPath);

      await documentDB.addDoc({
        tenantId,
        clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.CANCELLED_CHEQUE,
        value: url,
      });

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Old Path [${oldPath}], New Path [${newPath}], Url [${url}], Document Saved in DB.`
      );
    }

    let collection = 0;
    let advance = 0;
    const { totalCollection, advancePaid } =
      await ledgerDB.getTotalByTenantIdForMovingOut({
        tenantId,
        clientId,
      });
    collection = totalCollection;
    advance = advancePaid;

    collection = collection || 0;
    advance = advance || 0;
    let total = 0;
    if (advance < 0 && collection >= 0) {
      total = Number(collection) + Number(advance);
    } else if (advance < 0 && collection < 0) {
      total = Number(advance);
    } else {
      total = Number(collection);
    }
    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
    }
    const notiSettings = await settingsDB.getByClientIdAndPropId({
      clientId: occupancy.clientId,
      propId: occupancy.propId,
    });

    let footerText = notiSettings.footer || "Kipinn Team";

    await sendWhatsappNewRequest(
      client?.mobile || "",
      client?.name || "",
      tenant?.name || "",
      "Refund",
      property?.name || "",
      `${flatName} (${room.roomNum})`,
      footerText,
      clientId
    );

    logTenantRequestActivity(
      req.userType!,
      Number(req.id)!,
      0,
      Number(req.platform),
      CONSTANTS.ACTIVITY_TYPES.REFUND_REQUEST_INITIATED,
      property?.id,
      Number(tenantId),
      tenant.name,
      occupancy.roomId,
      0
    );

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Is Notification Sent [${isNotiSent}], Is SMS Sent [${isSent}], Refund request sent successfully`
    );

    return res.status(200).json({
      msg: "Refund request sent successfully",
      data: {
        requestId,
        refundAmount: Number(total) > 0 ? 0 : total,
      },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    if (file) {
      await removeTmpImages();
    }
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.RefundRequestStatus = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "RefundRequestStatus";

  try {
    const { requestId } = req.params;
    const tenantId = req.id;
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const request = await requestDB.getStatusById({ id: requestId });

    if (!request) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], No Request Found`
      );

      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let msg = "";

    if (request.status === CONSTANTS.REQUEST_STATUS.PENDING) {
      msg = "Your refund request is still pending";
    } else if (request.status === CONSTANTS.REQUEST_STATUS.REJECTED) {
      msg = "Your refund request was rejected by the owner";
    } else if (request.status === CONSTANTS.REQUEST_STATUS.APPROVED) {
      msg = "Your refund request was approved by the owner";
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Request Id [${requestId}], Refund request details sent successfully`
    );

    return res.status(200).json({
      msg: "Refund request details sent successfully",
      data: { ...request, msg },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.RejectRefundRequest = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "RejectRefundRequest";

  try {
    const { requestId } = req.body;

    const userType = req.userType;
    let clientId = req.id;

    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(
          `[${C}], [${F}], Request Id [${requestId}] Staff Id [${req.id}], No Staff Found`
        );
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}] Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}] Client Requested....`
      );
    }

    const client = await clientDB.getById({ id: clientId });

    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const request = await requestDB.getById({ id: requestId });

    if (!request) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], No Request Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    if (request.status !== CONSTANTS.REQUEST_STATUS.PENDING) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], Request Already Approved Or Rejected`
      );
      return res.status(400).json({
        msg: "Request already approved or rejected",
        isSuccess: false,
      });
    }

    await requestDB.updateStatus({
      id: request?.id,
      status: CONSTANTS.REQUEST_STATUS.REJECTED,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Request Id [${requestId}], Refund Request Rejected Successfully`
    );

    return res.status(200).json({
      isSuccess: true,
      msg: "Request rejected successfully",
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ToggleFood = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ToggleFood";

  try {
    const { tenantId, toggleValue } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Toggle Value [${toggleValue}]`
    );

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : staff.role === CONSTANTS.STAFF_ROLES.WARDEN ? "Warden" : "Back-Office"
        } Requesting....`
      );
    }

    await occupancyDB.updateIsFoodOpted({
      tenantId: tenantId,
      clientId: clientId,
      isFoodOpted: toggleValue,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Toggle Value [${toggleValue}], Food Option Toggled Successfully.`
    );

    return res.status(200).json({
      msg: `${Number(toggleValue) === 1 ? "Food option enabled successfully." : "Food option disabled successfully."}`,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ToggleBus = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ToggleBus";

  try {
    const { tenantId, toggleValue } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Toggle Value [${toggleValue}]`
    );

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : staff.role === CONSTANTS.STAFF_ROLES.WARDEN ? "Warden" : "Back-Office"
        } Requesting....`
      );
    }

    await occupancyDB.updateIsBusOpted({
      tenantId: tenantId,
      clientId: clientId,
      isBusOpted: toggleValue,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Toggle Value [${toggleValue}], Transportation Option Toggled Successfully.`
    );

    return res.status(200).json({
      msg: `${Number(toggleValue) === 1 ? "Transportation enabled successfully." : "Transportation disabled successfully."}`,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListRenewedRentAgreements = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ListRenewedRentAgreements";

  try {
    const { startDate, endDate, s, t } = req.query;

    log.info(
      `[${C}], [${F}], Start Date [${startDate}], End Date [${endDate}], Search Val [${s}], Search Type [${t}]`
    );

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : staff.role === CONSTANTS.STAFF_ROLES.WARDEN ? "Warden" : "Back-Office"
        } Requesting....`
      );
    }

    let renewedAgreements: any = [];

    if (s && s !== "" && s !== 'undefined') {
      renewedAgreements = await rentAgreementRecordDB.getByClientIdAndSearch({
        clientId,
        searchVal: s,
        searchType: t,
      });
    } else {
      renewedAgreements = await rentAgreementRecordDB.getByClientIdAndCreatedAtRange({
        clientId,
        startDate,
        endDate,
      });
    }
    if (renewedAgreements && renewedAgreements.length > 0) {
      for (let renewedAgreement of renewedAgreements) {
        let occupancy = await occupancyDB.getDetailByTenantIdAndClientId({ tenantId: renewedAgreement?.tenantId, clientId: renewedAgreement?.clientId });
        let doc = await documentDB.getIDByType({ tenantId: renewedAgreement?.tenantId, clientId: renewedAgreement?.clientId, type: CONSTANTS.DOCUMENT_TYPES.SELFI, moveOut: 0 });
        if (!occupancy) {
          occupancy = await moveOutDB.getByTenantIdAndClientId({ tenantId: renewedAgreement?.tenantId, clientId: renewedAgreement?.clientId });
          doc = await documentDB.getIDByType({ tenantId: renewedAgreement?.tenantId, clientId: renewedAgreement?.clientId, type: CONSTANTS.DOCUMENT_TYPES.SELFI, moveOut: 1 });
        }
        renewedAgreement.kycStatus = CONSTANTS.KYC_STATUS.PENDING;
        if (occupancy) {
          renewedAgreement.kycStatus = occupancy.kycStatus;
        }
        renewedAgreement.profilePicture = null;
        if (doc) {
          renewedAgreement.profilePicture = doc.value;
        }
      }
    }
    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Start Date [${startDate}], End Date [${endDate}], Search Val [${s}], Search Type [${t}], List Sent Successfully`
    );

    return res.status(200).json({
      msg: "List fetched successfully",
      isSuccess: true,
      list: renewedAgreements || [],
      count: renewedAgreements.length || 0,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetFullAndFinalBreakdown = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetFullAndFinalBreakdown";

  try {
    const { tenantId, sendFnF = "0" } = req.query;

    const userType = req.userType;

    let clientId = req.id;
    if (userType === CONSTANTS.USER_TYPE.STAFF) {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res
          .status(400)
          .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Staff Id [${req.id}], Staff Requested.....`
      );
    } else {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Client Requested....`
      );
    }

    let tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Tenant Found`);
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancy = await moveOutDB.getByTenantIdAndClientId({
      clientId: clientId,
      tenantId: tenantId,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Moved Occupancy Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const room = await roomDB.getById({ id: occupancy.roomId });

    const property = await propertyDB.getById({ id: occupancy.propId });

    if (!property) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Property Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const client = await clientDB.getById({ id: clientId });
    if (!client) {
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Client Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    // const isRefunded = await ledgerDB.getLastRefundEntry({
    //   tenantId,
    //   clientId,
    //   moveInDate: occupancy.moveInDate,
    // });

    // if (isRefunded) {
    if (occupancy.refundStatus === CONSTANTS.REFUND_STATUS.PROCESSED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Refund Already Processed, Sending Previous Document`
      );

      const settlementSummaryDoc = await documentDB.getIDByTypeAndStatus({
        tenantId,
        clientId,
        type: CONSTANTS.DOCUMENT_TYPES.SETTLEMENT_SUMMARY,
        moveOut: 1,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });

      if (!settlementSummaryDoc) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Refund Already Processed, But Previous Verified Document Not Found, Creating Document Again`
        );
      } else {
        return res.status(200).json({
          msg: "Tenant Settlement Summary document sent Successfully",
          link: settlementSummaryDoc?.value,
          tenantMobile: tenant?.mobile,
          isSuccess: true,
        });
      }
    }


    let templatePath = `${process.env.UPLOAD_PATH}/documents/defaults/tenant_moveout_summary.html`;
    let { data: html } = await axios.get(templatePath);

    if (!html) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Client Id [${occupancy.clientId}], Prop Id [${property.id}] Template found without content`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.ERROR_MESSAGE, isSuccess: false });
    }

    const folderName = `tenant_${occupancy.tenantId}`;

    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const urlBasePath = `${process.env.UPLOAD_PATH}/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;

    html = html.toString();

    let logo = process.env.RS_DEFAULT_LOGO_URI;

    let flatName = "";
    let fieldValue = "Room No.";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
      fieldValue = "Flat No.";
      html = html.replace(/{{flatFloor}}/g, "Flat");
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
      html = html.replace(/{{flatFloor}}/g, "Floor");
    }
    flatName += ` (${room.roomNum})`;
    html = html.replace(/{{tenant_name}}/g, tenant.name);
    html = html.replace(/{{flat_number}}/g, flatName);
    html = html.replace(/{{move_in_date}}/g, occupancy.moveInDate);
    html = html.replace(/{{move_out_date}}/g, occupancy.moveOutDate);
    html = html.replace(/{{rent_amount}}/g, occupancy.rent);
    html = html.replace(/{{logo}}/g, logo);
    html = html.replace(/{{propertyName}}/g, property.name);
    html = html.replace(/{{propertyAddress}}/g, property.address);

    const pendingDues = await moveOutDuesDB.getByTenantIdAndClientId({
      tenantId,
      clientId,
    });
    let rentDues: any = [];
    let fineDues: any = [];
    let utilityDues: any = [];
    if (pendingDues && pendingDues.length > 0) {
      rentDues = pendingDues.filter((due: any) => due.type === CONSTANTS.DUES_TYPES.RENT);
      let rentSection: any = [];
      fineDues = pendingDues.filter((due: any) => due.type === CONSTANTS.DUES_TYPES.FINE);
      let fineSection: any = [];
      utilityDues = pendingDues.filter((due: any) =>
        due.type !== CONSTANTS.DUES_TYPES.RENT &&
        due.type !== CONSTANTS.DUES_TYPES.FINE
      );
    }

    let noRentRecord = true;
    let noFineRecord = true;
    let noOtherRecord = true;

    //Rent Dues Section
    let rentRowsHtml = "";
    let rentTotal = 0;

    for (const due of rentDues) {
      noRentRecord = false;
      rentRowsHtml += `
        <tr>
          <td>${due.rentStartDate || "-"}</td>
          <td>${due.rentEndDate || "-"}</td>
          <td>₹${due.balance ?? due.amount}</td>
        </tr>
      `;
      rentTotal += Number(due.balance ?? due.amount);
    }

    //Late Fee Section
    let lateFeesRowsHtml = "";
    let lateFeesTotal = 0;

    for (const due of fineDues) {
      noFineRecord = false;
      lateFeesRowsHtml += `
        <tr>
          <td>${due.rentStartDate || "-"}</td>
          <td>${due.rentEndDate || "-"}</td>
          <td>₹${due.balance ?? due.amount}</td>
        </tr>
      `;
      lateFeesTotal += Number(due.balance ?? due.amount);
    }

    //Utility Dues Section
    let utilityRowsHtml = "";
    let utilityTotal = 0;

    for (const due of utilityDues) {
      noOtherRecord = false;
      let dueTypeDesc = await getDueDescription(due.type);
      utilityRowsHtml += `
        <tr>
          <td colspan = "2">${due.description || due.title || dueTypeDesc}</td>
          <td  colspan = "1">₹${due.balance ?? due.amount}</td>
        </tr>
      `;
      utilityTotal += Number(due.balance ?? due.amount);
    }

    const securityAdjustedDues = await ledgerDB.getSecurityAdjustedRecordsAfterEviction({
      clientId,
      tenantId,
      moveOutDate: moment(occupancy.moveOutDate).format("YYYY-MM-DD"),
    });

    let securityAdjustedRowsHtml = "";
    let securityAdjustedTotal = 0;

    if (securityAdjustedDues && securityAdjustedDues.length > 0) {
      for (const due of securityAdjustedDues) {
        if (due.type === CONSTANTS.DUES_TYPES.RENT) {
          noRentRecord = false;
          rentRowsHtml += `
            <tr>
              <td>${due.rentStartDate || "-"}</td>
              <td>${due.rentEndDate || "-"}</td>
              <td>₹${Math.abs(due.amount)}</td>
            </tr>
          `;
          rentTotal += Math.abs(Number(due.amount));
        } else if (due.type === CONSTANTS.DUES_TYPES.FINE) {
          noFineRecord = false;
          lateFeesRowsHtml += `
            <tr>
              <td>${due.rentStartDate || "-"}</td>
              <td>${due.rentEndDate || "-"}</td>
              <td>₹${Math.abs(due.amount)}</td>
            </tr>
          `;
          lateFeesTotal += Math.abs(Number(due.amount));
        } else {
          noOtherRecord = false;
          let initialDesc = await getDueDescription(due.type);
          //let desc = `${initialDesc} ${due.rentStartDate || due.dueDate || "-"}`
          let desc = initialDesc;
          if (Number(due.type) === CONSTANTS.DUES_TYPES.OTHER) {
            desc = due?.title || initialDesc;
          }
          if (due?.rentStartDate && due?.rentEndDate) {
            desc += ` from ${due.rentStartDate} to ${due.rentEndDate}`;
          } else if (due?.dueDate) {
            desc += ` on ${due.dueDate}`;
          }
          utilityRowsHtml += `
            <tr>
              <td colspan = "2">${desc}</td>
              <td  colspan = "1">₹${Math.abs(due.amount)}</td>
            </tr>
          `;
          utilityTotal += Math.abs(Number(due.amount));
        }
        securityAdjustedTotal += Math.abs(Number(due.amount));
      }
    }

    //Move-in date used to handle the security used due while active
    const markedFromSecurityDues = await ledgerDB.getMarkedFromSecurityDuesForFnF({
      clientId,
      tenantId,
      moveInDate: moment(occupancy.moveInDate).format("YYYY-MM-DD"),
    });

    if (markedFromSecurityDues && markedFromSecurityDues.length > 0) {
      for (const due of markedFromSecurityDues) {
        if (due.type === CONSTANTS.DUES_TYPES.RENT) {
          noRentRecord = false;
          rentRowsHtml += `
            <tr>
              <td>${due.rentStartDate || "-"}</td>
              <td>${due.rentEndDate || "-"}</td>
              <td>₹${Math.abs(due.amount)}</td>
            </tr>
          `;
          rentTotal += Math.abs(Number(due.amount));
        } else if (due.type === CONSTANTS.DUES_TYPES.FINE) {
          noFineRecord = false;
          lateFeesRowsHtml += `
            <tr>
              <td>${due.rentStartDate || "-"}</td>
              <td>${due.rentEndDate || "-"}</td>
              <td>₹${Math.abs(due.amount)}</td>
            </tr>
          `;
          lateFeesTotal += Math.abs(Number(due.amount));
        } else {
          noOtherRecord = false;
          let initialDesc = await getDueDescription(due.type);
          let desc = initialDesc;
          if (Number(due.type) === CONSTANTS.DUES_TYPES.OTHER) {
            desc = due?.title || initialDesc;
          }
          if (due?.rentStartDate && due?.rentEndDate) {
            desc += ` from ${due.rentStartDate} to ${due.rentEndDate}`;
          } else if (due?.dueDate) {
            desc += ` on ${due.dueDate}`;
          }
          utilityRowsHtml += `
            <tr>
              <td colspan = "2">${desc}</td>
              <td  colspan = "1">₹${Math.abs(due.amount)}</td>
            </tr>
          `;
          utilityTotal += Math.abs(Number(due.amount));
        }
      }
    }

    if (noRentRecord) {
      rentRowsHtml = `
        <tr>
          <td colspan="3" class="empty-row">No pending rent dues</td>
        </tr>
      `;
    }

    if (noFineRecord) {
      lateFeesRowsHtml = `
        <tr>
          <td colspan="3" class="empty-row">No late fees</td>
        </tr>
      `;
    }

    if (noOtherRecord) {
      utilityRowsHtml = `
        <tr>
          <td colspan="3" class="empty-row">No utility or other charges</td>
        </tr>
      `;
    }

    html = html.replace(/{{rent_rows}}/g, rentRowsHtml);
    html = html.replace(/{{rent_payable}}/g, rentTotal.toString());

    html = html.replace(/{{late_fees_rows}}/g, lateFeesRowsHtml);
    html = html.replace(/{{late_fees_payable}}/g, lateFeesTotal.toString());

    html = html.replace(/{{utility_rows}}/g, utilityRowsHtml);
    html = html.replace(/{{utility_total}}/g, utilityTotal.toString());

    let securityPaid = await ledgerDB.getSecurityTransactionAmountX({
      tenantId,
      type: CONSTANTS.DUES_TYPES.SECURITY,
      roomId: occupancy.roomId,
      propId: occupancy.propId,
    });

    securityPaid = Math.abs(Number(securityPaid?.amount)) || 0


    html = html.replace(/{{deposit_paid}}/g, securityPaid);

    let totalPendingDues = Number(rentTotal) + Number(lateFeesTotal) + Number(utilityTotal);
    totalPendingDues = Number(totalPendingDues) || 0;
    html = html.replace(/{{dues_pending_amt}}/g, Number(totalPendingDues).toString());

    const totalRefund = securityPaid - totalPendingDues;

    html = html.replace(/{{deposit_refundable}}/g, totalRefund);

    const refundProcessed = await ledgerDB.getLastRefundEntry({
      tenantId,
      clientId,
      moveInDate: moment(occupancy.moveInDate).format("YYYY-MM-DD"),
    });

    log.info(`Occupancy [${JSON.stringify(occupancy)}]`);
    log.info(`Refund Processed [${JSON.stringify(refundProcessed)}]`);

    if (refundProcessed) {
      const refundText = refundProcessed?.description.replace("Paid back", "Refunded");
      const refundInsertHtml = `<table>
        <tr>
          <th colspan="3"><b>Refund Processed</b></th>
        </tr>
        <tr>
          <th width="30%">Refund Description</th>
          <td width="70%">${refundText}</td>
        </tr>
      </table>`;

      html = html.replace(/{{refund_processed}}/g, refundInsertHtml);
    } else {
      const refundInsertHtml = `<table>
        <tr>
          <th colspan="2"><b>Refund Processed</b></th>
        </tr>
        <tr>
          <td colspan="2" class="empty-row">No refund processed</td>
        </tr>
      </table>`;

      html = html.replace(/{{refund_processed}}/g, refundInsertHtml);
    }

    const options = {
      format: "A4",
      orientation: "portrait",
      border: "10mm",
      childProcessOptions: {
        env: {
          OPENSSL_CONF: "/dev/null",
        },
      },
    };

    const filename = `SettlementSummary_${occupancy.id}.pdf`;
    let url: any = `${urlBasePath}/${filename}`;

    var document = {
      html: html,
      path: `${folderPath}/${filename}`,
      data: {},
      type: "",
    };

    await pdf.create(document, options);
    const docId = await documentDB.addDocWithStatus({
      tenantId,
      clientId: occupancy.clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      type: CONSTANTS.DOCUMENT_TYPES.SETTLEMENT_SUMMARY,
      value: url,
      status: Number(occupancy.refundStatus) === CONSTANTS.REFUND_STATUS.PROCESSED ? CONSTANTS.DOCUMENT_STATUS.VERIFIED : CONSTANTS.DOCUMENT_STATUS.UNVERIFIED,
    });
    await documentDB.updateMoveOutById({
      id: docId,
      moveOut: 1,
    });

    log.info(`[${C}], [${F}], Occupancy Id [${occupancy.id}], Refund Status [${occupancy.refundStatus}], [${CONSTANTS.REFUND_STATUS.PROCESSED}]`)

    if (occupancy.refundStatus !== CONSTANTS.REFUND_STATUS.PROCESSED && occupancy.refundStatus !== CONSTANTS.REFUND_STATUS.INITIATED) {
      log.info("Occupancy Id is not processed, updating status to statement generated");
      await moveOutDB.updateRefundStatus({
        id: occupancy.id,
        refundStatus: CONSTANTS.REFUND_STATUS.STATEMENT_GENERATED,
      });
    }

    let respMsg = "Tenant Settlement Summary document created Successfully";
    if (sendFnF === "1") {
      const propSettings = await settingsDB.getByClientIdAndPropId({
        clientId: Number(clientId),
        propId: occupancy.propId,
      });
      let footerText = propSettings.footer || "The Kipinn Team";
      // tenant.mobile = "9599054423" // to be removed after testing;
      const finalUrl = `${url}?v=${moment().format("YYYYMMDDHHmmss")}`;
      if (propSettings.whatsApp === 1) {
        sendWhatsappTenantSettlement(tenant?.mobile, tenant?.name, finalUrl, footerText, Number(clientId));
      } else {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Send FnF [${sendFnF}], URL [${url}], WhatsApp Settings Disabled`
        );
      }
      respMsg = "Tenant Settlement Summary document sent Successfully";
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Send FnF [${sendFnF}], URL [${url}], Tenant Settlement Summary document created Successfully`
    );
    return res.status(200).json({
      msg: respMsg,
      link: url,
      tenantMobile: tenant?.mobile,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ManuallyMoveIn = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ManuallyMoveIn";
  try {
    let { moveInDate, rentalCycle, tenantId } = req.body;

    if (rentalCycle === "00") {
      rentalCycle = moment(moveInDate).format("DD");
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Move In Date [${moveInDate}], Rent Cycle [${rentalCycle}]`
    );

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      if (
        staff.role !== CONSTANTS.STAFF_ROLES.ADMIN &&
        staff.role !== CONSTANTS.STAFF_ROLES.WARDEN &&
        staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE
      ) {
        log.info(
          `[${C}], [${F}], Staff Id [${staff.id}], Staff Role [${staff.role}], Staff Not Admin, Warden or Back Office`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], ${staff.role === CONSTANTS.STAFF_ROLES.ADMIN ? "Admin" : staff.role === CONSTANTS.STAFF_ROLES.WARDEN ? "Warden" : "Back-Office"
        } Requesting....`
      );
    }

    // const isManualMoveInEnabled = await clientConfigDB.getClientConfig({
    //   clientId,
    //   provider: CONSTANTS.CLIENT_CONFIG_PROVIDER.INTERNAL,
    //   type: CONSTANTS.CLIENT_CONFIG_TYPE.INTERNAL.IS_MANUAL_MOVEIN,
    // });

    // if (!isManualMoveInEnabled || Number(isManualMoveInEnabled?.value) !== 1) {
    //   log.info(`[${C}], [${F}], Client Id [${clientId}], Manual Move-In Not Enabled`);
    //   return res.status(400).json({
    //     msg: "Manual move-in not enabled",
    //     isSuccess: false,
    //   });
    // }

    let occupancies = await occupancyDB.getAllByTenantIdAndClientId({
      clientId,
      tenantId,
    });
    if (!occupancies) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Occupancy Found`
      );

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: true,
      });
    }

    const occupancy = occupancies[0];

    if (occupancy?.status !== CONSTANTS.OCCUPANCY_STATUS.RESERVED) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Status [${occupancy?.status}], Status Not Reserved`
      );

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: true,
      });
    }

    for (let record of occupancies) {
      const bed = await bedDB.getById({
        id: record?.bedId,
      });

      const tenants = await occupancyDB.getAllByBedId({
        bedId: bed.id,
      });

      const lastTenantToMoveIn = await occupancyDB.getLastTenantToMoveInByBedId({
        bedId: bed.id,
      });

      if (bed?.status === CONSTANTS.BED_STATUS.RESERVED) {
        let vacantSlots = await getVacantSlots(tenants);

        let isValidMoveInDate = false;
        for (let slot of vacantSlots) {
          if (lastTenantToMoveIn && Number(lastTenantToMoveIn.tenantId) === Number(tenantId) && moment(lastTenantToMoveIn.moveInDate).isSameOrBefore(moment(moveInDate), "date")) {
            isValidMoveInDate = true;
          } else if (moment(slot.startDate).isBefore(moment(moveInDate), "date") && slot.endDate === null) {
            isValidMoveInDate = true;
          } else if (moment(moveInDate).isBetween(slot.startDate, slot.endDate, "day", "[]")) {
            isValidMoveInDate = true;
          }
        }

        if (!isValidMoveInDate) {
          log.info(
            `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bed?.id}], Move-In Date Is In Conflict With Exsiting Tenant Occupancy`
          );

          return res.status(400).json({
            msg: "Move-in date is in conflict with existing tenant",
            isSUccess: false,
          });
        }

        // const movingOutTenant = await occupancyDB.getByBedIdAndStatus({
        //   clientId,
        //   bedId: bed?.id,
        //   status: CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT,
        // });

        // if (movingOutTenant) {
        //   if (moment(moveInDate).isSameOrBefore(moment(movingOutTenant?.moveOutDate), "day")) {
        //     log.info(
        //       `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bed?.id}], Moving Out Tenant Id [${movingOutTenant?.tenantId}], Moving Out Date [${movingOutTenant?.moveOutDate}], Move In Date Received [${moveInDate}], Tenant Is Currently Occupaying the Bed With Moving Out Status`
        //     );

        //     return res.status(400).json({
        //       msg: "A tenant with moving out status is occupying one of bed",
        //       isSUccess: false,
        //     });
        //   }
        // } else {
        //   log.info(
        //     `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Bed Id [${bed?.id}], Tenant Is Currently Occupaying the Bed With Moving Out Status`
        //   );

        //   return res.status(400).json({
        //     msg: "A tenant with moving out status is occupying one of bed",
        //     isSUccess: false,
        //   });
        // }
      }
    }

    const isFutureDate = moment(moveInDate).isAfter(moment(), "day");

    let isMovingOutTenant = false;

    if (!isFutureDate) {
      for (let record of occupancies) {
        const bed = await bedDB.getById({ id: record?.bedId });

        if (record.moveOutDate) {
          isMovingOutTenant = true;
          await occupancyDB.updateStatus({
            id: record?.id,
            status: CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT,
          });
        } else {
          await occupancyDB.updateStatus({
            id: record?.id,
            status: CONSTANTS.OCCUPANCY_STATUS.OCCUPIED,
          });
        }

        const tenants = await occupancyDB.getAllByBedId({
          bedId: bed.id,
        });

        if (tenants && tenants.length > 1) {
          await bedDB.updateStatus({
            id: bed.id,
            status: CONSTANTS.BED_STATUS.RESERVED,
          });
        } else if (isFutureDate) {
          await bedDB.updateStatus({
            id: bed.id,
            status: CONSTANTS.BED_STATUS.VACANT_RESERVED,
          });
        } else if (isMovingOutTenant) {
          await bedDB.updateStatus({
            id: bed.id,
            status: CONSTANTS.BED_STATUS.MOVING_OUT,
          });
        } else {
          await bedDB.updateStatus({
            id: bed.id,
            status: CONSTANTS.BED_STATUS.OCCUPIED,
          });
        }
      }
    }

    if (isMovingOutTenant) {
      //Moving Out Logic
      const securityTransanction = await ledgerDB.getSecurityTransactionAmountX({
        tenantId,
        roomId: occupancy.roomId,
        propId: occupancy.propId,
        type: CONSTANTS.DUES_TYPES.SECURITY,
      });

      let securityAmt = securityTransanction
        ? Math.abs(Number(securityTransanction.amount))
        : 0;

      const securityUsed = await ledgerDB.getUsedSecurityAmtByTenantIdAndClientId({
        clientId,
        tenantId,
        createdAt: occupancy.createdAt,
      });

      securityAmt = Number(securityAmt) - (Number(securityUsed) || 0);

      // To handle previous excess balance
      let prevBalance = await ledgerDB.getPreviousBalance({
        tenantId,
        clientId,
      });
      if (prevBalance && prevBalance < 0) {
        securityAmt += Math.abs(prevBalance);
      }

      const { totalDues } = await duesDB.getTotalDuesByTenantIdForEviction({
        tenantId: tenantId,
        propId: occupancy.propId,
      });

      for (const occupancy of occupancies) {
        let moveOutId = await moveOutDB.add({
          clientId,
          tenantId,
          propId: occupancy.propId,
          roomId: occupancy.roomId,
          bedId: occupancy.bedId,
          floor: occupancy.floor,
          reason: occupancy.reason,
          clientDues: securityAmt,
          tenantDues: totalDues || 0,
          rent: occupancy.rent,
          rentalCycle: occupancy.rentalCycle,
          rentalType: occupancy.rentalType,
          rentalMonths: occupancy.rentalMonths,
          noticePeriod: occupancy.noticePeriod,
          lockInPeriod: occupancy.lockInPeriod,
          security: occupancy.security,
          agreementPeriod: occupancy.agreementPeriod,
          agreementStartDate: occupancy.agreementStartDate,
          moveInDate: occupancy.moveInDate,
          moveOutDate: occupancy.moveOutDate,
          flatId: occupancy.flatId,
          notes: occupancy.notes,
          rentalBond: occupancy.rentalBond,
          moveOutReason: occupancy.moveOutReason,
          isPoliceVerified: occupancy.isPoliceVerified,
          isRentAgreementSigned: occupancy.isRentAgreementSigned,
          stayType: occupancy.stayType,
          kycStatus: occupancy.kycStatus,
          discountType: occupancy.discountType,
          discount: occupancy.discount,
          discountPeriod: occupancy.discountPeriod,
          discountStartDate: occupancy.discountStartDate,
          discountEndDate: occupancy.discountEndDate,
          isDiscountFromFirstMonth: occupancy.isDiscountFromFirstMonth,
          wasCancelled: 0,
          status: CONSTANTS.MOVE_OUT_STATUS.INITIATED,
          gId: occupancy?.gId || null,
          tallyStatus: occupancy.tallyStatus,
        });
        await moveOutDB.updateBookedBy({ bookedBy: occupancy.bookedBy, id: moveOutId });
      }

      const moveOutCharge = await extraChargeDB.getByPropAndType({
        clientId,
        propId: occupancy.propId,
        type: CONSTANTS.EXTRA_CHARGE_TYPES.MOVE_OUT_CHARGES,
      });

      if (moveOutCharge) {
        const referenceId = await generateLedgerReferenceId({ clientId });
        await duesDB.addWithStartEndDateX({
          tenantId,
          amount: moveOutCharge?.amount || 0,
          occupancyId: occupancy.id,
          roomId: occupancy.roomId,
          propId: occupancy.propId,
          clientId,
          dueDate: moment(occupancy.moveOutDate).format("YYYY-MM-DD"),
          rentStartDate: moment(occupancy.moveOutDate).format("YYYY-MM-DD"),
          rentEndDate: moment(occupancy.moveOutDate).format("YYYY-MM-DD"),
          type: CONSTANTS.DUES_TYPES.MOVE_OUT_CHARGES,
          balance: moveOutCharge?.amount || 0,
          ledgerReferenceId: referenceId,
          title: "Move Out Charges",
          description: "Move Out Charges automatically added while moving out",
        });
      }

      if (securityAmt > 0) {
        let referenceId = await generateLedgerReferenceId({ clientId });
        ledgerDB.add({
          tenantId,
          roomId: occupancy.roomId,
          propId: occupancy.propId,
          clientId,
          amount: 0,
          balance: -securityAmt,
          referenceId: referenceId,
          transactionId: null,
          type: CONSTANTS.DUES_TYPES.SECURITY,
          rentStartDate: null,
          rentEndDate: null,
          dueDate: occupancy.moveOutDate,
          description: "Security deposit and any excess amounts paid by tenant. Refund after eviction.",
        });
      }
    }

    await occupancyDB.updateMoveInDate({
      clientId,
      tenantId,
      moveInDate,
    });

    await occupancyDB.updateRentalCycle({
      tenantId,
      clientId,
      rentalCycle,
    });

    occupancies = await occupancyDB.getAllByTenantIdAndClientId({
      clientId,
      tenantId,
    });

    const rentDues = await duesDB.getByTenantIdAndClientIdAndType({
      clientId,
      tenantId,
      type: CONSTANTS.DUES_TYPES.RENT,
    });

    const paidRentLedgerRecord = await ledgerDB.getPaidRecordByTenantIdAndTypeAndDate({
      tenantId,
      clientId,
      type: CONSTANTS.DUES_TYPES.RENT,
      createdAt: occupancy?.createdAt,
    });

    if (rentDues && rentDues.length > 1) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Move In Date [${moveInDate}], Is Future Move In [${isFutureDate}], Rental Cycle [${rentalCycle}], Multiple Advance Rent Dues Skiping Rent Creation, Move In Date And Rental Cycle Updated Successfully`
      );

      return res.status(200).json({
        msg: "Move-in updated successfully",
        isSuccess: true,
      });
    }

    if (rentDues) {
      //Rent Due is there
      if (rentDues[0].amount === rentDues[0].balance) {
        //Rent Due is not paid
        await duesDB.removeDue({ id: rentDues[0]?.id });
        await ledgerDB.remove({ referenceId: rentDues[0]?.ledgerReferenceId });
        await addRentForMoveIn(
          clientId,
          tenantId,
          moveInDate,
          rentalCycle,
          isFutureDate,
          occupancies,
        );
      } else {
        //Rent Due is partially paid
        //Ignore Previous Rents and create new as discussed
        if (moment(rentDues[0].rentStartDate).format("YYYY-MM-DD") !== moment(moveInDate).format("YYYY-MM-DD")) {
          await addRentForMoveIn(
            clientId,
            tenantId,
            moveInDate,
            rentalCycle,
            isFutureDate,
            occupancies,
          );
        }
      }
    } else if (paidRentLedgerRecord) {
      //Rent Fully Paid
      //Ignore Previous Rents and create new as discussed
      log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Move In Date [${moveInDate}], Occupancy Move In Date [${occupancy?.moveInDate}], Rental Cycle [${rentalCycle}]`);
      // if (moment(paidRentLedgerRecord.rentStartDate).format("YYYY-MM-DD") !== moment(moveInDate).format("YYYY-MM-DD")) {
      if (moment(occupancy?.moveInDate).format("YYYY-MM-DD") !== moment(moveInDate).format("YYYY-MM-DD")) {
        await addRentForMoveIn(
          clientId,
          tenantId,
          moveInDate,
          rentalCycle,
          isFutureDate,
          occupancies,
        );
      }
    } else {
      //No Rent Added
      await addRentForMoveIn(
        clientId,
        tenantId,
        moveInDate,
        rentalCycle,
        isFutureDate,
        occupancies,
      );
    }

    await bookingsDB.updateStatusByTenantIdAndClientIdAndDetails({
      tenantId,
      clientId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      moveInDate: occupancy.moveInDate,
      status: isFutureDate ? CONSTANTS.BOOKING_STATUS.CONFIRMED : CONSTANTS.BOOKING_STATUS.MOVED_IN,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Move In Date [${moveInDate}], Is Future Move In [${isFutureDate}], Rental Cycle [${rentalCycle}], Move In Date And Rental Cycle Updated Successfully`
    );

    return res.status(200).json({
      msg: "Move-in updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GuestRequest = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GuestRequest";

  try {
    const tenantId = req.id;
    const clientId = req.clientId;
    let { name, mobile, relation, noOfGuest, checkInDate, checkOutDate, vehicleNo, reason } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Guest Name [${name}], Guest Mobile [${mobile}], Relation [${relation}], No. Of Guests [${noOfGuest}], Check-In Date [${checkInDate}], Check-Out Date [${checkOutDate}], Vehicle No. [${vehicleNo}], Reason [${reason}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Tenant Found`
      );
      return res.status(400).json({
        msg: "Your are not allowed to send move out request",
        isSuccess: false,
      });
    }

    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      clientId,
      tenantId: tenant.id,
    })
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Occupancy Found`
      );
      return res.status(400).json({
        msg: "Unable to send Guest request because your are not occupied at any bed",
        isSuccess: false,
      });
    }

    if (occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.OCCUPIED && occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Tenant is not Occupied`
      );
      return res.status(400).json({
        msg: "Unable to send move out request because your are not occupied at any bed",
        isSuccess: false,
      });
    }

    const client = await clientDB.getById({ id: occupancy.clientId });

    const room = await roomDB.getById({ id: occupancy.roomId });
    const property = await propertyDB.getById({ id: occupancy.propId });

    const title = `Guest request received`;
    const description = `${tenant.name} has requested permission for guests in Room ${room.roomNum} at ${property.name}.`;


    const requestId = await requestDB.create({
      clientId: occupancy.clientId,
      occupancyId: occupancy.id,
      tenantId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      floor: occupancy.floor,
      title,
      description,
      type: CONSTANTS.REQUEST_TYPE.GUEST,
    });
    await tenantGuestsDB.create({ requestId, clientId, propId: occupancy.propId, roomId: occupancy.roomId, tenantId, name, mobile, relation, noOfGuest, checkInDate, checkOutDate, vehicleNo, description: reason });

    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
    }
    const notiSettings = await settingsDB.getByClientIdAndPropId({
      clientId: occupancy.clientId,
      propId: occupancy.propId,
    });

    let footerText = notiSettings.footer || "Kipinn Team";

    await sendWhatsappNewRequest(
      client?.mobile || "",
      client?.name || "",
      tenant?.name || "",
      "Guest",
      property?.name || "",
      `${flatName} (${room.roomNum})`,
      footerText,
      clientId
    );

    logTenantRequestActivity(
      req.userType!,
      Number(req.id)!,
      Number(req.parentClientId),
      Number(req.platform),
      CONSTANTS.ACTIVITY_TYPES.GUEST_REQUEST,
      property.id,
      Number(tenantId),
      tenant.name,
      occupancy.roomId,
      0
    );

    let isNotiSent = false;
    if (client.regId) {
      // const regIds = await clientDB.getRegIds({
      //   id: clientId,
      // });

      // if (regIds && regIds.length > 0) {
      //   for (let regId of regIds) {
      //     isNotiSent = await sendNotification({
      //       title: title,
      //       message: description,
      //       regId: regId.regId,
      //       userId: client.id,
      //       userType: CONSTANTS.USER_TYPE.CLIENT,
      //       notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.GUEST,
      //       clientId: occupancy.clientId,
      //     });
      //   }
      // } else {
      // }
      isNotiSent = await sendNotification({
        title: title,
        message: description,
        regId: client.regId,
        userId: client.id,
        userType: CONSTANTS.USER_TYPE.CLIENT,
        notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.GUEST,
        clientId: occupancy.clientId,
      });
    } else {
      await notificationDB.add({
        userId: client.id,
        userType: CONSTANTS.USER_TYPE.CLIENT,
        title,
        message: description,
        notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.GUEST,
      });
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], No RegId found for client [${client.id}]`
      );
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], [${client.id}], Reason [${reason}], Guest Request sent successfully`
    );


    return res.status(200).json({
      msg: "Guest request sent successfully",
      data: { requestId },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};


tenants.ListGuestRequestForTenant = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ListGuestRequestForTenant";

  try {
    const tenantId = req.id;
    const isEvicted = req.isEvicted;
    const clientId = req.clientId;
    const { pageNum } = req.query;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Page Number [${pageNum}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Page Number [${pageNum}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenantId,
      clientId: clientId,
    });

    if (isEvicted) {
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        tenantId: tenantId,
        clientId: clientId,
      });
    }

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Page Number [${pageNum}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const limit = 10;
    const requests = await tenantGuestsDB.getListForTenants({
      tenantId,
      clientId: occupancy.clientId,
      pageNum: Number(pageNum),
      limit,
    });
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Page Number [${pageNum}], Requests sent successfully`
    );

    return res.status(200).json({
      msg: "Transactions sent successfully",
      data: requests || [],
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.AddTenantNotes = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddTenantNotes";

  try {
    const { tenantId, note, type = CONSTANTS.NOTES_TYPES.TENANT_GENERAL } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Note [${note}], Note Type [${type}]`
    );

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    let isEvicted = false;
    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      clientId,
      tenantId,
    });

    if (!occupancy) {
      isEvicted = true;
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        clientId,
        tenantId,
      });

      if (!occupancy) {
        log.info(`[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Occupancy Found`);

        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
    }

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );

      await tenantNotesDB.addNote({
        tenantId: tenantId,
        clientId: clientId,
        addedBy: 0,
        note: note,
        type: type,
      });

    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
      await tenantNotesDB.addNote({
        tenantId: tenantId,
        clientId: clientId,
        addedBy: Number(req.id),
        note: note,
        type: type,
      });
    }

    if (type === CONSTANTS.NOTES_TYPES.TENANT_DUES) {
      if (isEvicted) {
        await moveOutDuesDB.updateRemarks({
          tenantId: tenantId,
          clientId,
          remarks: note,
        });
      } else {
        await duesDB.updateRemarks({
          tenantId: tenantId,
          clientId,
          remarks: note,
        });
      }
    }


    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Tenant note added Successfully.`
    );

    return res.status(200).json({
      msg: `Note added successfully`,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.listNotes = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "listNotes";

  try {
    const { pageNum, tenantId } = req.query;
    let limit = 10;
    const platform = req.platform;
    if (platform === CONSTANTS.TENANT_DEVICE_TYPE.WEB) {
      limit = 10e9;
    }

    log.info(
      `[${C}], [${F}], User Id [${req.id}], User Type [${req.userType}], Tenant Id [${tenantId}], Page Num. [${pageNum}], Get tenant notes`
    );
    if (!tenantId) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], Parameter missing`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
      clientId = staff?.clientId;
    }
    const client = await clientDB.getById({ id: clientId });
    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], No Client Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let notes = await tenantNotesDB.getByTenantAndClientId({
      clientId,
      tenantId,
      pageNum: Number(pageNum),
      limit
    });

    for (let note of notes) {
      if (0 === note?.addedBy) {
        note.addedByName = client?.name;
      } else {
        const staff = await staffDB.getById({ id: note.addedBy });
        note.addedByName = staff?.name;
      }
    }

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Tenant notes sent Successfully.`
    );

    return res.status(200).json({
      msg: `List sent successfully`,
      data: notes || [],
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.DeleteNote = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "DeleteNote";

  try {
    const { noteId } = req.body;
    let limit = 10;
    const platform = req.platform;
    if (platform === CONSTANTS.TENANT_DEVICE_TYPE.WEB) {
      limit = 10e9;
    }

    log.info(
      `[${C}], [${F}], User Id [${req.id}], User Type [${req.userType}], Note Id [${noteId}], Delete tenant note`
    );
    if (!noteId) {
      log.info(`[${C}], [${F}], Note Id [${noteId}], Parameter missing`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const userType = req.userType;

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner
          ? `Partner Id [${req.id}], Partner`
          : `Client Id [${clientId}], Client`
        } Requesting....`
      );
    } else {
      const staff = await staffDB.getById({ id: req.id });
      if (!staff) {
        log.info(`[${C}], [${F}], Staff Id [${req.id}], No Staff Found`);
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
      log.info(
        `[${C}], [${F}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
      clientId = staff?.clientId;
    }

    const note = await tenantNotesDB.getById({
      id: Number(noteId)
    });
    if(!note){
      log.info(`[${C}], [${F}], Note Id [${noteId}], No Note Found`);
      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    await tenantNotesDB.delete({ id: Number(noteId) });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Note Id [${noteId}], Tenant notes sent Successfully.`
    );

    return res.status(200).json({
      msg: `Note deleted successfully`,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

//Currently This is being used for Atharva Hostel
tenants.UploadKycDocument = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "UploadKycDocument";

  const files = req.files as Express.Multer.File[];
  const removeTmpImages = async () => {
    for (const file of files) {
      try {
        await fsPromises.unlink(file.path);
      } catch (err) { }
    }
  };

  try {
    const tenantId = req.id;

    log.info(`[${C}], [${F}], Tenant Id [${tenantId}], `);

    if (!files) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], , No files uploaded`);
      return res
        .status(400)
        .json({ msg: "No files uploaded", isSuccess: false });
    }

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], , No Tenant Found`);

      await removeTmpImages();
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const occupancy = await occupancyDB.getByTenantId({
      tenantId: tenant?.id,
    });

    if (!occupancy) {
      log.info(`[${C}], [${F}], Tenant Id [${tenantId}], , No Occupancy Found`);

      await removeTmpImages();

      return res.status(400).json({
        msg: CONSTANTS.MSG.INVALID_REQUEST,
        isSuccess: false,
      });
    }

    const folderName = `tenant_${occupancy.tenantId}`;
    const folderPath = `uploads/documents/client_${occupancy.clientId}/prop_${occupancy.propId}/room_${occupancy.roomId}_bed_${occupancy.bedId}/${folderName}`;
    const urlBase = folderPath.replace(
      "uploads/",
      `${process.env.UPLOAD_PATH}/`
    );

    if (!fs.existsSync(folderPath)) {
      await fsPromises.mkdir(folderPath, { recursive: true });
    }

    const oldFrontPath = `uploads/tmp/${files[0].filename}`;
    let oldBackPath = "";
    if (files[1]) oldBackPath = `uploads/tmp/${files[1].filename}`;

    const isExists = await documentDB.getIDByType({
      tenantId,
      clientId: occupancy?.clientId,
      type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
      moveOut: 0,
    });

    const ext = files[0].mimetype.split("/")[1];
    const fileName = `Id_document${moment().format("YYYYMMDDHHmmss")}.${ext}`;

    const newPath = `${folderPath}/${fileName}`;
    await fsPromises.copyFile(oldFrontPath, newPath);
    const url = `${urlBase}/${fileName}`;

    if (isExists) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Type [AADHAAR], Aadhaar Front document re-uploaded successfully`
      );

      await documentDB.updateDoc({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
        value: url,
        id: isExists.id,
      });
      await occupancyDB.updateKycStatusWithClientId({
        tenantId: tenantId,
        clientId: occupancy.clientId,
        kycStatus: CONSTANTS.KYC_STATUS.ID_UPLOADED
      });
    } else {
      await documentDB.addDocWithStatus({
        tenantId,
        clientId: occupancy.clientId,
        propId: occupancy.propId,
        roomId: occupancy.roomId,
        type: CONSTANTS.DOCUMENT_TYPES.AADHAAR,
        value: url,
        status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
      });
      await occupancyDB.updateKycStatusWithClientId({
        tenantId: tenantId,
        clientId: occupancy.clientId,
        kycStatus: CONSTANTS.KYC_STATUS.ID_UPLOADED
      });

      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Type [AADHAAR], Aadhaar Front document uploaded successfully`
      );
    }

    if (oldBackPath) {
      const ext = files[1].mimetype.split("/")[1];
      const fileName = `Id_document_back${moment().format("YYYYMMDDHHmmss")}.${ext}`;

      const newPath = `${folderPath}/${fileName}`;
      await fsPromises.copyFile(oldBackPath, newPath);
      const url = `${urlBase}/${fileName}`;

      const isExists = await documentDB.getIDByType({
        tenantId,
        clientId: occupancy?.clientId,
        type: CONSTANTS.DOCUMENT_TYPES.AADHAAR_BACK,
        moveOut: 0,
      });

      if (isExists) {
        await documentDB.updateDoc({
          tenantId,
          clientId: occupancy.clientId,
          propId: occupancy.propId,
          roomId: occupancy.roomId,
          type: CONSTANTS.DOCUMENT_TYPES.AADHAAR_BACK,
          value: url,
          id: isExists.id,
        });

        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Type [AADHAAR], URL [${url}], Aadhaar Front document re-uploaded successfully`
        );
      } else {
        await documentDB.addDocWithStatus({
          tenantId,
          clientId: occupancy.clientId,
          propId: occupancy.propId,
          roomId: occupancy.roomId,
          type: CONSTANTS.DOCUMENT_TYPES.AADHAAR_BACK,
          value: url,
          status: CONSTANTS.DOCUMENT_STATUS.VERIFIED,
        });

        log.info(
          `[${C}], [${F}], Tenant Id [${tenantId}], Type [AADHAAR], URL [${url}], Aadhaar Back document uploaded successfully`
        );
      }
    }


    const updatedTenant = await tenantDB.getById({ id: tenantId });
    await removeTmpImages();
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Id doc(s) uploaded sucessfully`
    );

    return res.status(200).json({
      msg: "Id doc(s) uploaded sucessfully",
      tenant: updatedTenant,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    await removeTmpImages();

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

//****SOS Alerts*****
tenants.SendSOSAlert = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "SendSOSAlert";

  try {
    const tenantId = req.id;
    const clientId = req.clientId;
    //let { n } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}] Send SOS Alert`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Tenant Found`
      );
      return res.status(400).json({
        msg: "Your are not allowed to send move out request",
        isSuccess: false,
      });
    }

    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      clientId,
      tenantId: tenant.id,
    })
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Occupancy Found`
      );
      return res.status(400).json({
        msg: "Unable to send Guest request because your are not occupied at any bed",
        isSuccess: false,
      });
    }

    if (occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.OCCUPIED && occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Tenant is not Occupied`
      );
      return res.status(400).json({
        msg: "Unable to send move out request because your are not occupied at any bed",
        isSuccess: false,
      });
    }

    const client = await clientDB.getById({ id: occupancy.clientId });

    const room = await roomDB.getById({ id: occupancy.roomId });
    const property = await propertyDB.getById({ id: occupancy.propId });

    await sosAlertDB.add ({
      tenantId, 
      clientId, 
      propId: occupancy.propId, 
      roomId: occupancy.roomId
    });

    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = `Flat ${name}`;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
      
    }
    flatName += `, ${room?.roomNum}`;

    const title = `SOS Alert Raised`;
    const description = `${tenant.name} has raised an SOS alert from ${flatName} at ${property?.name}. Immediate attention is required.`;

    

    let isNotiSent = false;
    if (client.regId) {
      isNotiSent = await sendNotification({
        title: title,
        message: description,
        regId: client.regId,
        userId: client.id,
        userType: CONSTANTS.USER_TYPE.CLIENT,
        notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.SOS,
        clientId: occupancy.clientId,
      });
    } else {
      await notificationDB.add({
        userId: client.id,
        userType: CONSTANTS.USER_TYPE.CLIENT,
        title,
        message: description,
        notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.SOS,
      });
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No RegId found for client [${client.id}]`
      );
    }

    //Sending to property Staffs 
    let sosStaffs = await staffDB.getSOSStaffsList({clientId, propId: occupancy.propId});
    if(sosStaffs) {
      for(let staff of sosStaffs) {
        if(staff.regId) {
          await sendNotification({
            title: title,
            message: description,
            regId: staff.regId,
            userId: staff.id,
            userType: CONSTANTS.USER_TYPE.STAFF,
            notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.SOS,
            clientId: occupancy.clientId,
          });
        } else {
          await notificationDB.add({
            userId: staff.id,
            userType: CONSTANTS.USER_TYPE.STAFF,
            title,
            message: description,
            notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.SOS,
          });
          log.info(
            `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Staff Id [${staff.id}], No RegId found for staff`
          );
        }
      }
    } else {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Staffs assigned to the property [${occupancy.propId}]`
      );
    }

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], [${client.id}], SOS alert sent successfully`
    );

    return res.status(200).json({
      msg: "SOS alert sent successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};
//****SOS Alerts*****

/* Movement Request */
tenants.AddMovementRequest = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddMovementRequest";

  try {
    const tenantId = req.id;
    const clientId = req.clientId;
    let { type, fromDate, toDate, reason, cabRequired=0 } = req.body;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Type [${type}], From [${fromDate}], To [${toDate}], Reason [${reason}], Cab Required [${cabRequired}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Tenant Found`
      );
      return res.status(400).json({
        msg: "Your are not allowed to send move out request",
        isSuccess: false,
      });
    }

    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      clientId,
      tenantId: tenant.id,
    })
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], No Occupancy Found`
      );
      return res.status(400).json({
        msg: "Unable to send Guest request because your are not occupied at any bed",
        isSuccess: false,
      });
    }

    if (occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.OCCUPIED && occupancy.status !== CONSTANTS.OCCUPANCY_STATUS.MOVING_OUT) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Client Id [${clientId}], Tenant is not Occupied`
      );
      return res.status(400).json({
        msg: "Unable to send move out request because your are not occupied at any bed",
        isSuccess: false,
      });
    }

    const client = await clientDB.getById({ id: occupancy.clientId });

    const room = await roomDB.getById({ id: occupancy.roomId });
    const property = await propertyDB.getById({ id: occupancy.propId });

    let title = ``;
    let description = '';
    if(type === CONSTANTS.TENANT_MOVEMENT_TYPES.LATE_ARRIVAL) {
      title = `Late arrival request`;
      description = `${tenant.name} has submitted a late arrival request for Room ${room.roomNum} at ${property.name}. Expected arrival time on ${moment(fromDate).format("YYYY-MM-DD")} is ${moment(toDate).format("hh:mm A")}.`;
    }
    else {//Going Home
      title = `Going home request`;
      description = `${tenant.name} has submitted a going home request for Room ${room.roomNum} at ${property.name} from ${fromDate} to ${toDate}.`;
    }

    const requestId = await requestDB.create({
      clientId: occupancy.clientId,
      occupancyId: occupancy.id,
      tenantId,
      propId: occupancy.propId,
      roomId: occupancy.roomId,
      floor: occupancy.floor,
      title,
      description,
      type: CONSTANTS.REQUEST_TYPE.TENANT_MOVEMENT,
    });
    await tenantMovementDB.add({ requestId, clientId, propId: occupancy.propId, roomId: occupancy.roomId, tenantId, type, fromDate, toDate, description: reason, cabRequired: cabRequired || 0 });

    let flatName = "";
    if (property.type === CONSTANTS.PROPERTY_TYPE.FLAT) {
      const { name } = await flatDB.getById({ id: occupancy.flatId });
      flatName = name;
    } else {
      flatName =
        occupancy.floor === "G" ? "Ground Floor" : "Floor " + occupancy.floor;
    }
    const notiSettings = await settingsDB.getByClientIdAndPropId({
      clientId: occupancy.clientId,
      propId: occupancy.propId,
    });

    let footerText = notiSettings.footer || "Kipinn Team";

    // await sendWhatsappNewRequest(
    //   client?.mobile || "",
    //   client?.name || "",
    //   tenant?.name || "",
    //   "Guest",
    //   property?.name || "",
    //   `${flatName} (${room.roomNum})`,
    //   footerText,
    //   clientId
    // );

    const activityTypeMap = {
    [CONSTANTS.TENANT_MOVEMENT_TYPES.LATE_ARRIVAL]:
      CONSTANTS.ACTIVITY_TYPES.TENANT_LATE_ARRIVAL_REQUEST,

    [CONSTANTS.TENANT_MOVEMENT_TYPES.GOING_HOME]:
      CONSTANTS.ACTIVITY_TYPES.TENANT_GOING_HOME_REQUEST,

    [CONSTANTS.TENANT_MOVEMENT_TYPES.NIGHT_OUT]:
      CONSTANTS.ACTIVITY_TYPES.TENANT_NIGHT_OUT_REQUEST,
  };

const activityType = activityTypeMap[type];

    logTenantRequestActivity(
      req.userType!,
      Number(req.id)!,
      Number(req.parentClientId),
      Number(req.platform),
      activityType,
      property.id,
      Number(tenantId),
      tenant.name,
      occupancy.roomId,
      0
    );

    // let isNotiSent = false;
    // if (client.regId) {
      // isNotiSent = await sendNotification({
      //   title: title,
      //   message: description,
      //   regId: client.regId,
      //   userId: client.id,
      //   userType: CONSTANTS.USER_TYPE.CLIENT,
      //   notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.TENANT_MOVEMENT,
      //   clientId: occupancy.clientId,
      // });
    // } else {
    // }
    await notificationDB.add({
      userId: client.id,
      userType: CONSTANTS.USER_TYPE.CLIENT,
      title,
      message: description,
      notiCategory: CONSTANTS.NOTIFICATION_CATEGORY.TENANT_MOVEMENT,
    });
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], No RegId found for client [${client.id}]`
    );

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], [${client.id}], Reason [${reason}], Record saved successfully.`
    );


    return res.status(200).json({
      msg: "Record saved successfully.",
      data: { requestId },
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListMovementRequestForTenant = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ListMovementRequestForTenant";

  try {
    const tenantId = req.id;
    const isEvicted = req.isEvicted;
    const clientId = req.clientId;
    const { pageNum = 0 } = req.query;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Page Number [${pageNum}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });

    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Page Number [${pageNum}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    let occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId: tenantId,
      clientId: clientId,
    });

    if (isEvicted) {
      occupancy = await moveOutDB.getByTenantIdAndClientId({
        tenantId: tenantId,
        clientId: clientId,
      });
    }

    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Page Number [${pageNum}], No Occupancy Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const limit = 10;
    const requests = await tenantMovementDB.getListForTenants({
      tenantId,
      clientId: occupancy.clientId,
      pageNum: Number(pageNum),
      limit
    });
    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Page Number [${pageNum}], Requests sent successfully`
    );

    return res.status(200).json({
      msg: "Requests sent successfully",
      data: requests || [],
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ListMovementRequestForClient = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ListMovementRequestForClient";

  try {
    const { pageNum, s} = req.query;
    const userType = req.userType;
    log.info(
        `[${C}], [${F}], Page Num [${pageNum}], Search Value [${s}]`
      );
    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, ${isPartner ? "Partner Requesting" : "Client Requesting"}....`
      );
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });

      if (!staff) {
        log.info(
          `[${C}], [${F}], User Id [${req.id}], User Type [${userType}] Unauthorized Access`
        );
        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }

      if (staff.role !== CONSTANTS.STAFF_ROLES.ADMIN && staff.role !== CONSTANTS.STAFF_ROLES.WARDEN && staff.role !== CONSTANTS.STAFF_ROLES.BACK_OFFICE) {
        log.info(
          `[${C}], [${F}], User Id [${req.id}], User Type [${userType}] Unauthorized Access`
        );
        return res.status(400).json({
          msg: "Unauthorized Access",
          isSuccess: false,
        });
      }

      clientId = staff.clientId;

      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Staff Id [${req.id}], Staff Role [${staff.role}], Staff Requesting....`
      );
    }

    const limit = 10;
    let requests: any;
    if (s && s !== "" && s !== "undefined" && s !== " ") {
      requests = await tenantMovementDB.getListForClientSearch({
        clientId: clientId,
        pageNum: Number(pageNum),
        limit,
        searchVal: s,
      });
    } else {
      requests = await tenantMovementDB.getListForClient({
        clientId: clientId,
        pageNum: Number(pageNum),
        limit
      });
    }
    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Page Number [${pageNum}], Requests sent successfully`
    );

    return res.status(200).json({
      msg: "Requests sent successfully",
      data: requests || [],
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.SendOTPToParents = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "SendOTPToParents";

  try {
    const { requestType } = req.query;
    const tenantId = req.id;
    const clientId = req.clientId;

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Request Type [${requestType}]`
    );

    const tenant = await tenantDB.getById({ id: tenantId });
    if (!tenant) {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], No Tenant Found`
      );
      return res
        .status(400)
        .json({ msg: CONSTANTS.MSG.INVALID_REQUEST, isSuccess: false });
    }

    const client = await clientDB.getById({
        id: clientId,
    });
    if (!client) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], No Client Found`
      );
      return res.status(400).json({ 
        msg: CONSTANTS.MSG.INVALID_REQUEST, 
        isSuccess: false 
      });
    }
      
    const occupancy = await occupancyDB.getByTenantIdAndClientId({
      tenantId,
      clientId,
    });
    if (!occupancy) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], No Client Found`
      );
      return res.status(400).json({ 
        msg: CONSTANTS.MSG.INVALID_REQUEST, 
        isSuccess: false 
      });
    }
    
    const tenantGuardians = await tenantGuardianDB.getByTenantId({
      tenantId: tenantId,
    });

    let mobile = null;
    let parentName = "";
    let tenantName = tenant?.name;

    if (tenantGuardians) {
      if (tenantGuardians?.fatherMobile) {
        mobile = tenantGuardians.fatherMobile;
        parentName = tenantGuardians?.fatherName;
      } else if (tenantGuardians?.motherMobile) {
        mobile = tenantGuardians.motherMobile;
        parentName = tenantGuardians?.motherName;
      } else if (tenantGuardians?.localGuardianMobile) {
        mobile = tenantGuardians.localGuardianMobile;
        parentName = tenantGuardians?.localGuardianName;
      }
    }

    if (!mobile || String(mobile).toLowerCase().trim() === "" || String(mobile).toLowerCase().trim() === "null" || String(mobile).toLowerCase().trim() === "undefined") {
      log.info(
        `[${C}], [${F}], Tenant Id [${tenantId}], Tenant Guardians [${JSON.stringify(tenantGuardians)}], No Guardian Mobile Found`
      );
      return res.status(400).json({ 
        msg: "Please provide guardian mobile number", 
        isSuccess: false 
      });
    }

    let otp = Math.floor(Math.random() * (999999 - 100000) + 100000).toString();

    if (process.env.ENABLE_RANDOM_OTP === "false") {
      otp = "111111";
    }

    await otpDB.create({ mobile, otp });

    const propSettings = await settingsDB.getByClientIdAndPropId({
      clientId: clientId,
      propId: occupancy.propId,
    });

    let footerText = propSettings.footer || "The Kipinn Team";

    let requestName = "";

    if (Number(requestType) === CONSTANTS.TENANT_MOVEMENT_TYPES.GOING_HOME) {
      requestName = "going home";
    } else if (Number(requestType) === CONSTANTS.TENANT_MOVEMENT_TYPES.LATE_ARRIVAL) {
      requestName = "late night check-in";
    } else if (Number(requestType) === CONSTANTS.TENANT_MOVEMENT_TYPES.NIGHT_OUT) {
      requestName = "night out";
    }

    // sendWhatsappOtp(
    //   mobile, 
    //   otp, 
    //   parentName, 
    //   clientId, 
    //   occupancy?.propId
    // );
    const msg = `${otp} ${CONSTANTS.MSG.OTP}`;
    
    const isSent = await sendSMS(mobile, msg, CONSTANTS.SMS_TEMPLATE_IDS.OTP);
    log.info(
      `[${C}], [${F}], Mobile [${mobile}], OTP [${otp}], ${isSent ? "OTP sent successfully" : "Failed to send OTP SMS"
      }`
    );

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Request Type [${requestType}], OTP Sent To Parent Successfully`
    );
    
    return res.status(200).json({
      msg: "OTP sent successfully",
      mobile,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.ParentOTPVerify = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "ParentOTPVerify";

  try {
    const { otp, mobile } = req.body;
    const tenantId = req.id;
    const clientId = req.clientId;
    
    log.info(`[${C}], [${F}], Client Id [${clientId}], Mobile [${mobile}], OTP [${otp}]`);

    const record = await otpDB.getByMobile({ mobile });
    if (!record) {
      log.info(
        `[${C}], [${F}], Mobile [${mobile}], OTP [${otp}] No Record Found`
      );

      return res.status(400).json({
        msg: "Invalid mobile number",
        isSuccess: false,
        isValid: true,
      });
    }

    if (record?.otp !== otp) {
      log.info(
        `[${C}], [${F}], Mobile [${mobile}], OTP [${otp}], DB OTP [${record?.otp}], Incorrect OTP`
      );

      return res.status(400).json({
        msg: "Please enter a valid OTP",
        isSuccess: false,
        isValid: true,
      });
    }

    log.info(`[${C}], [${F}], Client Id [${clientId}], Mobile [${mobile}], OTP [${otp}], OTP Matched`);

    await otpDB.updateStatus({
      otp,
      status: CONSTANTS.OTP_STATUS.VERIFIED,
      mobile,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Parent Mobile [${mobile}]`
    );

    return res.status(200).json({
      msg: "OTP verified successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);

    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
      isValid: true,
    });
  }
};

tenants.AddTenantVehicleInfo = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "AddTenantVehicleInfo";

  try {
    const { tenantId, vehicleNumber, type, model, color } = req.body;

    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], Vehicle Number [${vehicleNumber}], Type [${type}], Model [${model}], Color [${color}]`
    );

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, ${isPartner ? "Partner Requesting" : "Client Requesting"}....`
      );
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });

      if (!staff) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Staff Id [${req.id}], No Staff Found`
        );

        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
    }

    const vehicleInfo = await tenantVehicleInfoDB.getByTenantIdAndClientId({
      tenantId,
      clientId,
    });
    if (vehicleInfo) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Vehicle Info Already Exists`
      );
      return res.status(400).json({
        msg: "Vehicle info already exists",
        isSuccess: false,
      });
    }

    await tenantVehicleInfoDB.add({
      tenantId: tenantId,
      clientId: clientId,
      vehicleNumber: vehicleNumber,
      type: type,
      model: model,
      color: color,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Vehicle Info Added Successfully`
    );

    return res.status(200).json({
      msg: "Vehicle info added successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.GetTenantVehicleInfo = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "GetTenantVehicleInfo";

  try {
    const { tenantId } = req.query;

    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Tenant Id [${tenantId}], User Type [${userType}]`
    );

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, ${isPartner ? "Partner Requesting" : "Client Requesting"}....`
      );
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });

      if (!staff) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Staff Id [${req.id}], No Staff Found`
        );

        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
    }

    const vehicleInfo = await tenantVehicleInfoDB.getByTenantIdAndClientId({
      tenantId,
      clientId,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Tenant Id [${tenantId}], Vehicle Info Sent Successfully`
    );
    log.info(JSON.stringify({
      msg: "Tenant vehicle info fetched",
      vehicleInfo: vehicleInfo || null,
      isSuccess: true,
    }));
    return res.status(200).json({
      msg: "Tenant vehicle info fetched",
      vehicleInfo: vehicleInfo || null,
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.EditVehicleInfo = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "EditVehicleInfo";

  try {
    const { vehicleInfoId, vehicleNumber, type, model, color } = req.body;

    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Vehicle Info Id [${vehicleInfoId}], Vehicle Number [${vehicleNumber}], Type [${type}], Model [${model}], Color [${color}]`
    );

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, ${isPartner ? "Partner Requesting" : "Client Requesting"}....`
      );
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });

      if (!staff) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Staff Id [${req.id}], No Staff Found`
        );

        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
    }

    const vehicleInfo = await tenantVehicleInfoDB.getById({
      id: vehicleInfoId,
    });
    if (!vehicleInfo) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Vehicle Info Id [${vehicleInfoId}], No Vehicle Info Found`
      );
      return res.status(400).json({
        msg: "Vehicle info not found",
        isSuccess: false,
      });
    }

    await tenantVehicleInfoDB.update({
      id: vehicleInfoId,
      vehicleNumber,
      type,
      model,
      color,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Vehicle Info Id [${vehicleInfoId}], Vehicle Info Updated Successfully`
    );

    return res.status(200).json({
      msg: "Vehicle info updated successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

tenants.DeleteVehicleInfo = async (req: CustomRequest, res: Response) => {
  const C = "Tenant Controller";
  const F = "DeleteVehicleInfo";

  try {
    const { vehicleInfoId } = req.body;

    const userType = req.userType;

    log.info(
      `[${C}], [${F}], Vehicle Info Id [${vehicleInfoId}]`
    );

    let { isPartner, clientId } = await isUserPartner(
      Number(userType),
      Number(req.id)
    );

    if (userType === CONSTANTS.USER_TYPE.CLIENT || isPartner) {
      log.info(
        `[${C}], [${F}], ${isPartner ? `Partner Id [${req.id}]` : `Client Id [${clientId}]`
        }, ${isPartner ? "Partner Requesting" : "Client Requesting"}....`
      );
    } else {
      const staff = await staffDB.getById({
        id: req.id,
      });

      if (!staff) {
        log.info(
          `[${C}], [${F}], Client Id [${clientId}], Staff Id [${req.id}], No Staff Found`
        );

        return res.status(400).json({
          msg: CONSTANTS.MSG.INVALID_REQUEST,
          isSuccess: false,
        });
      }
    }

    const vehicleInfo = await tenantVehicleInfoDB.getById({
      id: vehicleInfoId,
    });
    if (!vehicleInfo) {
      log.info(
        `[${C}], [${F}], Client Id [${clientId}], Vehicle Info Id [${vehicleInfoId}], No Vehicle Info Found`
      );
      return res.status(400).json({
        msg: "Vehicle info not found",
        isSuccess: false,
      });
    }

    await tenantVehicleInfoDB.delete({
      id: vehicleInfoId,
    });

    log.info(
      `[${C}], [${F}], Client Id [${clientId}], Vehicle Info Id [${vehicleInfoId}], Vehicle Info Deleted Successfully`
    );

    return res.status(200).json({
      msg: "Vehicle info deleted successfully",
      isSuccess: true,
    });
  } catch (error: any) {
    log.info(`[${C}], [${F}], Error: ${error?.message || error}`);
    return res.status(500).json({
      msg: CONSTANTS.MSG.ERROR_MESSAGE,
      isSuccess: false,
    });
  }
};

export default tenants;

