From 1880763a8a91fa12588b09940fcf87bdae39d2c8 Mon Sep 17 00:00:00 2001 From: Varun Date: Wed, 10 Sep 2025 11:46:20 +0530 Subject: [PATCH 1/3] accept plans for supplier --- src/controllers/supplierController.js | 233 +++++++++++++++++++++++++- src/routes/supplierRoute.js | 33 +++- 2 files changed, 264 insertions(+), 2 deletions(-) diff --git a/src/controllers/supplierController.js b/src/controllers/supplierController.js index 827e2313..70e2d2ff 100644 --- a/src/controllers/supplierController.js +++ b/src/controllers/supplierController.js @@ -10,7 +10,7 @@ const saltRounds = 10; //Get the data models -const { RequestedBooking,Supplier , generateSupplierId, DeliveryBoy} = require('../models/supplier'); +const { RecurringRequestedBooking,RequestedBooking,Supplier , generateSupplierId, DeliveryBoy} = require('../models/supplier'); const { Tankerbooking} = require("../models/tankers") @@ -298,4 +298,235 @@ exports.respondToRequestedBooking = async (req, reply) => { } }; + +// controllers/supplier.controller.js +// const boom = require("@hapi/boom"); +// const mongoose = require("mongoose"); + +// // MODELS (adjust paths/names to your project) +// const RecurringRequestedBooking = require("../models/recurringRequestedBooking.model"); +// const TankerBooking = require("../models/tankerBooking.model"); + +// // Common party models you likely have in your DB: +// const Customer = require("../models/customer.model"); // e.g., { customerId, name, phone, address, latitude, longitude } +// const Supplier = require("../models/supplier.model"); // e.g., { supplierId, name, phone, tankerName, address, latitude, longitude } + +const parseNumber = (v, def = 0) => { + if (v === null || v === undefined) return def; + const n = parseFloat(String(v).replace(/,/g, "")); + return Number.isFinite(n) ? n : def; +}; +const mkBookingId = (prefix = "RBK") => { + const ts = new Date().toISOString().replace(/[-:TZ.]/g, "").slice(0, 14); + const rnd = Math.floor(Math.random() * 1e6).toString().padStart(6, "0"); + return `${prefix}-${ts}-${rnd}`; +}; +const isIsoYMD = (s) => /^\d{4}-\d{2}-\d{2}$/.test(s); + +// "2025-10-21" -> "21-Oct-2025" (to match your old saved sample) +const formatDDMonYYYY = (isoYmd) => { + if (!isIsoYMD(isoYmd)) return isoYmd; + const [y, m, d] = isoYmd.split("-").map(Number); + const mon = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][(m || 1) - 1]; + return `${String(d).padStart(2,"0")}-${mon}-${y}`; +}; + +exports.respondToRecurringRequestedBooking = async (req, reply) => { + const { _id } = req.params; + const { action, supplierId } = req.body; + + if (!mongoose.Types.ObjectId.isValid(_id)) { + return reply.code(400).send({ message: "Invalid recurring requested booking ID" }); + } + if (!["accept", "reject"].includes(action)) { + return reply.code(400).send({ message: "Action must be 'accept' or 'reject'" }); + } + + try { + const booking = await RecurringRequestedBooking.findById(_id); + if (!booking) { + return reply.code(404).send({ message: "Recurring requested booking not found" }); + } + + const supplierEntry = booking.requested_suppliers.find((s) => s.supplierId === supplierId); + if (!supplierEntry) { + return reply.code(404).send({ message: "Supplier not found in this booking" }); + } + + // Update supplier response on the recurring request + supplierEntry.status = action === "accept" ? "accepted" : "rejected"; + await booking.save(); + + if (action === "reject") { + return reply.code(200).send({ + status_code: 200, + message: "Recurring booking rejected by supplier successfully", + data: booking, + }); + } + + // ACCEPT: build per-date TankerBooking docs with rich fields + const allDates = Array.isArray(booking.dates) ? booking.dates.filter(isIsoYMD) : []; + if (!allDates.length) { + return reply.code(400).send({ message: "No valid ISO dates found in booking.dates" }); + } + + // Preload related party info to fill address/phones/names + const [customerDoc, supplierDoc] = await Promise.all([ + User.findOne({ customerId: booking.customerId }).lean(), + Supplier.findOne({ supplierId: supplierId }).lean(), + ]); + + // Pull commonly needed values (tolerant defaults to match legacy) + const customerPhone = customerDoc?.phone ?? null; + const supplierPhone = supplierDoc?.phone ?? null; + + const customerName = customerDoc?.username ?? customerDoc?.displayName ?? ""; + const supplierName = supplierDoc?.suppliername ?? supplierDoc?.companyName ?? ""; + + const tankerName = supplierDoc?.tankerName ?? null; + const tankName = null; // if you associate a tank per-customer, populate from your Tank model here + const tankLocation = null; // same as above + + // prefer customer address (your legacy sample stores a geocoded customer address) + const address = customerDoc?.profile.address1 ?? null; + const latitude = customerDoc?.latitude ?? null; + const longitude = customerDoc?.longitude ?? null; + + // price: from supplier’s quoted_amount in this request (fallback null) + const price = (supplierEntry?.quoted_amount ?? null) !== null + ? String(supplierEntry.quoted_amount) + : null; + + // numeric fields + const numericCapacity = parseNumber(booking.capacity); + const numericQuantity = parseNumber(booking.quantity); + const totalRequired = Number.isFinite(booking.total_required_capacity) + ? booking.total_required_capacity + : numericCapacity * numericQuantity; + + // dedupe check + const existing = await Tankerbooking.find( + { + customerId: booking.customerId, + supplierId: supplierId, + date: { $in: allDates }, + time: booking.time, // keep your stored time format intact + }, + { date: 1 } + ).lean(); + + const existingSet = new Set((existing || []).map(e => e.date)); + const newDates = allDates.filter(d => !existingSet.has(d)); + if (!newDates.length) { + return reply.code(200).send({ + status_code: 200, + message: "All dates already have bookings; nothing to create.", + data: { created: 0, skippedExistingDates: allDates }, + }); + } + + // ---------- BUILD ENRICHED DOCS (matches your legacy example fields) ---------- + const todayIso = new Date().toISOString().slice(0, 10); + + const docs = newDates.map((d) => ({ + // Required/IDs + customerId: booking.customerId, + supplierId: supplierId, + bookingid: mkBookingId("RBK"), + + // Legacy display & logistics + tankName, // null (fill if you link tank per-customer) + tankLocation, // null (fill if available) + tankerName, // from Supplier if present + + // Dates/times (kept both ISO & legacy formats as you showed) + dateOfOrder: todayIso, // "2025-09-10" + expectedDateOfDelivery: formatDDMonYYYY(d), // "21-Oct-2025" style (legacy sample) + date: d, // keep ISO in `date` too + time: booking.time, // keep your request time as-is + + // Water & capacity + type_of_water: booking.type_of_water, + typeofwater: booking.type_of_water, // legacy field name kept too + capacity: booking.capacity, // e.g., "100" or "10,000 L" + quantity: booking.quantity, // string + total_required_capacity: totalRequired, + + // Money / status + price: price, // from quoted_amount (string) or null + payment_status: "due", + orderStatus: "accepted", + + // Contacts & names + address: address, // from customer + customerPhone: customerPhone, + supplierPhone: supplierPhone, + customerName: customerName, + supplierName: supplierName, + + // Delivery defaults (match your legacy doc) + delivery_agent: "null", + delivery_agent_mobile: "null", + delivery_agent_alternative_mobile: "null", + + // Metering defaults + initial_water_level: "null", + final_water_level: "null", + start_time: "null", + stop_time: "null", + quantityDelivered: null, + + // Accounting defaults + amount_paid: null, + amount_due: null, + distrubance_price: "none", + amount_difference: "none", + payment_mode: null, + remarks: null, + + // Device/geo defaults + tankerRunningStatus: "0", + latitude: latitude ?? undefined, // keep same field names as your legacy doc + longitude: longitude ?? undefined, // if not available, omit field + + // Misc you already store + frequency: booking.frequency, + weekly_count: booking.weekly_count ?? 1, + deliveredDate: null, + distrubance_status: "0", + })); + // --------------------------------------------------------------------------- + + // Insert without transactions, tolerate duplicates if unique index exists + let insertedCount = 0; + let duplicateErrors = 0; + try { + const res = await Tankerbooking.collection.insertMany(docs, { ordered: false }); + insertedCount = res.insertedCount || 0; + } catch (e) { + if (e && e.writeErrors && Array.isArray(e.writeErrors)) { + insertedCount = e.result?.nInserted ?? 0; + duplicateErrors = e.writeErrors.length; + } else { + throw e; + } + } + + return reply.code(200).send({ + status_code: 200, + message: `Recurring booking accepted. Created ${insertedCount} tanker booking(s).`, + data: { + createdDates: newDates.slice(0, insertedCount), + skippedExistingDates: allDates.filter(d => existingSet.has(d)), + duplicateConflicts: duplicateErrors, + }, + }); + } catch (err) { + console.error(err); + throw boom.internal("Failed to update recurring supplier response", err); + } +}; + + \ No newline at end of file diff --git a/src/routes/supplierRoute.js b/src/routes/supplierRoute.js index 376eabe0..4d18c391 100644 --- a/src/routes/supplierRoute.js +++ b/src/routes/supplierRoute.js @@ -746,7 +746,38 @@ fastify.route({ handler: supplierController.respondToRequestedBooking }); - + fastify.route({ + method: "POST", + url: "/api/supplier/recurring/respond/:_id", + schema: { + description: + "Supplier accepts or rejects a recurring requested booking; on accept, creates bookings for each date in the stored 'dates' array.", + tags: ["Supplier-Data"], + summary: "Supplier action on recurring requested booking", + params: { + type: "object", + properties: { + _id: { type: "string", description: "Recurring Requested Booking ID" }, + }, + required: ["_id"], + }, + body: { + type: "object", + properties: { + supplierId: { type: "string", description: "Supplier ID" }, + action: { + type: "string", + enum: ["accept", "reject"], + description: "Action to perform by supplier", + }, + }, + required: ["supplierId", "action"], + }, + security: [{ basicAuth: [] }], + }, + // preHandler: fastify.auth([fastify.authenticate]), + handler: supplierController.respondToRecurringRequestedBooking, + }); next(); } From 2c38fb3a4fc39e9f8ff4c4650e8ef1f98bc2617b Mon Sep 17 00:00:00 2001 From: Varun Date: Wed, 10 Sep 2025 12:39:42 +0530 Subject: [PATCH 2/3] changes in fetch recurring --- src/controllers/userController.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/userController.js b/src/controllers/userController.js index 7ff9af9c..24ffcb87 100644 --- a/src/controllers/userController.js +++ b/src/controllers/userController.js @@ -1619,6 +1619,8 @@ exports.getuserRequestbookingsforplansforsupplier = async (req, reply) => { type_of_water: b.type_of_water, capacity: b.capacity, quantity: b.quantity, + frequency:b.frequency, + weekly_count:b.weekly_count, total_required_capacity: b.total_required_capacity, date: b.date, time: b.time, From e0b82e34229727eee74d7e3181b6a2c571f77ce2 Mon Sep 17 00:00:00 2001 From: Varun Date: Wed, 10 Sep 2025 13:05:19 +0530 Subject: [PATCH 3/3] changes --- src/controllers/supplierController.js | 476 +++++++++++++++++++++----- 1 file changed, 399 insertions(+), 77 deletions(-) diff --git a/src/controllers/supplierController.js b/src/controllers/supplierController.js index 70e2d2ff..67a090ed 100644 --- a/src/controllers/supplierController.js +++ b/src/controllers/supplierController.js @@ -311,6 +311,236 @@ exports.respondToRequestedBooking = async (req, reply) => { // const Customer = require("../models/customer.model"); // e.g., { customerId, name, phone, address, latitude, longitude } // const Supplier = require("../models/supplier.model"); // e.g., { supplierId, name, phone, tankerName, address, latitude, longitude } +// const parseNumber = (v, def = 0) => { +// if (v === null || v === undefined) return def; +// const n = parseFloat(String(v).replace(/,/g, "")); +// return Number.isFinite(n) ? n : def; +// }; +// const mkBookingId = (prefix = "RBK") => { +// const ts = new Date().toISOString().replace(/[-:TZ.]/g, "").slice(0, 14); +// const rnd = Math.floor(Math.random() * 1e6).toString().padStart(6, "0"); +// return `${prefix}-${ts}-${rnd}`; +// }; +// const isIsoYMD = (s) => /^\d{4}-\d{2}-\d{2}$/.test(s); + +// // "2025-10-21" -> "21-Oct-2025" (to match your old saved sample) +// const formatDDMonYYYY = (isoYmd) => { +// if (!isIsoYMD(isoYmd)) return isoYmd; +// const [y, m, d] = isoYmd.split("-").map(Number); +// const mon = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][(m || 1) - 1]; +// return `${String(d).padStart(2,"0")}-${mon}-${y}`; +// }; + +// exports.respondToRecurringRequestedBooking = async (req, reply) => { +// const { _id } = req.params; +// const { action, supplierId } = req.body; + +// if (!mongoose.Types.ObjectId.isValid(_id)) { +// return reply.code(400).send({ message: "Invalid recurring requested booking ID" }); +// } +// if (!["accept", "reject"].includes(action)) { +// return reply.code(400).send({ message: "Action must be 'accept' or 'reject'" }); +// } + +// try { +// const booking = await RecurringRequestedBooking.findById(_id); +// if (!booking) { +// return reply.code(404).send({ message: "Recurring requested booking not found" }); +// } + +// const supplierEntry = booking.requested_suppliers.find((s) => s.supplierId === supplierId); +// if (!supplierEntry) { +// return reply.code(404).send({ message: "Supplier not found in this booking" }); +// } + +// // Update supplier response on the recurring request +// supplierEntry.status = action === "accept" ? "accepted" : "rejected"; +// await booking.save(); + +// if (action === "reject") { +// return reply.code(200).send({ +// status_code: 200, +// message: "Recurring booking rejected by supplier successfully", +// data: booking, +// }); +// } + +// // ACCEPT: build per-date TankerBooking docs with rich fields +// const allDates = Array.isArray(booking.dates) ? booking.dates.filter(isIsoYMD) : []; +// if (!allDates.length) { +// return reply.code(400).send({ message: "No valid ISO dates found in booking.dates" }); +// } + +// // Preload related party info to fill address/phones/names +// const [customerDoc, supplierDoc] = await Promise.all([ +// User.findOne({ customerId: booking.customerId }).lean(), +// Supplier.findOne({ supplierId: supplierId }).lean(), +// ]); + +// // Pull commonly needed values (tolerant defaults to match legacy) +// const customerPhone = customerDoc?.phone ?? null; +// const supplierPhone = supplierDoc?.phone ?? null; + +// const customerName = customerDoc?.username ?? customerDoc?.displayName ?? ""; +// const supplierName = supplierDoc?.suppliername ?? supplierDoc?.companyName ?? ""; + +// const tankerName = supplierDoc?.tankerName ?? null; +// const tankName = null; // if you associate a tank per-customer, populate from your Tank model here +// const tankLocation = null; // same as above + +// // prefer customer address (your legacy sample stores a geocoded customer address) +// const address = customerDoc?.profile.address1 ?? null; +// const latitude = customerDoc?.latitude ?? null; +// const longitude = customerDoc?.longitude ?? null; + +// // price: from supplier’s quoted_amount in this request (fallback null) +// const price = (supplierEntry?.quoted_amount ?? null) !== null +// ? String(supplierEntry.quoted_amount) +// : null; + +// // numeric fields +// const numericCapacity = parseNumber(booking.capacity); +// const numericQuantity = parseNumber(booking.quantity); +// const totalRequired = Number.isFinite(booking.total_required_capacity) +// ? booking.total_required_capacity +// : numericCapacity * numericQuantity; + +// // dedupe check +// const existing = await Tankerbooking.find( +// { +// customerId: booking.customerId, +// supplierId: supplierId, +// date: { $in: allDates }, +// time: booking.time, // keep your stored time format intact +// }, +// { date: 1 } +// ).lean(); + +// const existingSet = new Set((existing || []).map(e => e.date)); +// const newDates = allDates.filter(d => !existingSet.has(d)); +// if (!newDates.length) { +// return reply.code(200).send({ +// status_code: 200, +// message: "All dates already have bookings; nothing to create.", +// data: { created: 0, skippedExistingDates: allDates }, +// }); +// } + +// // ---------- BUILD ENRICHED DOCS (matches your legacy example fields) ---------- +// const todayIso = new Date().toISOString().slice(0, 10); + +// const docs = newDates.map((d) => ({ +// // Required/IDs +// customerId: booking.customerId, +// supplierId: supplierId, +// bookingid: mkBookingId("RBK"), + +// // Legacy display & logistics +// tankName, // null (fill if you link tank per-customer) +// tankLocation, // null (fill if available) +// tankerName, // from Supplier if present + +// // Dates/times (kept both ISO & legacy formats as you showed) +// dateOfOrder: todayIso, // "2025-09-10" +// expectedDateOfDelivery: formatDDMonYYYY(d), // "21-Oct-2025" style (legacy sample) +// date: d, // keep ISO in `date` too +// time: booking.time, // keep your request time as-is + +// // Water & capacity +// type_of_water: booking.type_of_water, +// typeofwater: booking.type_of_water, // legacy field name kept too +// capacity: booking.capacity, // e.g., "100" or "10,000 L" +// quantity: booking.quantity, // string +// total_required_capacity: totalRequired, + +// // Money / status +// price: price, // from quoted_amount (string) or null +// payment_status: "due", +// orderStatus: "accepted", + +// // Contacts & names +// address: address, // from customer +// customerPhone: customerPhone, +// supplierPhone: supplierPhone, +// customerName: customerName, +// supplierName: supplierName, + +// // Delivery defaults (match your legacy doc) +// delivery_agent: "null", +// delivery_agent_mobile: "null", +// delivery_agent_alternative_mobile: "null", + +// // Metering defaults +// initial_water_level: "null", +// final_water_level: "null", +// start_time: "null", +// stop_time: "null", +// quantityDelivered: null, + +// // Accounting defaults +// amount_paid: null, +// amount_due: null, +// distrubance_price: "none", +// amount_difference: "none", +// payment_mode: null, +// remarks: null, + +// // Device/geo defaults +// tankerRunningStatus: "0", +// latitude: latitude ?? undefined, // keep same field names as your legacy doc +// longitude: longitude ?? undefined, // if not available, omit field + +// // Misc you already store +// frequency: booking.frequency, +// weekly_count: booking.weekly_count ?? 1, +// deliveredDate: null, +// distrubance_status: "0", +// })); +// // --------------------------------------------------------------------------- + +// // Insert without transactions, tolerate duplicates if unique index exists +// let insertedCount = 0; +// let duplicateErrors = 0; +// try { +// const res = await Tankerbooking.collection.insertMany(docs, { ordered: false }); +// insertedCount = res.insertedCount || 0; +// } catch (e) { +// if (e && e.writeErrors && Array.isArray(e.writeErrors)) { +// insertedCount = e.result?.nInserted ?? 0; +// duplicateErrors = e.writeErrors.length; +// } else { +// throw e; +// } +// } + +// return reply.code(200).send({ +// status_code: 200, +// message: `Recurring booking accepted. Created ${insertedCount} tanker booking(s).`, +// data: { +// createdDates: newDates.slice(0, insertedCount), +// skippedExistingDates: allDates.filter(d => existingSet.has(d)), +// duplicateConflicts: duplicateErrors, +// }, +// }); +// } catch (err) { +// console.error(err); +// throw boom.internal("Failed to update recurring supplier response", err); +// } +// }; + + + + +// controllers/supplier.controller.js (only the changed parts shown for brevity) +// const boom = require("@hapi/boom"); +// const mongoose = require("mongoose"); + +// const RecurringRequestedBooking = require("../models/recurringRequestedBooking.model"); +// const TankerBooking = require("../models/tankerBooking.model"); +// const Customer = require("../models/customer.model"); +// const Supplier = require("../models/supplier.model"); + +// ---------- helpers (IST + formatting) ---------- const parseNumber = (v, def = 0) => { if (v === null || v === undefined) return def; const n = parseFloat(String(v).replace(/,/g, "")); @@ -322,19 +552,135 @@ const mkBookingId = (prefix = "RBK") => { return `${prefix}-${ts}-${rnd}`; }; const isIsoYMD = (s) => /^\d{4}-\d{2}-\d{2}$/.test(s); +const MON = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; + +// Return a Date adjusted to IST (UTC+5:30) without changing the original instant +const toIST = (d = new Date()) => { + const utc = d.getTime() + (d.getTimezoneOffset() * 60000); + // IST = UTC + 5:30 + return new Date(utc + (5 * 60 + 30) * 60000); +}; -// "2025-10-21" -> "21-Oct-2025" (to match your old saved sample) -const formatDDMonYYYY = (isoYmd) => { - if (!isIsoYMD(isoYmd)) return isoYmd; +// Format Date -> "DD-Mon-YYYY - HH:MM" in IST +const fmtDDMonYYYY_HHMM = (dateObj) => { + const dd = String(dateObj.getDate()).padStart(2, "0"); + const mon = MON[dateObj.getMonth()]; + const yyyy = dateObj.getFullYear(); + const hh = String(dateObj.getHours()).padStart(2, "0"); + const mm = String(dateObj.getMinutes()).padStart(2, "0"); + return `${dd}-${mon}-${yyyy} - ${hh}:${mm}`; +}; + +// Extract "HH:MM" (24h) from arbitrary string like "21-Nov-2025 - 14:37" or "4:00 PM to 6:00 PM" +// If not found, default "00:00" +const extractHHMM = (timeStr) => { + if (!timeStr) return "00:00"; + // First try 24h "14:37" + const m24 = timeStr.match(/\b(\d{1,2}):(\d{2})\b/); + if (m24) { + const h = String(Math.min(23, parseInt(m24[1],10))).padStart(2,"0"); + const m = m24[2]; + return `${h}:${m}`; + } + // crude parse for "4:00 PM" → 16:00 + const ampm = timeStr.match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i); + if (ampm) { + let h = parseInt(ampm[1],10); + const m = ampm[2]; + const p = ampm[3].toUpperCase(); + if (p === "PM" && h !== 12) h += 12; + if (p === "AM" && h === 12) h = 0; + return `${String(h).padStart(2,"0")}:${m}`; + } + return "00:00"; +}; + +// Combine ISO date "YYYY-MM-DD" + a time string → IST "DD-Mon-YYYY - HH:MM" +const fmtFromISOAndTime = (isoYmd, timeStr) => { + if (!isIsoYMD(isoYmd)) return isoYmd; // fallback const [y, m, d] = isoYmd.split("-").map(Number); - const mon = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][(m || 1) - 1]; - return `${String(d).padStart(2,"0")}-${mon}-${y}`; + const [hh, mm] = extractHHMM(timeStr).split(":").map(Number); + // Construct a Date in UTC for that local time, then convert to IST display + const dt = new Date(Date.UTC(y, (m - 1), d, hh, mm, 0)); + // We only care about display in IST: + return fmtDDMonYYYY_HHMM(toIST(dt)); +}; +// ---- +// -------------------------------------------- + +// ---------- add these helpers near your other helpers ---------- +const to12h = (h24, m) => { + let h = h24 % 12 || 12; + const ampm = h24 >= 12 ? "PM" : "AM"; + return `${h}:${String(m).padStart(2,"0")} ${ampm}`; +}; + +// Extract first time from any string: supports "14:37", "4:00 PM", "21-Nov-2025 - 14:37", "4:00 PM to 6:00 PM" +const parseFirstTimeToHM = (timeStr) => { + if (!timeStr) return { h: 0, m: 0 }; + // Try explicit 12h with AM/PM + const ampm = timeStr.match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i); + if (ampm) { + let h = parseInt(ampm[1], 10); + const m = parseInt(ampm[2], 10); + const p = ampm[3].toUpperCase(); + if (p === "PM" && h !== 12) h += 12; + if (p === "AM" && h === 12) h = 0; + return { h, m }; + } + // Try any 24h HH:MM in the string + const m24 = timeStr.match(/\b(\d{1,2}):(\d{2})\b/); + if (m24) { + const h = Math.max(0, Math.min(23, parseInt(m24[1], 10))); + const m = Math.max(0, Math.min(59, parseInt(m24[2], 10))); + return { h, m }; + } + return { h: 0, m: 0 }; +}; + +// If already "X to Y" (case-insensitive), keep it. Otherwise, make a slot of `windowHours` starting at first parsed time. +const normalizeTimeForLegacy = (timeStr, windowHours = 2) => { + if (typeof timeStr === "string" && / to /i.test(timeStr)) { + return timeStr; // already in "4:00 PM to 6:00 PM" + } + const { h, m } = parseFirstTimeToHM(timeStr); + const start = to12h(h, m); + const endDate = new Date(Date.UTC(2000, 0, 1, h, m)); // dummy date, add hours + endDate.setUTCHours(endDate.getUTCHours() + windowHours); + const end = to12h(endDate.getUTCHours(), endDate.getUTCMinutes()); + return `${start} to ${end}`; +}; + +// ---- Booking ID helpers (IST-based) ---- +// Get YYYYMMDD in IST (UTC+05:30) +const getISTDatePart = (d = new Date()) => { + const utcMs = d.getTime() + d.getTimezoneOffset() * 60000; + const ist = new Date(utcMs + (5 * 60 + 30) * 60000); + const y = ist.getFullYear(); + const m = String(ist.getMonth() + 1).padStart(2, "0"); + const day = String(ist.getDate()).padStart(2, "0"); + return `${y}${m}${day}`; // YYYYMMDD +}; + +// Make ARM id with one random digit (0-9) +const mkArmBookingIdIST = () => `ARM${getISTDatePart()}${Math.floor(Math.random() * 10)}`; + +// Ensure uniqueness in DB (recommended since you may create many in one day) +const generateUniqueArmId = async () => { + // up to 20 tries with single digit; then fall back to two digits + for (let i = 0; i < 20; i++) { + const id = mkArmBookingIdIST(); + const exists = await Tankerbooking.exists({ bookingid: id }); + if (!exists) return id; + } + // fallback with two random digits to avoid collisions + const fallback = `ARM${getISTDatePart()}${Math.floor(Math.random()*10)}${Math.floor(Math.random()*10)}`; + return fallback; }; exports.respondToRecurringRequestedBooking = async (req, reply) => { const { _id } = req.params; const { action, supplierId } = req.body; - if (!mongoose.Types.ObjectId.isValid(_id)) { return reply.code(400).send({ message: "Invalid recurring requested booking ID" }); } @@ -344,16 +690,13 @@ exports.respondToRecurringRequestedBooking = async (req, reply) => { try { const booking = await RecurringRequestedBooking.findById(_id); - if (!booking) { - return reply.code(404).send({ message: "Recurring requested booking not found" }); - } + if (!booking) return reply.code(404).send({ message: "Recurring requested booking not found" }); - const supplierEntry = booking.requested_suppliers.find((s) => s.supplierId === supplierId); + const supplierEntry = booking.requested_suppliers.find(s => s.supplierId === supplierId); if (!supplierEntry) { return reply.code(404).send({ message: "Supplier not found in this booking" }); } - // Update supplier response on the recurring request supplierEntry.status = action === "accept" ? "accepted" : "rejected"; await booking.save(); @@ -365,57 +708,40 @@ exports.respondToRecurringRequestedBooking = async (req, reply) => { }); } - // ACCEPT: build per-date TankerBooking docs with rich fields const allDates = Array.isArray(booking.dates) ? booking.dates.filter(isIsoYMD) : []; if (!allDates.length) { return reply.code(400).send({ message: "No valid ISO dates found in booking.dates" }); } - // Preload related party info to fill address/phones/names const [customerDoc, supplierDoc] = await Promise.all([ User.findOne({ customerId: booking.customerId }).lean(), - Supplier.findOne({ supplierId: supplierId }).lean(), + Supplier.findOne({ supplierId }).lean(), ]); - // Pull commonly needed values (tolerant defaults to match legacy) const customerPhone = customerDoc?.phone ?? null; const supplierPhone = supplierDoc?.phone ?? null; - const customerName = customerDoc?.username ?? customerDoc?.displayName ?? ""; const supplierName = supplierDoc?.suppliername ?? supplierDoc?.companyName ?? ""; - const tankerName = supplierDoc?.tankerName ?? null; - const tankName = null; // if you associate a tank per-customer, populate from your Tank model here - const tankLocation = null; // same as above - - // prefer customer address (your legacy sample stores a geocoded customer address) const address = customerDoc?.profile.address1 ?? null; - const latitude = customerDoc?.latitude ?? null; - const longitude = customerDoc?.longitude ?? null; + const latitude = customerDoc?.latitude ?? undefined; + const longitude = customerDoc?.longitude ?? undefined; - // price: from supplier’s quoted_amount in this request (fallback null) - const price = (supplierEntry?.quoted_amount ?? null) !== null + const price = (supplierEntry?.quoted_amount ?? null) !== null ? String(supplierEntry.quoted_amount) : null; - // numeric fields const numericCapacity = parseNumber(booking.capacity); const numericQuantity = parseNumber(booking.quantity); - const totalRequired = Number.isFinite(booking.total_required_capacity) + const totalRequired = Number.isFinite(booking.total_required_capacity) ? booking.total_required_capacity : numericCapacity * numericQuantity; - // dedupe check + // dedupe by (customerId, supplierId, date, time) const existing = await Tankerbooking.find( - { - customerId: booking.customerId, - supplierId: supplierId, - date: { $in: allDates }, - time: booking.time, // keep your stored time format intact - }, + { customerId: booking.customerId, supplierId, date: { $in: allDates }, time: booking.time }, { date: 1 } ).lean(); - const existingSet = new Set((existing || []).map(e => e.date)); const newDates = allDates.filter(d => !existingSet.has(d)); if (!newDates.length) { @@ -425,59 +751,61 @@ exports.respondToRecurringRequestedBooking = async (req, reply) => { data: { created: 0, skippedExistingDates: allDates }, }); } - - // ---------- BUILD ENRICHED DOCS (matches your legacy example fields) ---------- - const todayIso = new Date().toISOString().slice(0, 10); - - const docs = newDates.map((d) => ({ - // Required/IDs + const legacyTime = normalizeTimeForLegacy(booking.time); + // --- FORMAT THESE THREE FIELDS EXACTLY AS LEGACY EXPECTS --- + const nowIST = toIST(new Date()); + const dateOfOrderFmt = fmtDDMonYYYY_HHMM(nowIST); // "DD-Mon-YYYY - HH:MM" + const today = new Date(); +const datePart = today.toISOString().slice(0, 10).replace(/-/g, ''); // YYYYMMDD +const randomDigit = Math.floor(Math.random() * 10); // 0–9 +const bookingId = `ARM${datePart}${randomDigit}`; + // ADD: pre-generate unique booking IDs (one per date) +const bookingIds = await Promise.all(newDates.map(() => generateUniqueArmId())); + + // ----------------------------------------------------------- + + const docs = newDates.map((d,i) => ({ + // IDs customerId: booking.customerId, - supplierId: supplierId, - bookingid: mkBookingId("RBK"), + supplierId, + bookingid: bookingIds[i], - // Legacy display & logistics - tankName, // null (fill if you link tank per-customer) - tankLocation, // null (fill if available) - tankerName, // from Supplier if present + // Legacy fields (match your old document) + tankName: null, + tankLocation: null, + tankerName, - // Dates/times (kept both ISO & legacy formats as you showed) - dateOfOrder: todayIso, // "2025-09-10" - expectedDateOfDelivery: formatDDMonYYYY(d), // "21-Oct-2025" style (legacy sample) - date: d, // keep ISO in `date` too - time: booking.time, // keep your request time as-is + dateOfOrder: dateOfOrderFmt, // e.g., "03-Sep-2025 - 13:25" + expectedDateOfDelivery: fmtFromISOAndTime(d, booking.time), // "DD-Mon-YYYY - HH:MM" + date: d, // keep ISO for backend logic + time: legacyTime, // keep whatever UI sent - // Water & capacity type_of_water: booking.type_of_water, - typeofwater: booking.type_of_water, // legacy field name kept too - capacity: booking.capacity, // e.g., "100" or "10,000 L" - quantity: booking.quantity, // string + typeofwater: booking.type_of_water, + capacity: booking.capacity, + quantity: booking.quantity, total_required_capacity: totalRequired, - // Money / status - price: price, // from quoted_amount (string) or null + price, payment_status: "due", orderStatus: "accepted", - // Contacts & names - address: address, // from customer - customerPhone: customerPhone, - supplierPhone: supplierPhone, - customerName: customerName, - supplierName: supplierName, + address, + customerPhone, + supplierPhone, + customerName, + supplierName, - // Delivery defaults (match your legacy doc) delivery_agent: "null", delivery_agent_mobile: "null", delivery_agent_alternative_mobile: "null", - // Metering defaults initial_water_level: "null", final_water_level: "null", start_time: "null", stop_time: "null", quantityDelivered: null, - // Accounting defaults amount_paid: null, amount_due: null, distrubance_price: "none", @@ -485,20 +813,17 @@ exports.respondToRecurringRequestedBooking = async (req, reply) => { payment_mode: null, remarks: null, - // Device/geo defaults tankerRunningStatus: "0", - latitude: latitude ?? undefined, // keep same field names as your legacy doc - longitude: longitude ?? undefined, // if not available, omit field + latitude, + longitude, - // Misc you already store frequency: booking.frequency, weekly_count: booking.weekly_count ?? 1, - deliveredDate: null, + deliveredDate: null, // new bookings: not delivered yet distrubance_status: "0", })); - // --------------------------------------------------------------------------- - // Insert without transactions, tolerate duplicates if unique index exists + // insert (no transactions, ordered:false) let insertedCount = 0; let duplicateErrors = 0; try { @@ -527,6 +852,3 @@ exports.respondToRecurringRequestedBooking = async (req, reply) => { throw boom.internal("Failed to update recurring supplier response", err); } }; - - - \ No newline at end of file