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/services/order/order.services.js
import PredefinedItemImageDB from "../../models/pre_defined_item_images.model.js";
import AddonSizeCafeItemDB from "../../models/addon_size_cafe_items.model.js";
import CafeMenuItemSizeDB from "../../models/cafe_menu_item_sizes.model.js";
import UserFavouriteCafeDB from "../../models/user_favorite_cafes.model.js";
import LoyaltyStampDB from "../../models/manage_loyalty_stamps.model.js";
import GroupCoffeeRunDB from "../../models/group_coffee_run.model.js";
import NotificationDB from "../../models/notification_list.model.js";
import CafeMenuItemDB from "../../models/cafe_menu_items.model.js";
import CafeTimingDB from "../../models/cafe_timings.model.js";
import GroupMemberDB from "../../models/group_members.model.js";
import { messages } from "../../config/response.messages.js";
import AddonSizeDB from "../../models/addon_sizes.model.js";
import GroupDB from "../../models/groups.model.js";
import OrderDB from "../../models/orders.model.js";
import DiscountDB from "../../models/discount.model.js";
import CafeDB from "../../models/cafes.model.js";
import UserDB from "../../models/users.model.js";
import UserStampsAwardedDB from "../../models/UserStampsAwarded.js";
import CafeManagementDB from "../../models/CafeManagement.model.js";
import CafeClickCollectTimingDB from "../../models/CafeClickCollectTiming.model.js";
import InHouseOrderItems from "../../models/in_house_order_items.model.js";
import CafeGoToOrdersDB from "../../models/cafe_go_to_orders.model.js";
import * as config from "../../config/config.js";
import SizeDB from "../../models/sizes.model.js";
import * as Helper from "../../helper/index.js";
import Sequelize, { json } from "sequelize";
import { Op, fn, col, where } from "sequelize";
import moment from "moment";
import coffeeRunInHouseOrders from "../../models/coffee_run_inhouse_order.model.js";
import schedule from "../../helper/schedule.js";
import notification from "../../helper/notification.js";
import Order from "../../models/orders.model.js";
import momentTime from "moment-timezone";

// create Group
export async function saveGroupCoffeeRun(data, loggedInUser) {
  try {
    // let request_unique_id = (
    //   await Helper.generateRandomString(8, true)
    // ).toUpperCase();
    let request_unique_id;
    let isUnique = false;

    while (!isUnique) {
      request_unique_id = (
        await Helper.generateRandomString(8, true)
      ).toUpperCase();

      const existing = await GroupCoffeeRunDB.findOne({
        where: { request_unique_id },
      });

      if (!existing) {
        isUnique = true;
      }
    }

    let createRequestArr = [];
    if (data.type === 0) {
      data.users.forEach(async (element) => {
        createRequestArr.push({
          request_unique_id: request_unique_id,
          type: data.type,
          group_id: data.group_id,
          user_id: element.id,
          is_contributor_person: element.is_contributor_person,
          request_created_by: loggedInUser.id,
          request_endtime: data.request_endtime,
          created_at: moment().unix(),
          updated_at: moment().unix(),
        });
      });
    } else {
      data.users.forEach(async (element) => {
        createRequestArr.push({
          request_unique_id: request_unique_id,
          cafe_id: data.cafe_id,
          group_id: data.group_id,
          user_id: element.id,
          is_contributor_person: element.is_contributor_person,
          request_created_by: loggedInUser.id,
          request_endtime: data.request_endtime,
          created_at: moment().unix(),
          updated_at: moment().unix(),
        });
      });
    }

    var requestResults = await GroupCoffeeRunDB.bulkCreate(createRequestArr, {
      returning: true,
    });

    var notificationData = requestResults
      .filter((e) => e.dataValues.user_id != loggedInUser.id)
      .map((e) => {
        return {
          sender_id: loggedInUser.id,
          receiver_id: e.dataValues.user_id,
          reference_id: e.dataValues.id,
          notification_type: 2,
          is_read: 0,
          created_at: moment().unix(),
          updated_at: moment().unix(),
        };
      });

    await NotificationDB.bulkCreate(notificationData);

    const coffeRun = await GroupCoffeeRunDB.findOne({
      where: {
        request_unique_id: request_unique_id,
        user_id: loggedInUser.id,
      },
      include: [
        {
          model: CafeDB,
          required: false,
          attributes: ["id"],
          include: [
            {
              model: CafeGoToOrdersDB,
              required: false,
              attributes: ["id"],
              where: {
                user_id: loggedInUser.id,
              },
            },
          ],
        },
      ],
    });
    //console.log(coffeRun);

    await notification.createCoffeeRunNotification({
      loggedInUser: loggedInUser.name,
      loggedInUserId: loggedInUser.id,
      userIds: notificationData.map((e) => e.receiver_id),
      cafe_id: data.cafe_id,
      group_id: data.group_id,
      request_unique_id: request_unique_id,
      request_endtime: data.request_endtime,
    });
    let clickCollectStatus;
    let CafeManagement = await CafeManagementDB.findOne({
      where: {
        cafe_id: data.cafe_id,
      },
    });
    if (!CafeManagement) {
      clickCollectStatus = null;
    }
    if (CafeManagement.click_and_collect == 0) {
      clickCollectStatus = null;
    }
    let today = moment().tz("Europe/London").day(); // Sunday = 0
    let yesterday = today === 0 ? 6 : today - 1;
    console.log(today, yesterday);
    let CafeClickCollectTiming = await CafeClickCollectTimingDB.findOne({
      where: {
        cafe_id: data.cafe_id,
        day: today,
        is_active: 1,
      },
    });
    if (!CafeClickCollectTiming) {
      clickCollectStatus = "Not Available!";
//return { clickandcollect: 0, message: "No timing found for today" };

    }
    let { start_time, end_time } = CafeClickCollectTiming || {};

let start = momentTime(start_time, "HH:mm:ss");
    let end = momentTime(end_time, "HH:mm:ss");

let slots = [];
    while (start.isBefore(end)) {
      let slotStart = start.clone();
      let slotEnd = momentTime.min(start.clone().add(10, "minutes"), end);

      slots.push({
        start_time: slotStart.format("HH:mm:ss"),
        end_time: slotEnd.format("HH:mm:ss"),
      });

      start.add(10, "minutes");
    }
    console.log("Available Click and Collect Slots: ", slots);

    // Get current UK time
    let now = momentTime().tz("Europe/London");
    let currentTime = now.format("HH:mm:ss");	console.log("current time", currentTime);

    let currentSlot = slots.find((slot) => {
      return currentTime >= slot.start_time && currentTime < slot.end_time;
    });

    if (!currentSlot) {
      return {
        clickandcollect: 0,
        message: "Click And Collect is not available (outside of slot time)",
      };
    }

    let orderCount = await OrderDB.count({
      where: {
        cafe_id: data.cafe_id,
        [Op.and]: [
          where(
            fn("DATE", fn("FROM_UNIXTIME", col("order_placed_at"))),
            now.format("YYYY-MM-DD")
          ),
          where(fn("TIME", fn("FROM_UNIXTIME", col("order_placed_at"))), {
            [Op.between]: [currentSlot.start_time, currentSlot.end_time],
          }),
        ],
        estimated_arival_time: { [Op.ne]: null },
      },
    });


    if (orderCount <= CafeManagement.max_orders_click_collect) {
      clickCollectStatus = "Click And Collect is available";
    } else {
      //await CafeManagementDB.update(
        //{ click_and_collect: 0 },
        //{
          //where: {
           // cafe_id: data.cafe_id,
          //},
        //}
      //);
      clickCollectStatus = "Click And Collect is not available";
    }
//console.log("orderCount :",orderCount );
  //  console.log("Click & Collect Status: ", clickCollectStatus);
    // Proceed to create schedule
    await schedule.createSchedule({
      sender_id: loggedInUser.id,
      time: coffeRun.dataValues.request_endtime,
      receiver_id: notificationData[0].receiver_id,
      reference_id: coffeRun.dataValues.id,
      coffeeRunId: coffeRun.dataValues.request_unique_id,
      clickCollectStatus: clickCollectStatus,
    });
	//console.log('service file', coffeRun);
    return coffeRun;
  } catch (error) {
    console.log(error);
    throw new Error(error);
  }
}

//get cafe list by group id and edit time
export async function getGroupCoffeeRun(data) {
  try {
    if ([null, undefined, 0, "", "0"].includes(data.cafe_id)) {
      let check = await GroupCoffeeRunDB.findOne({
        where: {
          group_id: data.group_id,
          request_endtime: {
            [Op.gte]: moment().unix(),
          },
        },
      });
      if (check) {
        data.cafe_id = check.cafe_id;
      } else {
        return [];
      }
    }

    let result = await GroupCoffeeRunDB.findAll({
      order: [["id", "DESC"]],
      where: {
        group_id: parseInt(data.group_id),
        cafe_id: parseInt(data.cafe_id),
        request_endtime: {
          [Op.gte]: moment().unix(),
        },
        order_id: null,
        request_endtime: {
          [Op.gte]: moment().unix(),
        },
      },
      order: [["id", "DESC"]],
      include: [
        {
          model: UserDB,
          as: "creator_data",
          where: {
            is_deleted: 0,
            is_active: 1,
          },
          required: true,
          attributes: ["id", "name", "profile_picture", "email"],
        },
        {
          model: GroupDB,
          where: {
            is_active: 1,
          },
          include: [
            {
              model: GroupMemberDB,
              as: "group_details",
              attributes: ["id", "user_id"],
              include: [
                {
                  model: UserDB,
                  as: "user_data",
                  attributes: ["id", "name", "email", "profile_picture"],
                  where: { is_deleted: 0, is_active: 1, is_verified: 1 },
                },
              ],
            },
          ],
          required: true,
          attributes: [
            "id",
            "group_name",
            "group_profile",
            "created_at",
            "group_code",
          ],
        },
        {
          model: UserDB,
          as: "user_data",
          where: {
            is_deleted: 0,
            is_active: 1,
          },
          required: true,
          attributes: ["id", "name", "profile_picture", "email"],
        },
      ],
      attributes: [
        "id",
        "request_unique_id",
        "is_contributor_person",
        "is_order_place",
        "order_id",
        "request_endtime",
        "created_at",
        "cafe_id",
      ],
    });
    return result;
  } catch (error) {
    console.log(error);
    throw new Error(error);
  }
}

// check same time group cafe run can create or not
export async function checkSameGroupCafeRunEndTime(data) {
  try {
    return await GroupCoffeeRunDB.findOne({
      where: {
        group_id: data.group_id,
        user_id: data.loggedInUser.id,
        request_endtime: {
          [Op.gte]: moment().unix(),
        },
      },
      include: [
        {
          model: CafeDB,
          required: false,
          attributes: ["id"],
          include: [
            {
              model: CafeGoToOrdersDB,
              required: false,
              attributes: ["id"],
              where: {
                user_id: data.loggedInUser.id,
              },
            },
          ],
        },
      ],
    });
  } catch (error) {
    return null;
  }
}

