HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux ip-10-0-8-47 6.8.0-1021-aws #23~22.04.1-Ubuntu SMP Tue Dec 10 16:31:58 UTC 2024 aarch64
User: ubuntu (1000)
PHP: 8.1.2-1ubuntu2.22
Disabled: NONE
Upload Files
File: /var/www/javago-api-updates/src/helper/schedule.js
import GroupCoffeeRun from "../models/group_coffee_run.model.js";
import User from "../models/users.model.js";
import Orders from "../models/orders.model.js";
import moment from "moment/moment.js";
import scheduleJob from "node-schedule";
import { messages } from "../config/response.messages.js";
import firebaseAdmin from "./firebase/firebase_admin.js";
import NotificationList from "../models/notification_list.model.js";
import { Op, where } from "sequelize";
import Cafes from "../models/cafes.model.js";
import notification from "./notification.js";
import { findById } from "../services/auth/auth.services.js";
const schedule = {};

schedule.createSchedule = async function (data) {
  try {
    const exactTime = moment.unix(data.time);
    const scheduleId = Math.floor(Math.random() * 1000);
    scheduleJob.scheduleJob(`${scheduleId}`, exactTime.toDate(), async () => {
      const usersGroupCoffeeRun = await GroupCoffeeRun.findAll({
        attributes: ["id", "group_id", "user_id"],
        where: {
          request_unique_id: data.coffeeRunId,
          user_id: data.sender_id,
        },
        include: [
          {
            model: User,
            as: "user_data",
            attributes: ["id", "fcm_token"],
            where: {
              notification_status: 1,
            },
          },
        ],
      });

      const scheduledNotifications = usersGroupCoffeeRun.map((e) => {
        return {
          sender_id: data.sender_id,
          receiver_id: e.dataValues.user_data.dataValues.id,
          reference_id: data.reference_id,
          notification_type: 6,
          is_read: 0,
          created_at: moment().unix(),
          updated_at: moment().unix(),
        };
      });

      if (
        data.clickCollectStatus &&
        data.clickCollectStatus.toLowerCase() ===
          "click and collect is available".toLowerCase()
      ) {
        await NotificationList.bulkCreate(scheduledNotifications);
      }

      const tokens = usersGroupCoffeeRun
        .filter((e) => e && e.dataValues.user_data.dataValues.fcm_token)
        .map((e) => e.dataValues.user_data.dataValues.fcm_token);

      if (tokens.length == 0) {
        console.log("No tokens available");
        return;
      }

      const payload = {
        tokens: tokens,
        title: messages.schedule_notification_title,
        body: messages.schedule_notification_body,
        notification_type: `6`,
        data: {
          group_id: `${usersGroupCoffeeRun[0].dataValues.group_id}`,
          notification_type: `6`,
        },
      };

      if (
        data.clickCollectStatus &&
        data.clickCollectStatus.toLowerCase() ===
          "click and collect is available".toLowerCase()
      ) {
        await firebaseAdmin.sendNotification(payload);
      }

      // ✅ Update orders table to set is_timer_completed = 1 for the coffee run
      await Orders.update(
        { is_timer_completed: 1 },
        {
          where: {
            request_unique_id: data.coffeeRunId,
          },
        }
      );

      await syncGroupOrdersToSquareGrouped(data.coffeeRunId);

      //here get the order of the coffee run and then save to square

      let isInitiator = await GroupCoffeeRun.findOne({
        where: {
          request_unique_id: data.coffeeRunId,
          user_id: data.sender_id,
          request_created_by: data.sender_id,
        },
        attributes: ["id", "request_created_by", "order_id"],
      });
      let userData = await User.findOne({
        where: {
          id: data.sender_id,
        },
        attributes: ["id", "name"],
      });
      console.log("isInitiator", isInitiator);
      if (isInitiator) {
        const orderId = isInitiator.dataValues.order_id;
        const result = await Orders.findOne({
          where: {
            id: orderId,
          },
          attributes: [
            "id",
            "cafe_id",
            "group_coffee_run_id",
            "user_id",
            "estimated_arival_time",
          ],
        });
        const cafeData = await Cafes.findOne({
          where: {
            id: result.cafe_id,
          },
        });
        const message = `${userData.name} has placed a new order. Order number is ${result.dataValues.group_coffee_run_id}`;
        //send notification to cafe
        await NotificationList.create({
          sender_id: userData.id,
          receiver_id: cafeData.id,
          reference_id: isInitiator.id,
          notification_type: 20,
          message: message,
          is_read: 0,
          created_at: moment().unix(),
          updated_at: moment().unix(),
        });

        const notification_response = notification.newOrderNotification({
          recieverId: result.cafe_id,
          orderId: result.dataValues.id,
          senderId: result.user_id,
          message: message,
          estimated_arival_time: result.estimated_arival_time,
          is_individual_order: 0,
        });
        console.log("notification", notification_response);
      }

      console.log(`Order status updated for coffee run: ${data.coffeeRunId}`);
    });
  } catch (e) {
    console.log(e);
    throw e;
  }
};

