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;
}
}