// place order
export async function placeOrder(data) {
  try {
    let todayDayId = new Date().getDay();
    let currentTime = moment().format("HH:mm");
    if (data.group_coffee_run_id) {
      let checkRequestEndTime = await GroupCoffeeRunDB.findOne({
        where: {
          request_unique_id: data.group_coffee_run_id,
        },
      });
      if (!checkRequestEndTime) {
        return {
          status: 203,
          message: messages.group_coffee_run_wrong_passed,
          result: {},
        };
      } else if (
        checkRequestEndTime &&
        checkRequestEndTime.request_endtime < moment().unix()
      ) {
        return {
          status: 203,
          message: messages.group_coffee_run_end_time_up,
          result: {},
        };
      }
    }
    let itemsArray = data.order_item_array.map(function (currentValue) {
      return currentValue.item_id;
    });
    let result = await CafeDB.findOne({
      where: {
        id: data.cafe_id,
        is_active: 1,
        deleted_at: 0,
      },
      include: [
        {
          model: CafeTimingDB,
          where: {
            day: todayDayId,
            open_time: {
              [Op.lte]: currentTime,
            },
            close_time: {
              [Op.gte]: currentTime,
            },
          },
          required: false,
          attributes: ["id"],
          as: "time_sheet_data",
        },
        {
          model: CafeMenuItemDB,
          where: {
            id: {
              [Op.in]: itemsArray,
            },
          },
          include: [
            {
              model: CafeMenuItemSizeDB,
              required: false,
              include: [
                {
                  model: SizeDB,
                  required: false,
                  attributes: ["size_name"],
                },
              ],
              attributes: ["item_size_price"],
            },
            {
              model: AddonSizeCafeItemDB,
              required: false,
              include: [
                {
                  model: AddonSizeDB,
                  required: false,
                  attributes: ["addon_size_price"],
                },
              ],
              attributes: ["addon_size_id"],
            },
          ],
          required: false,
          attributes: [
            "id",
            "item_price",
            "status",
            "item_deleted_at",
            "item_name",
          ],
        },
      ],
      attributes: ["id"],
    });
    let final_result;
    if (!result) {
      final_result = {
        status: 203,
        message: messages.cafe_is_not_available,
      };
    } else if (result && result.time_sheet_data.length == 0) {
      final_result = {
        status: 203,
        message: messages.cafe_has_been_closed,
      };
    } else if (result && result.cafe_menu_items.length == 0) {
      final_result = {
        status: 203,
        message: messages.cafe_selected_items_not_avaialble,
      };
    } else if (result && result.cafe_menu_items.length) {
      let message = "";
      let items_prices = 0;
      for (let index = 0; index < result.cafe_menu_items.length; index++) {
        const element = result.cafe_menu_items[index];
        if (element.status == 0 || element.item_deleted_at > 0) {
          message += element.item_name;
          if (
            result.cafe_menu_items.length > 1 &&
            index != parseInt(result.cafe_menu_items.length) - 1
          ) {
            message += ", ";
          }
        }
        if (
          data.order_item_array.some(
            (children) =>
              children.item_size == "Regular" && children.item_id == element.id
          )
        ) {
          var qty = 1;
          data.order_item_array.map((children) => {
            if (children.item_id == element.id) {
              qty = children.item_quantity;
            }
          });
          items_prices = items_prices + Number(element.item_price) * qty;

          // now check addons prices
          for (let i = 0; i < data.order_item_array.length; i++) {
            const element1 = data.order_item_array[i];
            if (element1.item_id == element.id) {
              for (let j = 0; j < element1.addon_sizes.length; j++) {
                const element2 = element1.addon_sizes[j];
                let check = 0;
                element.addon_size_cafe_items.map((grandChildren) => {
                  if (
                    grandChildren.addon_size_id == element2.addon_size_id &&
                    element1.item_id == element.id
                  ) {
                    check = grandChildren.addon_size.addon_size_price;
                  }
                });
                items_prices += check;
              }
            }
          }
        } else if (
          data.order_item_array.some(
            (children) =>
              children.item_size != "Regular" && children.item_id == element.id
          )
        ) {
          for (let i = 0; i < data.order_item_array.length; i++) {
            const element1 = data.order_item_array[i];
            if (
              element1.item_size != "Regular" &&
              element.cafe_menu_item_sizes.some(
                (children) =>
                  children.size.size_name == element1.item_size &&
                  element1.item_id == element.id
              )
            ) {
              var tmp = 0;
              element.cafe_menu_item_sizes.map(function (childrenElement) {
                if (childrenElement.size.size_name == element1.item_size) {
                  tmp = childrenElement.item_size_price;
                }
              });
              items_prices += tmp ? tmp * element1.item_quantity : 0;
            }
            // now check addons prices
            if (element1.item_id == element.id) {
              for (let j = 0; j < element1.addon_sizes.length; j++) {
                const element2 = element1.addon_sizes[j];
                let check = 0;
                element.addon_size_cafe_items.map((grandChildren) => {
                  if (
                    grandChildren.addon_size_id == element2.addon_size_id &&
                    element1.item_id == element.id
                  ) {
                    check = grandChildren.addon_size.addon_size_price;
                  }
                });
                items_prices += check;
              }
            }
          }
        }
      }
      if (message) {
        final_result = {
          status: 203,
          message: message + " items not available",
          result: result,
        };
      } else if (
        message == "" &&
        Number(items_prices) != Number(data.items_amount)
      ) {
        final_result = {
          status: 203,
          message: messages.item_price_updated_error,
          result: result,
        };
      } else {
        if (data.loyalty_stamp_id) {
          let checkLoyaltyStamp = await LoyaltyStampDB.findOne({
            where: {
              id: parseInt(data.loyalty_stamp_id),
            },
          });
          if (checkLoyaltyStamp) {
            final_result = {
              status: 200,
              message: messages.data_found,
              result: result,
            };
          } else {
            final_result = {
              status: 203,
              message: messages.redeem_code_no_longer_available,
              result: result,
            };
          }
        } else {
          final_result = {
            status: 200,
            message: messages.data_found,
            result: result,
          };
        }
      }
      console.log(items_prices);
    }
    return final_result;
  } catch (error) {
    throw new Error(error);
  }
}

