Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

91 changes: 58 additions & 33 deletions src/controllers/AdminControllers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Request, Response, NextFunction } from 'express';
import { prisma } from '../utils/prisma';
import { BadRequestError, NotFoundError } from '../utils/error';
import { SelfpickupStatus, ShippingStatus, Size } from '@prisma/client';
import { sendShippingStartedMail } from '../utils/mailer';
import { OrderStatus, SelfpickupStatus, ShippingStatus, Size } from '@prisma/client';
import { sendShippingStartedMail ,sendReadyForPickupMail, sendOrderCompletedMail} from '../utils/mailer';
import { razorpay } from '../utils/razorpay';

interface UpdateOrderStatusRequest {
Expand Down Expand Up @@ -60,15 +60,35 @@ export async function updateOrderStatus(
'Order status must be confirmed and payment must be received to update self-pickup status'
);
}
// update selfpickup status
const updatedOrder = await prisma.order.update({
where: { orderId },
data: { selfpickupStatus: selfpickupStatus },
});


const previousStatus = order.selfpickupStatus;

const updatedOrder = await prisma.order.update({
where: { orderId },
data: { selfpickupStatus },
});

if (previousStatus !== selfpickupStatus) {
if (selfpickupStatus === SelfpickupStatus.ready_for_pickup) {
await sendReadyForPickupMail(
order.user.name,
order.orderId,
order.user.email
);
}

if (selfpickupStatus === SelfpickupStatus.picked_up) {
await sendOrderCompletedMail(
order.user.name,
order.orderId,
order.user.email
);
}
}

return res.status(200).json({
order: updatedOrder,
message: 'Self-pickup status updated successfully',
order: updatedOrder,
message: 'Self-pickup status updated successfully',
});
} else {
/**
Expand All @@ -83,30 +103,35 @@ export async function updateOrderStatus(
);
}

if (
order.shippingStatus !== ShippingStatus.shipping &&
shippingStatus === ShippingStatus.shipping
) {
/**
* When admin sets shipping status to shipping, send mail to user
* with tracking id if present
*/
sendShippingStartedMail(
order.user.name,
order.orderId,
order.user.email,
trackingId
);
}
const previousShippingStatus = order.shippingStatus;

const updatedOrder = await prisma.order.update({
where: { orderId: orderId },
data: {
shippingStatus,
trackingId,
},
});

const updatedOrder = await prisma.order.update({
where: { orderId },
data: {
shippingStatus,
trackingId,
},
});


