tender controller -- Fixes mailing issue

PHOTO EMBED

Mon Jun 23 2025 10:09:17 GMT+0000 (Coordinated Universal Time)

Saved by @SrijanVerma

import Tender from "../models/tenderSchema.js";
import Quotation from "../models/quotationSchema.js";
import { generateSignedUrl } from "../utils/minioClient.js";
import mongoose from "mongoose";
import User from "../models/userSchema.js"; // Replace with your actual user model path
import { sendMail } from "../utils/sendMail.js"; // You must have this utility created
import userModel from "../models/userSchema.js";
// ✅ Create Tender with bidding window + delivery window
import moment from "moment-timezone";

export const createTender = async (req, res) => {
  try {
    const {
      dispatchLocation,
      address,
      pincode,
      materials,
      transporters,
      remarks,
      closeDate,
      deliveryWindow,
      biddingStart,
      biddingEnd,
      totalWeight,
      totalQuantity,
      projectName,
      projectCode,
      purchaseOrder,
      projectRemark,
      maxBidAmount,
      maxBidUnit,
    } = req.body;

    console.log("Received Bidding Start (Local):", biddingStart);
    console.log("Received Bidding End (Local):", biddingEnd);

    // 🔐 Basic validations
    if (!projectName || !projectCode || !purchaseOrder) {
      return res.status(400).json({
        success: false,
        message: "Project name, code and PO are required",
      });
    }

    if (!biddingStart || !biddingEnd) {
      return res.status(400).json({
        success: false,
        message: "Bidding start and end time are required",
      });
    }

    if (
      maxBidAmount === undefined ||
      isNaN(maxBidAmount) ||
      Number(maxBidAmount) < 0) {
      return res.status(400).json({
        success: false,
        message: "Valid max bid amount is required and must be non-negative",
      });
    }

    if (!maxBidUnit || !["Per MT", "Per Tender"].includes(maxBidUnit)) {
      return res.status(400).json({
        success: false,
        message: "Valid max bid unit is required (Per MT or Per Tender)",
      });
    }

    if (!deliveryWindow?.from || !deliveryWindow?.to) {
      return res.status(400).json({
        success: false,
        message: "Delivery window (from and to dates) is required",
      });
    }

    if (!materials || !Array.isArray(materials) || materials.length === 0) {
      return res.status(400).json({
        success: false,
        message: "At least one material entry is required",
      });
    }

    // 🌐 Convert all date/time inputs from 'Asia/Kolkata' to UTC
    const timezone = "Asia/Kolkata";
    const utcBiddingStart = moment.tz(biddingStart, timezone).utc().toDate();
    const utcBiddingEnd = moment.tz(biddingEnd, timezone).utc().toDate();
    const utcDeliveryFrom = moment
      .tz(deliveryWindow.from, timezone)
      .utc()
      .toDate();
    const utcDeliveryTo = moment.tz(deliveryWindow.to, timezone).utc().toDate();
    let utcCloseDate = null;
    if (closeDate) {
      const [year, month, day] = closeDate.split("-").map(Number);
      utcCloseDate = new Date(Date.UTC(year, month - 1, day));
    }
    const normalizedMaterials = materials.map((mat) => ({
      material: mat.material,
      subMaterial: mat.subMaterial || "",
      weight: mat.weight,
      quantity: mat.quantity,
    }));

    const tender = new Tender({
      createdBy: req.user.id,
      dispatchLocation,
      address,
      pincode,
      materials: normalizedMaterials,
      transporters,
      remarks: remarks || "",
      closeDate: utcCloseDate,
      biddingStart: utcBiddingStart,
      biddingEnd: utcBiddingEnd,
      deliveryWindow: {
        from: utcDeliveryFrom,
        to: utcDeliveryTo,
      },
      totalWeight,
      totalQuantity,
      projectName,
      projectCode,
      purchaseOrder,
      projectRemark: projectRemark || "",
      maxBidAmount,
      maxBidUnit,
    });

    await tender.save();

    // ✅ Respond Immediately
    res.status(201).json({ success: true, data: tender });


    // 📨 Fire-and-forget Email Notification to Transporters
    (async () => {
      try {
        // 📨 Notify transporters
        const transporterUsers = await User.find({
          _id: { $in: transporters.map((id) => new mongoose.Types.ObjectId(id)) },
        });

        const transporterEmails = transporterUsers.map((user) => user.email);
        const subject = "📦 New Tender Invitation - RR ISPAT";

        // 🌟 Professional Multilingual Email Body
        const websiteUrl = "https://logiyatra.rrispat.in/signin";
        const supportEmail = "techsupport@rrispat.com";
        const backgroundImageUrl =
          "https://images.unsplash.com/photo-1526403227912-7d60d6cc6b4a?ixlib=rb-4.0.3&auto=format&fit=crop&w=1950&q=80";

        const htmlBody = `
      <div style="margin:0; padding:0; background-image: url('${backgroundImageUrl}'); background-size: cover; background-position: center; background-repeat: no-repeat; background-color: #f4f4f4;">
        <table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width:620px; background: #ffffff; margin-top:30px; margin-bottom:30px; border-radius:10px; overflow:hidden; box-shadow: 0 6px 20px rgba(0,0,0,0.15);">

          <tr>
            <td align="center" style="background:rgb(12, 25, 206); background-size: 600% 600%; animation: gradientBG 8s ease infinite; padding: 20px;">
             <div style="font-family: Arial, sans-serif; font-size: 28px; font-weight: bold;">
                 <span style="color: #e74c3c;">RR</span> <span style="color: #ffffff;">ISPAT</span>
             </div>
                <div style="font-family: Arial, sans-serif; font-size: 14px; margin-top: 5px; color:#ffffff;">
                  A Unit of Godawari Power and Ispat Limited
              </div>
            </td>
          </tr>

          <tr>
            <td style="padding: 40px 30px 30px 30px; font-family: Arial, sans-serif; color: #333; font-size: 16px;">

              <h2 style="color:#2E86C1; text-align:center;">📢 New Tender Invitation</h2>

              <p>Dear Transporter,<br/><small>(प्रिय ट्रांसपोर्टर)</small></p>

              <p>We are excited to invite you to participate in a new tender from <strong>RR ISPAT</strong>.<br/><small>(RR ISPAT द्वारा एक नए टेंडर में भाग लेने के लिए आपका स्वागत है।)</small></p>

              <table cellpadding="5" cellspacing="0" width="100%" style="margin: 25px 0;">
                <tr>
                  <td style="font-weight:bold; width:40%;">📍 Dispatch Location:</td>
                  <td>
                    ${dispatchLocation}
                    <br/>
                    <small style="color:#555;">(डिस्पैच स्थान: ${dispatchLocation})</small>
                  </td>
                </tr>
                <tr>
                  <td style="font-weight:bold;">🚚 Delivery Window:</td>
                  <td>
                    ${moment(utcDeliveryFrom)
            .tz(timezone)
            .format("DD MMM YYYY")} to ${moment(utcDeliveryTo)
              .tz(timezone)
              .format("DD MMM YYYY")}
                    <br/>
                    <small style="color:#555;">(वितरण अवधि: ${moment(
                utcDeliveryFrom
              )
            .tz(timezone)
            .format("DD MMM YYYY")} से ${moment(utcDeliveryTo)
              .tz(timezone)
              .format("DD MMM YYYY")})</small>
                  </td>
                </tr>
                <tr>
                  <td style="font-weight:bold;">🕒 Bidding Starts:</td>
                  <td>
                    ${moment(utcBiddingStart)
            .tz(timezone)
            .format("DD MMM YYYY, hh:mm A")}
                    <br/>
                    <small style="color:#555;">(बिडिंग प्रारंभ: ${moment(
              utcBiddingStart
            )
            .tz(timezone)
            .format("DD MMM YYYY, hh:mm A")})</small>
                  </td>
                </tr>
                <tr>
                  <td style="font-weight:bold;">⏳ Bidding Ends:</td>
                  <td>
                    ${moment(utcBiddingEnd)
            .tz(timezone)
            .format("DD MMM YYYY, hh:mm A")}
                    <br/>
                    <small style="color:#555;">(बिडिंग समाप्ति: ${moment(
              utcBiddingEnd
            )
            .tz(timezone)
            .format("DD MMM YYYY, hh:mm A")})</small>
                  </td>
                </tr>
              </table>

              <div style="text-align:center; margin:40px 0;">
                <a href="${websiteUrl}" target="_blank" style="background:rgb(12, 25, 206); color:#fff; padding:14px 28px; font-size:16px; border-radius:6px; text-decoration:none; display:inline-block; box-shadow:0 4px 8px rgba(0,0,0,0.2);">
                  🔗 Login to Dashboard<br/><small style="font-size:12px;">(डैशबोर्ड में लॉगिन करें)</small>
                </a>
              </div>

              <p style="text-align:center; margin-top:30px;">
                Need help? Email us at <a href="mailto:${supportEmail}" style="color:#2E86C1;">${supportEmail}</a><br/>
                <small>(सहायता चाहिए? हमें ईमेल करें: ${supportEmail})</small>
              </p>

            <p style="margin-top:50px; font-family: Arial, sans-serif; font-size: 16px; color: #333; text-align: center;">
               Thanks & Regards,<br>
               <span style="font-weight:bold; font-size:18px;">RR ISPAT</span><br>
               <small style="font-size: 13px; ">- A Unit of Godawari Power and Ispat Limited</small>
                </p>
      
            </td>
          </tr>

          <tr>
            <td style="background-color: #f1f1f1; text-align: center; padding: 20px; font-size: 12px; color: #777;">
              Growing Stronger Together | <a href="${websiteUrl}" style="color: #2E86C1; text-decoration: none;">www.logiyatra.rrispat.in</a><br/>
            </td>
          </tr>

        </table>

        <style>
          @keyframes gradientBG {
            0% {background-position: 0% 50%;}
            50% {background-position: 100% 50%;}
            100% {background-position: 0% 50%;}
          }
        </style>
      </div>`;

        for (const email of transporterEmails) {
          await sendMail({ to: email, subject, html: htmlBody });
        }
      } catch (emailErr) {
        console.error("Email sending failed:", emailErr.message);
      }
    })();
  } catch (error) {
    console.error("Tender creation failed:", error);
    res.status(500).json({ success: false, message: error.message });
  }
};