// save order
export async function saveOrder(data) {
  try {
    for (let index = 0; index < data.order_item_array.length; index++) {
      const element = data.order_item_array[index];
      let itemImageData = await CafeMenuItemDB.findByPk(element.item_id, {
        include: [
          {
            model: PredefinedItemImageDB,
            as: "image_data",
            required: true,
          },
        ],
      });
      element.item_image = itemImageData
        ? itemImageData.image_data.item_image
        : "";
    }
    //const dateInUK = new Date().toLocaleString("en-US", {
      //timeZone: "Europe/London",
    //});
    //const timestampUK = Math.floor(new Date(dateInUK).getTime() / 1000);

const timestampUTC = Math.floor(Date.now() / 1000);

// When you display, format to UK or India
const ukTime = new Date(timestampUTC * 1000).toLocaleString("en-GB", {
  timeZone: "Europe/London",
});
    let result = await OrderDB.create({
      order_number: await Helper.generateRandomString(6, true),
      user_id: data.loggedInUser,
      cafe_id: data.cafe_id,
      group_id: data.group_id ? data.group_id : null,
      group_coffee_run_id: data.group_coffee_run_id
        ? data.group_coffee_run_id
        : null,
      loyalty_stamp_id: data.loyalty_stamp_id ? data.loyalty_stamp_id : null,
      is_main_order: data.is_main_order,
      order_item_array: JSON.stringify(data.order_item_array),
      additional_note: data.additional_note,
      total_amount: data.total_amount ? Number(data.total_amount) : 0,
      tax: data.tax ? Number(data.tax) : 0,
      service_charge: data.service_charge ? Number(data.service_charge) : 0,
      other_charge: data.other_charge ? Number(data.other_charge) : 0,
      transaction_id: data.transaction_id,
      estimated_arival_time: data.estimated_arival_time ?? null,
      discount_amount: data.discount_amount ? Number(data.discount_amount) : 0,
      order_placed_at: moment().unix(),
      //order_placed_at: ukTime,
      status: 1,
      created_at: moment().unix(),
      updated_at: moment().unix(),
    });

    let nonUniversalStampsDetails = await getStampsByCafe(data.cafe_id);
    //Subtract the non-universal stamps if applied
    if (data.discount_amount > 0) {
      if (data.stamp_type == "non_universal") {
        let stampAward = await UserStampsAwardedDB.findOne({
          where: {
            user_id: data.loggedInUser,
            cafe_id: data.cafe_id,
            stamp_id: nonUniversalStampsDetails.id,
          },
        });

        if (stampAward) {
          await stampAward.update({
            quantity: stampAward.quantity - 1,
          });
        }
      } else {
        const userStamp = await UserDB.findByPk(data.loggedInUser);
        if (userStamp) {
          // Assuming remaining_awarded_stamps is a field in UserDB
          await UserDB.update(
            {
              remaining_awarded_stamps: userStamp.remaining_awarded_stamps - 1,
            },
            {
              where: {
                id: data.loggedInUser,
              },
            }
          );
        }
      }
    }

    let totalOrdersCount = await OrderDB.count({
      where: {
        user_id: data.loggedInUser,
        cafe_id: data.cafe_id,
      },
    });
    let currentDate = moment().format("YYYY-MM-DD");
    nonUniversalStampsDetails.is_expired = 0;

    let cafeExpireDate = getLastExpireCafeDate(data.loggedInUser, data.cafe_id);
    if (cafeExpireDate) {
      if (
        currentDate >
        moment
          .unix(cafeExpireDate.cafe_coupon_expired_time)
          .format("YYYY-MM-DD")
      ) {
        nonUniversalStampsDetails.is_expired = 1;
      }
    }

    const totalOrders = totalOrdersCount / nonUniversalStampsDetails.stamp_no;
    console.log("totalOrders", totalOrders);
    let awardStamp = null;
    if (Math.floor(totalOrders) > 0 && totalOrders == Math.floor(totalOrders)) {
      awardStamp = await UserStampsAwardedDB.findOne({
        where: {
          user_id: data.loggedInUser,
          cafe_id: data.cafe_id,
          stamp_id: nonUniversalStampsDetails.id,
        },
      });

      if (awardStamp) {
        await awardStamp.update({
          quantity: awardStamp.quantity + 1,
        });
      } else {
        awardStamp = await UserStampsAwardedDB.create({
          user_id: data.loggedInUser,
          cafe_id: data.cafe_id,
          stamp_id: nonUniversalStampsDetails.id,
          quantity: 1,
        });
      }
    }
    console.log("awardStamp", awardStamp);
    // console.log(data);
    // console.log(result);

    //Square implementation
    // Square implementation for pickup order
    let cafeData = await CafeDB.findByPk(data.cafe_id);
    let userData = await UserDB.findByPk(data.loggedInUser);

    // if (
    //   cafeData &&
    //   cafeData.square_access_token &&
    //   cafeData.square_location_id
    // ) {
    //   try {
    //     const locationIds = JSON.parse(cafeData.square_location_id);
    //     const locationId = locationIds[0]; // pick the first location

    //     // Handle pickup time from 'estimated_arrival_time'
    //     let pickupTime;
    //     if (!result.estimated_arival_time ||
    //       result.estimated_arival_time?.toLowerCase() === "asap"
    //     ) {
    //       // Default: 10 minutes from now
    //       pickupTime = new Date(Date.now() + 10 * 60 * 1000).toISOString();
    //     } else {
    //       // Use provided time
    //       const timeStr = result.estimated_arival_time.trim(); // "05:59"
    //       const today = new Date();
    //       const [hours, minutes] = timeStr.split(":");

    //       if (!hours || !minutes || isNaN(hours) || isNaN(minutes)) {
    //         throw new Error("Invalid estimated arrival time: " + timeStr);
    //       }

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

    //       if (!isNaN(composedDate)) {
    //         pickupTime = composedDate.toISOString();
    //       } else {
    //         throw new Error("Invalid constructed pickup time from: " + timeStr);
    //       }
    //     }

    //     const squareOrderPayload = {
    //       order: {
    //         location_id: locationId,
    //         line_items: data.order_item_array.map((item) => {
    //           // Base line item
    //           const lineItem = {
    //             name: item.item_name,
    //             quantity: item.item_quantity.toString(),
    //             base_price_money: {
    //               amount: Math.round(Number(item.item_amount) * 100), // Square expects amount in cents
    //               currency: "GBP",
    //             },
    //             variation_name: item.variation_name || null,
    //             note: item.item_notes || item.item_description || null,
    //           };

    //           // Add modifiers with ZERO price
    //           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}`, // Combined name for clarity
    //               base_price_money: {
    //                 amount: 0, // SET TO ZERO - This prevents adding to total
    //                 currency: "GBP",
    //               },
    //               quantity: addon.quantity ? addon.quantity.toString() : "1",
    //             }));
    //           }

    //           return lineItem;
    //         }),
    //         fulfillments: [
    //           {
    //             type: "PICKUP",
    //             state: "PROPOSED",
    //             pickup_details: {
    //               recipient: {
    //                 display_name: userData.name || "Guest",
    //                 phone_number: userData.customer_phone || "",
    //                 email_address: userData.email || "",
    //               },
    //               pickup_at: pickupTime,
    //               note: "Customer will pick up the order at the front desk",
    //               pickup_note: `Order placed via Javago Cafe App. Contact: ${
    //                 data.customer_phone || userData.customer_phone || "N/A"
    //               }`,
    //             },
    //           },
    //         ],
    //         metadata: {
    //           source: "Javago Cafe App",
    //           order_type: "pickup",
    //           group_order_id: data.group_coffee_run_id || null,
    //           customer_id:
    //             data.customer_id?.toString() ||
    //             data.loggedInUser?.toString() ||
    //             "guest",
    //           internal_order_id: result.id?.toString() || "unknown",
    //           // SOLUTION 1: Remove or truncate the long addons_with_prices field
    //           // addons_count: data.order_item_array.filter(item => item.addon_sizes && item.addon_sizes.length > 0).length.toString()
    //         },
    //         order_source: {
    //           name: "Javago Cafe App",
    //         },
    //       },
    //       idempotency_key: `${result.id}-${Date.now()}`,
    //     };

    //     // Enhanced logging to debug the payload
    //     console.log(
    //       "Square Order Payload (modifiers fixed):",
    //       JSON.stringify(squareOrderPayload, null, 2)
    //     );

    //     // Log each line item's modifiers specifically
    //     squareOrderPayload.order.line_items.forEach((lineItem, index) => {
    //       if (lineItem.modifiers && lineItem.modifiers.length > 0) {
    //         console.log(
    //           `Line item ${index + 1} (${lineItem.name}) has ${
    //             lineItem.modifiers.length
    //           } modifiers:`
    //         );
    //         lineItem.modifiers.forEach((modifier, modIndex) => {
    //           console.log(
    //             `  Modifier ${modIndex + 1}: ${modifier.name} - £${
    //               modifier.base_price_money.amount / 100
    //             }`
    //           );
    //         });
    //       }
    //     });

    //     // Simple validation function
    //     function validateSquarePayload(payload) {
    //       const errors = [];

    //       payload.order.line_items.forEach((item, index) => {
    //         if (!item.name || item.name.trim() === "") {
    //           errors.push(`Line item ${index + 1}: Name is required`);
    //         }

    //         if (!item.base_price_money || item.base_price_money.amount < 0) {
    //           errors.push(`Line item ${index + 1}: Invalid price`);
    //         }

    //         if (item.modifiers) {
    //           item.modifiers.forEach((modifier, modIndex) => {
    //             if (!modifier.name || modifier.name.trim() === "") {
    //               errors.push(
    //                 `Line item ${index + 1}, Modifier ${
    //                   modIndex + 1
    //                 }: Name is required`
    //               );
    //             }

    //             if (!modifier.base_price_money ||
    //               modifier.base_price_money.amount < 0
    //             ) {
    //               errors.push(
    //                 `Line item ${index + 1}, Modifier ${
    //                   modIndex + 1
    //                 }: Invalid price`
    //               );
    //             }
    //           });
    //         }
    //       });

    //       return errors;
    //     }

    //     // Validate before sending
    //     const validationErrors = validateSquarePayload(squareOrderPayload);
    //     if (validationErrors.length > 0) {
    //       console.error("Square payload validation errors:", validationErrors);
    //       throw new Error(
    //         `Invalid Square payload: ${validationErrors.join(", ")}`
    //       );
    //     }

    //     console.log(
    //       "Creating Square order with payload:",
    //       JSON.stringify(squareOrderPayload, null, 2)
    //     );

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

    //     const squareResult = await squareResponse.json();

    //     if (!squareResponse.ok) {
    //       console.error("Square Order Error:", {
    //         status: squareResponse.status,
    //         statusText: squareResponse.statusText,
    //         error: squareResult,
    //       });

    //       // Log specific error details
    //       if (squareResult.errors) {
    //         squareResult.errors.forEach((error) => {
    //           console.error(
    //             `Square API Error - ${error.category}: ${error.code} - ${error.detail}`
    //           );
    //         });
    //       }
    //     } else {
    //       console.log("Order sent to Square successfully:", {
    //         orderId: squareResult.order?.id,
    //         state: squareResult.order?.state,
    //         totalMoney: squareResult.order?.total_money,
    //         createdAt: squareResult.order?.created_at,
    //       });

    //       // Store Square order ID in your database
    //       try {
    //         await OrderDB.update(
    //           {
    //             square_order_id: squareResult.order?.id,
    //             square_order_state: squareResult.order?.state,
    //             square_created_at: squareResult.order?.created_at,
    //           },
    //           { where: { id: result.id } }
    //         );
    //         console.log(
    //           `Updated local order ${result.id} with Square order ID: ${squareResult.order?.id}`
    //         );
    //       } catch (updateError) {
    //         console.error(
    //           "Failed to update local order with Square order ID:",
    //           updateError
    //         );
    //       }

    //       // Optional: After successful order creation, update fulfillment to make it actionable
    //       // This two-step process ensures the order appears in dashboard
    //       try {
    //         const fulfillmentUpdatePayload = {
    //           order: {
    //             version: squareResult.order.version,
    //             fulfillments: [
    //               {
    //                 uid: squareResult.order.fulfillments[0].uid,
    //                 type: "PICKUP",
    //                 state: "PREPARED", // Now we can change to PREPARED
    //                 pickup_details:
    //                   squareResult.order.fulfillments[0].pickup_details,
    //               },
    //             ],
    //           },
    //         };

    //         const fulfillmentUpdate = await fetch(
    //           `https://connect.squareup.com/v2/orders/${squareResult.order.id}`,
    //           {
    //             method: "PUT",
    //             headers: {
    //               Authorization: `Bearer ${cafeData.square_access_token}`,
    //               "Content-Type": "application/json",
    //               "Square-Version": "2024-06-04",
    //             },
    //             body: JSON.stringify(fulfillmentUpdatePayload),
    //           }
    //         );

    //         const fulfillmentResult = await fulfillmentUpdate.json();

    //         if (fulfillmentUpdate.ok) {
    //           console.log("Order fulfillment updated to PREPARED");

    //           // CRITICAL: Create a payment to make order visible in dashboard
    //           // Using EXTERNAL payment since customer will pay on pickup
    //           try {
    //             const paymentPayload = {
    //               source_id: "EXTERNAL",
    //               order_id: squareResult.order.id,
    //               amount_money: {
    //                 amount: squareResult.order.total_money.amount,
    //                 currency: squareResult.order.total_money.currency,
    //               },
    //               // Required for EXTERNAL payments
    //               external_details: {
    //                 type: "OTHER",
    //                 source: "Javago Cafe App - Pay on Pickup",
    //                 source_id: `javago-${result.id}`,
    //                 source_fee_money: {
    //                   amount: 0,
    //                   currency: squareResult.order.total_money.currency,
    //                 },
    //               },
    //               idempotency_key: `payment-${result.id}-${Date.now()}`,
    //               autocomplete: true,
    //               note: "Pre-authorized payment - Customer will pay on pickup",
    //             };

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

    //             const paymentResult = await paymentResponse.json();

    //             if (paymentResponse.ok) {
    //               console.log(
    //                 "Payment created - Order should now be visible in dashboard:",
    //                 {
    //                   paymentId: paymentResult.payment?.id,
    //                   orderId: squareResult.order.id,
    //                 }
    //               );

    //               // Update local database with payment info
    //               await OrderDB.update(
    //                 {
    //                   square_payment_id: paymentResult.payment?.id,
    //                   square_payment_status: paymentResult.payment?.status,
    //                   square_paid_at: new Date(),
    //                 },
    //                 { where: { id: result.id } }
    //               );
    //             } else {
    //               console.error("Failed to create payment:", paymentResult);
    //             }
    //           } catch (paymentError) {
    //             console.error("Failed to process payment:", paymentError);
    //           }
    //         } else {
    //           console.error("Failed to update fulfillment:", fulfillmentResult);
    //         }
    //       } catch (fulfillmentError) {
    //         console.error(
    //           "Failed to update fulfillment status:",
    //           fulfillmentError
    //         );
    //       }
    //     }
    //   } catch (err) {
    //     console.error("Failed to create Square order:", {
    //       error: err.message,
    //       stack: err.stack,
    //       cafeId: data.cafe_id,
    //       orderId: result.id,
    //     });

    //     // Optional: You might want to store this error in your database
    //     try {
    //       await OrderDB.update(
    //         {
    //           square_error: err.message,
    //           square_sync_attempted_at: new Date(),
    //         },
    //         { where: { id: result.id } }
    //       );
    //     } catch (dbError) {
    //       console.error("Failed to log Square error to database:", dbError);
    //     }
    //   }
    // } else {
    //   console.warn("Square integration not configured for cafe:", {
    //     cafeId: data.cafe_id,
    //     hasAccessToken: !!cafeData?.square_access_token,
    //     hasLocationId: !!cafeData?.square_location_id,
    //   });
    // }

    if (data.group_coffee_run_id) {
      await GroupCoffeeRunDB.update(
        { order_id: result.id, updated_at: moment().unix() },
        {
          where: {
            request_unique_id: data.group_coffee_run_id,
            user_id: data.loggedInUser,
          },
          returning: true,
          plain: true,
        }
      );
      let returnDoc = await GroupCoffeeRunDB.findOne({
        where: {
          request_unique_id: data.group_coffee_run_id,
        },
      });
      await OrderDB.update(
        {
          request_unique_id: returnDoc.request_unique_id,
        },
        {
          where: {
            id: result.id,
          },
        }
      );
    }
    if (typeof result.order_item_array == "string")
      result.order_item_array = JSON.parse(result.order_item_array);
    let send_notification_exists = await GroupCoffeeRunDB.findOne({
      where: {
        request_unique_id: result.dataValues.group_coffee_run_id,
        user_id: result.dataValues.user_id,
        request_created_by: {
          [Op.ne]: result.dataValues.user_id,
        },
        is_contributor_person: 1,
      },
      attributes: ["id", "request_created_by", "order_id"],
    });
    // console.log(send_notification_exists);
    if (send_notification_exists) {
      await NotificationDB.create({
        sender_id: result.dataValues.user_id,
        receiver_id: send_notification_exists.request_created_by,
        reference_id: send_notification_exists.id,
        notification_type: 5,
        is_read: 0,
        created_at: moment().unix(),
        updated_at: moment().unix(),
      });

      notification.payOrderNotification({
        loggedInUser: send_notification_exists.request_created_by,
        orderId: result.dataValues.id,
        groupId: data.group_id,
        senderId: result.dataValues.user_id,
      });

      console.log("Notification sent.");
    } else {
      console.log("Notification not sent because record does not exist.");
    }

    // let isInitiator = await GroupCoffeeRunDB.findOne({
    //   where: {
    //     request_unique_id: result.dataValues.group_coffee_run_id,
    //     user_id: result.dataValues.user_id,
    //     request_created_by: result.dataValues.user_id,
    //   },
    //   attributes: ["id", "request_created_by", "order_id"],
    // });
    // console.log("isInitiator", isInitiator);
    // if (isInitiator) {
    //   const message = `${userData.name} has placed a new order. Order number is ${result.dataValues.group_coffee_run_id}`;
    //   //send notification to cafe
    //   await NotificationDB.create({
    //     sender_id: result.user_id,
    //     receiver_id: result.cafe_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,
    //     is_individual_order: 0,
    //   });
    //   console.log("notification", notification_response);
    // }
    return result;
  } catch (error) {
    console.log(error);
    throw new Error(error);
  }
}

