You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

863 lines
31 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

const fastify = require("fastify")({
logger: true,
});
const boom = require("boom");
const customJwtAuth = require("../customAuthJwt");
//const bcrypt = require("bcrypt");
const bcrypt = require('bcryptjs');
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 suppliers 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); // 09
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);
}
};