// ✅ 2. Finalize Tender

export const finalizeTender = async (req, res) => {
  try {
    const { quotationId, finalPrice } = req.body;
    console.log(finalPrice);

    // 🔎 Find tender
    const tender = await Tender.findById(req.params.id);
    if (!tender) {
      return res
        .status(404)
        .json({ success: false, message: "Tender not found" });
    }

    if (tender.status === "finalized" || tender.selectedQuotation) {
      return res.status(400).json({
        success: false,
        message: "Tender has already been finalized and cannot be changed.",
      });
    }

    if (tender.createdBy.toString() !== req.user.id) {
      return res.status(403).json({ success: false, message: "Unauthorized" });
    }

    // 🔎 Find quotation
    const quotation = await Quotation.findOne({
      _id: quotationId,
      tender: tender._id,
    });
    if (!quotation) {
      return res
        .status(400)
        .json({ success: false, message: "Invalid quotation" });
    }

    // 🔎 Find transport user
    const transportUser = await userModel.findById(quotation.transportUser);
    if (!transportUser) {
      return res
        .status(400)
        .json({ success: false, message: "Transport user not found" });
    }

    // ✅ Update tender with finalization
    tender.selectedQuotation = quotation._id;
    tender.finalTransporter = quotation.transportUser;
    tender.finalPrice = finalPrice;
    tender.status = "finalized";
    await tender.save();

    // ✅ Email Notification to Transport User
    try {
      await sendMail({
        to: transportUser.email,
        subject:
          "🎉 Congratulations! Your Quotation Has Been Accepted - RR ISPAT",
        html: `
          <div style="margin:0; padding:0; background-color:#f4f4f4;">
            <table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width:620px; background:#ffffff; margin-top:30px; margin-bottom:30px; border-radius:10px; overflow:hidden; box-shadow: 0 6px 20px rgba(0,0,0,0.15);">
              
              <tr>
                <td align="center" style="background: #ffffff; padding: 30px;">
                  <div style="font-family: Arial, sans-serif; font-size: 28px; font-weight: bold;">
                    <span style="color: #e74c3c;">RR</span> <span style="color: #000;">ISPAT</span>
                  </div>
                  <div style="font-family: Arial, sans-serif; font-size: 13px; margin-top: 5px; color: #777;">
                    A Unit of Godawari Power and Ispat Limited
                  </div>
                </td>
              </tr>

              <tr>
                <td style="padding: 40px 30px; font-family: Arial, sans-serif; color: #333; font-size: 16px;">

                  <p>Hello <strong>${transportUser.name}</strong>,</p>

                  <p style="margin-top:20px;">
                    🎉 <strong>Congratulations!</strong> Your quotation has been <span style="color: #2E86C1;">ACCEPTED</span> for the following journey:
                  </p>

                  <table cellpadding="5" cellspacing="0" width="100%" style="margin: 20px 0;">
                    <tr>
                      <td style="font-weight:bold;">📍 Dispatch Location:</td>
                      <td>${tender.dispatchLocation}</td>
                    </tr>
                    <tr>
                      <td style="font-weight:bold;">🚚 Delivery Window:</td>
                      <td>${moment(tender.deliveryWindow.from)
            .tz("Asia/Kolkata")
            .format("DD MMM YYYY")} to ${moment(
              tender.deliveryWindow.to
            )
              .tz("Asia/Kolkata")
              .format("DD MMM YYYY")}</td>
                    </tr>
                  </table>

                  <p style="margin-top:30px;"><strong>📦 Tender Items:</strong></p>
                  <ul style="margin-top:10px; padding-left:20px;">
                    ${tender.materials
            .map(
              (mat) => `
                      <li>${mat.material} (${mat.subMaterial || "N/A"}) - ${mat.weight
                } MT, ${mat.quantity} Qty</li>
                    `
            )
            .join("")}
                  </ul>

                  <p style="margin-top:30px;">
                    <strong>✅ Finalized Price:</strong> ₹${finalPrice}
                  </p>

                  <p style="margin-top:30px;">
                    We sincerely appreciate your cooperation. Further communication regarding dispatch schedules will follow shortly.
                  </p>

                  <p style="margin-top:40px;">
                    Thanks & Regards,<br>
                    <span style="font-weight:bold; font-size:18px;">RR ISPAT</span><br>
                    <small style="color:#777;">A Unit of Godawari Power and Ispat Limited</small>
                  </p>

                </td>
              </tr>

              <tr>
                <td style="background-color: #f1f1f1; text-align: center; padding: 15px; font-size: 12px; color: #777;">
                  Building Strong Foundations | <a href="https://www.rrispat.com" style="color: #2E86C1; text-decoration: none;">www.rrispat.com</a>
                </td>
              </tr>

            </table>
          </div>
        `,
      });
    } catch (emailErr) {
      console.error("Failed to send finalization email:", emailErr.message);
    }

    res.status(200).json({
      success: true,
      message: "Tender finalized and email sent to transport user",
      tender,
    });
  } catch (error) {
    console.error("Error in finalizeTender:", error);
    res.status(400).json({ success: false, message: error.message });
  }
};