// place order
export async function placeIndividualOrder(data) {
  try {
    let todayDayId = new Date().getDay();
    let currentTime = moment().format("HH:mm");
    let itemsArray = data.order_item_array.map(function (currentValue) {
      return currentValue.item_id;
    });
    let result = await CafeDB.findOne({
      where: {
        id: data.cafe_id,
        is_active: 1,
        deleted_at: 0,
      },
      include: [
        {
          model: CafeTimingDB,
          where: {
            day: todayDayId,
            open_time: {
              [Op.lte]: currentTime,
            },
            close_time: {
              [Op.gte]: currentTime,
            },
          },
          required: false,
          attributes: ["id"],
          as: "time_sheet_data",
        },
        {
          model: CafeMenuItemDB,
          where: {
            id: {
              [Op.in]: itemsArray,
            },
          },
          include: [
            {
              model: CafeMenuItemSizeDB,
              required: false,
              include: [
                {
                  model: SizeDB,
                  required: false,
                  attributes: ["size_name"],
                },
              ],
              attributes: ["item_size_price"],
            },
            {
              model: AddonSizeCafeItemDB,
              required: false,
              include: [
                {
                  model: AddonSizeDB,
                  required: false,
                  attributes: ["addon_size_price"],
                },
              ],
              attributes: ["addon_size_id"],
            },
          ],
          required: false,
          attributes: [
            "id",
            "item_price",
            "status",
            "item_deleted_at",
            "item_name",
          ],
        },
      ],
      attributes: ["id"],
    });
    let final_result;
    if (!result) {
      final_result = {
        status: 203,
        message: messages.cafe_is_not_available,
      };
    } else if (result && result.time_sheet_data.length == 0) {
      final_result = {
        status: 203,
        message: messages.cafe_has_been_closed,
      };
    } else if (result && result.cafe_menu_items.length == 0) {
      final_result = {
        status: 203,
        message: messages.cafe_selected_items_not_avaialble,
      };
    } else if (result && result.cafe_menu_items.length) {
      let message = "";
      let items_prices = 0;
      for (let index = 0; index < result.cafe_menu_items.length; index++) {
        const element = result.cafe_menu_items[index];
        if (element.status == 0 || element.item_deleted_at > 0) {
          message += element.item_name;
          if (
            result.cafe_menu_items.length > 1 &&
            index != parseInt(result.cafe_menu_items.length) - 1
          ) {
            message += ", ";
          }
        }
        if (
          data.order_item_array.some(
            (children) =>
              children.item_size == "Regular" && children.item_id == element.id
          )
        ) {
          var qty = 1;
          data.order_item_array.map((children) => {
            if (children.item_id == element.id) {
              qty = children.item_quantity;
            }
          });
          items_prices = items_prices + Number(element.item_price) * qty;

          // now check addons prices
          for (let i = 0; i < data.order_item_array.length; i++) {
            const element1 = data.order_item_array[i];
            if (element1.item_id == element.id) {
              for (let j = 0; j < element1.addon_sizes.length; j++) {
                const element2 = element1.addon_sizes[j];
                let check = 0;
                element.addon_size_cafe_items.map((grandChildren) => {
                  if (
                    grandChildren.addon_size_id == element2.addon_size_id &&
                    element1.item_id == element.id
                  ) {
                    check = grandChildren.addon_size.addon_size_price;
                  }
                });
                items_prices += check;
              }
            }
          }
        } else if (
          data.order_item_array.some(
            (children) =>
              children.item_size != "Regular" && children.item_id == element.id
          )
        ) {
          for (let i = 0; i < data.order_item_array.length; i++) {
            const element1 = data.order_item_array[i];
            if (
              element1.item_size != "Regular" &&
              element.cafe_menu_item_sizes.some(
                (children) =>
                  children.size.size_name == element1.item_size &&
                  element1.item_id == element.id
              )
            ) {
              var tmp = 0;
              element.cafe_menu_item_sizes.map(function (childrenElement) {
                if (childrenElement.size.size_name == element1.item_size) {
                  tmp = childrenElement.item_size_price;
                }
              });
              items_prices += tmp ? tmp * element1.item_quantity : 0;
            }
            // now check addons prices
            if (element1.item_id == element.id) {
              for (let j = 0; j < element1.addon_sizes.length; j++) {
                const element2 = element1.addon_sizes[j];
                let check = 0;
                element.addon_size_cafe_items.map((grandChildren) => {
                  if (
                    grandChildren.addon_size_id == element2.addon_size_id &&
                    element1.item_id == element.id
                  ) {
                    check = grandChildren.addon_size.addon_size_price;
                  }
                });
                items_prices += check;
              }
            }
          }
        }
      }
      if (message) {
        final_result = {
          status: 203,
          message: message + " items not available",
          result: result,
        };
      } else if (
        message == "" &&
        Number(items_prices) != Number(data.items_amount)
      ) {
        final_result = {
          status: 203,
          message: messages.item_price_updated_error,
          result: result,
        };
      } else {
        if (data.loyalty_stamp_id) {
          let checkLoyaltyStamp = await LoyaltyStampDB.findOne({
            where: {
              id: parseInt(data.loyalty_stamp_id),
            },
          });
          if (checkLoyaltyStamp) {
            final_result = {
              status: 200,
              message: messages.data_found,
              result: result,
            };
          } else {
            final_result = {
              status: 203,
              message: messages.redeem_code_no_longer_available,
              result: result,
            };
          }
        } else {
          final_result = {
            status: 200,
            message: messages.data_found,
            result: result,
          };
        }
      }
      console.log(items_prices);
    }
    return final_result;
  } catch (error) {
    throw new Error(error);
  }
}

