From 1da15a4539b8b40c5459877a60f85264c8dfe56b Mon Sep 17 00:00:00 2001 From: Varun Date: Thu, 21 Aug 2025 13:07:19 +0530 Subject: [PATCH] changes in get suppliers --- src/handlers/supplierHandler.js | 184 ++++++++++++++++++++++++++------ src/routes/supplierRoute.js | 24 +++-- 2 files changed, 167 insertions(+), 41 deletions(-) diff --git a/src/handlers/supplierHandler.js b/src/handlers/supplierHandler.js index dd91ae42..18c240ec 100644 --- a/src/handlers/supplierHandler.js +++ b/src/handlers/supplierHandler.js @@ -1929,46 +1929,135 @@ exports.getSuppliersForPlanSearch = async (req, reply) => { type_of_water, capacity: requestedCapacityStr, quantity: requestedQuantityStr, - // frequency, start_date, end_date are provided by UI but not used for filtering now + frequency, start_date, end_date, // currently not used to filter suppliers + + // new filters + radius_from, radius_to, + rating_from, rating_to, + price_from, price_to, + pump } = req.body; - // helpers inside function (per your preference) - const parseCapacity = (v) => parseFloat((v || "0").toString().replace(/,/g, "")) || 0; + // ---- helpers (kept inside as you prefer) ---- + const parseFloatSafe = (v) => { + const n = parseFloat((v ?? "").toString().replace(/,/g, "")); + return Number.isFinite(n) ? n : NaN; + }; + const parseIntSafe = (v) => { + const n = parseInt((v ?? "").toString().replace(/,/g, ""), 10); + return Number.isFinite(n) ? n : NaN; + }; + const isValid = (n) => Number.isFinite(n); + const inRange = (n, from, to) => + (!isValid(from) || n >= from) && (!isValid(to) || n <= to); + + const normalizePump = (val) => { + if (val == null) return undefined; + const s = String(val).trim().toLowerCase(); + if (["1","true","yes","y"].includes(s)) return true; + if (["0","false","no","n"].includes(s)) return false; + return undefined; // ignore if unknown + }; - const requestedCapacity = parseCapacity(requestedCapacityStr); - const requestedQuantity = parseInt((requestedQuantityStr || "0").toString(), 10) || 0; + const parseLatLng = (raw) => { + // supports: "17.38,78.49" | {lat: 17.38, lng: 78.49} | [17.38, 78.49] + if (!raw) return null; + try { + if (typeof raw === "string") { + const parts = raw.split(",").map(x => parseFloat(x.trim())); + if (parts.length === 2 && parts.every(Number.isFinite)) return { lat: parts[0], lng: parts[1] }; + // try JSON + const j = JSON.parse(raw); + return parseLatLng(j); + } + if (Array.isArray(raw) && raw.length === 2) { + const [lat, lng] = raw.map(Number); + if (Number.isFinite(lat) && Number.isFinite(lng)) return { lat, lng }; + } + if (typeof raw === "object" && raw !== null) { + const lat = parseFloat(raw.lat ?? raw.latitude); + const lng = parseFloat(raw.lng ?? raw.lon ?? raw.longitude); + if (Number.isFinite(lat) && Number.isFinite(lng)) return { lat, lng }; + } + } catch (_) {} + return null; + }; + + const haversineKm = (a, b) => { + const R = 6371; + const dLat = (b.lat - a.lat) * Math.PI / 180; + const dLng = (b.lng - a.lng) * Math.PI / 180; + const s1 = Math.sin(dLat/2) ** 2; + const s2 = Math.cos(a.lat*Math.PI/180) * Math.cos(b.lat*Math.PI/180) * Math.sin(dLng/2) ** 2; + return 2 * R * Math.asin(Math.sqrt(s1 + s2)); + }; + + const getSupplierRating = (s) => { + // adapt to whatever field you actually store + const cands = [s.rating, s.avgRating, s.averageRating, s.overallRating]; + const n = cands.find(x => Number.isFinite(Number(x))); + return Number(n ?? NaN); + }; + // ---- end helpers ---- + + // parse inputs + const requestedCapacity = parseFloatSafe(requestedCapacityStr) || 0; + const requestedQuantity = parseIntSafe(requestedQuantityStr) || 0; const totalRequiredCapacity = requestedCapacity * requestedQuantity; + const priceFrom = parseIntSafe(price_from); + const priceTo = parseIntSafe(price_to); + const ratingFrom = parseFloatSafe(rating_from); + const ratingTo = parseFloatSafe(rating_to); + const radiusFrom = parseFloatSafe(radius_from); + const radiusTo = parseFloatSafe(radius_to); + const pumpWanted = normalizePump(pump); + try { - // favorites - const customer = await User.findOne({ customerId }, { favorate_suppliers: 1 }).lean(); + // favorites + customer coords (for radius) + const customer = await User.findOne({ customerId }, { favorate_suppliers: 1, googleLocation: 1, location: 1 }).lean(); const favoriteSet = new Set(customer?.favorate_suppliers || []); + const customerCoords = + parseLatLng(customer?.googleLocation) || + parseLatLng(customer?.location); - // Tanker filter: ONLY by type_of_water (NO booked tanker exclusion, NO price/radius/rating) + // 1) Tankers base query: by type_of_water (+ pump if requested) const tankerQuery = {}; - if (type_of_water && type_of_water.trim() !== "") { - tankerQuery.typeofwater = type_of_water; + if (type_of_water?.trim()) tankerQuery.typeofwater = type_of_water.trim(); + if (pumpWanted !== undefined) { + // try to match common representations + tankerQuery.$or = [ + { pump: pumpWanted ? { $in: [true, "1", "yes", "true", 1, "Y", "y"] } : { $in: [false, "0", "no", "false", 0, "N", "n"] } }, + { pumpAvailable: pumpWanted } // if you store as boolean + ]; } - const tankers = await Tanker.find(tankerQuery).lean(); - // Group tankers by supplier + let tankers = await Tanker.find(tankerQuery).lean(); + + // 2) Price range on tanker.price + if (isValid(priceFrom) || isValid(priceTo)) { + tankers = tankers.filter(t => { + const p = parseIntSafe(t.price); + return isValid(p) && inRange(p, priceFrom, priceTo); + }); + } + + // 3) Group by supplier const supplierTankerMap = {}; for (const t of tankers) { - if (!supplierTankerMap[t.supplierId]) supplierTankerMap[t.supplierId] = []; - supplierTankerMap[t.supplierId].push(t); + if (!t?.supplierId) continue; + (supplierTankerMap[t.supplierId] ||= []).push(t); } - // Capacity check per supplier (capacity * quantity) - const qualified = []; + // 4) Capacity qualification + let qualified = []; for (const [supplierId, supplierTankers] of Object.entries(supplierTankerMap)) { - const totalAvail = supplierTankers.reduce((sum, tt) => sum + parseCapacity(tt.capacity), 0); - if (requestedCapacity > 0 && requestedQuantity > 0) { - if (totalAvail < totalRequiredCapacity) continue; - } + const totalAvail = supplierTankers.reduce((sum, tt) => sum + (parseFloatSafe(tt.capacity) || 0), 0); + if (requestedCapacity > 0 && requestedQuantity > 0 && totalAvail < totalRequiredCapacity) continue; qualified.push({ supplierId, tankers: supplierTankers }); } - // Fetch suppliers + connection flags + // 5) Fetch suppliers for remaining filters (rating & radius) + flags const supplierIds = qualified.map(q => q.supplierId); const [suppliersData, acceptedReqs] = await Promise.all([ Supplier.find({ supplierId: { $in: supplierIds } }).lean(), @@ -1977,31 +2066,59 @@ exports.getSuppliersForPlanSearch = async (req, reply) => { { supplierId: 1, _id: 0 } ).lean() ]); + + // Build quick lookup const supplierById = new Map(suppliersData.map(s => [s.supplierId, s])); const connectedSet = new Set(acceptedReqs.map(r => r.supplierId)); - // Optional: check if any (single-day) requested booking exists with that supplier + // 6) Apply rating & radius filters on suppliers + if (isValid(ratingFrom) || isValid(ratingTo) || (isValid(radiusFrom) || isValid(radiusTo))) { + qualified = qualified.filter(q => { + const s = supplierById.get(q.supplierId); + if (!s) return false; + + // rating + if (isValid(ratingFrom) || isValid(ratingTo)) { + const r = getSupplierRating(s); + if (!isValid(r) || !inRange(r, ratingFrom, ratingTo)) return false; + } + + // radius (requires coords on both sides) + if ((isValid(radiusFrom) || isValid(radiusTo)) && customerCoords) { + const supCoords = + parseLatLng(s.googleLocation) || + parseLatLng(s.location) || + parseLatLng(s.addressLocation); + if (!supCoords) return false; + const distKm = haversineKm(customerCoords, supCoords); + if (!inRange(distKm, radiusFrom, radiusTo)) return false; + } + + return true; + }); + } + + // 7) Build response with flags + optional 'requestedBooking' flag const suppliers = []; for (const q of qualified) { - const supplierData = supplierById.get(q.supplierId); - const friendRequestAccepted = connectedSet.has(q.supplierId); - const isFavorite = favoriteSet.has(q.supplierId); + const s = supplierById.get(q.supplierId); + if (!s) continue; + const isConnected = connectedSet.has(q.supplierId); + const isFavorite = favoriteSet.has(q.supplierId); + + // If you want to expose a hint that user has already sent a single-day request earlier const requestedBookingRecord = await RequestedBooking.findOne({ customerId, "requested_suppliers.supplierId": q.supplierId }, { time: 1 }).lean(); - const requestedBooking = requestedBookingRecord - ? { status: true, time: requestedBookingRecord.time } - : { status: false }; - suppliers.push({ - supplier: supplierData, + supplier: s, tankers: q.tankers, - isConnected: friendRequestAccepted, + isConnected, isFavorite, - requestedBooking + requestedBooking: requestedBookingRecord ? { status: true, time: requestedBookingRecord.time } : { status: false } }); } @@ -2014,8 +2131,7 @@ exports.getSuppliersForPlanSearch = async (req, reply) => { error: err.message }); } -}; - + }; // controllers/validationHandler.js (add below the previous handler) diff --git a/src/routes/supplierRoute.js b/src/routes/supplierRoute.js index 65cad2c1..107f99b1 100644 --- a/src/routes/supplierRoute.js +++ b/src/routes/supplierRoute.js @@ -160,11 +160,11 @@ fastify.post("/api/requestedbookings", { handler: validationHandler.getPendingSuppliers, }); - fastify.post("/api/plan/suppliers/:customerId", { + fastify.post("/api/plan/suppliers/:customerId", { schema: { tags: ["Supplier-Data"], summary: "Search suppliers for Plans page", - description: "Filters by type_of_water and capacityƗquantity. No bookings/price/radius/rating filters.", + description: "Filters by type_of_water, capacityƗquantity, price, rating, radius, pump. No booked-tanker exclusion.", params: { type: "object", required: ["customerId"], @@ -174,15 +174,25 @@ fastify.post("/api/requestedbookings", { type: "object", required: ["type_of_water", "capacity", "quantity", "frequency", "start_date", "end_date"], properties: { + // UI fields type_of_water: { type: "string" }, - capacity: { type: "string" }, // UI field - quantity: { type: "string" }, // UI field - frequency: { // UI field: daily/weekly_once/_twice/_thrice + capacity: { type: "string" }, + quantity: { type: "string" }, + frequency: { type: "string", enum: ["daily","weekly_once","weekly_twice","weekly_thrice","weekly"] }, - start_date: { type: "string" }, // UI field "Start date" - end_date: { type: "string" } // UI field "End date" + start_date: { type: "string" }, + end_date: { type: "string" }, + + // Extra filters from your payload + radius_from: { type: "string" }, + radius_to: { type: "string" }, + rating_from: { type: "string" }, + rating_to: { type: "string" }, + price_from: { type: "string" }, + price_to: { type: "string" }, + pump: { type: "string" }, // "true"/"false" | "1"/"0" | "yes"/"no" }, additionalProperties: false },