// ✅ 3. Get All Tenders Created by RR User
export const getAllTendersByRRUser = async (req, res) => {
  try {
    const tenders = await Tender.find({ createdBy: req.user.id })
      .sort({ createdAt: -1 })
      .populate("selectedQuotation");

    res.status(200).json({ success: true, data: tenders });
  } catch (error) {
    res.status(500).json({ success: false, message: error.message });
  }
};

// ✅ 4. Get Tenders Assigned to a Transporter (excluding already quoted ones)

export const getTendersForTransporter = async (req, res) => {
  try {
    const transporterId = req.user.id;
    const now = new Date();

    // Step 1: Get all open tenders assigned to this transporter and within bidding window
    const tenders = await Tender.find({
      transporters: transporterId,
      status: "open",
      biddingStart: { $lte: now },
      biddingEnd: { $gte: now },
    }).sort({ createdAt: -1 });

    const tenderIds = tenders.map((t) => t._id);

    // Step 2: Get all quotations by this transporter for these tenders
    const transporterQuotations = await Quotation.find({
      transportUser: transporterId,
      tender: { $in: tenderIds },
    }).select("tender");

    const quotedTenderIds = new Set(
      transporterQuotations.map((q) => q.tender.toString())
    );

    // Count how many times transporter quoted per tender
    const bidCountMap = {};
    transporterQuotations.forEach((q) => {
      const id = q.tender.toString();
      bidCountMap[id] = (bidCountMap[id] || 0) + 1;
    });

    // Step 3: Prepare tender list with hasQuoted and bidsLeft
    const tendersWithStatus = tenders.map((tender) => {
      const tenderObj = tender.toObject();
      const tid = tender._id.toString();
      tenderObj.hasQuoted = quotedTenderIds.has(tid);
      tenderObj.bidsUsed = bidCountMap[tid] || 0;
      tenderObj.bidsRemaining = Math.max(0, 3 - tenderObj.bidsUsed);
      return tenderObj;
    });

    // Step 4: Populate createdBy field
    await Tender.populate(tendersWithStatus, {
      path: "createdBy",
      select: "name email",
    });

    res.status(200).json({ success: true, data: tendersWithStatus });
  } catch (error) {
    console.error("Error in getTendersForTransporter:", error);
    res.status(500).json({ success: false, message: error.message });
  }
};