// save order
export async function saveIndividualOrder(data) {
  try {
    for (let index = 0; index < data.order_item_array.length; index++) {
      const element = data.order_item_array[index];
      let itemImageData = await CafeMenuItemDB.findByPk(element.item_id, {
        include: [
          {
            model: PredefinedItemImageDB,
            as: "image_data",
            required: true,
          },
        ],
      });
      element.item_image = itemImageData
        ? itemImageData.image_data.item_image
        : "";
    }

    const dateInUK = new Date().toLocaleString("en-US", {
      timeZone: "Europe/London",
    });
    const timestampUK = Math.floor(new Date(dateInUK).getTime() / 1000);
    let result = await OrderDB.create({
      order_number: await Helper.generateRandomString(6, true),
      user_id: data.loggedInUser,
      cafe_id: data.cafe_id,
      group_id: data.group_id ? data.group_id : null,
      group_coffee_run_id: data.group_coffee_run_id
        ? data.group_coffee_run_id
        : null,
      loyalty_stamp_id: data.loyalty_stamp_id ? data.loyalty_stamp_id : null,
      is_main_order: data.is_main_order,
      order_item_array: JSON.stringify(data.order_item_array),
      additional_note: data.additional_note,
      total_amount: data.total_amount ? Number(data.total_amount) : 0,
      tax: data.tax ? Number(data.tax) : 0,
      service_charge: data.service_charge ? Number(data.service_charge) : 0,
      other_charge: data.other_charge ? Number(data.other_charge) : 0,
      transaction_id: data.transaction_id,
      estimated_arrival_time: data.estimated_arrival_time,
      discount_amount: data.discount_amount ? Number(data.discount_amount) : 0,
      order_placed_at: moment().unix(),
	//order_placed_at: timestampUK,
      estimated_arival_time: data.estimated_arival_time ?? null,
      is_individual_order: 1,
      status: 1,
      created_at: moment().unix(),
      updated_at: moment().unix(),
    });

    let totalOrdersCount = await OrderDB.count({
      where: {
        user_id: data.loggedInUser,
        cafe_id: data.cafe_id,
      },
    });
    let currentDate = moment().format("YYYY-MM-DD");
    let nonUniversalStampsDetails = await getStampsByCafe(data.cafe_id);
    nonUniversalStampsDetails.is_expired = 0;

    let cafeExpireDate = getLastExpireCafeDate(data.loggedInUser, data.cafe_id);
    if (cafeExpireDate) {
      if (
        currentDate >
        moment
          .unix(cafeExpireDate.cafe_coupon_expired_time)
          .format("YYYY-MM-DD")
      ) {
        nonUniversalStampsDetails.is_expired = 1;
      }
    }
    //Subtract the non-universal stamps if applied
    if (data.discount_amount > 0) {
       const userStamp = await UserDB.findByPk(data.loggedInUser);
        if (userStamp && userStamp.remaining_awarded_stamps != 0) {
          // Assuming remaining_awarded_stamps is a field in UserDB
          await UserDB.update(
            {
              remaining_awarded_stamps: userStamp.remaining_awarded_stamps - 1,
            },
            {
              where: {
                id: data.loggedInUser,
              },
            }
          );
        }else{
			let stampAward = await UserStampsAwardedDB.findOne({
			  where: {
				user_id: data.loggedInUser,
				cafe_id: data.cafe_id,
				stamp_id: nonUniversalStampsDetails.id,
			  },
			});

			if (stampAward) {
			  await stampAward.update({
				quantity: stampAward.quantity - 1,
			  });
			}
		}    }

    //Award loyalty stamp if applicable

    const totalOrders = totalOrdersCount / nonUniversalStampsDetails.stamp_no;
    if (Math.floor(totalOrders) > 0 && totalOrders == Math.floor(totalOrders)) {
      let stampAward = await UserStampsAwardedDB.findOne({
        where: {
          user_id: data.loggedInUser,
          cafe_id: data.cafe_id,
          stamp_id: nonUniversalStampsDetails.id,
        },
      });

      if (stampAward) {
        await stampAward.update({
          quantity: stampAward.quantity + 1,
        });
      } else {
        await UserStampsAwardedDB.create({
          user_id: data.loggedInUser,
          cafe_id: data.cafe_id,
          stamp_id: nonUniversalStampsDetails.id,
          quantity: 1,
        });
      }
    }

    // Square implementation for pickup order
    let cafeData = await CafeDB.findByPk(data.cafe_id);
    let userData = await UserDB.findByPk(data.loggedInUser);

    if (
      cafeData &&
      cafeData.square_access_token &&
      cafeData.square_location_id
    ) {
      try {
        const locationIds = JSON.parse(cafeData.square_location_id);
        const locationId = locationIds[0]; // pick the first location

        // Handle pickup time from 'estimated_arrival_time'
        let pickupTime;
        if (
          !result.estimated_arival_time ||
          result.estimated_arival_time?.toLowerCase() === "asap"
        ) {
          // Default: 10 minutes from now
pickupTime = new Date().toISOString();
          //pickupTime = new Date(Date.now() + 05 * 60 * 1000).toISOString();
        } else {
          // Use provided time
          const timeStr = result.estimated_arival_time.trim();
const today = new Date();
const [hours, minutes] = timeStr.split(":");
if (!hours || !minutes || isNaN(hours) || isNaN(minutes)) {
    throw new Error("Invalid estimated arrival time: " + timeStr);
}
const composedDate = new Date(today.getFullYear(), today.getMonth(), today.getDate(), parseInt(hours), parseInt(minutes), 0);
if (!isNaN(composedDate)) {
    pickupTime = composedDate.toISOString();
} else {
    throw new Error("Invalid constructed pickup time from: " + timeStr);
}
        }

        const squareOrderPayload = {
          order: {
            location_id: locationId,
            line_items: data.order_item_array.map((item) => {
              // Base line item
              const lineItem = {
                name: item.item_name,
                quantity: item.item_quantity.toString(),
                base_price_money: {
                  amount: Math.round(Number(item.item_amount) * 100), // Square expects amount in cents
                  currency: "GBP",
                },
                variation_name: item.variation_name || null,
                //note: item.item_notes || item.item_description || null,
		note: `User: ${userData?.name || "Guest"} | Order ID: ${result.id}`,
              };

              // Add modifiers with ZERO price
              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}`, // Combined name for clarity
                  base_price_money: {
                    amount: 0, // SET TO ZERO - This prevents adding to total
                    currency: "GBP",
                  },
                  quantity: addon.quantity ? addon.quantity.toString() : "1",
                }));
              }

              return lineItem;
            }),
            fulfillments: [
              {
                type: "PICKUP",
                state: "PROPOSED",
                pickup_details: {
                  recipient: {
                    display_name: userData.name || "Guest",
                    phone_number: userData.customer_phone || "",
                    email_address: userData.email || "",
                  },
                  pickup_at: pickupTime,
                  note: "Customer will pick up the order at the front desk",
                  pickup_note: `Order placed via Javago Cafe App. Contact: ${
                    data.customer_phone || userData.customer_phone || "N/A"
                  }`,
                },
              },
            ],
            metadata: {
              source: "Javago Cafe App",
              order_type: "pickup",
              customer_id:
                data.customer_id?.toString() ||
                data.loggedInUser?.toString() ||
                "guest",
              internal_order_id: result.id?.toString() || "unknown",
              // SOLUTION 1: Remove or truncate the long addons_with_prices field
              // addons_count: data.order_item_array.filter(item => item.addon_sizes && item.addon_sizes.length > 0).length.toString()
            },
            order_source: {
              name: "Javago Cafe App",
            },
          },
          idempotency_key: `${result.id}-${Date.now()}`,
        };

        // Enhanced logging to debug the payload
        

        // Log each line item's modifiers specifically
        squareOrderPayload.order.line_items.forEach((lineItem, index) => {
          if (lineItem.modifiers && lineItem.modifiers.length > 0) {
            console.log(
              `Line item ${index + 1} (${lineItem.name}) has ${
                lineItem.modifiers.length
              } modifiers:`
            );
            lineItem.modifiers.forEach((modifier, modIndex) => {
              console.log(
                `  Modifier ${modIndex + 1}: ${modifier.name} - £${
                  modifier.base_price_money.amount / 100
                }`
              );
            });
          }
        });

        // Simple validation function
        function validateSquarePayload(payload) {
          const errors = [];

          payload.order.line_items.forEach((item, index) => {
            if (!item.name || item.name.trim() === "") {
              errors.push(`Line item ${index + 1}: Name is required`);
            }

            if (!item.base_price_money || item.base_price_money.amount < 0) {
              errors.push(`Line item ${index + 1}: Invalid price`);
            }

            if (item.modifiers) {
              item.modifiers.forEach((modifier, modIndex) => {
                if (!modifier.name || modifier.name.trim() === "") {
                  errors.push(
                    `Line item ${index + 1}, Modifier ${
                      modIndex + 1
                    }: Name is required`
                  );
                }

                if (
                  !modifier.base_price_money ||
                  modifier.base_price_money.amount < 0
                ) {
                  errors.push(
                    `Line item ${index + 1}, Modifier ${
                      modIndex + 1
                    }: Invalid price`
                  );
                }
              });
            }
          });

          return errors;
        }

        // Validate before sending
        const validationErrors = validateSquarePayload(squareOrderPayload);
        if (validationErrors.length > 0) {
          console.error("Square payload validation errors:", validationErrors);
          throw new Error(
            `Invalid Square payload: ${validationErrors.join(", ")}`
          );
        }

        console.log(
          "Creating Square order with payload:",
          JSON.stringify(squareOrderPayload, null, 2)
        );

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

        const squareResult = await squareResponse.json();
	console.log(
          "Square Order detail:",
          JSON.stringify(squareResult , null, 2)
        );
        if (!squareResponse.ok) {
          console.error("Square Order Error:", {
            status: squareResponse.status,
            statusText: squareResponse.statusText,
            error: squareResult,
          });

          // Log specific error details
          if (squareResult.errors) {
            squareResult.errors.forEach((error) => {
              console.error(
                `Square API Error - ${error.category}: ${error.code} - ${error.detail}`
              );
            });
          }
        } else {
          console.log("Order sent to Square successfully:", {
            orderId: squareResult.order?.id,
            state: squareResult.order?.state,
            totalMoney: squareResult.order?.total_money,
            createdAt: squareResult.order?.created_at,
          });

          // Store Square order ID in your database
          try {
            await OrderDB.update(
              {
                square_order_id: squareResult.order?.id,
                square_order_state: squareResult.order?.state,
                square_created_at: squareResult.order?.created_at,
              },
              { where: { id: result.id } }
            );
            console.log(
              `Updated local order ${result.id} with Square order ID: ${squareResult.order?.id}`
            );
          } catch (updateError) {
            console.error(
              "Failed to update local order with Square order ID:",
              updateError
            );
          }

          // Optional: After successful order creation, update fulfillment to make it actionable
          // This two-step process ensures the order appears in dashboard
          try {
            const fulfillmentUpdatePayload = {
              order: {
                version: squareResult.order.version,
                fulfillments: [
                  {
                    uid: squareResult.order.fulfillments[0].uid,
                    type: "PICKUP",
                    state: "PROPOSED", // Now we can change to PREPARED
                    pickup_details:
                      squareResult.order.fulfillments[0].pickup_details,
                  },
                ],
              },
            };

            const fulfillmentUpdate = await fetch(
              `https://connect.squareup.com/v2/orders/${squareResult.order.id}`,
              {
                method: "PUT",
                headers: {
                  Authorization: `Bearer ${cafeData.square_access_token}`,
                  "Content-Type": "application/json",
                  "Square-Version": "2024-06-04",
                },
                body: JSON.stringify(fulfillmentUpdatePayload),
              }
            );

            const fulfillmentResult = await fulfillmentUpdate.json();

            if (fulfillmentUpdate.ok) {
              console.log("Order fulfillment updated to PREPARED");

              // CRITICAL: Create a payment to make order visible in dashboard
              // Using EXTERNAL payment since customer will pay on pickup
              try {
                const paymentPayload = {
                  source_id: "EXTERNAL",
                  order_id: squareResult.order.id,
                  amount_money: {
                    amount: squareResult.order.total_money.amount,
                    currency: squareResult.order.total_money.currency,
                  },
                  // Required for EXTERNAL payments
                  external_details: {
                    type: "OTHER",
                    source: "Javago Cafe App - Pay on Pickup",
                    source_id: `javago-${result.id}`,
                    source_fee_money: {
                      amount: 0,
                      currency: squareResult.order.total_money.currency,
                    },
                  },
                  idempotency_key: `payment-${result.id}-${Date.now()}`,
                  autocomplete: true,
                  note: "Pre-authorized payment - Customer will pay on pickup",
                };

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

                const paymentResult = await paymentResponse.json();

                if (paymentResponse.ok) {
                  console.log(
                    "Payment created - Order should now be visible in dashboard:",
                    {
                      paymentId: paymentResult.payment?.id,
                      orderId: squareResult.order.id,
                    }
                  );

                  // Update local database with payment info
                  await OrderDB.update(
                    {
                      square_payment_id: paymentResult.payment?.id,
                      square_payment_status: paymentResult.payment?.status,
                      square_paid_at: new Date(),
                    },
                    { where: { id: result.id } }
                  );
                } else {
                  console.error("Failed to create payment:", paymentResult);
                }
              } catch (paymentError) {
                console.error("Failed to process payment:", paymentError);
              }
            } else {
              console.error("Failed to update fulfillment:", fulfillmentResult);
            }
          } catch (fulfillmentError) {
            console.error(
              "Failed to update fulfillment status:",
              fulfillmentError
            );
          }
        }
      } catch (err) {
        console.error("Failed to create Square order:", {
          error: err.message,
          stack: err.stack,
          cafeId: data.cafe_id,
          orderId: result.id,
        });

        // Optional: You might want to store this error in your database
        try {
          await OrderDB.update(
            {
              square_error: err.message,
              square_sync_attempted_at: new Date(),
            },
            { where: { id: result.id } }
          );
        } catch (dbError) {
          console.error("Failed to log Square error to database:", dbError);
        }
      }
    } else {
      console.warn("Square integration not configured for cafe:", {
        cafeId: data.cafe_id,
        hasAccessToken: !!cafeData?.square_access_token,
        hasLocationId: !!cafeData?.square_location_id,
      });
    }

    const message = `${userData.name} has placed a new order. Order number is ${result.order_number}`;
    //send notification to cafe
    await NotificationDB.create({
      sender_id: result.user_id,
      receiver_id: result.cafe_id,
      reference_id: result.order_number,
      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,
      is_individual_order: 1,
    });
    console.log(notification_response);

    if (typeof result.order_item_array == "string")
      result.order_item_array = JSON.parse(result.order_item_array);

    return result;
  } catch (error) {
    throw new Error(error);
  }
}

// get order list
export async function getOrder(userId, data) {
  try {
    if ([null, undefined, "", 0].includes(data.page)) data.page = 1;
    data.page = parseInt(data.page);
    let result = await OrderDB.paginate({
      page: data.page,
      paginate: parseInt(config.config.limit),
      where: {
        user_id: userId,
      },
      include: [
        {
          model: CafeDB,
          where: {
            is_active: 1,
            deleted_at: 0,
          },
          include: [
            {
              model: UserFavouriteCafeDB,
              as: "user_favorite_cafes",
              required: false,
              where: {
                is_favorite: 1,
                user_id: parseInt(userId),
              },
              attributes: ["is_favorite", "user_id"],
            },
          ],
          required: false,
          attributes: [
            "id",
            "cafe_name",
            "phone",
            "latitude",
            "bio",
            "email",
            "cafe_tax",
            "longitude",
            "address",
            "banner_image",
            "postcode",
          ],
        },
        {
          model: GroupDB,
          required: false,
          attributes: ["id", "group_name", "group_profile"],
          include: [
            {
              model: GroupMemberDB,
              as: "group_details",
              attributes: ["id", "user_id"],
              include: [
                {
                  model: UserDB,
                  as: "user_data",
                  attributes: ["id", "name", "email", "profile_picture"],
                  where: { is_deleted: 0, is_active: 1, is_verified: 1 },
                },
              ],
            },
          ],
        },
      ],
      attributes: [
        "id",
        "order_number",
        "created_at",
        "updated_at",
        "order_item_array",
        "additional_note",
        "total_amount",
        "tax",
        "discount_amount",
        "order_placed_at",
        "order_collected_at",
        "status",
      ],
      order: [["id", "DESC"]],
    });
    result = JSON.stringify(result);
    result = JSON.parse(result);
    for (let index = 0; index < result.docs.length; index++) {
      const element = result.docs[index];
      element.group_coffee_run = await GroupCoffeeRunDB.findAll({
        where: {
          order_id: element.id,
        },
        include: [
          {
            model: UserDB,
            attributes: ["id", "name", "profile_picture", "email"],
            required: true,
            where: {
              is_deleted: 0,
              is_active: 1,
            },
            as: "user_data",
          },
          {
            model: UserDB,
            attributes: ["id", "name", "profile_picture", "email"],
            required: true,
            where: {
              is_deleted: 0,
              is_active: 1,
            },
            as: "creator_data",
          },
        ],
        attributes: [
          "id",
          "request_unique_id",
          "is_contributor_person",
          "is_order_place",
          "request_endtime",
        ],
      });
      element.cafe.is_favorite =
        element.cafe.user_favorite_cafes.length > 0 ? 1 : 0;
      element.cafe.distance = 0;
      element.cafe.time_sheet_data = [];
      delete element.cafe.user_favorite_cafes;
    }
    return result;
  } catch (error) {
    throw new Error(error);
  }
}

export async function getOrderDetails(id) {
  try {
    let result = await OrderDB.findByPk(id, {
      attributes: [
        "id",
        "order_number",
        "group_id",
        "cafe_id",
        "total_amount",
        "tax",
        "service_charge",
        "other_charge",
        "order_item_array",
        "discount_amount",
        "order_placed_at",
        "transaction_id",
      ],
      include: [
        {
          model: UserDB,
          attributes: ["name", "profile_picture", "email"],
        },
      ],
    });
    return result;
  } catch (error) {
    throw new Error(error);
  }
}

