Added order new time and time proposals endpoint
parent
37e9ad7489
commit
fbb0f60a23
@ -0,0 +1,16 @@
|
|||||||
|
import Trainer from "./trainer";
|
||||||
|
|
||||||
|
export type OrderStatus = "Created" | "Confirmed" | "Failed" | "CancelledByTrainer" | "CancelledByUser";
|
||||||
|
export type PaymentIntentStatus = "Created" | "Successful" | "Failed";
|
||||||
|
|
||||||
|
export interface OrderObject {
|
||||||
|
id: number
|
||||||
|
trainer: Trainer
|
||||||
|
startDate: string
|
||||||
|
endDate: string
|
||||||
|
status: OrderObjectStatus
|
||||||
|
price: number
|
||||||
|
created_at: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OrderObjectStatus = "Successful" | "Processing" | "CancelledByTrainer" | "CancelledByUser";
|
||||||
@ -0,0 +1,214 @@
|
|||||||
|
import express, { Router, Response } from "express";
|
||||||
|
import Joi from "joi"
|
||||||
|
import dayjs, { Dayjs } from "dayjs"
|
||||||
|
import isoWeek from "dayjs/plugin/isoWeek"
|
||||||
|
import utc from "dayjs/plugin/utc"
|
||||||
|
import LocalizedFormat from "dayjs/plugin/localizedFormat"
|
||||||
|
import { } from "dayjs/locale/da";
|
||||||
|
|
||||||
|
import { client as pool } from "../db";
|
||||||
|
import { DatabaseError } from "pg";
|
||||||
|
import { idSchema } from "../schemas";
|
||||||
|
import { UserAuth } from "../middlewares/auth";
|
||||||
|
import { AuthedRequest } from "../interfaces/auth";
|
||||||
|
import Stripe from 'stripe';
|
||||||
|
import { stripe } from "../stripe";
|
||||||
|
import Trainer from "../interfaces/trainer";
|
||||||
|
|
||||||
|
dayjs.extend(isoWeek)
|
||||||
|
dayjs.extend(utc)
|
||||||
|
dayjs.extend(LocalizedFormat);
|
||||||
|
|
||||||
|
const router: Router = express.Router();
|
||||||
|
|
||||||
|
const orderSchema = Joi.object({
|
||||||
|
trainer: idSchema.required(),
|
||||||
|
startDate: Joi.date().min("now").required(),
|
||||||
|
endDate: Joi.date().min(Joi.ref("startDate")).required()
|
||||||
|
});
|
||||||
|
|
||||||
|
interface OrderBody {
|
||||||
|
trainer: number
|
||||||
|
startDate: Date
|
||||||
|
endDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimeslotValidQueryResult {
|
||||||
|
weekly_timeslot_available: boolean
|
||||||
|
time_already_reserved: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(date: Dayjs): string {
|
||||||
|
let output: string = dayjs(date).locale("da").format("dddd D. MMM YYYY");
|
||||||
|
let outputStringArray = output.split("");
|
||||||
|
outputStringArray[0] = outputStringArray[0].toLocaleUpperCase();
|
||||||
|
output = outputStringArray.join("");
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(date: Dayjs): string {
|
||||||
|
let output: string = dayjs(date).locale("da").format("HH:mm");
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.post("/order", UserAuth, async (req: AuthedRequest, res: Response) => {
|
||||||
|
try {
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
const validation = orderSchema.validate(req.body, { abortEarly: false });
|
||||||
|
if (validation.error !== undefined) {
|
||||||
|
return res.status(400).send(validation.error.details);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderBody: OrderBody = validation.value;
|
||||||
|
|
||||||
|
const startDate: dayjs.Dayjs = dayjs(orderBody.startDate).utcOffset(0);
|
||||||
|
const endDate: dayjs.Dayjs = dayjs(orderBody.endDate).utcOffset(0);
|
||||||
|
|
||||||
|
const startTime: string = `${startDate.hour()}:${startDate.minute()}`;
|
||||||
|
const endTime: string = `${endDate.hour()}:${endDate.minute()}`;
|
||||||
|
|
||||||
|
const weekday = startDate.isoWeekday();
|
||||||
|
|
||||||
|
await client.query("BEGIN");
|
||||||
|
|
||||||
|
const trainerLookup = await client.query(`
|
||||||
|
SELECT trainers.id, first_name, last_name, center_id, centers.name as center_name FROM trainers
|
||||||
|
JOIN users ON trainers.user_id = users.id
|
||||||
|
JOIN centers on trainers.center_id = centers.id
|
||||||
|
WHERE trainers.id = $1;
|
||||||
|
`, [
|
||||||
|
orderBody.trainer
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (trainerLookup.rows.length !== 1)
|
||||||
|
return res.status(400).send([{
|
||||||
|
message: "\"trainer\" was not found",
|
||||||
|
path: [
|
||||||
|
"trainer"
|
||||||
|
],
|
||||||
|
type: "trainer.not_found"
|
||||||
|
}]);
|
||||||
|
|
||||||
|
const trainer: Trainer = trainerLookup.rows[0];
|
||||||
|
|
||||||
|
const timeslotValidQuery = await client.query(`
|
||||||
|
select
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1 FROM weekly_timeslots
|
||||||
|
WHERE
|
||||||
|
trainer_id = $1
|
||||||
|
AND day_of_week = $2
|
||||||
|
AND start_time = $3
|
||||||
|
AND end_time = $4
|
||||||
|
) as weekly_timeslot_available,
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1 FROM public.reserved_timeslots
|
||||||
|
WHERE
|
||||||
|
((start_time >= $5 AND start_time < $6)
|
||||||
|
OR (end_time > $5 AND end_time <= $6))
|
||||||
|
AND trainer_id = $1
|
||||||
|
) as time_already_reserved;
|
||||||
|
`, [
|
||||||
|
orderBody.trainer,
|
||||||
|
weekday,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
startDate.toISOString(),
|
||||||
|
endDate.toISOString()
|
||||||
|
]);
|
||||||
|
|
||||||
|
const timeslotValidQueryResult: TimeslotValidQueryResult = timeslotValidQuery.rows[0];
|
||||||
|
|
||||||
|
if (timeslotValidQueryResult.time_already_reserved || !timeslotValidQueryResult.weekly_timeslot_available)
|
||||||
|
return res.status(400).send([{
|
||||||
|
message: "timeslot was not found",
|
||||||
|
path: [
|
||||||
|
"startDate"
|
||||||
|
],
|
||||||
|
type: "startDate.invalid"
|
||||||
|
}]);
|
||||||
|
|
||||||
|
const priceQuery = await client.query(`
|
||||||
|
SELECT hourly_price FROM trainers WHERE id = $1;
|
||||||
|
`, [
|
||||||
|
orderBody.trainer
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hourlyPrice = priceQuery.rows[0].hourly_price;
|
||||||
|
|
||||||
|
const hours = endDate.diff(startDate, "hours", true);
|
||||||
|
const price = Math.ceil(hours * hourlyPrice);
|
||||||
|
|
||||||
|
const productName: string = `Personlig træningstime ${trainer.center_name}`;
|
||||||
|
const productDescription: string = `Personlig træningstime med ${trainer.first_name} ${trainer.last_name} - ${formatDate(startDate)} ${formatTime(startDate)} - ${formatTime(endDate)}`;
|
||||||
|
|
||||||
|
const checkoutSession: Stripe.Checkout.Session = await stripe.checkout.sessions.create({
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
price_data: {
|
||||||
|
currency: "DKK",
|
||||||
|
unit_amount: price,
|
||||||
|
product_data: {
|
||||||
|
name: productName,
|
||||||
|
description: productDescription,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quantity: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
expires_at: Math.floor(Date.now()/1000 + 60*31),
|
||||||
|
mode: 'payment',
|
||||||
|
success_url: `http://localhost:5173/orders`,
|
||||||
|
cancel_url: `http://localhost:5173`
|
||||||
|
});
|
||||||
|
|
||||||
|
const insertQuery = await client.query(`
|
||||||
|
WITH inserted_reserved_timeslot AS (
|
||||||
|
INSERT INTO reserved_timeslots (trainer_id, start_time, end_time)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING *
|
||||||
|
)
|
||||||
|
|
||||||
|
INSERT INTO orders (timeslot_id, user_id, price, checkout_session)
|
||||||
|
select id, $4, $5, $6
|
||||||
|
FROM inserted_reserved_timeslot
|
||||||
|
RETURNING id;
|
||||||
|
`, [
|
||||||
|
orderBody.trainer,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
req.user?.userId,
|
||||||
|
price,
|
||||||
|
checkoutSession.id
|
||||||
|
]);
|
||||||
|
|
||||||
|
const insertedData = insertQuery.rows[0];
|
||||||
|
|
||||||
|
await client.query("COMMIT");
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
id: insertedData.id,
|
||||||
|
trainerId: orderBody.trainer,
|
||||||
|
startDate: orderBody.startDate,
|
||||||
|
endDate: orderBody.endDate,
|
||||||
|
price,
|
||||||
|
url: checkoutSession.url
|
||||||
|
});
|
||||||
|
} catch (error: DatabaseError | Error | any) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
console.error(error);
|
||||||
|
return res.sendStatus(500);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
} catch (error: DatabaseError | Error | any) {
|
||||||
|
console.error(error);
|
||||||
|
return res.sendStatus(500);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router;
|
||||||
@ -0,0 +1,162 @@
|
|||||||
|
import express, { Router, Response } from "express";
|
||||||
|
import Joi from "joi"
|
||||||
|
import dayjs, { Dayjs } from "dayjs"
|
||||||
|
import isoWeek from "dayjs/plugin/isoWeek"
|
||||||
|
import utc from "dayjs/plugin/utc"
|
||||||
|
import LocalizedFormat from "dayjs/plugin/localizedFormat"
|
||||||
|
import { } from "dayjs/locale/da";
|
||||||
|
|
||||||
|
import { client } from "../db";
|
||||||
|
import { DatabaseError } from "pg";
|
||||||
|
import { idSchema } from "../schemas";
|
||||||
|
import { UserAuth } from "../middlewares/auth";
|
||||||
|
import { AuthedRequest } from "../interfaces/auth";
|
||||||
|
import Stripe from 'stripe';
|
||||||
|
import { stripe } from "../stripe";
|
||||||
|
import Trainer from "../interfaces/trainer";
|
||||||
|
import { OrderStatus, PaymentIntentStatus } from "../interfaces/order";
|
||||||
|
|
||||||
|
dayjs.extend(isoWeek)
|
||||||
|
dayjs.extend(utc)
|
||||||
|
dayjs.extend(LocalizedFormat);
|
||||||
|
|
||||||
|
const router: Router = express.Router();
|
||||||
|
|
||||||
|
const orderSchema = Joi.object({
|
||||||
|
startDate: Joi.date().min("now").required(),
|
||||||
|
endDate: Joi.date().min(Joi.ref("startDate")).required()
|
||||||
|
});
|
||||||
|
|
||||||
|
interface OrderBody {
|
||||||
|
trainer: number
|
||||||
|
startDate: Date
|
||||||
|
endDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimeslotValidQueryResult {
|
||||||
|
weekly_timeslot_available: boolean
|
||||||
|
time_already_reserved: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderLookup {
|
||||||
|
order_status: OrderStatus
|
||||||
|
start_time: Date
|
||||||
|
end_time: Date
|
||||||
|
trainer_id: number
|
||||||
|
timeslot_id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
router.put("/order/:id", UserAuth, async (req: AuthedRequest, res: Response) => {
|
||||||
|
try {
|
||||||
|
const lookupResult = await client.query(`
|
||||||
|
SELECT
|
||||||
|
order_status,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
trainer_id,
|
||||||
|
timeslot_id
|
||||||
|
FROM orders
|
||||||
|
LEFT JOIN payment_intents ON payment_intents.id = orders.payment_intent
|
||||||
|
LEFT JOIN reserved_timeslots ON reserved_timeslots.id = orders.timeslot_id
|
||||||
|
WHERE orders.id = $1
|
||||||
|
AND user_id = $2
|
||||||
|
AND order_status = 'Confirmed';
|
||||||
|
`, [
|
||||||
|
req.params.id,
|
||||||
|
req.user?.userId
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (lookupResult.rows.length !== 1) {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const order: OrderLookup = lookupResult.rows[0];
|
||||||
|
|
||||||
|
const originalStartDate: dayjs.Dayjs = dayjs(order.start_time).utcOffset(0);
|
||||||
|
const originalEndDate: dayjs.Dayjs = dayjs(order.end_time).utcOffset(0);
|
||||||
|
|
||||||
|
/** The duration of the original timeslot in miliseconds */
|
||||||
|
const originalDuration: number = originalEndDate.diff(originalStartDate);
|
||||||
|
|
||||||
|
const validation = orderSchema.validate(req.body, { abortEarly: false });
|
||||||
|
if (validation.error !== undefined) {
|
||||||
|
return res.status(400).send(validation.error.details);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderBody: OrderBody = validation.value;
|
||||||
|
|
||||||
|
const startDate: dayjs.Dayjs = dayjs(orderBody.startDate).utcOffset(0);
|
||||||
|
const endDate: dayjs.Dayjs = dayjs(orderBody.endDate).utcOffset(0);
|
||||||
|
|
||||||
|
const startTime: string = `${startDate.hour()}:${startDate.minute()}`;
|
||||||
|
const endTime: string = `${endDate.hour()}:${endDate.minute()}`;
|
||||||
|
|
||||||
|
const weekday = startDate.isoWeekday();
|
||||||
|
|
||||||
|
const timeslotValidQuery = await client.query(`
|
||||||
|
select
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1 FROM weekly_timeslots
|
||||||
|
WHERE
|
||||||
|
trainer_id = $1
|
||||||
|
AND day_of_week = $2
|
||||||
|
AND start_time = $3
|
||||||
|
AND end_time = $4
|
||||||
|
) as weekly_timeslot_available,
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1 FROM public.reserved_timeslots
|
||||||
|
WHERE
|
||||||
|
((start_time >= $5 AND start_time < $6)
|
||||||
|
OR (end_time > $5 AND end_time <= $6))
|
||||||
|
AND trainer_id = $1
|
||||||
|
) as time_already_reserved;
|
||||||
|
`, [
|
||||||
|
order.trainer_id,
|
||||||
|
weekday,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
startDate.toISOString(),
|
||||||
|
endDate.toISOString()
|
||||||
|
]);
|
||||||
|
|
||||||
|
const timeslotValidQueryResult: TimeslotValidQueryResult = timeslotValidQuery.rows[0];
|
||||||
|
|
||||||
|
if (timeslotValidQueryResult.time_already_reserved || !timeslotValidQueryResult.weekly_timeslot_available)
|
||||||
|
return res.status(400).send([{
|
||||||
|
message: "timeslot was not found",
|
||||||
|
path: [
|
||||||
|
"startDate"
|
||||||
|
],
|
||||||
|
type: "startDate.invalid"
|
||||||
|
}]);
|
||||||
|
|
||||||
|
const duration = endDate.diff(startDate);
|
||||||
|
|
||||||
|
if (duration !== originalDuration)
|
||||||
|
return res.status(400).send([{
|
||||||
|
message: "timeslot is not same length as original",
|
||||||
|
path: [
|
||||||
|
"startDate"
|
||||||
|
],
|
||||||
|
type: "startDate.invalid"
|
||||||
|
}]);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
UPDATE reserved_timeslots SET
|
||||||
|
start_time = $1,
|
||||||
|
end_time = $2
|
||||||
|
WHERE id = $3;
|
||||||
|
`, [
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
order.timeslot_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
return res.sendStatus(204);
|
||||||
|
} catch (error: DatabaseError | Error | any) {
|
||||||
|
console.error(error);
|
||||||
|
return res.sendStatus(500);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router;
|
||||||
@ -0,0 +1,178 @@
|
|||||||
|
import express, { Router, Request, Response } from "express";
|
||||||
|
import Joi from "joi";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
import { client } from "../db";
|
||||||
|
import { DatabaseError } from "pg";
|
||||||
|
import { UserAuth } from "../middlewares/auth";
|
||||||
|
import { AuthedRequest } from "../interfaces/auth";
|
||||||
|
import Trainer from "../interfaces/trainer";
|
||||||
|
import { OrderObject, OrderObjectStatus, OrderStatus, PaymentIntentStatus } from "../interfaces/order";
|
||||||
|
import { ReservedTimeslots, Timeslot, WeeklyTimeslot } from "../interfaces/timeslot";
|
||||||
|
|
||||||
|
const router: Router = express.Router();
|
||||||
|
|
||||||
|
interface OrderLookup {
|
||||||
|
order_status: OrderStatus
|
||||||
|
start_time: Date
|
||||||
|
end_time: Date
|
||||||
|
trainer_id: number
|
||||||
|
timeslot_id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimeProposal {
|
||||||
|
startDate: string
|
||||||
|
endDate: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NewTimeslotPropsalFilter {
|
||||||
|
startDate: Date
|
||||||
|
endDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTimeslotPropsalFilterSchema = Joi.object({
|
||||||
|
startDate: Joi.date().default(Date.now()),
|
||||||
|
endDate: Joi.date().default(Date.now())
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/order/:id/newtimeslots", UserAuth, async (req: AuthedRequest, res: Response) => {
|
||||||
|
try {
|
||||||
|
const lookupResult = await client.query(`
|
||||||
|
SELECT
|
||||||
|
order_status,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
trainer_id,
|
||||||
|
timeslot_id
|
||||||
|
FROM orders
|
||||||
|
LEFT JOIN payment_intents ON payment_intents.id = orders.payment_intent
|
||||||
|
LEFT JOIN reserved_timeslots ON reserved_timeslots.id = orders.timeslot_id
|
||||||
|
WHERE orders.id = $1
|
||||||
|
AND user_id = $2
|
||||||
|
AND order_status = 'Confirmed';
|
||||||
|
`, [
|
||||||
|
req.params.id,
|
||||||
|
req.user?.userId
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (lookupResult.rows.length !== 1) {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const order: OrderLookup = lookupResult.rows[0];
|
||||||
|
|
||||||
|
const dateValiadtion = Joi.date().min(Date.now()).validate(order.start_time)
|
||||||
|
if (dateValiadtion.error !== undefined) {
|
||||||
|
return res.status(400).send({ message: "Timeslot has already occurred" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterValidation = newTimeslotPropsalFilterSchema.validate(req.query, { abortEarly: false, stripUnknown: true });
|
||||||
|
if (filterValidation.error !== undefined) {
|
||||||
|
return res.status(400).send(filterValidation.error.details);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTimeslotPropsalFilter: NewTimeslotPropsalFilter = filterValidation.value;
|
||||||
|
|
||||||
|
const originalStartDate: dayjs.Dayjs = dayjs(order.start_time).utcOffset(0);
|
||||||
|
const originalEndDate: dayjs.Dayjs = dayjs(order.end_time).utcOffset(0);
|
||||||
|
|
||||||
|
/** The duration of the original timeslot in miliseconds */
|
||||||
|
const originalDuration: number = originalStartDate.diff(originalEndDate);
|
||||||
|
|
||||||
|
const latestAllowedFilterEndDate: dayjs.Dayjs = originalStartDate.add(1, "month");
|
||||||
|
|
||||||
|
const nowDate: dayjs.Dayjs = dayjs().utcOffset(0).startOf("date");
|
||||||
|
|
||||||
|
let filterStartDate: dayjs.Dayjs = dayjs(newTimeslotPropsalFilter.startDate).utcOffset(0).startOf("date");
|
||||||
|
let filterEndDate: dayjs.Dayjs = dayjs(newTimeslotPropsalFilter.endDate).utcOffset(0).startOf("date");
|
||||||
|
|
||||||
|
if (filterStartDate.isBefore(nowDate)) {
|
||||||
|
filterStartDate = nowDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterEndDate.isAfter(latestAllowedFilterEndDate)) {
|
||||||
|
filterEndDate = latestAllowedFilterEndDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
let weekdays: number[] = [];
|
||||||
|
for (let day: dayjs.Dayjs = filterStartDate; !day.isAfter(filterEndDate); day = day.add(1, "day")) {
|
||||||
|
if (!weekdays.includes(day.isoWeekday()))
|
||||||
|
weekdays.push(day.isoWeekday());
|
||||||
|
}
|
||||||
|
|
||||||
|
const weeklyTimeslotLookup = await client.query(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
day_of_week,
|
||||||
|
start_time,
|
||||||
|
end_time
|
||||||
|
FROM weekly_timeslots
|
||||||
|
WHERE trainer_id = $1
|
||||||
|
AND day_of_week = ANY($2);
|
||||||
|
`, [
|
||||||
|
order.trainer_id,
|
||||||
|
weekdays
|
||||||
|
]);
|
||||||
|
|
||||||
|
const weeklyTimeslots: WeeklyTimeslot[] = weeklyTimeslotLookup.rows;
|
||||||
|
|
||||||
|
const reservedTimeslotLookup = await client.query(`
|
||||||
|
SELECT
|
||||||
|
start_time,
|
||||||
|
end_time
|
||||||
|
FROM public.reserved_timeslots
|
||||||
|
JOIN orders ON reserved_timeslots.id = orders.timeslot_id
|
||||||
|
WHERE
|
||||||
|
(start_time >= $1 AND end_time <= $2)
|
||||||
|
AND reserved_timeslots.trainer_id = $3
|
||||||
|
AND (
|
||||||
|
orders.order_status = 'Confirmed'
|
||||||
|
OR orders.order_status = 'Created'
|
||||||
|
);
|
||||||
|
`, [
|
||||||
|
filterStartDate,
|
||||||
|
filterEndDate.add(1, "day"),
|
||||||
|
order.trainer_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
const reservedTimeslots: ReservedTimeslots[] = reservedTimeslotLookup.rows;
|
||||||
|
|
||||||
|
const timeProposals: TimeProposal[] = [];
|
||||||
|
|
||||||
|
for (let day: dayjs.Dayjs = filterStartDate; !day.isAfter(filterEndDate); day = day.add(1, "day")){
|
||||||
|
const weekDay = day.isoWeekday();
|
||||||
|
|
||||||
|
weeklyTimeslots: for (const weeklyTimeslot of weeklyTimeslots) {
|
||||||
|
if (weeklyTimeslot.day_of_week !== weekDay)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const startTime = dayjs(`${day.toISOString().split("T")[0]}T${weeklyTimeslot.start_time}`);
|
||||||
|
const endTime = dayjs(`${day.toISOString().split("T")[0]}T${weeklyTimeslot.end_time}`);
|
||||||
|
for (const reservedTimeslot of reservedTimeslots) {
|
||||||
|
const reservedTimeStart = dayjs(reservedTimeslot.start_time);
|
||||||
|
const reservedTimeEnd = dayjs(reservedTimeslot.end_time);
|
||||||
|
if ((!reservedTimeStart.isBefore(startTime) && reservedTimeStart.isBefore(endTime)) ||
|
||||||
|
(reservedTimeEnd.isAfter(startTime) && !reservedTimeEnd.isAfter(endTime))) {
|
||||||
|
continue weeklyTimeslots;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const proposedDuration: number = startTime.diff(endTime);
|
||||||
|
if (proposedDuration !== originalDuration)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
timeProposals.push({
|
||||||
|
startDate: `${day.toISOString().split("T")[0]}T${weeklyTimeslot.start_time}Z`,
|
||||||
|
endDate: `${day.toISOString().split("T")[0]}T${weeklyTimeslot.end_time}Z`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(timeProposals);
|
||||||
|
} catch (error: DatabaseError | Error | any) {
|
||||||
|
console.error(error);
|
||||||
|
return res.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
Loading…
Reference in New Issue