if (previousShippingStatus !== shippingStatus) {
if (shippingStatus === ShippingStatus.shipping) {
await sendShippingStartedMail(
order.user.name,
order.orderId,
order.user.email,
trackingId
);
}

if (shippingStatus === ShippingStatus.delivered) {
await sendOrderCompletedMail(
order.user.name,
order.orderId,
order.user.email
);
}
}
return res.status(200).json({
order: updatedOrder,
message: 'Order status updated successfully',
Expand Down
168 changes: 78 additions & 90 deletions src/utils/adjustPreorder.ts
Original file line number Diff line number Diff line change
@@ -1,113 +1,101 @@
import { prisma } from "./prisma";
import { OrderStatus } from "@prisma/client";
import { OrderStatus, PaymentStatus } from "@prisma/client";

export async function adjustPreorders(itemId: number) {
try {
// Use a transaction for the entire operation
await prisma.$transaction(async (tx) => {
// 1. Get all preorders for this item, oldest first
const preorders = await tx.order.findMany({
where: {
paymentStatus: "payment_received",
orderStatus: OrderStatus.pre_ordered,
orderItems: {
some: {
isPreorder: true,
itemId,
// Only consider items that aren't fully fulfilled
fulfilledQuantity: { lt: prisma.orderItem.fields.quantity },
},
await prisma.$transaction(async (tx) => {
const preorders = await tx.order.findMany({
where: {
paymentStatus: PaymentStatus.payment_received,
orderStatus: OrderStatus.pre_ordered,
orderItems: {
some: {
isPreorder: true,
itemId,
},
},
include: {
orderItems: {
where: {
itemId,
fulfilledQuantity: { lt: prisma.orderItem.fields.quantity },
},
},
include: {
orderItems: {
where: {
itemId,
isPreorder: true,
},
},
orderBy: { orderDate: "asc" },
});

// 2. Get current stock counts for all variants at once
const stockCounts = await tx.stockCount.findMany({
where: { itemId },
});
},
orderBy: { orderDate: "asc" },
});

// Create a map for easy access
const stockMap = new Map();
stockCounts.forEach((stock) => {
const key = `${stock.colorOption}-${stock.sizeOption}`;
stockMap.set(key, stock);
});
const stockCounts = await tx.stockCount.findMany({
where: { itemId },
});

// 3. Process each preorder
for (const order of preorders) {
let fullyFulfilled = true;
const stockMap = new Map<string, number>();
stockCounts.forEach((s) => {
stockMap.set(`${s.colorOption}-${s.sizeOption}`, s.count);
});

for (const orderItem of order.orderItems) {
if (orderItem.itemId !== itemId) continue;
for (const order of preorders) {
// Fulfill preorder items for THIS itemId
for (const orderItem of order.orderItems) {
const remainingQty =
orderItem.quantity - orderItem.fulfilledQuantity;

// Calculate remaining quantity
const remainingQty =
orderItem.quantity - (orderItem.fulfilledQuantity || 0);
if (remainingQty <= 0) continue;
if (remainingQty <= 0) continue;

// Get current stock for this variant
const stockKey = `${orderItem.colorOption}-${orderItem.sizeOption}`;
const stock = stockMap.get(stockKey);
const key = `${orderItem.colorOption}-${orderItem.sizeOption}`;
const availableStock = stockMap.get(key) ?? 0;

if (!stock || stock.count <= 0) {
fullyFulfilled = false;
continue;
}
if (availableStock <= 0) continue;

const fulfillNow = Math.min(stock.count, remainingQty);
const fulfillNow = Math.min(availableStock, remainingQty);

// Update the order item
await tx.orderItem.update({
where: { id: orderItem.id },
data: {
fulfilledQuantity:
(orderItem.fulfilledQuantity || 0) + fulfillNow,
},
});
await tx.orderItem.update({
where: { id: orderItem.id },
data: {
fulfilledQuantity:
orderItem.fulfilledQuantity + fulfillNow,
},
});

// Update stock in our map and database
const newStockCount = stock.count - fulfillNow;
stockMap.set(stockKey, { ...stock, count: newStockCount });
stockMap.set(key, availableStock - fulfillNow);

await tx.stockCount.update({
where: {
itemId_colorOption_sizeOption: {
itemId,
colorOption: stock.colorOption,
sizeOption: stock.sizeOption,
},
await tx.stockCount.update({
where: {
itemId_colorOption_sizeOption: {
itemId,
colorOption: orderItem.colorOption,
sizeOption: orderItem.sizeOption,
},
data: { count: newStockCount },
});
},
data: { count: availableStock - fulfillNow },
});
}

if (fulfillNow < remainingQty) {
fullyFulfilled = false;
}
}
// RECHECK ENTIRE ORDER (ALL preorder items)
const allPreorderItems = await tx.orderItem.findMany({
where: {
orderId: order.orderId,
isPreorder: true,
},
select: {
quantity: true,
fulfilledQuantity: true,
},
});

const newStatus = fullyFulfilled
? OrderStatus.order_confirmed
: OrderStatus.pre_ordered
const hasPending = allPreorderItems.some(
(oi) => oi.fulfilledQuantity < oi.quantity
);

await tx.order.update({
where: { orderId: order.orderId },
data: { orderStatus: newStatus },
});
}
});
await tx.order.update({
where: { orderId: order.orderId },
data: {
orderStatus: hasPending
? OrderStatus.pre_ordered
: OrderStatus.order_confirmed,
},
});
}
});

console.log(`Successfully processed preorders for item ${itemId}`);
} catch (error) {
console.error(`Error processing preorders for item ${itemId}:`, error);
throw error;
}
}
58 changes: 58 additions & 0 deletions src/utils/mailTemplates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,64 @@ export async function getOrderConfirmationHTML(
return html;
}

export async function getOrderCompletedHTML(
userName: string,
orderId: string
){
const bodyTextRow1 = `
<p style="font-size: 14px; line-height: 170%;">
Your order with ID <b>${orderId}</b> has been successfully completed.
</p>
`;

const bodyTextRow2 = `
<p style="font-size: 14px; line-height: 170%;">
Thank you for supporting <b>Excel MEC</b>
</p>
`;

const mainHeader = `Thank You for Your Support!`;
const dateText = new Date().toLocaleString('en-IN', {
timeZone: 'Asia/Kolkata',
day: 'numeric',
month: 'short',
year: 'numeric',
});

const bodyHeader = `Hi ${userName}!`;
const bodyText = `${bodyTextRow1}\n${bodyTextRow2}`;

const rawHtml = await readFile(`${__dirname}/index.html`, 'utf8');

return rawHtml
.replace('{{MAIN_HEADER}}', mainHeader)
.replace('{{DATE}}', dateText)
.replace('{{BODY_HEADER}}', bodyHeader)
.replace('{{BODY_TEXT}}', bodyText);
}

export async function getReadyForPickupHTML(
userName: string,
orderId: string
) {
const bodyText = `
<p style="font-size: 14px; line-height: 170%;">
Your order with ID <b>${orderId}</b> is ready for pickup.
</p>
<p style="font-size: 14px; line-height: 170%;">
Please collect it from Front Desk.
</p>
`;

const rawHtml = await readFile(`${__dirname}/index.html`, 'utf8');

return rawHtml
.replace('{{MAIN_HEADER}}', 'Order Ready for Pickup')
.replace('{{DATE}}', new Date().toLocaleDateString('en-IN'))
.replace('{{BODY_HEADER}}', `Hi ${userName}!`)
.replace('{{BODY_TEXT}}', bodyText);
}

export async function getRefundConfirmationHTML(
userName: string,
totalAmt: number,
Expand Down
Loading