|
|
@ -1,6 +1,6 @@
|
|
|
|
//Get the data models
|
|
|
|
//Get the data models
|
|
|
|
const { Supplier, DeliveryBoy, profilePictureSupplier } = require("../models/supplier");
|
|
|
|
const { Supplier, DeliveryBoy, profilePictureSupplier } = require("../models/supplier");
|
|
|
|
const { FriendRequest,RequestedBooking } = require("../models/supplier");
|
|
|
|
const { FriendRequest,RequestedBooking,RecurringRequestedBooking } = require("../models/supplier");
|
|
|
|
const { Tanker,Tankerbooking } = require("../models/tankers");
|
|
|
|
const { Tanker,Tankerbooking } = require("../models/tankers");
|
|
|
|
const { ProfilePicture, User } = require("../models/User");
|
|
|
|
const { ProfilePicture, User } = require("../models/User");
|
|
|
|
const supplierController = require("../controllers/supplierController");
|
|
|
|
const supplierController = require("../controllers/supplierController");
|
|
|
@ -1920,3 +1920,221 @@ fastify.get('/api/users/profile-picture-supplier/:supplierId', async (req, res)
|
|
|
|
res.status(500).send({ error: error.message });
|
|
|
|
res.status(500).send({ error: error.message });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.getSuppliersForPlanSearch = async (req, reply) => {
|
|
|
|
|
|
|
|
const { customerId } = req.params;
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
|
|
type_of_water,
|
|
|
|
|
|
|
|
capacity: requestedCapacityStr,
|
|
|
|
|
|
|
|
quantity: requestedQuantityStr,
|
|
|
|
|
|
|
|
// frequency, start_date, end_date are provided by UI but not used for filtering now
|
|
|
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// helpers inside function (per your preference)
|
|
|
|
|
|
|
|
const parseCapacity = (v) => parseFloat((v || "0").toString().replace(/,/g, "")) || 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const requestedCapacity = parseCapacity(requestedCapacityStr);
|
|
|
|
|
|
|
|
const requestedQuantity = parseInt((requestedQuantityStr || "0").toString(), 10) || 0;
|
|
|
|
|
|
|
|
const totalRequiredCapacity = requestedCapacity * requestedQuantity;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// favorites
|
|
|
|
|
|
|
|
const customer = await User.findOne({ customerId }, { favorate_suppliers: 1 }).lean();
|
|
|
|
|
|
|
|
const favoriteSet = new Set(customer?.favorate_suppliers || []);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Tanker filter: ONLY by type_of_water (NO booked tanker exclusion, NO price/radius/rating)
|
|
|
|
|
|
|
|
const tankerQuery = {};
|
|
|
|
|
|
|
|
if (type_of_water && type_of_water.trim() !== "") {
|
|
|
|
|
|
|
|
tankerQuery.typeofwater = type_of_water;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const tankers = await Tanker.find(tankerQuery).lean();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Group tankers by supplier
|
|
|
|
|
|
|
|
const supplierTankerMap = {};
|
|
|
|
|
|
|
|
for (const t of tankers) {
|
|
|
|
|
|
|
|
if (!supplierTankerMap[t.supplierId]) supplierTankerMap[t.supplierId] = [];
|
|
|
|
|
|
|
|
supplierTankerMap[t.supplierId].push(t);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Capacity check per supplier (capacity * quantity)
|
|
|
|
|
|
|
|
const qualified = [];
|
|
|
|
|
|
|
|
for (const [supplierId, supplierTankers] of Object.entries(supplierTankerMap)) {
|
|
|
|
|
|
|
|
const totalAvail = supplierTankers.reduce((sum, tt) => sum + parseCapacity(tt.capacity), 0);
|
|
|
|
|
|
|
|
if (requestedCapacity > 0 && requestedQuantity > 0) {
|
|
|
|
|
|
|
|
if (totalAvail < totalRequiredCapacity) continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
qualified.push({ supplierId, tankers: supplierTankers });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Fetch suppliers + connection flags
|
|
|
|
|
|
|
|
const supplierIds = qualified.map(q => q.supplierId);
|
|
|
|
|
|
|
|
const [suppliersData, acceptedReqs] = await Promise.all([
|
|
|
|
|
|
|
|
Supplier.find({ supplierId: { $in: supplierIds } }).lean(),
|
|
|
|
|
|
|
|
FriendRequest.find(
|
|
|
|
|
|
|
|
{ customerId, supplierId: { $in: supplierIds }, status: "accepted" },
|
|
|
|
|
|
|
|
{ supplierId: 1, _id: 0 }
|
|
|
|
|
|
|
|
).lean()
|
|
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
const supplierById = new Map(suppliersData.map(s => [s.supplierId, s]));
|
|
|
|
|
|
|
|
const connectedSet = new Set(acceptedReqs.map(r => r.supplierId));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Optional: check if any (single-day) requested booking exists with that supplier
|
|
|
|
|
|
|
|
const suppliers = [];
|
|
|
|
|
|
|
|
for (const q of qualified) {
|
|
|
|
|
|
|
|
const supplierData = supplierById.get(q.supplierId);
|
|
|
|
|
|
|
|
const friendRequestAccepted = connectedSet.has(q.supplierId);
|
|
|
|
|
|
|
|
const isFavorite = favoriteSet.has(q.supplierId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const requestedBookingRecord = await RequestedBooking.findOne({
|
|
|
|
|
|
|
|
customerId,
|
|
|
|
|
|
|
|
"requested_suppliers.supplierId": q.supplierId
|
|
|
|
|
|
|
|
}, { time: 1 }).lean();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const requestedBooking = requestedBookingRecord
|
|
|
|
|
|
|
|
? { status: true, time: requestedBookingRecord.time }
|
|
|
|
|
|
|
|
: { status: false };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
suppliers.push({
|
|
|
|
|
|
|
|
supplier: supplierData,
|
|
|
|
|
|
|
|
tankers: q.tankers,
|
|
|
|
|
|
|
|
isConnected: friendRequestAccepted,
|
|
|
|
|
|
|
|
isFavorite,
|
|
|
|
|
|
|
|
requestedBooking
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return reply.send({ status_code: 200, suppliers });
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
console.error(err);
|
|
|
|
|
|
|
|
return reply.send({
|
|
|
|
|
|
|
|
status_code: 500,
|
|
|
|
|
|
|
|
message: "Something went wrong",
|
|
|
|
|
|
|
|
error: err.message
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// controllers/validationHandler.js (add below the previous handler)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.createRequestedPlanBooking = async (req, reply) => {
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
|
|
customerId,
|
|
|
|
|
|
|
|
type_of_water,
|
|
|
|
|
|
|
|
capacity,
|
|
|
|
|
|
|
|
quantity,
|
|
|
|
|
|
|
|
start_date,
|
|
|
|
|
|
|
|
end_date,
|
|
|
|
|
|
|
|
time,
|
|
|
|
|
|
|
|
frequency, // "daily" | "weekly_once" | "weekly_twice" | "weekly_thrice" | "weekly"
|
|
|
|
|
|
|
|
weekly_count, // used only if frequency === "weekly"
|
|
|
|
|
|
|
|
requested_suppliers
|
|
|
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// helpers inside function (as you prefer)
|
|
|
|
|
|
|
|
const parseCapacity = (v) => parseFloat((v || "0").toString().replace(/,/g, "")) || 0;
|
|
|
|
|
|
|
|
const parseIntSafe = (v) => parseInt((v || "0").toString().replace(/,/g, ""), 10) || 0;
|
|
|
|
|
|
|
|
const toISODate = (d) => d.toISOString().slice(0, 10);
|
|
|
|
|
|
|
|
const mkUTCDate = (yyyy_mm_dd) => {
|
|
|
|
|
|
|
|
const [y, m, d] = (yyyy_mm_dd || "").split("-").map(Number);
|
|
|
|
|
|
|
|
return new Date(Date.UTC(y, (m || 1) - 1, d || 1));
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const normalizeWeeklyCount = (freq, wc) => {
|
|
|
|
|
|
|
|
if (freq === "weekly_once") return 1;
|
|
|
|
|
|
|
|
if (freq === "weekly_twice") return 2;
|
|
|
|
|
|
|
|
if (freq === "weekly_thrice") return 3;
|
|
|
|
|
|
|
|
if (freq === "weekly") return wc || 1;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const computeWeeklyDOWs = ({ anchorDow, weeklyCount }) => {
|
|
|
|
|
|
|
|
if (weeklyCount === 1) return [anchorDow];
|
|
|
|
|
|
|
|
if (weeklyCount === 2) return [anchorDow, (anchorDow + 3) % 7];
|
|
|
|
|
|
|
|
if (weeklyCount === 3) return [anchorDow, (anchorDow + 2) % 7, (anchorDow + 4) % 7];
|
|
|
|
|
|
|
|
return [anchorDow];
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const generateDates = ({ frequency, start_date, end_date, weekly_count }) => {
|
|
|
|
|
|
|
|
const start = mkUTCDate(start_date);
|
|
|
|
|
|
|
|
const end = mkUTCDate(end_date);
|
|
|
|
|
|
|
|
if (isNaN(start) || isNaN(end)) throw new Error("Invalid start_date or end_date");
|
|
|
|
|
|
|
|
if (end < start) throw new Error("end_date must be after or equal to start_date");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ~3 months cap
|
|
|
|
|
|
|
|
const maxMs = 92 * 24 * 60 * 60 * 1000;
|
|
|
|
|
|
|
|
if ((end - start) > maxMs) throw new Error("Range exceeds 3 months");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const out = [];
|
|
|
|
|
|
|
|
if (frequency === "daily") {
|
|
|
|
|
|
|
|
for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
|
|
|
|
|
|
|
|
out.push(toISODate(d));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (frequency.startsWith("weekly") || frequency === "weekly") {
|
|
|
|
|
|
|
|
const wc = normalizeWeeklyCount(frequency, weekly_count);
|
|
|
|
|
|
|
|
const dows = computeWeeklyDOWs({ anchorDow: start.getUTCDay(), weeklyCount: wc });
|
|
|
|
|
|
|
|
const set = new Set(dows);
|
|
|
|
|
|
|
|
for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
|
|
|
|
|
|
|
|
if (set.has(d.getUTCDay())) out.push(toISODate(d));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error("Unsupported frequency");
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
if (!customerId || !type_of_water || !capacity || !quantity ||
|
|
|
|
|
|
|
|
!start_date || !end_date || !time || !frequency || !requested_suppliers) {
|
|
|
|
|
|
|
|
return reply.code(400).send({
|
|
|
|
|
|
|
|
status_code: 400,
|
|
|
|
|
|
|
|
message: "Missing required fields"
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const cap = parseCapacity(capacity);
|
|
|
|
|
|
|
|
const qty = parseIntSafe(quantity);
|
|
|
|
|
|
|
|
const total_required_capacity = cap * qty;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dates = generateDates({ frequency, start_date, end_date, weekly_count });
|
|
|
|
|
|
|
|
if (dates.length === 0) {
|
|
|
|
|
|
|
|
return reply.code(400).send({ status_code: 400, message: "No dates generated for given inputs" });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const doc = new RecurringRequestedBooking({
|
|
|
|
|
|
|
|
customerId,
|
|
|
|
|
|
|
|
type_of_water,
|
|
|
|
|
|
|
|
capacity,
|
|
|
|
|
|
|
|
quantity,
|
|
|
|
|
|
|
|
total_required_capacity,
|
|
|
|
|
|
|
|
frequency,
|
|
|
|
|
|
|
|
weekly_count: normalizeWeeklyCount(frequency, weekly_count),
|
|
|
|
|
|
|
|
start_date,
|
|
|
|
|
|
|
|
end_date,
|
|
|
|
|
|
|
|
time,
|
|
|
|
|
|
|
|
dates,
|
|
|
|
|
|
|
|
requested_suppliers,
|
|
|
|
|
|
|
|
status: "pending"
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await doc.save();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return reply.send({
|
|
|
|
|
|
|
|
status_code: 200,
|
|
|
|
|
|
|
|
message: "Plan requested booking created successfully",
|
|
|
|
|
|
|
|
count: dates.length,
|
|
|
|
|
|
|
|
dates,
|
|
|
|
|
|
|
|
data: doc
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
console.error(err);
|
|
|
|
|
|
|
|
return reply.code(500).send({
|
|
|
|
|
|
|
|
status_code: 500,
|
|
|
|
|
|
|
|
message: "Something went wrong while saving",
|
|
|
|
|
|
|
|
error: err.message
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|