From 6cb30b7dfe5654c6578bd9117fed22f2a66fb7aa Mon Sep 17 00:00:00 2001 From: Bhaskar Date: Thu, 21 Aug 2025 12:06:43 +0530 Subject: [PATCH 1/2] This is for fetching team members for a given installation --- src/controllers/installationController.js | 14 +++++++++++++- src/routes/installationRoute.js | 20 +++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/controllers/installationController.js b/src/controllers/installationController.js index 74b2669b..e6ad8d01 100644 --- a/src/controllers/installationController.js +++ b/src/controllers/installationController.js @@ -782,7 +782,19 @@ exports.assignTeamMemberToQuotation = async (request, reply) => { } }; - +exports.getInstallationTeamMembers = async (request, reply) => { + try { + const installationId = request.params.installationId; + const installation = await Install.findOne({ installationId }); + if (!installation) { + return reply.status(404).send({ message: 'Installation not found' }); + } + const teamMembers = installation.team_member.team_member; + reply.send({ teamMembers }); + } catch (err) { + reply.status(500).send({ message: err.message }); + } +}; exports.getQuotationsByInstallationId = async (request, reply) => { try { const { installationId } = request.params; diff --git a/src/routes/installationRoute.js b/src/routes/installationRoute.js index 10efa575..99df2cb6 100644 --- a/src/routes/installationRoute.js +++ b/src/routes/installationRoute.js @@ -88,7 +88,7 @@ fastify.get("/api/getAllDepartments/:officeName/:city", { fastify.get("/api/getTeamMembers/:officeName/:city/:departmentId", { schema: { description: "Get all team members under a specific department", - tags: ["Installation"], + tags: ["Department"], summary: "Get Team Members by Department ID", params: { type: "object", @@ -154,6 +154,24 @@ fastify.get("/api/getAllDepartments/:officeName/:city", { }, handler: installationController.getQuotationsByInstallationAndTeamMember }); + + + fastify.route({ + method: "GET", + url: "/api/installationsTeammembers/:installationId", + schema: { + tags: ["Installation"], + description: "This is for fetching team members for a given installation", + summary: "This is for fetching team members for a given installation", + params: { + type: "object", + properties: { + installationId: { type: "string" } + } + } + }, + handler: installationController.getInstallationTeamMembers +}); fastify.post("/api/assignTeammember/:installationId", { schema: { description: "Assign a team member to an installation's quotation", From 3f347b4f32f3deccb33f3774129116bfd00a17f8 Mon Sep 17 00:00:00 2001 From: Varun Date: Thu, 21 Aug 2025 12:32:43 +0530 Subject: [PATCH 2/2] plans apis for book a tanker --- src/handlers/supplierHandler.js | 220 +++++++++++++++++++++++++++++++- src/models/supplier.js | 30 ++++- src/routes/supplierRoute.js | 76 +++++++++++ 3 files changed, 324 insertions(+), 2 deletions(-) diff --git a/src/handlers/supplierHandler.js b/src/handlers/supplierHandler.js index 81f362f6..dd91ae42 100644 --- a/src/handlers/supplierHandler.js +++ b/src/handlers/supplierHandler.js @@ -1,6 +1,6 @@ //Get the data models const { Supplier, DeliveryBoy, profilePictureSupplier } = require("../models/supplier"); -const { FriendRequest,RequestedBooking } = require("../models/supplier"); +const { FriendRequest,RequestedBooking,RecurringRequestedBooking } = require("../models/supplier"); const { Tanker,Tankerbooking } = require("../models/tankers"); const { ProfilePicture, User } = require("../models/User"); const supplierController = require("../controllers/supplierController"); @@ -1920,3 +1920,221 @@ fastify.get('/api/users/profile-picture-supplier/:supplierId', async (req, res) res.status(500).send({ error: error.message }); } }); + + + +exports.getSuppliersForPlanSearch = async (req, reply) => { + const { customerId } = req.params; + const { + type_of_water, + capacity: requestedCapacityStr, + quantity: requestedQuantityStr, + // frequency, start_date, end_date are provided by UI but not used for filtering now + } = req.body; + + // helpers inside function (per your preference) + const parseCapacity = (v) => parseFloat((v || "0").toString().replace(/,/g, "")) || 0; + + const requestedCapacity = parseCapacity(requestedCapacityStr); + const requestedQuantity = parseInt((requestedQuantityStr || "0").toString(), 10) || 0; + const totalRequiredCapacity = requestedCapacity * requestedQuantity; + + try { + // favorites + const customer = await User.findOne({ customerId }, { favorate_suppliers: 1 }).lean(); + const favoriteSet = new Set(customer?.favorate_suppliers || []); + + // Tanker filter: ONLY by type_of_water (NO booked tanker exclusion, NO price/radius/rating) + const tankerQuery = {}; + if (type_of_water && type_of_water.trim() !== "") { + tankerQuery.typeofwater = type_of_water; + } + const tankers = await Tanker.find(tankerQuery).lean(); + + // Group tankers by supplier + const supplierTankerMap = {}; + for (const t of tankers) { + if (!supplierTankerMap[t.supplierId]) supplierTankerMap[t.supplierId] = []; + supplierTankerMap[t.supplierId].push(t); + } + + // Capacity check per supplier (capacity * quantity) + const 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; + } + qualified.push({ supplierId, tankers: supplierTankers }); + } + + // Fetch suppliers + connection flags + const supplierIds = qualified.map(q => q.supplierId); + const [suppliersData, acceptedReqs] = await Promise.all([ + Supplier.find({ supplierId: { $in: supplierIds } }).lean(), + FriendRequest.find( + { customerId, supplierId: { $in: supplierIds }, status: "accepted" }, + { supplierId: 1, _id: 0 } + ).lean() + ]); + 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 + 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 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, + tankers: q.tankers, + isConnected: friendRequestAccepted, + isFavorite, + requestedBooking + }); + } + + return reply.send({ status_code: 200, suppliers }); + } catch (err) { + console.error(err); + return reply.send({ + status_code: 500, + message: "Something went wrong", + error: err.message + }); + } +}; + + +// controllers/validationHandler.js (add below the previous handler) + +exports.createRequestedPlanBooking = async (req, reply) => { + const { + customerId, + type_of_water, + capacity, + quantity, + start_date, + end_date, + time, + frequency, // "daily" | "weekly_once" | "weekly_twice" | "weekly_thrice" | "weekly" + weekly_count, // used only if frequency === "weekly" + requested_suppliers + } = req.body; + + // helpers inside function (as you prefer) + const parseCapacity = (v) => parseFloat((v || "0").toString().replace(/,/g, "")) || 0; + const parseIntSafe = (v) => parseInt((v || "0").toString().replace(/,/g, ""), 10) || 0; + const toISODate = (d) => d.toISOString().slice(0, 10); + const mkUTCDate = (yyyy_mm_dd) => { + const [y, m, d] = (yyyy_mm_dd || "").split("-").map(Number); + return new Date(Date.UTC(y, (m || 1) - 1, d || 1)); + }; + const normalizeWeeklyCount = (freq, wc) => { + if (freq === "weekly_once") return 1; + if (freq === "weekly_twice") return 2; + if (freq === "weekly_thrice") return 3; + if (freq === "weekly") return wc || 1; + return 1; + }; + const computeWeeklyDOWs = ({ anchorDow, weeklyCount }) => { + if (weeklyCount === 1) return [anchorDow]; + if (weeklyCount === 2) return [anchorDow, (anchorDow + 3) % 7]; + if (weeklyCount === 3) return [anchorDow, (anchorDow + 2) % 7, (anchorDow + 4) % 7]; + return [anchorDow]; + }; + const generateDates = ({ frequency, start_date, end_date, weekly_count }) => { + const start = mkUTCDate(start_date); + const end = mkUTCDate(end_date); + if (isNaN(start) || isNaN(end)) throw new Error("Invalid start_date or end_date"); + if (end < start) throw new Error("end_date must be after or equal to start_date"); + + // ~3 months cap + const maxMs = 92 * 24 * 60 * 60 * 1000; + if ((end - start) > maxMs) throw new Error("Range exceeds 3 months"); + + const out = []; + if (frequency === "daily") { + for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) { + out.push(toISODate(d)); + } + return out; + } + + if (frequency.startsWith("weekly") || frequency === "weekly") { + const wc = normalizeWeeklyCount(frequency, weekly_count); + const dows = computeWeeklyDOWs({ anchorDow: start.getUTCDay(), weeklyCount: wc }); + const set = new Set(dows); + for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) { + if (set.has(d.getUTCDay())) out.push(toISODate(d)); + } + return out; + } + + throw new Error("Unsupported frequency"); + }; + + try { + if (!customerId || !type_of_water || !capacity || !quantity || + !start_date || !end_date || !time || !frequency || !requested_suppliers) { + return reply.code(400).send({ + status_code: 400, + message: "Missing required fields" + }); + } + + const cap = parseCapacity(capacity); + const qty = parseIntSafe(quantity); + const total_required_capacity = cap * qty; + + const dates = generateDates({ frequency, start_date, end_date, weekly_count }); + if (dates.length === 0) { + return reply.code(400).send({ status_code: 400, message: "No dates generated for given inputs" }); + } + + const doc = new RecurringRequestedBooking({ + customerId, + type_of_water, + capacity, + quantity, + total_required_capacity, + frequency, + weekly_count: normalizeWeeklyCount(frequency, weekly_count), + start_date, + end_date, + time, + dates, + requested_suppliers, + status: "pending" + }); + + await doc.save(); + + return reply.send({ + status_code: 200, + message: "Plan requested booking created successfully", + count: dates.length, + dates, + data: doc + }); + } catch (err) { + console.error(err); + return reply.code(500).send({ + status_code: 500, + message: "Something went wrong while saving", + error: err.message + }); + } +}; + diff --git a/src/models/supplier.js b/src/models/supplier.js index 24507c1b..f28208a5 100644 --- a/src/models/supplier.js +++ b/src/models/supplier.js @@ -179,8 +179,36 @@ const requestedBookingSchema = new mongoose.Schema({ status: { type: String, default: "pending" }, }, { timestamps: true }); +// models/RecurringRequestedBooking.js + +const requestedSupplier1Schema = new mongoose.Schema({ + supplierId: String, + quoted_amount: Number, + time: { type: String, default: null }, + status: { type: String, default: "pending" }, +}, { _id: false }); + +const recurringRequestedBookingSchema = new mongoose.Schema({ + customerId: { type: String, required: true }, + type_of_water: String, + capacity: String, + quantity: String, + total_required_capacity: Number, + frequency: { type: String, enum: ["daily","weekly_once","weekly_twice","weekly_thrice","weekly"], required: true }, + weekly_count: { type: Number, enum: [1,2,3] }, + start_date: { type: String, required: true }, + end_date: { type: String, required: true }, + time: String, + dates: [String], + requested_suppliers: [requestedSupplier1Schema], + status: { type: String, default: "pending" }, +}, { timestamps: true }); + + + const RequestedBooking = mongoose.model('RequestedBooking', requestedBookingSchema); +const RecurringRequestedBooking = mongoose.model("RecurringRequestedBooking", recurringRequestedBookingSchema); const Supplier = mongoose.model("Supplier", supplierSchema); //const DeliveryAgent = mongoose.model("DeliveryAgent", deliveryAgent); @@ -188,6 +216,6 @@ const FriendRequest = mongoose.model('FriendRequest', friendRequestSchema); const DeliveryBoy = mongoose.model('DeliveryBoy', deliveryBoySchema); const profilePictureSupplier = mongoose.model('ProfilePictureSupplier', profilePictureSupplierSchema); -module.exports = { Supplier, generateSupplierId, FriendRequest,DeliveryBoy, profilePictureSupplier,RequestedBooking} +module.exports = { Supplier, generateSupplierId, FriendRequest,DeliveryBoy, profilePictureSupplier,RequestedBooking,RecurringRequestedBooking} diff --git a/src/routes/supplierRoute.js b/src/routes/supplierRoute.js index f522c8f9..65cad2c1 100644 --- a/src/routes/supplierRoute.js +++ b/src/routes/supplierRoute.js @@ -160,6 +160,82 @@ fastify.post("/api/requestedbookings", { handler: validationHandler.getPendingSuppliers, }); + 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.", + params: { + type: "object", + required: ["customerId"], + properties: { customerId: { type: "string" } }, + }, + body: { + type: "object", + required: ["type_of_water", "capacity", "quantity", "frequency", "start_date", "end_date"], + properties: { + type_of_water: { type: "string" }, + capacity: { type: "string" }, // UI field + quantity: { type: "string" }, // UI field + frequency: { // UI field: daily/weekly_once/_twice/_thrice + 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" + }, + additionalProperties: false + }, + security: [{ basicAuth: [] }], + }, + handler: validationHandler.getSuppliersForPlanSearch, + }); + + fastify.post("/api/requestedplanbookings", { + schema: { + tags: ["Supplier-Data"], + summary: "Create plan requested booking (daily/weekly once|twice|thrice)", + body: { + type: "object", + required: [ + "customerId","type_of_water","capacity","quantity", + "start_date","end_date","time","frequency","requested_suppliers" + ], + properties: { + customerId: { type: "string" }, + type_of_water: { type: "string" }, + capacity: { type: "string" }, + quantity: { type: "string" }, + start_date: { type: "string" }, // "YYYY-MM-DD" + end_date: { type: "string" }, // "YYYY-MM-DD" + time: { type: "string" }, // "HH:mm" + frequency: { + type: "string", + enum: ["daily","weekly_once","weekly_twice","weekly_thrice","weekly"] + }, + weekly_count: { type: "integer", minimum: 1, maximum: 3 }, // only if frequency === "weekly" + requested_suppliers: { + type: "array", + minItems: 1, + items: { + type: "object", + required: ["supplierId","quoted_amount"], + properties: { + supplierId: { type: "string" }, + quoted_amount: { type: "number" }, + time: { type: "string" } + }, + additionalProperties: false + } + } + }, + additionalProperties: false + }, + security: [{ basicAuth: [] }] + }, + handler: validationHandler.createRequestedPlanBooking + }); + fastify.get("/api/rejectSuppliers/:customerId", { schema: { tags: ["Supplier-Data"],