export async function updateOrderDetails(id, transaction_id) {
  try {
    let result = await OrderDB.update(
      {
        transaction_id: transaction_id,
      },
      {
        where: { id: id },
      }
    );
    return result;
  } catch (error) {
    throw new Error(error);
  }
}

// get Individual order list
export async function getIndividualOrder(user_id, order_id, cafe_id) {
  try {
    let result = await OrderDB.findOne({
      where: {
        user_id: user_id,
        id: order_id,
        cafe_id: cafe_id,
      },
      include: [
        {
          model: CafeDB,
          where: {
            is_active: 1,
            deleted_at: 0,
          },
          include: [
            {
              model: UserFavouriteCafeDB,
              as: "user_favorite_cafes",
              required: false,
              where: {
                is_favorite: 1,
                user_id: parseInt(user_id),
              },
              attributes: ["is_favorite", "user_id"],
            },
          ],
          required: false,
          attributes: [
            "id",
            "cafe_name",
            "phone",
            "latitude",
            "longitude",
            "address",
            "banner_image",
            "postcode",
          ],
        },
        {
          model: GroupDB,
          required: false,
          attributes: ["id", "group_name", "group_profile"],
          include: [
            {
              model: GroupMemberDB,
              as: "group_details",
              attributes: ["id", "user_id"],
              include: [
                {
                  model: UserDB,
                  as: "user_data",
                  attributes: ["id", "name", "email", "profile_picture"],
                  where: { is_deleted: 0, is_active: 1, is_verified: 1 },
                },
              ],
            },
          ],
        },
      ],
      attributes: [
        "id",
        "order_number",
        "created_at",
        "updated_at",
        "order_item_array",
        "additional_note",
        "total_amount",
        "tax",
        "discount_amount",
        "order_placed_at",
        "order_collected_at",
        "status",
      ],
    });
    return result;
  } catch (error) {
    throw new Error(error);
  }
}

// change order status
export async function changeOrderStatus(orderId, data) {
  try {
    let check = await OrderDB.findByPk(orderId);
    if (!check) {
      return {
        status: 501,
        message: messages.wrong_order_id_applied,
      };
    }
    if (check.status == parseInt(data.flag)) {
      if (check.status == 1) {
        return {
          status: 409,
          message:
            messages.order_has_been_already_placed +
            " at " +
            check.order_placed_at,
          data: {
            cafe_id: check.cafe_id,
          },
        };
      }
      return {
        status: 409,
        message:
          messages.order_has_been_already_collected +
          " at " +
          check.order_collected_at,
        data: {
          cafe_id: check.cafe_id,
        },
      };
    }
    let updateObj = {
      status: parseInt(data.flag),
      updated_at: moment().unix(),
    };
    if (parseInt(data.flag) == 1) {
      updateObj.order_placed_at = moment().unix();
    } else {
      updateObj.order_collected_at = moment().unix();
    }
    let result = await OrderDB.update(updateObj, {
      where: {
        id: orderId,
      },
    });
    return {
      status: 202,
      message:
        parseInt(data.flag) == 1
          ? messages.order_has_been_placed
          : messages.order_has_been_collected,
      data: {
        cafe_id: check.cafe_id,
      },
    };
  } catch (error) {
    throw new Error(error);
  }
}

// Get Total Order cafe wise
export async function getAllCafeOrders(user_id, cafe_id) {
  try {
    return await OrderDB.count({
      where: {
        user_id: parseInt(user_id),
        cafe_id: cafe_id,
        status: 2,
        loyalty_stamp_id: null,
        // cafe_coupon_expired_time: 0
      },
    });
  } catch (error) {
    throw new Error(error);
  }
}

// Get Total Order
export async function getUniversalOrders(user_id) {
  try {
    return await OrderDB.count({
      where: {
        user_id: parseInt(user_id),
        status: 2,
        universal_coupon_expired_time: 0,
      },
    });
  } catch (error) {
    throw new Error(error);
  }
}

// Get last universal expires time
export async function getLastExpireDateUniversal(user_id) {
  try {
    return await OrderDB.findOne({
      where: {
        user_id: parseInt(user_id),
        status: 2,
        universal_coupon_expired_time: {
          [Op.ne]: 0,
        },
      },
      order: [["id", "DESC"]],
    });
  } catch (error) {
    throw new Error(error);
  }
}

// Get last universal expires time
export async function getLastExpireCafeDate(user_id, cafe_id) {
  try {
    return await OrderDB.findOne({
      where: {
        user_id: parseInt(user_id),
        status: 2,
        cafe_id: cafe_id,
        cafe_coupon_expired_time: {
          [Op.ne]: 0,
        },
      },
      order: [["id", "DESC"]],
    });
  } catch (error) {
    throw new Error(error);
  }
}

//Get cafe universal stamp
export async function getUniversalStamp() {
  try {
    let nonUniversal = await LoyaltyStampDB.findOne({
      where: {
        cafe_id: null,
        is_universal: 1,
      },
    });
    return nonUniversal;
  } catch (error) {
    throw new Error(error);
  }
}

//Get cafe stemp by cafe ID
export async function getStampsByCafe(cafe_id) {
  try {
    let nonUniversal = await LoyaltyStampDB.findOne({
      where: {
        cafe_id: parseInt(cafe_id),
        is_universal: 0,
      },
    });
    return nonUniversal;
  } catch (error) {
    console.log(error);
    throw new Error(error);
  }
}

//get awaded stamps by user ID
export async function getAwardedStamps(user_id, cafe_id) {
  try {
    let awardedStamps = await UserStampsAwardedDB.findOne({
      where: {
        user_id: parseInt(user_id),
        cafe_id: parseInt(cafe_id),
      },
    });
    if (awardedStamps) {
      let loyaltyStamps = await LoyaltyStampDB.findOne({
        where: {
          id: awardedStamps.stamp_id,
        },
      });
      awardedStamps.dataValues.stamp_details = loyaltyStamps;
    }
    return awardedStamps;
  } catch (error) {
    throw new Error(error);
  }
}

//update coupons by cafe
export async function updateCouponByCafeId(
  user_id,
  coupon_id,
  cafe_id,
  stamp_no,
  stamp_expires_in
) {
  try {
    let orderIds = [];
    let expireDay = moment().unix();
    let orderDetails = await OrderDB.findAll({
      where: {
        user_id: parseInt(user_id),
        status: 2,
        cafe_coupon_expired_time: 0,
        cafe_id: cafe_id,
      },
      attributes: ["id"],
      order: [["id", "ASC"]],
      limit: stamp_no,
    });
    for (let i = 0; i < orderDetails.length; i++) {
      orderIds.push(orderDetails[i].id);
    }
    return await OrderDB.update(
      {
        cafe_coupon_order_id: coupon_id,
        cafe_coupon_expired_time: moment(expireDay, "X")
          .add(stamp_expires_in, "days")
          .unix(),
      },
      {
        where: {
          cafe_id: parseInt(cafe_id),
          user_id: user_id,
          id: {
            [Op.in]: orderIds,
          },
        },
      }
    );
  } catch (error) {
    console.log("error: ", error);
    throw new Error(error);
  }
}

//update universal coupons
export async function updateUniversalCoupon(
  user_id,
  coupon_id,
  stamp_no,
  stamp_expires_in
) {
  try {
    let orderIds = [];
    let expireDay = moment().unix();
    let orderDetails = await OrderDB.findAll({
      where: {
        user_id: parseInt(user_id),
        status: 2,
        universal_coupon_expired_time: 0,
      },
      attributes: ["id"],
      order: [["id", "ASC"]],
      limit: stamp_no,
    });
    for (let i = 0; i < orderDetails.length; i++) {
      orderIds.push(orderDetails[i].id);
    }
    return await OrderDB.update(
      {
        universal_coupon_order_id: coupon_id,
        universal_coupon_expired_time: moment(expireDay, "X")
          .add(stamp_expires_in, "days")
          .unix(),
      },
      {
        where: {
          user_id: user_id,
          id: {
            [Op.in]: orderIds,
          },
        },
      }
    );
  } catch (error) {
    throw new Error(error);
  }
}

// Get Loyalty list
export async function loyaltyList(data, query) {
  try {
    let loyaltyDetails = await OrderDB.paginate({
      paginate: 10,
      page: query.page,
      order: [["id", "ASC"]],
      attributes: [
        "user_id",
        "cafe_id",
        [Sequelize.fn("COUNT", Sequelize.col("orders.id")), "Count"],
      ],
      include: [
        {
          model: CafeDB,
          attributes: ["id", "cafe_name"],
          include: [
            {
              model: LoyaltyStampDB,
              attributes: ["stamp_no", "cafe_id", "offer_text", "stamp_color"],
            },
          ],
        },
      ],
      distinct: false,
      col: "status",
      group: ["cafe_id"],
      where: {
        user_id: data,
        status: 2,
        cafe_coupon_expired_time: 0,
        cafe_coupon_order_id: null,
      },
    });
    return loyaltyDetails;
  } catch (error) {
    throw new Error(error);
  }
}

// Get universal Loyalty list
export async function universalCardList(user_id) {
  try {
    return await OrderDB.count({
      where: {
        user_id: parseInt(user_id),
        // universal_coupon_order_id: null,
        universal_coupon_expired_time: 0,
        status: 2,
      },
    });
  } catch (error) {
    throw new Error(error);
  }
}

// get redeem code
export async function getRedeemCode(data) {
  try {
    let final_result = {
      universal: {},
      nonUniversal: {},
    };
    let checkTotalOrder = await OrderDB.count({
      where: {
        user_id: data.userId,
        status: 2,
      },
    });
    let nonUniversal = await LoyaltyStampDB.findOne({
      where: {
        cafe_id: parseInt(data.cafe_id),
        is_universal: 0,
      },
    });
    let universal = await LoyaltyStampDB.findOne({
      where: {
        cafe_id: null,
        is_universal: 1,
      },
    });
    let checkTotalRedeem = await OrderDB.count({
      where: {
        user_id: parseInt(data.userId),
        loyalty_stamp_id: universal.id,
        status: 2,
      },
    });
    let checkTotalCafeRedeem = await OrderDB.count({
      where: {
        user_id: parseInt(data.userId),
        loyalty_stamp_id: parseInt(data.cafe_id),
        status: 2,
      },
    });
    if (universal.stamp_no <= checkTotalOrder) {
      let deno =
        checkTotalRedeem > 0
          ? checkTotalOrder - checkTotalRedeem * universal.stamp_no
          : checkTotalOrder;
      final_result.universal = deno >= universal.stamp_no ? universal : {};
    }
    if (nonUniversal.stamp_no <= checkTotalOrder) {
      let deno =
        checkTotalCafeRedeem > 0
          ? checkTotalOrder - checkTotalCafeRedeem * nonUniversal.stamp_no
          : checkTotalOrder;
      final_result.nonUniversal =
        deno >= nonUniversal.stamp_no ? nonUniversal : {};
    }
    return final_result;
  } catch (error) {
    throw new Error(error);
  }
}

// apply redeem code
export async function applyRedeemCode(order_id) {
  try {
    let final_result = await OrderDB.update(
      {
        cafe_coupon_order_id: order_id,
      },
      {
        where: {
          id: order_id,
        },
      }
    );
    return final_result;
  } catch (error) {
    throw new Error(error);
  }
}