import { v4 as uuidv4 } from "uuid"; // Ensure you have uuid installed

async function syncGroupOrdersToSquareGrouped(coffeeRunId) {
  try {
    const orders = await Orders.findAll({
      where: { request_unique_id: coffeeRunId },
    });

    if (!orders.length) return;

    const cafeId = orders[0].cafe_id;

    const cafe = await Cafes.findOne({
      where: { id: cafeId },
    });

    if (!cafe || !cafe.square_access_token || !cafe.square_location_id) {
      console.error("Missing cafe Square config");
      return;
    }

    const locationId = JSON.parse(cafe.square_location_id)[0];
    const allLineItems = [];

    // Get user for pickup info (initiator)
    const initiator = await User.findOne({ where: { id: orders[0].user_id } });
    //const pickupTime = new Date(Date.now() + 10 * 60 * 1000).toISOString();

let pickupTime;

const estimatedTimeRaw = orders?.[0]?.estimated_arival_time;

if (!estimatedTimeRaw || estimatedTimeRaw.toLowerCase() === "asap") {
  // Use current time
  pickupTime = new Date().toISOString();
//pickupTime = new Date(Date.now() + 05 * 60 * 1000).toISOString();
} else {
  try {
    const timeStr = estimatedTimeRaw.trim(); // e.g., "05:59"
    const [hoursStr, minutesStr] = timeStr.split(":");

    const hours = parseInt(hoursStr, 10);
    const minutes = parseInt(minutesStr, 10);

    if (
      isNaN(hours) ||
      isNaN(minutes) ||
      hours < 0 || hours > 23 ||
      minutes < 0 || minutes > 59
    ) {
      throw new Error("Invalid estimated arrival time format: " + timeStr);
    }

    const now = new Date();
    const composedDate = new Date(
      now.getFullYear(),
      now.getMonth(),
      now.getDate(),
      hours,
      minutes,
      0
    );

    pickupTime = composedDate.toISOString();
  } catch (err) {
    throw new Error("Failed to parse estimated arrival time: " + err.message);
  }
}


    for (const order of orders) {
      const user = await User.findOne({ where: { id: order.user_id } });
      const orderItems = JSON.parse(order.order_item_array || "[]");

      orderItems.forEach((item) => {
        const lineItem = {
          name: item.item_name,
          quantity: item.item_quantity.toString(),
          base_price_money: {
            amount: Math.round(Number(item.item_amount) * 100),
            currency: "GBP",
          },
          note: `User: ${user?.name || "Guest"} | Order ID: ${order.id}`,
        };

        if (
          item.addon_sizes &&
          Array.isArray(item.addon_sizes) &&
          item.addon_sizes.length > 0
        ) {
          lineItem.modifiers = item.addon_sizes.map((addon) => ({
            name: `${addon.addon_name} - ${addon.addon_size_name}`,
            base_price_money: {
              amount: 0,
              currency: "GBP",
            },
            quantity: addon.quantity ? addon.quantity.toString() : "1",
          }));
        }

        allLineItems.push(lineItem);
      });
    }

    const orderPayload = {
      order: {
        location_id: locationId,
        line_items: allLineItems,
        fulfillments: [
          {
            type: "PICKUP",
            state: "PROPOSED",
            pickup_details: {
              recipient: {
                display_name: initiator?.name || "Coffee Run",
                phone_number: initiator?.customer_phone || "",
                email_address: initiator?.email || "",
              },
              pickup_at: pickupTime,
              note: `Coffee run group order: ${coffeeRunId}`,
            },
          },
        ],
        metadata: {
          source: "Javago Group Order",
          coffee_run_id: coffeeRunId,
        },
      },
      idempotency_key: `grouped-${coffeeRunId}-${Date.now()}`,
    };

    const squareOrderRes = await fetch(
      "https://connect.squareup.com/v2/orders",
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${cafe.square_access_token}`,
          "Content-Type": "application/json",
          "Square-Version": "2024-06-04",
        },
        body: JSON.stringify(orderPayload),
      }
    );

    const orderResult = await squareOrderRes.json();

    if (!squareOrderRes.ok || !orderResult.order?.id) {
      console.error("❌ Square grouped order failed:", orderResult);
      return;
    }

    const squareOrderId = orderResult.order.id;

    // Now mark payment as external (cash or other)
    const paymentPayload = {
      source_id: "EXTERNAL",
      order_id: squareOrderId,
      amount_money: {
        amount: orderResult.order.total_money.amount,
        currency: orderResult.order.total_money.currency,
      },
      external_details: {
        type: "OTHER",
        source: "Javago Cafe App - Pay on Pickup",
        source_id: `javago-${orders[0].id}`,
        source_fee_money: {
          amount: 0,
          currency: orderResult.order.total_money.currency,
        },
      },
      idempotency_key: `payment-${orders[0].id}-${Date.now()}`,
      autocomplete: true,
      note: "Pre-authorized payment - Customer will pay on pickup",
    };

    const paymentRes = await fetch("https://connect.squareup.com/v2/payments", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${cafe.square_access_token}`,
        "Content-Type": "application/json",
        "Square-Version": "2024-06-04",
      },
      body: JSON.stringify(paymentPayload),
    });

    const paymentResult = await paymentRes.json();

    if (!paymentRes.ok || !paymentResult.payment?.id) {
      console.error("❌ Square payment failed:", paymentResult);
      return;
    }

    // ✅ Save the Square order ID to all matching local orders
    await Orders.update(
      {
        square_order_id: squareOrderId,
        square_order_state: orderResult.order.state,
        square_created_at: orderResult.order.created_at,
      },
      { where: { request_unique_id: coffeeRunId } }
    );

    console.log(
      `✅ Grouped order created and saved for coffeeRunId: ${coffeeRunId}`
    );
  } catch (err) {
    console.error("🚨 syncGroupOrdersToSquareGrouped error:", err);
  }
}

