const fastify = require("fastify")({ logger: true, }); const boom = require("boom"); const customJwtAuth = require("../customAuthJwt"); const bcrypt = require("bcrypt"); const saltRounds = 10; //Get the data models const { RecurringRequestedBooking,RequestedBooking,Supplier , generateSupplierId, DeliveryBoy} = require('../models/supplier'); const { Tankerbooking} = require("../models/tankers") // Get Data Models const { User,Counter, generateBookingId,resetCounter,generateCustomerId,ProfilePicture, AddTeamMembers,Cart} = require('../models/User') async function bcryptPassword(password) { encryptedPwd = bcrypt.hash(password, saltRounds); return encryptedPwd; } async function bcryptComparePassword(pwd, encpassword) { isSame = bcrypt.compare(pwd, encpassword); return isSame; } //Supplier Login Controller exports.loginSupplier = async (req) => { try { const phone = req.body.phone; const password = req.body.password; const supplier = await Supplier.findOne({ phone: phone }); // compare supplier password with what is supplied if (supplier) { isSame = await bcryptComparePassword( password, supplier.services.password.bcrypt ); // if password supplied matches return object if (isSame) { return { same: true, supplier: supplier }; } else { return { same: false }; } } else { return { same: false }; } } catch (err) { throw boom.boomify(err); } }; //DeliveryBoy Login Controller exports.loginDeliveryBoy = async (req) => { try { const phone = req.body.phone; const delivery = await DeliveryBoy.findOne({ phone: phone }); if (delivery) { return { same: true, delivery: delivery }; } else { return { same: false }; } } catch (err) { throw boom.boomify(err); } }; exports.addSupplier = async (req, reply) => { try { // await resetCounter();//to set customer id back to 0 var s_id = await generateSupplierId() var building= ((req.body.suppliername).slice(0, 3)).toUpperCase(); var supplier_id = `AWSS${building}${s_id}` // console.log("This is the reply in the handler after the validations", reply); s_data = { supplierId: supplier_id, suppliername: req.body.suppliername, emails: req.body.emails, password: req.body.password, phone: req.body.phone, description: req.body.description, bussinessname: req.body.description, registration_number: req.body.description, years_in_business: req.body.description, profile: { firstName: req.body.firstName, lastName: req.body.lastName, contactNumber: req.body.phone, alternativeContactNumber: req.body.alternativeContactNumber, office_address: req.body.office_address, country: req.body.country, state: req.body.state, city: req.body.city, office_adress: req.body.office_adress, zip: req.body.zip, }, latitude: req.body.latitude, longitude: req.body.longitude, fcmId : req.body.fcmId }; var supplier = new Supplier(s_data); //password is not at the top level in the collection. supplierpass = req.body.password; // If fields are sent via form encoding , capture the fields and assign them to the supplier Object. checkFormEncoding = isSupplierFormUrlEncoded(req); if (checkFormEncoding.isSupplierFormUrlEncoded) { suppliertobeInserted = checkFormEncoding.supplier; console.log("thsi true url string"); supplier.suppliername = suppliertobeInserted.suppliername; supplier.firstName = suppliertobeInserted.firstName; supplier.lastName = suppliertobeInserted.lastName; supplier.phone = suppliertobeInserted.phone; supplier.emails = suppliertobeInserted.emails; supplier.passsword = suppliertobeInserted.password; supplier.supplierId = suppliertobeInserted.supplier_id; supplier.office_adress = suppliertobeInserted.office_adress; supplier.alternativeContactNumber = suppliertobeInserted.alternativeContactNumber; supplier.latitude = suppliertobeInserted.latitude; supplier.longitude = suppliertobeInserted.longitude; supplier.fcmId = suppliertobeInserted.fcmId supplier.description = suppliertobeInserted.description } console.log("---------checkurl ecnoded string-----------------------"); // Store hash in your password DB. hash = await bcryptPassword(supplierpass); if (hash) { supplier.services.password.bcrypt = hash; if (req.body.role) { supplier.profile.role = req.body.role; console.log("******************************************************"); console.log(supplier); } else { role = ["supplier"]; supplier.profile.role = role; } insertedSupplier = await supplier.save(); console.log(insertedSupplier); if (insertedSupplier) { // Prepare supplier object and wrap it inside the armintatankdata var retSupplier = { armintatankdata: { suppliername: insertedSupplier.suppliername, phone: insertedSupplier.phone, supplierId: insertedSupplier.supplierId, office_adress: insertedSupplier.office_adress, emails: [ { email: insertedSupplier.emails[0].email, }, ], profile: insertedSupplier.profile, latitude: insertedSupplier.latitude, longitude: insertedSupplier.longitude, fcmId : insertedSupplier.fcmId, description : insertedSupplier.description }, status_code: 200, }; return retSupplier; } } } catch (err) { throw boom.boomify(err); } }; exports.editCuurentSupplierInfo = async (req, reply) => { try { const { supplierId } = req.params; const supplierInfo = await Supplier.findOne({ supplierId: supplierId.toString() }); const updateData = req.body; if (updateData.firstName) supplierInfo.profile.firstName = updateData.firstName; if (updateData.lastName) supplierInfo.profile.lastName = updateData.lastName; if (updateData.suppliername) supplierInfo.suppliername = updateData.suppliername; if (updateData.phone) supplierInfo.profile.contactNumber = updateData.phone; if (updateData.office_address) supplierInfo.profile.office_address = updateData.office_address; if (updateData.alternativeContactNumber) supplierInfo.profile.alternativeContactNumber = updateData.alternativeContactNumber; if (updateData.city) supplierInfo.profile.city = updateData.city; if (updateData.state) supplierInfo.profile.state = updateData.state; if (updateData.country) supplierInfo.profile.country = updateData.country; if (updateData.zip) supplierInfo.profile.zip = updateData.zip; if (updateData.phone) supplierInfo.phone = updateData.phone; if (updateData.description) supplierInfo.description = updateData.description; if (updateData.startingPrice) supplierInfo.startingPrice = updateData.startingPrice; if (updateData.status) supplierInfo.status = updateData.status; if (updateData.emails) supplierInfo.emails = updateData.emails; console.log(supplierInfo.emails[0].email) if (updateData.role) supplierInfo.profile.role = updateData.role; if (updateData.phone) { const phoneNumber = updateData.phone //libphonenumberjs.parsePhoneNumber(updateData.phone); if (phoneNumber) { // access returned collection if (!phoneNumber) { //if (!phoneNumber.isValid()) { error = { armintatankdata: { error: true, code: 10002, message: "10002 - Phone # " + updateData.phone + " is not a valid phone number", }, }; req.body.regError = error; reply.status(406).send(error); } } } if (supplierInfo.phone == updateData.phone) { console.log("IF++++++++++++++="); supplierInfo.phone = updateData.phone; supplierInfo.phoneVerified = true; } else { console.log("Ilse++++++++++++++="); supplierInfo.phone = updateData.phone; supplierInfo.phoneVerified = false; } const supplier = await supplierInfo.save(); return supplier; } catch (err) { throw boom.boomify(err); } }; const mongoose = require('mongoose'); exports.respondToRequestedBooking = 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 requested booking ID" }); } if (!["accept", "reject"].includes(action)) { return reply.code(400).send({ message: "Action must be 'accept' or 'reject'" }); } try { const booking = await RequestedBooking.findById(_id); if (!booking) { return reply.code(404).send({ message: "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 custom_field (status) for that supplier supplierEntry.status = action === "accept" ? "accepted_by_supplier" : "rejected_by_supplier"; await booking.save(); return reply.code(200).send({ status_code: 200, message: `Booking ${action}ed by supplier successfully`, data: booking }); } catch (err) { console.error(err); throw boom.internal("Failed to update supplier response", err); } }; // 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); // } // }; // 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, "")); 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); 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); }; // 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 [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" }); } 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" }); } supplierEntry.status = action === "accept" ? "accepted" : "rejected"; await booking.save(); // 🔽 ADD THIS const allAccepted = booking.requested_suppliers.every(s => s.status === "accepted"); if (allAccepted) { booking.status = "processed"; await booking.save(); } if (action === "reject") { return reply.code(200).send({ status_code: 200, message: "Recurring booking rejected by supplier successfully", data: booking, }); } 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" }); } const [customerDoc, supplierDoc] = await Promise.all([ User.findOne({ customerId: booking.customerId }).lean(), Supplier.findOne({ supplierId }).lean(), ]); 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 address = customerDoc?.profile.address1 ?? null; const latitude = customerDoc?.latitude ?? undefined; const longitude = customerDoc?.longitude ?? undefined; const price = (supplierEntry?.quoted_amount ?? null) !== null ? String(supplierEntry.quoted_amount) : null; 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 by (customerId, supplierId, date, time) const existing = await Tankerbooking.find( { 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) { return reply.code(200).send({ status_code: 200, message: "All dates already have bookings; nothing to create.", data: { created: 0, skippedExistingDates: allDates }, }); } 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, bookingid: bookingIds[i], // Legacy fields (match your old document) tankName: null, tankLocation: null, tankerName, 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 type_of_water: booking.type_of_water, typeofwater: booking.type_of_water, capacity: booking.capacity, quantity: booking.quantity, total_required_capacity: totalRequired, price, payment_status: "due", orderStatus: "accepted", address, customerPhone, supplierPhone, customerName, supplierName, delivery_agent: "null", delivery_agent_mobile: "null", delivery_agent_alternative_mobile: "null", initial_water_level: "null", final_water_level: "null", start_time: "null", stop_time: "null", quantityDelivered: null, amount_paid: null, amount_due: null, distrubance_price: "none", amount_difference: "none", payment_mode: null, remarks: null, tankerRunningStatus: "0", latitude, longitude, frequency: booking.frequency, weekly_count: booking.weekly_count ?? 1, deliveredDate: null, // new bookings: not delivered yet distrubance_status: "0", })); // insert (no transactions, ordered:false) 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); } };