//upcomming tender

export const getUpcomingTendersForTransporter = async (req, res) => {
  try {
    const transporterId = req.user.id;
    const now = new Date();

    // 🟡 Step 1: Find upcoming tenders assigned to this transporter
    const upcomingTenders = await Tender.find({
      transporters: transporterId,
      status: "open",
      biddingStart: { $gt: now },
    })
      .sort({ biddingStart: 1 })
      .populate("createdBy", "name email");

    // 🕒 Step 2: Add biddingOpensIn to each tender
    const tendersWithCountdown = upcomingTenders.map((tender) => {
      const tenderObj = tender.toObject();
      tenderObj.biddingOpensIn =
        new Date(tender.biddingStart).getTime() - now.getTime(); // in milliseconds
      return tenderObj;
    });

    res.status(200).json({ success: true, data: tendersWithCountdown });
  } catch (error) {
    console.error("Error in getUpcomingTendersForTransporter:", error);
    res.status(500).json({ success: false, message: error.message });
  }
};

// ✅ 5. Get Quotations for a Tender

export const getTenderQuotations = async (req, res) => {
  try {
    const tenderId = req.params.id;
    const userId = req.user.id;

    const tender = await Tender.findById(tenderId).populate(
      "createdBy",
      "name email"
    );
    if (!tender) {
      return res
        .status(404)
        .json({ success: false, message: "Tender not found" });
    }

    if (tender.createdBy._id.toString() !== userId) {
      return res.status(403).json({ success: false, message: "Unauthorized" });
    }

    const now = new Date();
    if (now < tender.biddingEnd) {
      return res.status(403).json({
        success: false,
        message:
          "Top quotations can be viewed only after the bidding window closes.",
      });
    }

    const allQuotes = await Quotation.find({ tender: tenderId })
      .populate("transportUser", "name email")
      .sort({ price: 1, createdAt: 1 });

    const seen = new Set();
    const bestQuotes = [];

    for (const q of allQuotes) {
      const uid = q.transportUser._id.toString();
      if (!seen.has(uid)) {
        seen.add(uid);
        bestQuotes.push(q);
      }
    }

    let quotesToReturn = [];
    if (tender.reopenCount === 0) {
      quotesToReturn = bestQuotes.slice(0, 3); // L1, L2, L3
    } else if (tender.reopenCount === 1) {
      quotesToReturn = bestQuotes.slice(1, 3); // L2, L3
    } else if (tender.reopenCount === 2) {
      quotesToReturn = bestQuotes.slice(2, 3); // Only L3
    }

    const ranked = quotesToReturn.map((q, index) => {
      const signedFiles = (q.files || []).map((file) => {
        const key = file.url?.split("/").pop();
        return { ...file, url: generateSignedUrl(key) };
      });

      return {
        rank: `L${bestQuotes.indexOf(q) + 1}`,
        transportUser: q.transportUser,
        price: q.price,
        vehicleNumber: q.vehicleNumber,
        createdAt: q.createdAt,
        files: signedFiles,
        _id: q._id,
      };
    });

    res.status(200).json({ success: true, data: ranked });
  } catch (error) {
    console.error("Error fetching top 3 quotations:", error);
    res.status(500).json({ success: false, message: error.message });
  }
};