async function oldsyncGroupOrdersToSquareGrouped(coffeeRunId) {
  try {
    const orders = await Orders.findAll({
      where: { request_unique_id: coffeeRunId },
    });

    if (!orders.length) {
      console.warn(`No orders found for coffeeRunId: ${coffeeRunId}`);
      return;
    }

    const cafe_id = orders[0].cafe_id;

    // Fetch cafe separately
    const cafe = await Cafes.findOne({
      where: { id: cafe_id },
    });

    if (!cafe || !cafe.square_access_token || !cafe.square_location_id) {
      console.error("Missing cafe Square config");
      return;
    }

    const locationId = JSON.parse(cafe.square_location_id)[0];
    const allLineItems = [];

    for (const order of orders) {
      const user = await User.findOne({ where: { id: order.user_id } });
      const orderItems = JSON.parse(order.order_item_array || "[]");

      orderItems.forEach((item) => {
        const lineItem = {
          name: item.item_name,
          quantity: item.item_quantity.toString(),
          base_price_money: {
            amount: Math.round(Number(item.item_amount) * 100),
            currency: "GBP",
          },
          note: `User: ${user?.name || "Unknown"} | Order ID: ${order.id}`,
        };

        if (Array.isArray(item.addon_sizes) && item.addon_sizes.length > 0) {
          lineItem.modifiers = item.addon_sizes.map((addon) => ({
            name: `${addon.addon_name} - ${addon.addon_size_name}`,
            base_price_money: {
              amount: 0,
              currency: "GBP",
            },
            quantity: addon.quantity ? addon.quantity.toString() : "1",
          }));
        }

        allLineItems.push(lineItem);
      });
    }

    // Use first user's info as the initiator
    const initiator = await User.findOne({ where: { id: orders[0].user_id } });
    const pickupTime = new Date(Date.now() + 10 * 60 * 1000).toISOString();

    const orderPayload = {
      order: {
        location_id: locationId,
        line_items: allLineItems,
        fulfillments: [
          {
            type: "PICKUP",
            state: "PROPOSED",
            pickup_details: {
              recipient: {
                display_name: initiator?.name || "Coffee Run",
                phone_number: initiator?.customer_phone || "",
                email_address: initiator?.email || "",
              },
              pickup_at: pickupTime,
              note: `Coffee run group order: ${coffeeRunId}`,
            },
          },
        ],
        metadata: {
          source: "Javago Group Order",
          coffee_run_id: coffeeRunId,
        },
      },
      idempotency_key: `grouped-${coffeeRunId}-${Date.now()}`,
    };

    // Create Square Order
    const squareRes = await fetch("https://connect.squareup.com/v2/orders", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${cafe.square_access_token}`,
        "Content-Type": "application/json",
        "Square-Version": "2024-06-04",
      },
      body: JSON.stringify(orderPayload),
    });

    const squareResult = await squareRes.json();

    if (squareRes.ok && squareResult.order?.id) {
      const squareOrderId = squareResult.order.id;

      // Save the same Square order ID to all Orders
      await Orders.update(
        {
          square_order_id: squareOrderId,
          square_order_state: squareResult.order.state,
          square_created_at: squareResult.order.created_at,
        },
        { where: { request_unique_id: coffeeRunId } }
      );

      // 💳 Create payment to make it visible in Square dashboard
      const paymentPayload = {
        source_id: "EXTERNAL",
        order_id: squareOrderId,
        amount_money: {
          amount: squareResult.order.total_money.amount,
          currency: squareResult.order.total_money.currency,
        },
        external_details: {
          type: "OTHER",
          source: "Javago Cafe App - Pay on Pickup",
          source_id: `javago-${orders[0].id}`,
          source_fee_money: {
            amount: 0,
            currency: squareResult.order.total_money.currency,
          },
        },
        idempotency_key: `payment-${orders[0].id}-${Date.now()}`,
        autocomplete: true,
        note: "Pre-authorized payment - Customer will pay on pickup",
      };

      const paymentRes = await fetch(
        "https://connect.squareup.com/v2/payments",
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${cafe.square_access_token}`,
            "Content-Type": "application/json",
            "Square-Version": "2024-06-04",
          },
          body: JSON.stringify(paymentPayload),
        }
      );

      const paymentResult = await paymentRes.json();

      if (paymentRes.ok && paymentResult.payment?.id) {
        await Orders.update(
          {
            square_payment_id: paymentResult.payment.id,
            square_payment_status: paymentResult.payment.status,
            square_paid_at: new Date(),
          },
          { where: { request_unique_id: coffeeRunId } }
        );

        console.log(
          `✅ Grouped order & payment created for coffeeRunId: ${coffeeRunId}`
        );
      } else {
        console.error("❌ Square payment creation failed:", paymentResult);
      }
    } else {
      console.error("❌ Square order creation failed:", squareResult);
    }
  } catch (err) {
    console.error("🚨 syncGroupOrdersToSquareGrouped error:", err);
  }
}

export default schedule;