export async function orderListByGroup(data) {
  try {
    if ([null, undefined, "", 0].includes(data.page)) data.page = 1;
    data.page = parseInt(data.page);
    let result = await OrderDB.paginate({
      page: data.page,
      paginate: parseInt(config.config.limit),
      where: {
        group_id: parseInt(data.group_id),
      },
      include: [
        {
          model: CafeDB,
          where: {
            is_active: 1,
            deleted_at: 0,
          },
          include: [
            {
              model: UserFavouriteCafeDB,
              as: "user_favorite_cafes",
              required: false,
              where: {
                is_favorite: 1,
                user_id: parseInt(data.userId),
              },
              attributes: ["is_favorite", "user_id"],
            },
          ],
          required: false,
          attributes: [
            "id",
            "cafe_name",
            "phone",
            "latitude",
            "bio",
            "email",
            "cafe_tax",
            "longitude",
            "address",
            "banner_image",
            "postcode",
          ],
        },
        {
          model: GroupDB,
          required: false,
          attributes: ["id", "group_name", "group_profile"],
          include: [
            {
              model: GroupMemberDB,
              as: "group_details",
              attributes: ["id", "user_id"],
              include: [
                {
                  model: UserDB,
                  as: "user_data",
                  attributes: ["id", "name", "email", "profile_picture"],
                  where: { is_deleted: 0, is_active: 1, is_verified: 1 },
                },
              ],
            },
          ],
        },
      ],
      attributes: [
        "id",
        "order_number",
        "created_at",
        "updated_at",
        "order_item_array",
        "additional_note",
        "total_amount",
        "tax",
        "discount_amount",
        "order_placed_at",
        "order_collected_at",
        "status",
      ],
      order: [["id", "DESC"]],
    });
    result = JSON.stringify(result);
    result = JSON.parse(result);
    for (let index = 0; index < result.docs.length; index++) {
      const element = result.docs[index];
      element.group_coffee_run = await GroupCoffeeRunDB.findAll({
        where: {
          order_id: element.id,
        },
        include: [
          {
            model: UserDB,
            attributes: ["id", "name", "profile_picture", "email"],
            required: true,
            where: {
              is_deleted: 0,
              is_active: 1,
            },
            as: "user_data",
          },
          {
            model: UserDB,
            attributes: ["id", "name", "profile_picture", "email"],
            required: true,
            where: {
              is_deleted: 0,
              is_active: 1,
            },
            as: "creator_data",
          },
        ],
        attributes: [
          "id",
          "request_unique_id",
          "is_contributor_person",
          "is_order_place",
          "request_endtime",
        ],
      });
      element.cafe.is_favorite =
        element.cafe.user_favorite_cafes.length > 0 ? 1 : 0;
      element.cafe.distance = 0;
      element.cafe.time_sheet_data = [];
      if (typeof element.order_item_array == "string")
        element.order_item_array = JSON.parse(element.order_item_array);
      delete element.cafe.user_favorite_cafes;
    }
    return result;
  } catch (error) {
    throw new Error(error);
  }
}

//get cafe list by group id and edit time
export async function getGroupCoffeeRunOrders(coffeeRunId, groupId, user_id) {
  try {
    let groupCoffeeRun;
    groupCoffeeRun = await GroupCoffeeRunDB.findOne({
      where: {
        group_id: groupId,
      },
      order: [["id", "DESC"]],
      attributes: [
        "request_unique_id",
        "cafe_id",
        "group_id",
        "request_endtime",
        "type",
        "request_created_by",
      ],
    });

    if (groupCoffeeRun == null) {
      return null;
    }

    var inHouseOrders = [];
    var orderDetails = [];
    if (groupCoffeeRun.dataValues.type == 0) {
      inHouseOrders = await coffeeRunInHouseOrders.findAll({
        where: {
          coffee_run_id: groupCoffeeRun.dataValues.request_unique_id,
        },
        include: [
          {
            model: UserDB,
            attributes: ["id", "name"],
          },
        ],
      });
    } else {
      orderDetails = await OrderDB.findAll({
        where: {
          group_coffee_run_id: groupCoffeeRun.dataValues.request_unique_id,
        },
        attributes: [
          "order_number",
          "order_item_array",
          "total_amount",
          "tax",
          "discount_amount",
          "service_charge",
          "other_charge",
          "created_at",
          "order_collected_at",
          "transaction_id",
          "status",
          "order_completed",
        ],
        include: [
          {
            model: UserDB,
            attributes: ["id", "name"],
          },
        ],
      });
    }

    const orderCollected = orderDetails.find(
      (e) => e.dataValues.order_collected_at != 0
    );
    const inHouseOrderCollected = inHouseOrders.find(
      (e) => e.dataValues.order_collected_at != 0
    );
    if (orderCollected || inHouseOrderCollected) {
      return null;
    }

    var cafeDetails = null;
    if (groupCoffeeRun.dataValues.type == 1) {
      cafeDetails = await CafeDB.findOne({
        where: {
          id: groupCoffeeRun.dataValues.cafe_id,
        },
        include: [
          {
            model: UserFavouriteCafeDB,
            as: "user_favorite_cafes",
            required: false,
            where: {
              is_favorite: 1,
              user_id: parseInt(user_id),
            },
            attributes: ["is_favorite"],
          },
        ],
        attributes: [
          "id",
          "cafe_name",
          "banner_image",
          "address",
          "latitude",
          "longitude",
        ],
      });
    }

    const groupDetails = await GroupDB.findOne({
      where: {
        id: groupCoffeeRun.dataValues.group_id,
      },
      attributes: ["group_name", "group_profile"],
      include: [
        {
          model: GroupMemberDB,
          as: "group_details",
          attributes: ["user_id"],
          include: [
            {
              model: UserDB,
              as: "user_data",
              attributes: ["name", "profile_picture", "email"],
            },
          ],
        },
      ],
    });

    return {
      cafe_details: cafeDetails,
      group_details: groupDetails,
      coffe_run: groupCoffeeRun,
      order_details: orderDetails,
      in_house_orders: inHouseOrders,
    };
  } catch (error) {
    throw new Error(error);
  }
}

//get individual orders

export async function getIndividualOrders(user_id) {
  try {
    // Step 1: Fetch all orders for the user
    let orderList = await OrderDB.findAll({
      where: {
        user_id: user_id,
        is_individual_order: 1,
      },
      attributes: [
        "order_number",
        "cafe_id",
        "order_item_array",
        "total_amount",
        "tax",
        "discount_amount",
        "service_charge",
        "other_charge",
        "created_at",
        "order_collected_at",
        "transaction_id",
        "status",
        "order_completed",
      ],
      include: [
        {
          model: UserDB,
          attributes: ["id", "name"],
        },
      ],
    });

    // Step 2: Loop through orders and fetch cafe details for each
    let updatedOrders = [];
    for (const order of orderList) {
      const cafeDetails = await CafeDB.findOne({
        where: {
          id: order.cafe_id,
        },
        include: [
          {
            model: UserFavouriteCafeDB,
            as: "user_favorite_cafes",
            required: false,
            where: {
              is_favorite: 1,
              user_id: parseInt(user_id),
            },
            attributes: ["is_favorite"],
          },
        ],
        attributes: [
          "id",
          "cafe_name",
          "banner_image",
          "address",
          "latitude",
          "longitude",
        ],
      });

      updatedOrders.push({
        order_details: order,
        cafe_details: cafeDetails,
      });
    }

    return updatedOrders;
  } catch (error) {
    throw new Error(error);
  }
}

//mark order collected
export async function markOrderCollected(coffeeRunId, type) {
  try {
    let updatedOrders;
    if (type == 1) {
      updatedOrders = await OrderDB.update(
        {
          order_collected_at: moment().unix(),
          status: 2,
        },
        {
          where: {
            group_coffee_run_id: coffeeRunId,
            status: 1,
          },
        }
      );
    } else {
      updatedOrders = await coffeeRunInHouseOrders.update(
        {
          order_collected_at: moment().unix(),
        },
        {
          where: {
            coffee_run_id: coffeeRunId,
          },
        }
      );
    }

    if (updatedOrders[0] === 0) {
      return null;
    }
    //Update endtime if it is greater than current time
    const currentTimeUnix = moment().unix();
    await GroupCoffeeRunDB.update(
      { request_endtime: currentTimeUnix },
      {
        where: {
          request_unique_id: coffeeRunId,
          request_endtime: { [Op.gt]: currentTimeUnix },
        },
      }
    );
const oneOrder = await OrderDB.findOne({
      where: { group_coffee_run_id: coffeeRunId },
    });

console.log("oneOrder :",oneOrder);

    if (oneOrder?.square_order_id) {
      const cafe = await CafeDB.findOne({ where: { id: oneOrder.cafe_id } });

      if (cafe?.square_access_token) {
        await markSquareOrderPickedUp(
          oneOrder.square_order_id,
          cafe.square_access_token
        );
      }
    }
    return updatedOrders[0];
  } catch (error) {
    throw new Error(error);
  }
}

export async function getPastCoffeeRunOrders(user_id) {
  try {
    const cafeOrders = await GroupCoffeeRunDB.findAll({
      where: {
        user_id: user_id,
        [Op.or]: [
          {
            in_house_order_id: {
              [Op.ne]: null,
            },
          },
          {
            order_id: {
              [Op.ne]: null,
            },
          },
        ],
      },
      attributes: ["id", "user_id", "order_id", "in_house_order_id"],
      order: [["id", "DESC"]],
      include: [
        {
          model: GroupDB,
          attributes: ["group_name"],
        },
        {
          model: CafeDB,
          attributes: ["cafe_name"],
        },
        {
          model: OrderDB,
          required: false,
          attributes: [
            "id",
            "order_item_array",
            "total_amount",
            "tax",
            "discount_amount",
            "created_at",
            "order_collected_at",
          ],
        },
        {
          model: coffeeRunInHouseOrders,
          required: false,
          attributes: [
            "id",
            "in_house_order_items",
            "customized_order",
            "created_at",
            "order_collected_at",
          ],
        },
      ],
    });
    return cafeOrders;
  } catch (error) {
    throw new Error(error);
  }
}

//Non Universal Card List
export async function nonuniversalCardList(user_id, cafe_id) {
  try {
    return await OrderDB.count({
      where: {
        user_id: parseInt(user_id),
        cafe_id: parseInt(cafe_id),
        // universal_coupon_order_id: null,
        universal_coupon_expired_time: 0,
        // is_individual_order: 0,
        status: 2,
      },
    });
  } catch (error) {
    throw new Error(error);
  }
}

//Get cafe universal stamp
export async function getnonUniversalStamp(cafe_id) {
  try {
    let nonUniversal = await LoyaltyStampDB.findOne({
      where: {
        cafe_id: cafe_id,
        is_universal: 0,
      },
    });
    return nonUniversal;
  } catch (error) {
    throw new Error(error);
  }
}

//Non Universal Card List for individual order
export async function nonuniversalCardListIndividualOrder(user_id, cafe_id) {
  try {
    return await OrderDB.count({
      where: {
        user_id: parseInt(user_id),
        cafe_id: parseInt(cafe_id),
        // universal_coupon_order_id: null,
        universal_coupon_expired_time: 0,
        is_individual_order: 1,
        status: 2,
      },
    });
  } catch (error) {
    throw new Error(error);
  }
}

// get Previous Order
export async function getOngoingOrder(data) {
  try {
    const orders = await OrderDB.findAll({
      where: {
        user_id: data.user_id,
        status: {
          [Sequelize.Op.in]: [1, 3],
        },
        is_individual_order: 1,
      },
      order: [["id", "DESC"]],
      attributes: [
        "id",
        "order_number",
        "group_id",
        "cafe_id",
        "total_amount",
        "tax",
        "service_charge",
        "other_charge",
        "order_item_array",
        "discount_amount",
        "order_placed_at",
        "status",
        "order_completed",
      ],
    });

    if (!orders || orders.length === 0) {
      return null;
    }

    const cafeIds = [...new Set(orders.map((order) => order.cafe_id))];

    const cafes = await CafeDB.findAll({
      where: { id: cafeIds },
    });

    // Create a map of cafe_id to cafe object for easy lookup
    const cafeMap = {};
    cafes.forEach((cafe) => {
      cafeMap[cafe.id] = cafe;
    });

    // Attach each cafe to its corresponding order
    const result = orders.map((order) => ({
      order_details: order,
      cafe_details: cafeMap[order.cafe_id] || null,
    }));

    return result;
  } catch (error) {
    throw new Error(error);
  }
}