//reopen tender
export const reopenTender = async (req, res) => {
  try {
    const { id } = req.params;
    const { reason } = req.body;

    if (!reason || reason.trim() === "") {
      return res.status(400).json({ error: "Reason is required." });
    }

    const tender = await Tender.findById(id);
    if (!tender)
      return res.status(404).json({ success: false, message: "Not found" });

    // 🚫 Prevent reopening more than twice
    if (tender.reopenCount >= 2) {
      return res.status(403).json({
        success: false,
        message:
          "This tender has already been reopened twice and cannot be reopened again.",
      });
    }

    // ✅ Perform reopen
    tender.status = "open";
    tender.selectedQuotation = null;
    tender.finalTransporter = null;
    tender.finalPrice = null;
    tender.reopenCount = (tender.reopenCount || 0) + 1; // ✅ increment counter
    tender.winnerComment = `[Reopened: ${reason}]`;

    await tender.save();

    res.status(200).json({
      success: true,
      message: "Tender reopened successfully",
      reopenCount: tender.reopenCount,
    });
  } catch (error) {
    res.status(500).json({ success: false, message: error.message });
  }
};

// ✅ 6. Get Single Tender
export const getSingleTender = async (req, res) => {
  try {
    const tender = await Tender.findById(req.params.id)
      .populate("createdBy", "name email")
      .populate("quotations")
      .populate("selectedQuotation");

    if (!tender) {
      return res
        .status(404)
        .json({ success: false, message: "Tender not found" });
    }

    res.status(200).json({ success: true, data: tender });
  } catch (error) {
    res.status(400).json({ success: false, message: error.message });
  }
};

