Snai3i-MarketPlace / backend / src / services / order / order.service.ts
order.service.ts
Raw
import orderLogs, { IOrderLogs, orderLogger } from './order.logs';
import { formatString } from '../../utils/Strings';
import { HttpCodes } from '../../config/Errors';
import { ErrorResponseC, SuccessResponseC } from '../services.response';
import { db } from '../../settings';
import { ResultSetHeader, RowDataPacket } from 'mysql2';
import { EnrollmentServices } from '../enrollment/enrollment.service';
import courseLogs from '../course/course.logs';

export class OrderServices {
  /**
   * @description Create an order
   * @param course_id - Number
   * @param pack_id - Number
   * @param school_id - Number
   * @param price - Number
   * @param status - String
   * @param note - String
   * @returns ResponseT
   */
  static createSchoolOrder = async (
    course_id: number,
    pack_id: number,
    school_id: number,
    price: number,
    status: string,
    note: string
  ): Promise<ResponseT> => {
    try {
      // check if the status is active
      const courseStatusQuery =
        'SELECT status FROM courses WHERE course_id = ?';
      const [courseStatus] = await db.query<RowDataPacket[]>(
        courseStatusQuery,
        [course_id]
      );
      if (courseStatus.length === 0) {
        const msg = formatString(courseLogs.COURSE_ERROR_NOT_FOUND.message, {
          course_id,
        });
        orderLogger.error(msg);
        return new ErrorResponseC(
          courseLogs.COURSE_ERROR_NOT_FOUND.type,
          HttpCodes.NotFound.code,
          msg
        );
      }
      if (courseStatus[0].status !== 'active') {
        const msg = formatString(courseLogs.COURSE_NOT_ACTIVE.message, {
          course_id,
        });
        orderLogger.error(msg);
        return new ErrorResponseC(
          courseLogs.COURSE_NOT_ACTIVE.type,
          HttpCodes.BadRequest.code,
          msg
        );
      }

      // check if there is already an order for the course and school
      const orderExistsQuery =
        'SELECT * FROM orders WHERE course_id = ? AND school_id = ?';
      const [orderExists] = await db.query<OrderResI[]>(orderExistsQuery, [
        course_id,
        school_id,
      ]);
      if (orderExists.length > 0) {
        const msg = formatString(orderLogs.ORDER_ERROR_INVALID_INPUT.message, {
          input: `Order ${orderExists[0].order_id} already exists for the course and school`,
        });
        orderLogger.error(msg);
        return new ErrorResponseC(
          orderLogs.ORDER_ERROR_INVALID_INPUT.type,
          HttpCodes.BadRequest.code,
          msg
        );
      }

      const createdAt = new Date();
      const updatedAt = createdAt;

      const sqlInsertQuery =
        'INSERT INTO orders (course_id, pack_id, school_id, price, status, note, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';

      const result: any = await db.query<ResultSetHeader[]>(sqlInsertQuery, [
        course_id,
        pack_id,
        school_id,
        price,
        status,
        note,
        createdAt,
        updatedAt,
      ]);

      const orderId = result[0].insertId;

      const order: OrderI = {
        order_id: orderId,
        course_id,
        pack_id,
        school_id,
        price,
        status,
        note,
      };

      const resp: ICode<IOrderLogs> = orderLogs.CREATE_ORDER_SUCCESS;
      const msg = formatString(resp.message, {
        orderId,
      });
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        order,
        msg,
        HttpCodes.Created.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.CREATE_ORDER_ERROR.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.CREATE_ORDER_ERROR.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  /**
   * @description Create an order
   * @param course_id - Number
   * @param pack_id - Number
   * @param teacher_id - Number
   * @param price - Number
   * @param status - String
   * @param note - String
   * @returns ResponseT
   */
  static createTeacherOrder = async (
    course_id: number,
    pack_id: number,
    teacher_id: number,
    price: number,
    status: string,
    note: string
  ): Promise<ResponseT> => {
    try {
      // check if the status is active
      const courseStatusQuery =
        'SELECT status FROM courses WHERE course_id = ?';
      const [courseStatus] = await db.query<RowDataPacket[]>(
        courseStatusQuery,
        [course_id]
      );
      if (courseStatus.length === 0) {
        const msg = formatString(courseLogs.COURSE_ERROR_NOT_FOUND.message, {
          course_id,
        });
        orderLogger.error(msg);
        return new ErrorResponseC(
          courseLogs.COURSE_ERROR_NOT_FOUND.type,
          HttpCodes.NotFound.code,
          msg
        );
      }
      if (courseStatus[0].status !== 'active') {
        const msg = formatString(courseLogs.COURSE_NOT_ACTIVE.message, {
          course_id,
        });
        orderLogger.error(msg);
        return new ErrorResponseC(
          courseLogs.COURSE_NOT_ACTIVE.type,
          HttpCodes.BadRequest.code,
          msg
        );
      }

      // check if there is already an order for the course and teacher
      const orderExistsQuery =
        'SELECT * FROM orders WHERE course_id = ? AND teacher_id = ?';
      const [orderExists] = await db.query<RowDataPacket[]>(orderExistsQuery, [
        course_id,
        teacher_id,
      ]);
      if (orderExists.length > 0) {
        const msg = formatString(orderLogs.ORDER_ERROR_INVALID_INPUT.message, {
          error: 'Order already exists for the course and teacher',
        });
        orderLogger.error(msg);
        return new ErrorResponseC(
          orderLogs.ORDER_ERROR_INVALID_INPUT.type,
          HttpCodes.BadRequest.code,
          msg
        );
      }

      const createdAt = new Date();
      const updatedAt = createdAt;

      const sqlInsertQuery =
        'INSERT INTO orders (course_id, pack_id, teacher_id, price, status, note, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';

      const result: any = await db.query<ResultSetHeader[]>(sqlInsertQuery, [
        course_id,
        pack_id,
        teacher_id,
        price,
        status,
        note,
        createdAt,
        updatedAt,
      ]);
      const orderId = result[0].insertId;

      // create enrollment
      const enrollmentResult = await EnrollmentServices.createEnrollment(
        course_id,
        teacher_id
      );
      if (enrollmentResult instanceof ErrorResponseC) {
        // remove order if enrollment creation fails
        const sqlDeleteQuery = 'DELETE FROM orders WHERE order_id = ?';
        await db.query<RowDataPacket[]>(sqlDeleteQuery, [orderId]);
        return enrollmentResult;
      }

      const order: OrderI = {
        order_id: orderId,
        course_id,
        pack_id,
        teacher_id,
        price,
        status,
        note,
      };

      const resp: ICode<IOrderLogs> = orderLogs.CREATE_ORDER_SUCCESS;
      const msg = formatString(resp.message, {
        orderId,
      });
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        order,
        msg,
        HttpCodes.Created.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.CREATE_ORDER_ERROR.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.CREATE_ORDER_ERROR.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  /**
   * @description Delete an order
   * @param orderId - Number
   * @returns ResponseT
   */
  static deleteOrder = async (orderId: number): Promise<ResponseT> => {
    try {
      // order is associated with teacher, delete enrollment
      const sqlSelectQuery =
        'SELECT teacher_id, course_id FROM orders WHERE order_id = ?';
      const [order] = await db.query<OrderResI[]>(sqlSelectQuery, [orderId]);
      if (order.length > 0) {
        // const { teacher_id, course_id } = order[0];
        // if (teacher_id) {
        //   const enrollmentResult = await EnrollmentServices.deleteEnrollment(
        //     teacher_id,
        //     course_id
        //   );
        //   if (enrollmentResult instanceof ErrorResponseC) {
        //     return enrollmentResult;
        //   }
        // }
      }

      const sqlDeleteQuery = 'DELETE FROM orders WHERE order_id = ?';
      await db.query<RowDataPacket[]>(sqlDeleteQuery, [orderId]);

      const resp: ICode<IOrderLogs> = orderLogs.DELETE_ORDER_SUCCESS;
      const msg = formatString(resp.message, {
        orderId,
      });
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        null,
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.DELETE_ORDER_ERROR.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.DELETE_ORDER_ERROR.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  /**
   * @description Get an order
   * @param orderId - Number
   * @returns ResponseT
   */
  static getOrder = async (orderId: number): Promise<ResponseT> => {
    try {
      const sqlquery = 'SELECT * FROM orders WHERE order_id = ?';
      const [order] = await db.query<OrderResI[]>(sqlquery, [orderId]);

      if (order.length === 0) {
        const msg = formatString(orderLogs.ORDER_ERROR_NOT_FOUND.message, {
          orderId,
        });
        orderLogger.error(msg);
        return new ErrorResponseC(
          orderLogs.ORDER_ERROR_NOT_FOUND.type,
          HttpCodes.NotFound.code,
          msg
        );
      }

      const resp: ICode<IOrderLogs> = orderLogs.GET_ORDER_SUCCESS;
      const msg = formatString(resp.message, {
        orderId,
      });
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        order[0],
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.ORDER_ERROR_GENERIC.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.ORDER_ERROR_GENERIC.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  /**
   * @description Get all orders
   * @returns ResponseT
   */
  static getOrders = async (): Promise<ResponseT> => {
    try {
      // get orders where school_id is not null and join with schools table
      const sqlquery =
        'SELECT o.*, u.email, s.schoolName, p.name as packName, c.title as courseName,p.nb_teachers_accounts FROM orders o LEFT JOIN schools s ON o.school_id = s.school_id LEFT JOIN users u ON s.user_id = u.user_id LEFT JOIN packs p ON p.pack_id = o.pack_id LEFT JOIN courses c ON c.course_id = o.course_id WHERE o.school_id IS NOT NULL ORDER BY o.createdAt DESC';
      // 'SELECT o.*, s.schoolName FROM Orders o LEFT JOIN Schools s ON o.school_id = s.school_id WHERE o.school_id IS NOT NULL';
      const [schoolOrders] = await db.query<OrderResI[]>(sqlquery);

      // get orders where teacher_id is not null and get user_id from teachers table and join with users table
      const sqlquery2 =
        'SELECT o.*, u.firstName, u.lastName, u.email, c.title as courseName  FROM orders o LEFT JOIN teachers t ON o.teacher_id = t.teacher_id LEFT JOIN users u ON t.user_id = u.user_id JOIN courses c ON c.course_id = o.course_id WHERE o.teacher_id IS NOT NULL ORDER BY o.createdAt DESC';
      const [teacherOrders] = await db.query<OrderResI[]>(sqlquery2);

      const resp: ICode<IOrderLogs> = orderLogs.GET_ORDERS_SUCCESS;
      const msg = resp.message;
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        { schoolOrders, teacherOrders },
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.GET_ORDERS_ERROR.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.GET_ORDERS_ERROR.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  /**
   * @description Get all orders by status
   * @param status - String
   * @returns ResponseT
   */
  static getOrdersByStatus = async (status: string): Promise<ResponseT> => {
    try {
      const sqlquery = 'SELECT * FROM orders WHERE status = ?';
      const [orders] = await db.query<OrderResI[]>(sqlquery, [status]);

      const resp: ICode<IOrderLogs> = orderLogs.GET_ORDERS_BY_STATUS_SUCCESS;
      const msg = resp.message;
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        orders,
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.GET_ORDERS_BY_STATUS_ERROR.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.GET_ORDERS_BY_STATUS_ERROR.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  /**
   * @description Update order status
   * @param orderId - Number
   * @param status - String
   * @returns ResponseT
   */
  static updateOrderStatus = async (
    orderId: number,
    status: string
  ): Promise<ResponseT> => {
    try {
      const sqlUpdateQuery = 'UPDATE orders SET status = ? WHERE order_id = ?';

      await db.query<OrderResI[]>(sqlUpdateQuery, [status, orderId]);

      const resp: ICode<IOrderLogs> = orderLogs.UPDATE_ORDER_STATUS_SUCCESS;
      const msg = formatString(resp.message, {
        orderId,
      });
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        null,
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.UPDATE_ORDER_STATUS_ERROR.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.UPDATE_ORDER_STATUS_ERROR.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  static getOrdersByInstructor = async (
    inst_designer_id: number
  ): Promise<ResponseT> => {
    try {
      const sqlquery = 'SELECT * FROM orders WHERE inst_designer_id = ?';
      const [orders] = await db.query<OrderResI[]>(sqlquery, [
        inst_designer_id,
      ]);

      const resp: ICode<IOrderLogs> = orderLogs.GET_ORDERS_BY_STATUS_SUCCESS;
      const msg = resp.message;
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        orders,
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.GET_ORDERS_BY_STATUS_ERROR.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.GET_ORDERS_BY_STATUS_ERROR.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  static updateSchoolOrder = async (
    orderId: number,
    course_id: number,
    pack_id: number,
    school_id: number,
    price: number,
    status: string,
    note: string | null
  ): Promise<ResponseT> => {
    try {
      const updatedAt = new Date();

      const sqlUpdateQuery =
        'UPDATE orders SET course_id = ?, pack_id = ?, school_id = ?, price = ?, status = ?, note = ?, updatedAt = ? WHERE order_id = ?';

      await db.query<RowDataPacket[]>(sqlUpdateQuery, [
        course_id,
        pack_id,
        school_id,
        price,
        status,
        note,
        updatedAt,
        orderId,
      ]);

      const resp: ICode<IOrderLogs> = orderLogs.UPDATE_ORDER_SUCCESS;
      const msg = formatString(resp.message, {
        orderId,
      });
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        null,
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.UPDATE_ORDER_ERROR.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.UPDATE_ORDER_ERROR.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };

  static updateTeacherOrder = async (
    orderId: number,
    course_id: number,
    pack_id: number,
    teacher_id: number,
    price: number,
    status: string,
    note: string | null
  ): Promise<ResponseT> => {
    try {
      const updatedAt = new Date();

      const sqlUpdateQuery =
        'UPDATE orders SET course_id = ?, pack_id = ?, teacher_id = ?, price = ?, status = ?, note = ?, updatedAt = ? WHERE order_id = ?';

      await db.query<RowDataPacket[]>(sqlUpdateQuery, [
        course_id,
        pack_id,
        teacher_id,
        price,
        status,
        note,
        updatedAt,
        orderId,
      ]);

      const resp: ICode<IOrderLogs> = orderLogs.UPDATE_ORDER_SUCCESS;
      const msg = formatString(resp.message, {
        orderId,
      });
      orderLogger.info(msg, { type: resp.type });

      return new SuccessResponseC(
        resp.type,
        null,
        msg,
        HttpCodes.Accepted.code
      );
    } catch (err) {
      const msg = formatString(orderLogs.UPDATE_ORDER_ERROR.message, {
        error: (err as Error)?.message || '',
      });
      orderLogger.error(msg, err as Error);
      return new ErrorResponseC(
        orderLogs.UPDATE_ORDER_ERROR.type,
        HttpCodes.InternalServerError.code,
        msg
      );
    }
  };
}

// Orders Table:
// order_id (Primary Key),
// course_id  (Foreign Key referencing Courses table),
// pack_id  (Foreign Key referencing Packs table),
// school_id (Foreign Key referencing Schools table),
// teacher_id (Foreign Key referencing Teachers table),
// price
// status
// note