diff --git a/src/controllers/installationController.js b/src/controllers/installationController.js index 4efde35b..3aa0831c 100644 --- a/src/controllers/installationController.js +++ b/src/controllers/installationController.js @@ -8392,6 +8392,256 @@ exports.getLongTermCustomerDetails = async (req, reply) => { } }; +exports.powerOutageCustomerDetails = async (req, reply) => { + try { + const { supportId } = req.params; + + if (!supportId) { + return reply.code(400).send({ error: "supportId is required" }); + } + + const supportRecord = await Support.findOne({ supportId }).lean(); + if (!supportRecord) { + return reply.code(404).send({ message: "No support record found for this supportId" }); + } + + const categorizedIssues = supportRecord.categorizedIssues || []; + const longTermIssues = categorizedIssues.filter(issue => issue.category === "Power Outage"); + + if (!longTermIssues.length) { + return reply.code(404).send({ message: "No Long Term Issues to process" }); + } + + const hardwareIds = []; + for (const issue of longTermIssues) { + if (issue.hardwareId) hardwareIds.push(issue.hardwareId); + if (Array.isArray(issue.hardwareIds)) hardwareIds.push(...issue.hardwareIds); + } + + if (!hardwareIds.length) { + return reply.code(404).send({ message: "No hardware IDs in Long Term Issues" }); + } + + // Fetch related sensors to get customerId + const sensors = await Insensors.find({ + $or: [ + { hardwareId: { $in: hardwareIds } }, + { connected_to: { $in: hardwareIds } } + ] + }).lean(); + + if (!sensors.length) { + return reply.code(404).send({ message: "No sensors found for Long Term hardware" }); + } + + const customerIds = [...new Set(sensors.map(s => s.customerId))]; + + const customers = await User.find({ customerId: { $in: customerIds } }).lean(); + + // Build map of movedAt per hardwareId + const movedAtMap = {}; + for (const issue of longTermIssues) { + const movedAt = issue.movedAt; + if (issue.hardwareId) movedAtMap[issue.hardwareId] = movedAt; + if (Array.isArray(issue.hardwareIds)) { + issue.hardwareIds.forEach(hid => { + movedAtMap[hid] = movedAt; + }); + } + } + + const uniqueCustomerMap = {}; + + for (const user of customers) { + const cid = user.customerId; + + if (!uniqueCustomerMap[cid]) { + const customerSensorHardwareIds = sensors + .filter(s => s.customerId === cid) + .map(s => s.hardwareId || s.tankhardwareId || s.connected_to) + .filter(Boolean); + + const movedTimes = customerSensorHardwareIds + .map(hid => movedAtMap[hid]) + .filter(Boolean) + .sort((a, b) => new Date(b) - new Date(a)); // Descending + + uniqueCustomerMap[cid] = { + customer: { + customerId: cid, + username: user.username || "", + firstName: user.profile?.firstName || user.firstName || "", + lastName: user.profile?.lastName || user.lastName || "", + phone: user.phone || user.profile?.contactNumber || user.alternativeNumber || "", + email: user.emails?.[0]?.email || user.email || "", + phoneVerified: user.phoneVerified || false, + address1: user.profile?.address1 || user.address1 || "", + address2: user.profile?.address2 || user.address2 || "", + city: user.profile?.city || user.city || "", + state: user.profile?.state || user.state || "", + country: user.profile?.country || user.country || "", + zip: user.profile?.zip || "", + notes: user.profile?.notes || "", + latitude: user.latitude || 0, + longitude: user.longitude || 0, + fcmIds: (user.fcmIds || []).filter(fcm => typeof fcm === "string" && fcm.startsWith("d")), + installationId: user.installationId || "", + notificationPreferences: { + allowNotifications: user.allowNotifications || false, + automaticStartAndStopNotify: user.automaticStartAndStopNotify || false, + manualStartAndStopNotify: user.manualStartAndStopNotify || false, + criticalLowWaterAlert: user.criticalLowWaterAlert || false, + lowWaterAlert: user.lowWaterAlert || false, + notificationPreference: user.notificationPreference || "never" + }, + surveyStatus: user.survey_status || "pending", + buildingName: user.buildingName || "", + stripePaymentStatus: user.stripePaymentStatus || false, + stripeSubscriptionStatus: user.stripeSubscriptionStatus || false, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + movedAt: movedTimes[0] || null // ⏳ Latest movedAt + } + }; + } + } + + return reply.send({ + status_code: 200, + data: Object.values(uniqueCustomerMap) + }); + + } catch (error) { + console.error("❌ Error in getLongTermCustomerDetails:", error); + return reply.code(500).send({ error: "Internal server error" }); + } +}; + +exports.outDoorEscalationCustomerDetails = async (req, reply) => { + try { + const { supportId } = req.params; + + if (!supportId) { + return reply.code(400).send({ error: "supportId is required" }); + } + + const supportRecord = await Support.findOne({ supportId }).lean(); + if (!supportRecord) { + return reply.code(404).send({ message: "No support record found for this supportId" }); + } + + const categorizedIssues = supportRecord.categorizedIssues || []; + const longTermIssues = categorizedIssues.filter(issue => issue.category === "OutDoor Escalation"); + + if (!longTermIssues.length) { + return reply.code(404).send({ message: "No Long Term Issues to process" }); + } + + const hardwareIds = []; + for (const issue of longTermIssues) { + if (issue.hardwareId) hardwareIds.push(issue.hardwareId); + if (Array.isArray(issue.hardwareIds)) hardwareIds.push(...issue.hardwareIds); + } + + if (!hardwareIds.length) { + return reply.code(404).send({ message: "No hardware IDs in Long Term Issues" }); + } + + // Fetch related sensors to get customerId + const sensors = await Insensors.find({ + $or: [ + { hardwareId: { $in: hardwareIds } }, + { connected_to: { $in: hardwareIds } } + ] + }).lean(); + + if (!sensors.length) { + return reply.code(404).send({ message: "No sensors found for Long Term hardware" }); + } + + const customerIds = [...new Set(sensors.map(s => s.customerId))]; + + const customers = await User.find({ customerId: { $in: customerIds } }).lean(); + + // Build map of movedAt per hardwareId + const movedAtMap = {}; + for (const issue of longTermIssues) { + const movedAt = issue.movedAt; + if (issue.hardwareId) movedAtMap[issue.hardwareId] = movedAt; + if (Array.isArray(issue.hardwareIds)) { + issue.hardwareIds.forEach(hid => { + movedAtMap[hid] = movedAt; + }); + } + } + + const uniqueCustomerMap = {}; + + for (const user of customers) { + const cid = user.customerId; + + if (!uniqueCustomerMap[cid]) { + const customerSensorHardwareIds = sensors + .filter(s => s.customerId === cid) + .map(s => s.hardwareId || s.tankhardwareId || s.connected_to) + .filter(Boolean); + + const movedTimes = customerSensorHardwareIds + .map(hid => movedAtMap[hid]) + .filter(Boolean) + .sort((a, b) => new Date(b) - new Date(a)); // Descending + + uniqueCustomerMap[cid] = { + customer: { + customerId: cid, + username: user.username || "", + firstName: user.profile?.firstName || user.firstName || "", + lastName: user.profile?.lastName || user.lastName || "", + phone: user.phone || user.profile?.contactNumber || user.alternativeNumber || "", + email: user.emails?.[0]?.email || user.email || "", + phoneVerified: user.phoneVerified || false, + address1: user.profile?.address1 || user.address1 || "", + address2: user.profile?.address2 || user.address2 || "", + city: user.profile?.city || user.city || "", + state: user.profile?.state || user.state || "", + country: user.profile?.country || user.country || "", + zip: user.profile?.zip || "", + notes: user.profile?.notes || "", + latitude: user.latitude || 0, + longitude: user.longitude || 0, + fcmIds: (user.fcmIds || []).filter(fcm => typeof fcm === "string" && fcm.startsWith("d")), + installationId: user.installationId || "", + notificationPreferences: { + allowNotifications: user.allowNotifications || false, + automaticStartAndStopNotify: user.automaticStartAndStopNotify || false, + manualStartAndStopNotify: user.manualStartAndStopNotify || false, + criticalLowWaterAlert: user.criticalLowWaterAlert || false, + lowWaterAlert: user.lowWaterAlert || false, + notificationPreference: user.notificationPreference || "never" + }, + surveyStatus: user.survey_status || "pending", + buildingName: user.buildingName || "", + stripePaymentStatus: user.stripePaymentStatus || false, + stripeSubscriptionStatus: user.stripeSubscriptionStatus || false, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + movedAt: movedTimes[0] || null // ⏳ Latest movedAt + } + }; + } + } + + return reply.send({ + status_code: 200, + data: Object.values(uniqueCustomerMap) + }); + + } catch (error) { + console.error("❌ Error in getLongTermCustomerDetails:", error); + return reply.code(500).send({ error: "Internal server error" }); + } +}; + exports.getLongTermIssuesByCustomer = async (req, reply) => { try { const { supportId, customerId } = req.params; @@ -8540,6 +8790,301 @@ exports.getLongTermIssuesByCustomer = async (req, reply) => { } }; +exports.getPowerOutageIssuesByCustomer = async (req, reply) => { + try { + const { supportId, customerId } = req.params; + + if (!supportId || !customerId) { + return reply.code(400).send({ error: "supportId and customerId are required in path params" }); + } + + const support = await Support.findOne({ supportId }).lean(); + if (!support) return reply.code(404).send({ message: "Support record not found" }); + + const longTermIssues = (support.categorizedIssues || []).filter(i => i.category === "Power Outage"); + if (!longTermIssues.length) return reply.code(404).send({ message: "No Long Term Issues found" }); + + const hardwareIds = [...new Set(longTermIssues.map(i => i.hardwareId).filter(Boolean))]; + const sensors = await Insensors.find({ + customerId, + hardwareId: { $in: hardwareIds } + }).lean(); + + if (!sensors.length) return reply.code(404).send({ message: "No sensors found for this customer" }); + + const orders = await Order.find({ customerId }).lean(); + const orderMap = {}; + for (const order of orders) { + for (const conn of order.master_connections || []) { + const trimmedId = (conn.hardwareId || "").trim(); + if (trimmedId) { + orderMap[trimmedId] = { + masterName: conn.master_name?.trim() || "", + location: conn.location?.trim() || "" + }; + } + } + } + + const issueMap = {}; + for (const issue of longTermIssues) { + issueMap[issue.hardwareId] = issue; + } + + const disconnectedIssues = []; + for (const master of sensors.filter(s => s.type === "master")) { + const slaves = await Insensors.find({ connected_to: master.hardwareId, customerId }).lean(); + const latestIotData = await IotData.findOne({ hardwareId: master.hardwareId }).sort({ date: -1 }).lean(); + const now = moment.tz("Asia/Kolkata"); + + let gsmConnected = false; + if (latestIotData?.date) { + const gsmTime = moment.tz(latestIotData.date, "Asia/Kolkata"); + gsmConnected = now.diff(gsmTime, "minutes") <= 1; + } + + const slaveDetails = await Promise.all(slaves.map(async (slave) => { + const slaveHardwareId = slave.tankhardwareId?.trim(); + const matchedTank = latestIotData?.tanks?.find(tank => tank.tankhardwareId === slaveHardwareId); + + let loraConnected = false; + if (matchedTank?.date && matchedTank?.tankHeight !== "0") { + const tankTime = moment.tz(matchedTank.date, "Asia/Kolkata"); + loraConnected = now.diff(tankTime, "minutes") <= 1; + } + + const tankInfo = await Tank.findOne({ + $or: [ + { hardwareId: slaveHardwareId }, + { tankhardwareId: slaveHardwareId } + ] + }).lean(); + + const slaveComments = (support.comments || []).filter( + comment => comment.hardwareId === slave.hardwareId && comment.customerId === customerId + ).map(c => ({ + text: c.text, + commentsTime: c.createdAt ? moment(c.createdAt).tz("Asia/Kolkata").format("DD-MM-YYYY HH:mm") : null + })); + + return { + hardwareId: slave.tankhardwareId, + tankName: slave.tankName || "", + location: slave.tankLocation || "", + connected_status: loraConnected ? "connected" : "disconnected", + connected_to: slave.connected_to || "", + gsm_last_check_time: slave.gsm_last_check_time || null, + gsm_last_disconnect_time: slave.gsm_last_disconnect_time || null, + lora_last_disconnect_time: slave.lora_last_disconnect_time || null, + connected_gsm_date: slave.connected_gsm_date || "", + connected_gsm_time: slave.connected_gsm_time || "", + connected_lora_date: slave.connected_lora_date || "", + connected_lora_time: slave.connected_lora_time || "", + support_lora_last_check_time: slave.support_lora_last_check_time || null, + masterName: orderMap[master.hardwareId?.trim()]?.masterName || "", + type: "slave", + typeOfWater: tankInfo?.typeOfWater || "", + outDoor_status: slave.outDoor_status || "inprogress" + }; + })); + + const masterComments = (support.comments || []).filter( + comment => comment.hardwareId === master.hardwareId && comment.customerId === customerId + ).map(c => ({ + text: c.text, + commentsTime: c.createdAt ? moment(c.createdAt).tz("Asia/Kolkata").format("DD-MM-YYYY HH:mm") : null + })); + + const orderDetails = orderMap[master.hardwareId?.trim()] || {}; + const issue = issueMap[master.hardwareId]; + + disconnectedIssues.push({ + hardwareId: master.hardwareId, + masterName: orderDetails.masterName || "", + location: orderDetails.location || "", + type: "master", + connected_status: gsmConnected ? "connected" : "disconnected", + connected_slave_count: slaveDetails.length, + gsm_last_check_time: master.gsm_last_check_time || null, + gsm_last_disconnect_time: master.gsm_last_disconnect_time || null, + lora_last_disconnect_time: master.lora_last_disconnect_time || null, + connected_gsm_date: master.connected_gsm_date || "", + connected_gsm_time: master.connected_gsm_time || "", + connected_lora_date: master.connected_lora_date || "", + connected_lora_time: master.connected_lora_time || "", + support_gm_last_check_time: master.support_gsm_last_check_time || null, + connected_slaves: slaveDetails, + comments: masterComments, + outDoor_status: master.outDoor_status || "inprogress", + movedAt: issue?.movedAt || null, + resolvedAt: issue?.resolvedAt || null, + category: issue?.category || "Uncategorized", + hardwareList: master.hardwareList || {}, + assignedTo: issue?.assignedTo || null + }); + } + + return reply.send({ + status_code: 200, + supportId, + customerId, + totalMasters: disconnectedIssues.length, + disconnectedIssues + }); + + } catch (err) { + console.error("❌ Error in getLongTermIssuesByCustomer:", err); + return reply.code(500).send({ error: "Internal server error" }); + } +}; + +exports.getOutDoorEscalationIssuesByCustomer = async (req, reply) => { + try { + const { supportId, customerId } = req.params; + + if (!supportId || !customerId) { + return reply.code(400).send({ error: "supportId and customerId are required in path params" }); + } + + const support = await Support.findOne({ supportId }).lean(); + if (!support) return reply.code(404).send({ message: "Support record not found" }); + + const longTermIssues = (support.categorizedIssues || []).filter(i => i.category === "OutDoor Escalation"); + if (!longTermIssues.length) return reply.code(404).send({ message: "No Long Term Issues found" }); + + const hardwareIds = [...new Set(longTermIssues.map(i => i.hardwareId).filter(Boolean))]; + const sensors = await Insensors.find({ + customerId, + hardwareId: { $in: hardwareIds } + }).lean(); + + if (!sensors.length) return reply.code(404).send({ message: "No sensors found for this customer" }); + + const orders = await Order.find({ customerId }).lean(); + const orderMap = {}; + for (const order of orders) { + for (const conn of order.master_connections || []) { + const trimmedId = (conn.hardwareId || "").trim(); + if (trimmedId) { + orderMap[trimmedId] = { + masterName: conn.master_name?.trim() || "", + location: conn.location?.trim() || "" + }; + } + } + } + + const issueMap = {}; + for (const issue of longTermIssues) { + issueMap[issue.hardwareId] = issue; + } + + const disconnectedIssues = []; + for (const master of sensors.filter(s => s.type === "master")) { + const slaves = await Insensors.find({ connected_to: master.hardwareId, customerId }).lean(); + const latestIotData = await IotData.findOne({ hardwareId: master.hardwareId }).sort({ date: -1 }).lean(); + const now = moment.tz("Asia/Kolkata"); + + let gsmConnected = false; + if (latestIotData?.date) { + const gsmTime = moment.tz(latestIotData.date, "Asia/Kolkata"); + gsmConnected = now.diff(gsmTime, "minutes") <= 1; + } + + const slaveDetails = await Promise.all(slaves.map(async (slave) => { + const slaveHardwareId = slave.tankhardwareId?.trim(); + const matchedTank = latestIotData?.tanks?.find(tank => tank.tankhardwareId === slaveHardwareId); + + let loraConnected = false; + if (matchedTank?.date && matchedTank?.tankHeight !== "0") { + const tankTime = moment.tz(matchedTank.date, "Asia/Kolkata"); + loraConnected = now.diff(tankTime, "minutes") <= 1; + } + + const tankInfo = await Tank.findOne({ + $or: [ + { hardwareId: slaveHardwareId }, + { tankhardwareId: slaveHardwareId } + ] + }).lean(); + + const slaveComments = (support.comments || []).filter( + comment => comment.hardwareId === slave.hardwareId && comment.customerId === customerId + ).map(c => ({ + text: c.text, + commentsTime: c.createdAt ? moment(c.createdAt).tz("Asia/Kolkata").format("DD-MM-YYYY HH:mm") : null + })); + + return { + hardwareId: slave.tankhardwareId, + tankName: slave.tankName || "", + location: slave.tankLocation || "", + connected_status: loraConnected ? "connected" : "disconnected", + connected_to: slave.connected_to || "", + gsm_last_check_time: slave.gsm_last_check_time || null, + gsm_last_disconnect_time: slave.gsm_last_disconnect_time || null, + lora_last_disconnect_time: slave.lora_last_disconnect_time || null, + connected_gsm_date: slave.connected_gsm_date || "", + connected_gsm_time: slave.connected_gsm_time || "", + connected_lora_date: slave.connected_lora_date || "", + connected_lora_time: slave.connected_lora_time || "", + support_lora_last_check_time: slave.support_lora_last_check_time || null, + masterName: orderMap[master.hardwareId?.trim()]?.masterName || "", + type: "slave", + typeOfWater: tankInfo?.typeOfWater || "", + outDoor_status: slave.outDoor_status || "inprogress" + }; + })); + + const masterComments = (support.comments || []).filter( + comment => comment.hardwareId === master.hardwareId && comment.customerId === customerId + ).map(c => ({ + text: c.text, + commentsTime: c.createdAt ? moment(c.createdAt).tz("Asia/Kolkata").format("DD-MM-YYYY HH:mm") : null + })); + + const orderDetails = orderMap[master.hardwareId?.trim()] || {}; + const issue = issueMap[master.hardwareId]; + + disconnectedIssues.push({ + hardwareId: master.hardwareId, + masterName: orderDetails.masterName || "", + location: orderDetails.location || "", + type: "master", + connected_status: gsmConnected ? "connected" : "disconnected", + connected_slave_count: slaveDetails.length, + gsm_last_check_time: master.gsm_last_check_time || null, + gsm_last_disconnect_time: master.gsm_last_disconnect_time || null, + lora_last_disconnect_time: master.lora_last_disconnect_time || null, + connected_gsm_date: master.connected_gsm_date || "", + connected_gsm_time: master.connected_gsm_time || "", + connected_lora_date: master.connected_lora_date || "", + connected_lora_time: master.connected_lora_time || "", + support_gm_last_check_time: master.support_gsm_last_check_time || null, + connected_slaves: slaveDetails, + comments: masterComments, + outDoor_status: master.outDoor_status || "inprogress", + movedAt: issue?.movedAt || null, + resolvedAt: issue?.resolvedAt || null, + category: issue?.category || "Uncategorized", + hardwareList: master.hardwareList || {}, + assignedTo: issue?.assignedTo || null + }); + } + + return reply.send({ + status_code: 200, + supportId, + customerId, + totalMasters: disconnectedIssues.length, + disconnectedIssues + }); + + } catch (err) { + console.error("❌ Error in getLongTermIssuesByCustomer:", err); + return reply.code(500).send({ error: "Internal server error" }); + } +}; // const bcrypt = require("bcrypt"); diff --git a/src/routes/installationRoute.js b/src/routes/installationRoute.js index b74faa88..5dbc8b51 100644 --- a/src/routes/installationRoute.js +++ b/src/routes/installationRoute.js @@ -707,11 +707,45 @@ module.exports = function (fastify, opts, next) { handler: installationController.getLongTermCustomerDetails, }); - fastify.get("/api/longTermissusesmasterslavedetails/:supportId/:customerId", { + fastify.get("/api/outDoorEscalationissusesbuildingdetails/:supportId", { schema: { - description: "Long Term Issues the master and slave details move for Support", + description: "Out Door Escalation Issues the ticket Get building details move for Support", tags: ["Support"], - summary: "Long Term Issues the master and slave details move for Support", + summary: "Out Door Escalation Issues the ticket Get building details move for Support", + params: { + type: "object", + properties: { + supportId: { type: "string" }, + + }, + required: [ "supportId"], + }, + }, + handler: installationController.outDoorEscalationCustomerDetails, + }); + + fastify.get("/api/powerOutageissusesbuildingdetails/:supportId", { + schema: { + description: "Power Outage Issues the ticket Get building details move for Support", + tags: ["Support"], + summary: "Power Outage Issues the ticket Get building details move for Support", + params: { + type: "object", + properties: { + supportId: { type: "string" }, + + }, + required: [ "supportId"], + }, + }, + handler: installationController.powerOutageCustomerDetails, + }); + + fastify.get("/api/powerOutageissusesmasterslavedetails/:supportId/:customerId", { + schema: { + description: "Power Outgae Issues the master and slave details move for Support", + tags: ["Support"], + summary: "Power Outage Issues the master and slave details move for Support", params: { type: "object", properties: { @@ -725,6 +759,25 @@ module.exports = function (fastify, opts, next) { }, handler: installationController.getLongTermIssuesByCustomer, }); + + fastify.get("/api/outDoorEscalationissusesmasterslavedetails/:supportId/:customerId", { + schema: { + description: "Out Door Escalation Issues the master and slave details move for Support", + tags: ["Support"], + summary: "Out Door Escalation Issues the master and slave details move for Support", + params: { + type: "object", + properties: { + supportId: { type: "string" }, + customerId: { type: "string" }, + + + }, + required: [ "supportId"], + }, + }, + handler: installationController.getOutDoorEscalationIssuesByCustomer, + }); fastify.route({ method: 'POST', url: '/api/supportCreateTeamMember/:supportId',