// ✅ 7. Delete Tender (by RR User)
export const deleteTender = async (req, res) => {
  try {
    const tender = await Tender.findById(req.params.id);

    if (!tender) {
      return res
        .status(404)
        .json({ success: false, message: "Tender not found" });
    }

    if (tender.createdBy.toString() !== req.user.id) {
      return res.status(403).json({ success: false, message: "Unauthorized" });
    }

    await Tender.findByIdAndDelete(req.params.id);

    res
      .status(200)
      .json({ success: true, message: "Tender deleted successfully" });
  } catch (error) {
    res.status(500).json({ success: false, message: error.message });
  }
};

// ✅ Get Quotation History for Transporter

export const getQuotationHistoryForTransporter = async (req, res) => {
  try {
    const transporterId = new mongoose.Types.ObjectId(req.user.id);

    const today = new Date();
    today.setHours(0, 0, 0, 0); // normalize to start of today

    // ✅ Get all quotations by this transporter, grouped by tender
    const quotations = await Quotation.find({ transportUser: transporterId })
      .populate({
        path: "tender",
        populate: {
          path: "createdBy",
          select: "name email",
        },
      })
      .sort({ createdAt: 1 });

    const tenderQuotesMap = new Map();

    // ✅ Group quotations by tender ID (and skip tenders whose closeDate >= today)
    for (const q of quotations) {
      const tender = q.tender;
      const tenderId = tender?._id?.toString();
      if (!tenderId) continue;

      const biddingEndTime = new Date(tender.biddingEnd);
      if (biddingEndTime > new Date()) continue;

      if (!tenderQuotesMap.has(tenderId)) {
        tenderQuotesMap.set(tenderId, []);
      }
      tenderQuotesMap.get(tenderId).push(q);
    }

    const result = [];

    for (const [tenderId, tenderQuotes] of tenderQuotesMap.entries()) {
      const tender = tenderQuotes[0].tender;

      // ✅ Determine if any of the quotations match the selectedQuotation
      const isSelected =
        tender.selectedQuotation &&
        tenderQuotes.some(
          (q) => q._id.toString() === tender.selectedQuotation.toString()
        );

      const formattedQuotes = tenderQuotes.map((q) => {
        const signedFiles = (q.files || []).map((file) => {
          const key = file.url?.split("/").pop();
          return {
            ...file,
            url: generateSignedUrl(key),
          };
        });

        return {
          _id: q._id,
          price: q.price,
          vehicleNumber: q.vehicleNumber,
          createdAt: q.createdAt,
          files: signedFiles,
        };
      });

      result.push({
        tenderId: tender._id,
        tender: {
          dispatchLocation: tender.dispatchLocation,
          address: tender.address,
          deliveryWindow: tender.deliveryWindow || { from: null, to: null },
          closeDate: tender.closeDate,
          status: tender.status,
          remarks: tender.remarks,
          materials: tender.materials || [],
          totalWeight: tender.totalWeight,
          totalQuantity: tender.totalQuantity,
          createdBy: tender.createdBy || null,
          maxBidAmount: tender.maxBidAmount,
          maxBidUnit: tender.maxBidUnit || null,
          finalizedStatus: isSelected
            ? "Your quotation was finalized"
            : "Your quotation was not selected",
        },
        quotations: formattedQuotes,
      });
    }

    res.status(200).json({
      success: true,
      data: result,
    });
  } catch (error) {
    console.error("Error fetching quotation history:", error);
    res.status(500).json({ success: false, message: error.message });
  }
};

