710 lines
22 KiB
TypeScript
710 lines
22 KiB
TypeScript
import { db } from '../db/db_index'
|
|
import {
|
|
addresses,
|
|
complaints,
|
|
couponUsage,
|
|
orderItems,
|
|
orders,
|
|
orderStatus,
|
|
payments,
|
|
refunds,
|
|
} from '../db/schema'
|
|
import { and, desc, eq, inArray, lt, SQL } from 'drizzle-orm'
|
|
import type {
|
|
AdminOrderDetails,
|
|
AdminOrderRow,
|
|
AdminOrderStatusRecord,
|
|
AdminOrderUpdateResult,
|
|
AdminOrderItemPackagingResult,
|
|
AdminOrderMessageResult,
|
|
AdminOrderBasicResult,
|
|
AdminGetSlotOrdersResult,
|
|
AdminGetAllOrdersResultWithUserId,
|
|
AdminRebalanceSlotsResult,
|
|
AdminCancelOrderResult,
|
|
AdminRefundRecord,
|
|
RefundStatus,
|
|
PaymentStatus,
|
|
} from '@packages/shared'
|
|
import type { InferSelectModel } from 'drizzle-orm'
|
|
|
|
const isPaymentStatus = (value: string): value is PaymentStatus =>
|
|
value === 'pending' || value === 'success' || value === 'cod' || value === 'failed'
|
|
|
|
const isRefundStatus = (value: string): value is RefundStatus =>
|
|
value === 'success' || value === 'pending' || value === 'failed' || value === 'none' || value === 'na' || value === 'processed'
|
|
|
|
type OrderStatusRow = InferSelectModel<typeof orderStatus>
|
|
|
|
const mapOrderStatusRecord = (record: OrderStatusRow): AdminOrderStatusRecord => ({
|
|
id: record.id,
|
|
orderTime: record.orderTime,
|
|
userId: record.userId,
|
|
orderId: record.orderId,
|
|
isPackaged: record.isPackaged,
|
|
isDelivered: record.isDelivered,
|
|
isCancelled: record.isCancelled,
|
|
cancelReason: record.cancelReason ?? null,
|
|
isCancelledByAdmin: record.isCancelledByAdmin ?? null,
|
|
paymentStatus: isPaymentStatus(record.paymentStatus) ? record.paymentStatus : 'pending',
|
|
cancellationUserNotes: record.cancellationUserNotes ?? null,
|
|
cancellationAdminNotes: record.cancellationAdminNotes ?? null,
|
|
cancellationReviewed: record.cancellationReviewed,
|
|
cancellationReviewedAt: record.cancellationReviewedAt ?? null,
|
|
refundCouponId: record.refundCouponId ?? null,
|
|
})
|
|
|
|
export async function updateOrderNotes(orderId: number, adminNotes: string | null): Promise<AdminOrderRow | null> {
|
|
const [result] = await db
|
|
.update(orders)
|
|
.set({ adminNotes })
|
|
.where(eq(orders.id, orderId))
|
|
.returning()
|
|
return result || null
|
|
}
|
|
|
|
export async function updateOrderPackaged(orderId: string, isPackaged: boolean): Promise<AdminOrderUpdateResult> {
|
|
const orderIdNumber = parseInt(orderId)
|
|
|
|
await db
|
|
.update(orderItems)
|
|
.set({ is_packaged: isPackaged })
|
|
.where(eq(orderItems.orderId, orderIdNumber))
|
|
|
|
if (!isPackaged) {
|
|
await db
|
|
.update(orderStatus)
|
|
.set({ isPackaged, isDelivered: false })
|
|
.where(eq(orderStatus.orderId, orderIdNumber))
|
|
} else {
|
|
await db
|
|
.update(orderStatus)
|
|
.set({ isPackaged })
|
|
.where(eq(orderStatus.orderId, orderIdNumber))
|
|
}
|
|
|
|
const order = await db.query.orders.findFirst({
|
|
where: eq(orders.id, orderIdNumber),
|
|
})
|
|
|
|
return { success: true, userId: order?.userId ?? null }
|
|
}
|
|
|
|
export async function updateOrderDelivered(orderId: string, isDelivered: boolean): Promise<AdminOrderUpdateResult> {
|
|
const orderIdNumber = parseInt(orderId)
|
|
|
|
await db
|
|
.update(orderStatus)
|
|
.set({ isDelivered })
|
|
.where(eq(orderStatus.orderId, orderIdNumber))
|
|
|
|
const order = await db.query.orders.findFirst({
|
|
where: eq(orders.id, orderIdNumber),
|
|
})
|
|
|
|
return { success: true, userId: order?.userId ?? null }
|
|
}
|
|
|
|
export async function getOrderDetails(orderId: number): Promise<AdminOrderDetails | null> {
|
|
// Single optimized query with all relations
|
|
const orderData = await db.query.orders.findFirst({
|
|
where: eq(orders.id, orderId),
|
|
with: {
|
|
user: true,
|
|
address: true,
|
|
slot: true,
|
|
orderItems: {
|
|
with: {
|
|
product: {
|
|
with: {
|
|
unit: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
payment: true,
|
|
paymentInfo: true,
|
|
orderStatus: true,
|
|
refunds: true,
|
|
},
|
|
})
|
|
|
|
if (!orderData) {
|
|
return null
|
|
}
|
|
|
|
const couponUsageData = await db.query.couponUsage.findMany({
|
|
where: eq(couponUsage.orderId, orderData.id),
|
|
with: {
|
|
coupon: true,
|
|
},
|
|
})
|
|
|
|
let couponData = null
|
|
if (couponUsageData.length > 0) {
|
|
let totalDiscountAmount = 0
|
|
const orderTotal = parseFloat(orderData.totalAmount.toString())
|
|
|
|
for (const usage of couponUsageData) {
|
|
let discountAmount = 0
|
|
|
|
if (usage.coupon.discountPercent) {
|
|
discountAmount =
|
|
(orderTotal * parseFloat(usage.coupon.discountPercent.toString())) /
|
|
100
|
|
} else if (usage.coupon.flatDiscount) {
|
|
discountAmount = parseFloat(usage.coupon.flatDiscount.toString())
|
|
}
|
|
|
|
if (
|
|
usage.coupon.maxValue &&
|
|
discountAmount > parseFloat(usage.coupon.maxValue.toString())
|
|
) {
|
|
discountAmount = parseFloat(usage.coupon.maxValue.toString())
|
|
}
|
|
|
|
totalDiscountAmount += discountAmount
|
|
}
|
|
|
|
couponData = {
|
|
couponCode: couponUsageData.map((u) => u.coupon.couponCode).join(', '),
|
|
couponDescription: `${couponUsageData.length} coupons applied`,
|
|
discountAmount: totalDiscountAmount,
|
|
}
|
|
}
|
|
|
|
const statusRecord = orderData.orderStatus?.[0]
|
|
const orderStatusRecord = statusRecord ? mapOrderStatusRecord(statusRecord) : null
|
|
let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
|
|
if (orderStatusRecord?.isCancelled) {
|
|
status = 'cancelled'
|
|
} else if (orderStatusRecord?.isDelivered) {
|
|
status = 'delivered'
|
|
}
|
|
|
|
const refund = orderData.refunds?.[0]
|
|
const refundStatus = refund?.refundStatus && isRefundStatus(refund.refundStatus)
|
|
? refund.refundStatus
|
|
: null
|
|
const refundRecord: AdminRefundRecord | null = refund
|
|
? {
|
|
id: refund.id,
|
|
orderId: refund.orderId,
|
|
refundAmount: refund.refundAmount,
|
|
refundStatus,
|
|
merchantRefundId: refund.merchantRefundId,
|
|
refundProcessedAt: refund.refundProcessedAt,
|
|
createdAt: refund.createdAt,
|
|
}
|
|
: null
|
|
|
|
return {
|
|
id: orderData.id,
|
|
readableId: orderData.id,
|
|
userId: orderData.user.id,
|
|
customerName: `${orderData.user.name}`,
|
|
customerEmail: orderData.user.email,
|
|
customerMobile: orderData.user.mobile,
|
|
address: {
|
|
name: orderData.address.name,
|
|
line1: orderData.address.addressLine1,
|
|
line2: orderData.address.addressLine2,
|
|
city: orderData.address.city,
|
|
state: orderData.address.state,
|
|
pincode: orderData.address.pincode,
|
|
phone: orderData.address.phone,
|
|
},
|
|
slotInfo: orderData.slot
|
|
? {
|
|
time: orderData.slot.deliveryTime.toISOString(),
|
|
sequence: orderData.slot.deliverySequence,
|
|
}
|
|
: null,
|
|
isCod: orderData.isCod,
|
|
isOnlinePayment: orderData.isOnlinePayment,
|
|
totalAmount:
|
|
parseFloat(orderData.totalAmount?.toString() || '0') -
|
|
parseFloat(orderData.deliveryCharge?.toString() || '0'),
|
|
deliveryCharge: parseFloat(orderData.deliveryCharge?.toString() || '0'),
|
|
adminNotes: orderData.adminNotes,
|
|
userNotes: orderData.userNotes,
|
|
createdAt: orderData.createdAt,
|
|
status,
|
|
isPackaged: orderStatusRecord?.isPackaged || false,
|
|
isDelivered: orderStatusRecord?.isDelivered || false,
|
|
items: orderData.orderItems.map((item) => ({
|
|
id: item.id,
|
|
name: item.product.name,
|
|
quantity: item.quantity,
|
|
productSize: item.product.productQuantity,
|
|
price: item.price,
|
|
unit: item.product.unit?.shortNotation,
|
|
amount: parseFloat(item.price.toString()) * parseFloat(item.quantity || '0'),
|
|
isPackaged: item.is_packaged,
|
|
isPackageVerified: item.is_package_verified,
|
|
})),
|
|
payment: orderData.payment
|
|
? {
|
|
status: orderData.payment.status,
|
|
gateway: orderData.payment.gateway,
|
|
merchantOrderId: orderData.payment.merchantOrderId,
|
|
}
|
|
: null,
|
|
paymentInfo: orderData.paymentInfo
|
|
? {
|
|
status: orderData.paymentInfo.status,
|
|
gateway: orderData.paymentInfo.gateway,
|
|
merchantOrderId: orderData.paymentInfo.merchantOrderId,
|
|
}
|
|
: null,
|
|
cancelReason: orderStatusRecord?.cancelReason || null,
|
|
cancellationReviewed: orderStatusRecord?.cancellationReviewed || false,
|
|
isRefundDone: refundStatus === 'processed' || false,
|
|
refundStatus,
|
|
refundAmount: refund?.refundAmount
|
|
? parseFloat(refund.refundAmount.toString())
|
|
: null,
|
|
couponData,
|
|
couponCode: couponData?.couponCode || null,
|
|
couponDescription: couponData?.couponDescription || null,
|
|
discountAmount: couponData?.discountAmount || null,
|
|
orderStatus: orderStatusRecord,
|
|
refundRecord,
|
|
isFlashDelivery: orderData.isFlashDelivery,
|
|
}
|
|
}
|
|
|
|
export async function updateOrderItemPackaging(
|
|
orderItemId: number,
|
|
isPackaged?: boolean,
|
|
isPackageVerified?: boolean
|
|
): Promise<AdminOrderItemPackagingResult> {
|
|
const orderItem = await db.query.orderItems.findFirst({
|
|
where: eq(orderItems.id, orderItemId),
|
|
})
|
|
|
|
if (!orderItem) {
|
|
return { success: false, updated: false }
|
|
}
|
|
|
|
const updateData: Partial<{
|
|
is_packaged: boolean
|
|
is_package_verified: boolean
|
|
}> = {}
|
|
|
|
if (isPackaged !== undefined) {
|
|
updateData.is_packaged = isPackaged
|
|
}
|
|
if (isPackageVerified !== undefined) {
|
|
updateData.is_package_verified = isPackageVerified
|
|
}
|
|
|
|
await db
|
|
.update(orderItems)
|
|
.set(updateData)
|
|
.where(eq(orderItems.id, orderItemId))
|
|
|
|
return { success: true, updated: true }
|
|
}
|
|
|
|
export async function removeDeliveryCharge(orderId: number): Promise<AdminOrderMessageResult | null> {
|
|
const order = await db.query.orders.findFirst({
|
|
where: eq(orders.id, orderId),
|
|
})
|
|
|
|
if (!order) {
|
|
return null
|
|
}
|
|
|
|
const currentDeliveryCharge = parseFloat(order.deliveryCharge?.toString() || '0')
|
|
const currentTotalAmount = parseFloat(order.totalAmount?.toString() || '0')
|
|
const newTotalAmount = currentTotalAmount - currentDeliveryCharge
|
|
|
|
await db
|
|
.update(orders)
|
|
.set({
|
|
deliveryCharge: '0',
|
|
totalAmount: newTotalAmount.toString(),
|
|
})
|
|
.where(eq(orders.id, orderId))
|
|
|
|
return { success: true, message: 'Delivery charge removed' }
|
|
}
|
|
|
|
export async function getSlotOrders(slotId: string): Promise<AdminGetSlotOrdersResult> {
|
|
const slotOrders = await db.query.orders.findMany({
|
|
where: eq(orders.slotId, parseInt(slotId)),
|
|
with: {
|
|
user: true,
|
|
address: true,
|
|
slot: true,
|
|
orderItems: {
|
|
with: {
|
|
product: {
|
|
with: {
|
|
unit: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
orderStatus: true,
|
|
},
|
|
})
|
|
|
|
const filteredOrders = slotOrders.filter((order) => {
|
|
const statusRecord = order.orderStatus[0]
|
|
return order.isCod || (statusRecord && statusRecord.paymentStatus === 'success')
|
|
})
|
|
|
|
const formattedOrders = filteredOrders.map((order) => {
|
|
const statusRecord = order.orderStatus[0]
|
|
let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
|
|
if (statusRecord?.isCancelled) {
|
|
status = 'cancelled'
|
|
} else if (statusRecord?.isDelivered) {
|
|
status = 'delivered'
|
|
}
|
|
|
|
const items = order.orderItems.map((item) => ({
|
|
id: item.id,
|
|
name: item.product.name,
|
|
quantity: parseFloat(item.quantity),
|
|
price: parseFloat(item.price.toString()),
|
|
amount: parseFloat(item.quantity) * parseFloat(item.price.toString()),
|
|
unit: item.product.unit?.shortNotation || '',
|
|
isPackaged: item.is_packaged,
|
|
isPackageVerified: item.is_package_verified,
|
|
}))
|
|
|
|
const paymentMode: 'COD' | 'Online' = order.isCod ? 'COD' : 'Online'
|
|
|
|
return {
|
|
id: order.id,
|
|
readableId: order.id,
|
|
customerName: order.user.name || order.user.mobile+'',
|
|
address: `${order.address.addressLine1}${
|
|
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ''
|
|
}, ${order.address.city}, ${order.address.state} - ${
|
|
order.address.pincode
|
|
}, Phone: ${order.address.phone}`,
|
|
addressId: order.addressId,
|
|
latitude: order.address.adminLatitude ?? order.address.latitude,
|
|
longitude: order.address.adminLongitude ?? order.address.longitude,
|
|
totalAmount: parseFloat(order.totalAmount),
|
|
items,
|
|
deliveryTime: order.slot?.deliveryTime.toISOString() || null,
|
|
status,
|
|
isPackaged: order.orderItems.every((item) => item.is_packaged) || false,
|
|
isDelivered: statusRecord?.isDelivered || false,
|
|
isCod: order.isCod,
|
|
paymentMode,
|
|
paymentStatus: isPaymentStatus(statusRecord?.paymentStatus || 'pending')
|
|
? statusRecord?.paymentStatus || 'pending'
|
|
: 'pending',
|
|
slotId: order.slotId,
|
|
adminNotes: order.adminNotes,
|
|
userNotes: order.userNotes,
|
|
}
|
|
})
|
|
|
|
return { success: true, data: formattedOrders }
|
|
}
|
|
|
|
export async function updateAddressCoords(
|
|
addressId: number,
|
|
latitude: number,
|
|
longitude: number
|
|
): Promise<AdminOrderBasicResult> {
|
|
const result = await db
|
|
.update(addresses)
|
|
.set({
|
|
adminLatitude: latitude,
|
|
adminLongitude: longitude,
|
|
})
|
|
.where(eq(addresses.id, addressId))
|
|
.returning()
|
|
|
|
return { success: result.length > 0 }
|
|
}
|
|
|
|
type GetAllOrdersInput = {
|
|
cursor?: number
|
|
limit: number
|
|
slotId?: number | null
|
|
packagedFilter?: 'all' | 'packaged' | 'not_packaged'
|
|
deliveredFilter?: 'all' | 'delivered' | 'not_delivered'
|
|
cancellationFilter?: 'all' | 'cancelled' | 'not_cancelled'
|
|
flashDeliveryFilter?: 'all' | 'flash' | 'regular'
|
|
}
|
|
|
|
export async function getAllOrders(input: GetAllOrdersInput): Promise<AdminGetAllOrdersResultWithUserId> {
|
|
const {
|
|
cursor,
|
|
limit,
|
|
slotId,
|
|
packagedFilter,
|
|
deliveredFilter,
|
|
cancellationFilter,
|
|
flashDeliveryFilter,
|
|
} = input
|
|
|
|
let whereCondition: SQL<unknown> | undefined = eq(orders.id, orders.id)
|
|
if (cursor) {
|
|
whereCondition = and(whereCondition, lt(orders.id, cursor))
|
|
}
|
|
if (slotId) {
|
|
whereCondition = and(whereCondition, eq(orders.slotId, slotId))
|
|
}
|
|
if (packagedFilter === 'packaged') {
|
|
whereCondition = and(whereCondition, eq(orderStatus.isPackaged, true))
|
|
} else if (packagedFilter === 'not_packaged') {
|
|
whereCondition = and(whereCondition, eq(orderStatus.isPackaged, false))
|
|
}
|
|
if (deliveredFilter === 'delivered') {
|
|
whereCondition = and(whereCondition, eq(orderStatus.isDelivered, true))
|
|
} else if (deliveredFilter === 'not_delivered') {
|
|
whereCondition = and(whereCondition, eq(orderStatus.isDelivered, false))
|
|
}
|
|
if (cancellationFilter === 'cancelled') {
|
|
whereCondition = and(whereCondition, eq(orderStatus.isCancelled, true))
|
|
} else if (cancellationFilter === 'not_cancelled') {
|
|
whereCondition = and(whereCondition, eq(orderStatus.isCancelled, false))
|
|
}
|
|
if (flashDeliveryFilter === 'flash') {
|
|
whereCondition = and(whereCondition, eq(orders.isFlashDelivery, true))
|
|
} else if (flashDeliveryFilter === 'regular') {
|
|
whereCondition = and(whereCondition, eq(orders.isFlashDelivery, false))
|
|
}
|
|
|
|
const allOrders = await db.query.orders.findMany({
|
|
where: whereCondition,
|
|
orderBy: desc(orders.createdAt),
|
|
limit: limit + 1,
|
|
with: {
|
|
user: true,
|
|
address: true,
|
|
slot: true,
|
|
orderItems: {
|
|
with: {
|
|
product: {
|
|
with: {
|
|
unit: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
orderStatus: true,
|
|
},
|
|
})
|
|
|
|
const hasMore = allOrders.length > limit
|
|
const ordersToReturn = hasMore ? allOrders.slice(0, limit) : allOrders
|
|
|
|
const filteredOrders = ordersToReturn.filter((order) => {
|
|
const statusRecord = order.orderStatus[0]
|
|
return order.isCod || (statusRecord && statusRecord.paymentStatus === 'success')
|
|
})
|
|
|
|
const formattedOrders = filteredOrders.map((order) => {
|
|
const statusRecord = order.orderStatus[0]
|
|
let status: 'pending' | 'delivered' | 'cancelled' = 'pending'
|
|
if (statusRecord?.isCancelled) {
|
|
status = 'cancelled'
|
|
} else if (statusRecord?.isDelivered) {
|
|
status = 'delivered'
|
|
}
|
|
|
|
const items = order.orderItems
|
|
.map((item) => ({
|
|
id: item.id,
|
|
name: item.product.name,
|
|
quantity: parseFloat(item.quantity),
|
|
price: parseFloat(item.price.toString()),
|
|
amount: parseFloat(item.quantity) * parseFloat(item.price.toString()),
|
|
unit: item.product.unit?.shortNotation || '',
|
|
productSize: item.product.productQuantity,
|
|
isPackaged: item.is_packaged,
|
|
isPackageVerified: item.is_package_verified,
|
|
}))
|
|
.sort((first, second) => first.id - second.id)
|
|
|
|
return {
|
|
id: order.id,
|
|
orderId: order.id.toString(),
|
|
readableId: order.id,
|
|
customerName: order.user.name || order.user.mobile + '',
|
|
customerMobile: order.user.mobile,
|
|
address: `${order.address.addressLine1}${
|
|
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ''
|
|
}, ${order.address.city}, ${order.address.state} - ${
|
|
order.address.pincode
|
|
}, Phone: ${order.address.phone}`,
|
|
addressId: order.addressId,
|
|
latitude: order.address.adminLatitude ?? order.address.latitude,
|
|
longitude: order.address.adminLongitude ?? order.address.longitude,
|
|
totalAmount: parseFloat(order.totalAmount),
|
|
deliveryCharge: parseFloat(order.deliveryCharge || '0'),
|
|
items,
|
|
createdAt: order.createdAt,
|
|
deliveryTime: order.slot?.deliveryTime.toISOString() || null,
|
|
status,
|
|
isPackaged: order.orderItems.every((item) => item.is_packaged) || false,
|
|
isDelivered: statusRecord?.isDelivered || false,
|
|
isCod: order.isCod,
|
|
isFlashDelivery: order.isFlashDelivery,
|
|
userNotes: order.userNotes,
|
|
adminNotes: order.adminNotes,
|
|
userNegativityScore: 0,
|
|
userId: order.userId,
|
|
}
|
|
})
|
|
|
|
return {
|
|
orders: formattedOrders,
|
|
nextCursor: hasMore ? ordersToReturn[ordersToReturn.length - 1].id : undefined,
|
|
}
|
|
}
|
|
|
|
export async function rebalanceSlots(slotIds: number[]): Promise<AdminRebalanceSlotsResult> {
|
|
const ordersList = await db.query.orders.findMany({
|
|
where: inArray(orders.slotId, slotIds),
|
|
with: {
|
|
orderItems: {
|
|
with: {
|
|
product: true,
|
|
},
|
|
},
|
|
couponUsages: {
|
|
with: {
|
|
coupon: true,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const processedOrdersData = ordersList.map((order) => {
|
|
let newTotal = order.orderItems.reduce((acc, item) => {
|
|
const latestPrice = +item.product.price
|
|
const amount = latestPrice * Number(item.quantity)
|
|
return acc + amount
|
|
}, 0)
|
|
|
|
order.orderItems.forEach((item) => {
|
|
item.price = item.product.price
|
|
item.discountedPrice = item.product.price
|
|
})
|
|
|
|
const coupon = order.couponUsages[0]?.coupon
|
|
|
|
let discount = 0
|
|
if (coupon && !coupon.isInvalidated && (!coupon.validTill || new Date(coupon.validTill) > new Date())) {
|
|
const proportion = Number(order.orderGroupProportion || 1)
|
|
if (coupon.discountPercent) {
|
|
const maxDiscount = Number(coupon.maxValue || Infinity) * proportion
|
|
discount = Math.min((newTotal * parseFloat(coupon.discountPercent)) / 100, maxDiscount)
|
|
} else {
|
|
discount = Number(coupon.flatDiscount) * proportion
|
|
}
|
|
}
|
|
newTotal -= discount
|
|
|
|
const { couponUsages, orderItems: orderItemsRaw, ...rest } = order
|
|
const updatedOrderItems = orderItemsRaw.map((item) => {
|
|
const { product, ...rawOrderItem } = item
|
|
return rawOrderItem
|
|
})
|
|
return { order: rest, updatedOrderItems, newTotal }
|
|
})
|
|
|
|
const updatedOrderIds: number[] = []
|
|
await db.transaction(async (tx) => {
|
|
for (const { order, updatedOrderItems, newTotal } of processedOrdersData) {
|
|
await tx.update(orders).set({ totalAmount: newTotal.toString() }).where(eq(orders.id, order.id))
|
|
updatedOrderIds.push(order.id)
|
|
|
|
for (const item of updatedOrderItems) {
|
|
await tx
|
|
.update(orderItems)
|
|
.set({
|
|
price: item.price,
|
|
discountedPrice: item.discountedPrice,
|
|
})
|
|
.where(eq(orderItems.id, item.id))
|
|
}
|
|
}
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
updatedOrders: updatedOrderIds,
|
|
message: `Rebalanced ${updatedOrderIds.length} orders.`,
|
|
}
|
|
}
|
|
|
|
export async function cancelOrder(orderId: number, reason: string): Promise<AdminCancelOrderResult> {
|
|
const order = await db.query.orders.findFirst({
|
|
where: eq(orders.id, orderId),
|
|
with: {
|
|
orderStatus: true,
|
|
},
|
|
})
|
|
|
|
if (!order) {
|
|
return { success: false, message: 'Order not found', error: 'order_not_found' }
|
|
}
|
|
|
|
const status = order.orderStatus[0]
|
|
if (!status) {
|
|
return { success: false, message: 'Order status not found', error: 'status_not_found' }
|
|
}
|
|
|
|
if (status.isCancelled) {
|
|
return { success: false, message: 'Order is already cancelled', error: 'already_cancelled' }
|
|
}
|
|
|
|
if (status.isDelivered) {
|
|
return { success: false, message: 'Cannot cancel delivered order', error: 'already_delivered' }
|
|
}
|
|
|
|
const result = await db.transaction(async (tx) => {
|
|
await tx
|
|
.update(orderStatus)
|
|
.set({
|
|
isCancelled: true,
|
|
isCancelledByAdmin: true,
|
|
cancelReason: reason,
|
|
cancellationAdminNotes: reason,
|
|
cancellationReviewed: true,
|
|
cancellationReviewedAt: new Date(),
|
|
})
|
|
.where(eq(orderStatus.id, status.id))
|
|
|
|
const refundStatus = order.isCod ? 'na' : 'pending'
|
|
|
|
await tx.insert(refunds).values({
|
|
orderId: order.id,
|
|
refundStatus,
|
|
})
|
|
|
|
return { orderId: order.id, userId: order.userId }
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Order cancelled successfully',
|
|
orderId: result.orderId,
|
|
userId: result.userId,
|
|
}
|
|
}
|
|
|
|
export async function deleteOrderById(orderId: number): Promise<void> {
|
|
await db.transaction(async (tx) => {
|
|
await tx.delete(orderItems).where(eq(orderItems.orderId, orderId))
|
|
await tx.delete(orderStatus).where(eq(orderStatus.orderId, orderId))
|
|
await tx.delete(payments).where(eq(payments.orderId, orderId))
|
|
await tx.delete(refunds).where(eq(refunds.orderId, orderId))
|
|
await tx.delete(couponUsage).where(eq(couponUsage.orderId, orderId))
|
|
await tx.delete(complaints).where(eq(complaints.orderId, orderId))
|
|
await tx.delete(orders).where(eq(orders.id, orderId))
|
|
})
|
|
}
|