// check click and collect

export async function checkClickAndCollect(data) {
  try {
    let CafeManagement = await CafeManagementDB.findOne({
      where: {
        cafe_id: data.body.cafe_id,
      },
    });

    if (!CafeManagement) {
      return { clickandcollect: 0, message: "Cafe not found" };
    }

    if (CafeManagement.click_and_collect == 0) {
      return { clickandcollect: 0, message: "Click And Collect is disabled" };
    }

    let today = moment().tz("Europe/London").day(); // Sunday = 0
    let yesterday = today === 0 ? 6 : today - 1;
    console.log(today, yesterday);
    let CafeClickCollectTiming = await CafeClickCollectTimingDB.findOne({
      where: {
        cafe_id: data.body.cafe_id,
        day: today,
        is_active: 1,
      },
    });

    if (!CafeClickCollectTiming) {
      return { clickandcollect: 0, message: "No timing found for today" };
    }

    let { start_time, end_time } = CafeClickCollectTiming;

    let start = momentTime(start_time, "HH:mm:ss");
    let end = momentTime(end_time, "HH:mm:ss");

    let slots = [];
    while (start.isBefore(end)) {
      let slotStart = start.clone();
      let slotEnd = momentTime.min(start.clone().add(10, "minutes"), end);

      slots.push({
        start_time: slotStart.format("HH:mm:ss"),
        end_time: slotEnd.format("HH:mm:ss"),
      });

      start.add(10, "minutes");
    }
    console.log("Available Click and Collect Slots: ", slots);

    // Get current UK time
    let now = momentTime().tz("Europe/London");
    let currentTime = now.format("HH:mm:ss");	console.log("current time", currentTime);

    let currentSlot = slots.find((slot) => {
      return currentTime >= slot.start_time && currentTime < slot.end_time;
    });

    if (!currentSlot) {
      return {
        clickandcollect: 0,
        message: "Click And Collect is not available (outside of slot time)",
      };
    }

    let orderCount = await OrderDB.count({
      where: {
        cafe_id: data.body.cafe_id,
        [Op.and]: [
          where(
            fn("DATE", fn("FROM_UNIXTIME", col("order_placed_at"))),
            now.format("YYYY-MM-DD")
          ),
          where(fn("TIME", fn("FROM_UNIXTIME", col("order_placed_at"))), {
            [Op.between]: [currentSlot.start_time, currentSlot.end_time],
          }),
        ],
        estimated_arival_time: { [Op.ne]: null },
      },
    });

    console.log(
      `Current Slot ${currentSlot.start_time} - ${currentSlot.end_time}: Order Count = ${orderCount}`
    );

    if (orderCount < CafeManagement.max_orders_click_collect) {
      return { clickandcollect: 1, message: "Click And Collect is available" };
    } else {
      return {
        clickandcollect: 0,
        message: "Click And Collect is not available",
      };
    }
  } catch (error) {
    console.log(error);
    throw new Error("Failed to check Click And Collect availability");
  }
}

export async function oldcheckClickAndCollect(data) {
  try {
    let CafeManagement = await CafeManagementDB.findOne({
      where: {
        cafe_id: data.body.cafe_id,
      },
    });
    console.log("Cafe Management: ", CafeManagement);
    if (!CafeManagement) {
      return null;
    }
    if (CafeManagement.click_and_collect == 0) {
      return null;
    }
    let today = moment().day();
    let CafeClickCollectTiming = await CafeClickCollectTimingDB.findOne({
      where: {
        cafe_id: data.body.cafe_id,
        day: today - 1,
        is_active: 1,
      },
    });

    if (!CafeClickCollectTiming) {
      return "No timing found for today";
    }
    let { start_time, end_time } = CafeClickCollectTiming;
    let orderCount = await OrderDB.count({
      where: {
        cafe_id: data.body.cafe_id,
        [Op.and]: [
          where(
            fn("DATE", fn("FROM_UNIXTIME", col("order_placed_at"))),
            moment().format("YYYY-MM-DD")
          ), // Orders placed today
          where(fn("TIME", fn("FROM_UNIXTIME", col("order_placed_at"))), {
            [Op.between]: [start_time, end_time], // Orders placed within the time range
          }),
        ],
      },
    });
    console.log("Order Count: ", orderCount);
    if (orderCount <= CafeManagement.max_orders_click_collect) {
      return "Click And Collect is available";
    } else {
      // await CafeManagementDB.update(
      //   { click_and_collect: 0 },
      //   {
      //     where: {
      //       cafe_id: data.body.cafe_id,
      //     },
      //   }
      // );
      return "Click And Collect is not available";
    }
  } catch (error) {
    console.log(error);
    throw new Error(error);
  }
}

//mark individual order collected
export async function markIndividualOrderCollected(data) {
  try {
    let updatedOrders;
    updatedOrders = await OrderDB.update(
      {
        order_collected_at: moment().unix(),
        status: 2,
      },
      {
        where: {
          id: data.order_id,
          status: {
            [Sequelize.Op.in]: [1, 3],
          },
          //order_completed: 1,
        },
      }
    );

	const oneOrder = await OrderDB.findOne({
      where: { id: data.order_id },
    });

    if (oneOrder?.square_order_id) {
      const cafe = await CafeDB.findOne({ where: { id: oneOrder.cafe_id } });

      if (cafe?.square_access_token) {
        await markSquareOrderPickedUp(
          oneOrder.square_order_id,
          cafe.square_access_token
        );
      }
    }

    return updatedOrders;
  } catch (error) {
    throw new Error(error);
  }
}

//mark individual order collected
export async function getRemainingClickAndCollectOrders(data) {
  try {
    let CafeManagement = await CafeManagementDB.findOne({
      where: {
        cafe_id: data.cafe_id,
      },
    });
    let remainingOrders = "";
    if (!CafeManagement) {
      remainingOrders = 0;
      return remainingOrders;
    }

    let clickAndCollectOrders = CafeManagement.max_orders_click_collect;

    if (!clickAndCollectOrders) {
      remainingOrders = 0;
      return remainingOrders;
    }

    let todayTotalOrder = await OrderDB.count({
      where: {
        cafe_id: data.cafe_id,
        order_placed_at: {
          [Op.gte]: moment().startOf("day").unix(),
          [Op.lte]: moment().endOf("day").unix(),
        },
      },
    });
    if (!todayTotalOrder) {
      todayTotalOrder = 0;
    }

    remainingOrders = clickAndCollectOrders - todayTotalOrder;

    if (remainingOrders < 0) {
      remainingOrders = 0;
    }

    return remainingOrders;
  } catch (error) {
    throw new Error(error);
  }
}

export async function orderStampAvailability(data) {
  try {
    let orderCount = await OrderDB.count({
      where: {
        user_id: data.userId,
        order_completed: 2,
        status: 2,
      },
    });
    console.log("Order Count: ", orderCount);
    const getCafeStamp = await getStampsByCafe(data.cafe_id);
    console.log("Cafe Stamp: ", getCafeStamp);

    const user = await UserDB.findOne({
      where: {
        id: data.userId,
      },
      attributes: ["remaining_awarded_stamps"],
    });
    console.log("User Remaining Stamps: ", user?.remaining_awarded_stamps);

    const remainingStamps = user?.remaining_awarded_stamps ?? 0;

   // if (orderCount >= 1 && getCafeStamp && remainingStamps > 0) {
if (getCafeStamp && remainingStamps > 0) {

      getCafeStamp.rewarded_stamp = 1;
      return getCafeStamp;
    }
    return null;
  } catch (error) {
    console.log("Error in orderStampAvailability: ", error);
    throw new Error(error);
  }
}

export async function isUserEligibleForCoffeeRunDiscount(data) {
  const DISCOUNT_START_TIMESTAMP = moment("2025-09-13 12:00:00").unix();
  const MAX_DISCOUNTED_ORDERS = 100;

  // 2. Check if user already got discount for this group
  const userGroupDiscount = await DiscountDB.findOne({
    where: {
      user_id: data.userId,
      is_individual_order: 0,
      created_at: {
        [Sequelize.Op.gte]: DISCOUNT_START_TIMESTAMP,
      },
    },
    include: [
      {
        model: OrderDB,
        required: true,
        where: {
          group_id: data.group_id,
        },
      },
    ],
  });

  if (userGroupDiscount) {
    return {
      eligible: false,
      reason: "User has already received a coffee run discount for this group.",
    };
  }

  // 3. Check if total 100 discounts have already been given
  const totalDiscounts = await DiscountDB.count({
    where: {
      is_individual_order: 0,
      created_at: {
        [Sequelize.Op.gte]: DISCOUNT_START_TIMESTAMP,
      },
    },
  });

  if (totalDiscounts >= MAX_DISCOUNTED_ORDERS) {
    return {
      eligible: false,
      reason: "Coffee run discount limit (100) has been reached.",
    };
  }

  // 4. All checks passed
  return {
    eligible: true,
    reason: "User is eligible for 10% coffee run discount for this group.",
  };
}

export async function applyCoffeeRunDiscount(data) {
  try {
    let discountEntry = await DiscountDB.create({
      user_id: data.userId,
      is_individual_order: 0,
      orderId: data.order_id, // orderId is mapped to order_number
      requestUniqueId: data.requestUniqueId || null,
      discount_amount: data.discount_amount,
      created_at: moment().format("YYYY-MM-DD HH:mm:ss"),
      updated_at: moment().format("YYYY-MM-DD HH:mm:ss"),
    });

    const user = await UserDB.update(
      { has_used_welcome_discount: 1 },
      {
        where: {
          id: data.userId,
        },
      }
    );

    return discountEntry;
  } catch (error) {
    throw new Error(error);
  }
}

export async function markSquareOrderPickedUp(squareOrderId, accessToken) {
  try {
    // 1?? Get the Square order
    const orderRes = await fetch(`https://connect.squareup.com/v2/orders/${squareOrderId}`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
        "Square-Version": "2024-06-04",
      },
    });

    const orderData = await orderRes.json();
    if (!orderRes.ok || !orderData.order) {
      throw new Error("Failed to fetch Square order: " + JSON.stringify(orderData));
    }

    const order = orderData.order;

    // 2?? Get the active location ID
    const locationsRes = await fetch("https://connect.squareup.com/v2/locations", {
      method: "GET",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
        "Square-Version": "2024-06-04",
      },
    });

    const locationsData = await locationsRes.json();
    if (!locationsRes.ok || !locationsData.locations || !locationsData.locations.length) {
      throw new Error("Failed to fetch Square locations: " + JSON.stringify(locationsData));
    }

    const activeLocation = locationsData.locations.find((loc) => loc.status === "ACTIVE");
    if (!activeLocation) throw new Error("No active Square location found");
    const locationId = activeLocation.id;

    // 3?? Mark the first fulfillment as COMPLETED
    if (order.fulfillments && order.fulfillments.length > 0) {
      order.fulfillments[0].state = "COMPLETED";
    } else {
      throw new Error("Order has no fulfillments to complete");
    }

    // 4?? Update the order on Square
    const updateRes = await fetch(`https://connect.squareup.com/v2/orders/${squareOrderId}`, {
      method: "PUT",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
        "Square-Version": "2024-06-04",
      },
      body: JSON.stringify({
        order: {
          id: squareOrderId,
          location_id: locationId,
          version: order.version,
          fulfillments: order.fulfillments,
        },
        idempotency_key: `pickup-${squareOrderId}-${Date.now()}`,
      }),
    });

    const updateData = await updateRes.json();
    if (!updateRes.ok) {
      throw new Error("Failed to update Square order: " + JSON.stringify(updateData));
    }

    console.log("? Square order marked as picked up:", squareOrderId);
    return updateData;
  } catch (err) {
    console.error("?? markSquareOrderPickedUp error:", err);
    throw err;
  }
}