//all finalized tenders

// export const getAllFinalizedTendersWithQuotations = async (req, res) => {
//   try {
//     const rrUserId = req.user.id;

//     // ✅ Get all finalized tenders created by this RR user
//     const finalizedTenders = await Tender.find({
//       createdBy: rrUserId,
//       status: "finalized",
//     })
//       .populate({
//         path: "selectedQuotation",
//         populate: { path: "transportUser", select: "name email" },
//       })
//       .populate("createdBy", "name email")
//       .sort({ updatedAt: -1 });

//     const results = [];

//     for (const tender of finalizedTenders) {
//       // ✅ Get all quotations for this tender
//       const quotations = await Quotation.find({ tender: tender._id })
//         .populate("transportUser", "name email")
//         .sort({ createdAt: -1 });

//       const allQuotations = quotations.map((q) => {
//         const signedFiles = (q.files || []).map((file) => {
//           const key = file.url?.split("/").pop();
//           return {
//             ...file,
//             url: generateSignedUrl(key),
//           };
//         });

//         return {
//           _id: q._id,
//           price: q.price,
//           vehicleNumber: q.vehicleNumber,
//           createdAt: q.createdAt,
//           transportUser: q.transportUser,
//           files: signedFiles,
//         };
//       });

//       const selectedQuotationId = tender.selectedQuotation?._id?.toString();

//       results.push({
//         tender: {
//           _id: tender._id,
//           dispatchLocation: tender.dispatchLocation,
//           address: tender.address,
//           deliveryWindow: tender.deliveryWindow, // ✅ Updated from dateOfDelivery
//           closeDate: tender.closeDate,
//           remarks: tender.remarks,
//           status: tender.status,
//           finalPrice: tender.finalPrice,
//           materials: tender.materials,
//           totalWeight: tender.totalWeight,
//           totalQuantity: tender.totalQuantity,
//           createdBy: tender.createdBy,
//         },
//         selectedQuotation: tender.selectedQuotation
//           ? {
//               _id: tender.selectedQuotation._id,
//               price: tender.selectedQuotation.price,
//               vehicleNumber: tender.selectedQuotation.vehicleNumber,
//               transportUser: tender.selectedQuotation.transportUser,
//               files: (tender.selectedQuotation.files || []).map((file) => {
//                 const key = file.url?.split("/").pop();
//                 return {
//                   ...file,
//                   url: generateSignedUrl(key),
//                 };
//               }),
//             }
//           : null,
//         allQuotations: allQuotations.map((q) => ({
//           ...q,
//           selected: q._id.toString() === selectedQuotationId,
//         })),
//       });
//     }

//     res.status(200).json({ success: true, data: results });
//   } catch (error) {
//     console.error("Error fetching finalized tenders for RR user:", error);
//     res.status(500).json({ success: false, message: error.message });
//   }
// };

//your position for transporters

export const getMyQuotationPosition = async (req, res) => {
  try {
    const { tenderId } = req.params;
    const userId = req.user.id;

    console.log("Fetching position for tenderId:", tenderId);

    // ✅ Validate Tender ID
    if (!tenderId || !mongoose.Types.ObjectId.isValid(tenderId)) {
      return res.status(400).json({ message: "Invalid or missing Tender ID" });
    }

    // ✅ Get all quotations for the tender, sorted by price + createdAt
    const allQuotes = await Quotation.find({ tender: tenderId }).sort({
      price: 1,
      createdAt: 1,
    });

    // ✅ Group best (lowest) quote per transporter
    const bestQuotesMap = new Map(); // transportUserId => bestQuotation

    for (const quote of allQuotes) {
      const uid = quote.transportUser.toString();
      if (!bestQuotesMap.has(uid)) {
        bestQuotesMap.set(uid, quote); // first lowest quote per transporter
      }
    }

    // ✅ Sort those best quotes by price (and createdAt to break ties)
    const sortedBestQuotes = Array.from(bestQuotesMap.entries()).sort(
      ([, q1], [, q2]) => {
        if (q1.price === q2.price) {
          return new Date(q1.createdAt) - new Date(q2.createdAt);
        }
        return q1.price - q2.price;
      }
    );

    // ✅ Find current user's rank + their best quote
    let position = null;
    let bestQuote = null;

    for (let i = 0; i < sortedBestQuotes.length; i++) {
      const [uid, quote] = sortedBestQuotes[i];
      if (uid === userId) {
        position = `L${i + 1}`;
        bestQuote = {
          _id: quote._id,
          price: quote.price,
          vehicleNumber: quote.vehicleNumber,
          createdAt: quote.createdAt,
        };
        break;
      }
    }

    // ✅ No bids yet?
    if (!bestQuote) {
      return res.status(200).json({
        position: null,
        message: "You have not submitted any bids yet.",
      });
    }

    // ✅ Success response with rank + best bid
    res.status(200).json({
      position,
      bestQuotation: bestQuote,
    });
  } catch (error) {
    console.error("Error getting bid position:", error);
    res.status(500).json({ message: "Internal server error" });
  }
};
content_copyCOPY