enh
This commit is contained in:
parent
40a98e38f5
commit
d4afa75eaf
5 changed files with 565 additions and 22 deletions
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
405
apps/backend/src/services/user/order-service.ts
Normal file
405
apps/backend/src/services/user/order-service.ts
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
import { db } from '../../db/db_index'
|
||||
import {
|
||||
orders,
|
||||
orderItems,
|
||||
orderStatus,
|
||||
addresses,
|
||||
productInfo,
|
||||
paymentInfoTable,
|
||||
coupons,
|
||||
couponUsage,
|
||||
payments,
|
||||
cartItems,
|
||||
refunds,
|
||||
units,
|
||||
userDetails,
|
||||
} from '../../db/schema'
|
||||
import { eq, and, inArray, desc, gte } from 'drizzle-orm'
|
||||
|
||||
// ============ User/Auth Queries ============
|
||||
|
||||
/**
|
||||
* Get user details by user ID
|
||||
*/
|
||||
export async function getUserDetails(userId: number) {
|
||||
return db.query.userDetails.findFirst({
|
||||
where: eq(userDetails.userId, userId),
|
||||
})
|
||||
}
|
||||
|
||||
// ============ Address Queries ============
|
||||
|
||||
/**
|
||||
* Get user address by ID
|
||||
*/
|
||||
export async function getUserAddress(userId: number, addressId: number) {
|
||||
return db.query.addresses.findFirst({
|
||||
where: and(eq(addresses.userId, userId), eq(addresses.id, addressId)),
|
||||
})
|
||||
}
|
||||
|
||||
// ============ Product Queries ============
|
||||
|
||||
/**
|
||||
* Get product by ID
|
||||
*/
|
||||
export async function getProductById(productId: number) {
|
||||
return db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, productId),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple products by IDs with unit info
|
||||
*/
|
||||
export async function getProductsByIdsWithUnits(productIds: number[]) {
|
||||
return db
|
||||
.select({
|
||||
id: productInfo.id,
|
||||
name: productInfo.name,
|
||||
shortDescription: productInfo.shortDescription,
|
||||
price: productInfo.price,
|
||||
images: productInfo.images,
|
||||
isOutOfStock: productInfo.isOutOfStock,
|
||||
unitShortNotation: units.shortNotation,
|
||||
incrementStep: productInfo.incrementStep,
|
||||
})
|
||||
.from(productInfo)
|
||||
.innerJoin(units, eq(productInfo.unitId, units.id))
|
||||
.where(and(inArray(productInfo.id, productIds), eq(productInfo.isSuspended, false)))
|
||||
.orderBy(desc(productInfo.createdAt))
|
||||
}
|
||||
|
||||
// ============ Coupon Queries ============
|
||||
|
||||
/**
|
||||
* Get coupon with usages for user
|
||||
*/
|
||||
export async function getCouponWithUsages(couponId: number, userId: number) {
|
||||
return db.query.coupons.findFirst({
|
||||
where: eq(coupons.id, couponId),
|
||||
with: {
|
||||
usages: { where: eq(couponUsage.userId, userId) },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert coupon usage
|
||||
*/
|
||||
export async function insertCouponUsage(data: {
|
||||
userId: number
|
||||
couponId: number
|
||||
orderId: number
|
||||
orderItemId: number | null
|
||||
usedAt: Date
|
||||
}) {
|
||||
return db.insert(couponUsage).values(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get coupon usages for order
|
||||
*/
|
||||
export async function getCouponUsagesForOrder(orderId: number) {
|
||||
return db.query.couponUsage.findMany({
|
||||
where: eq(couponUsage.orderId, orderId),
|
||||
with: {
|
||||
coupon: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ============ Cart Queries ============
|
||||
|
||||
/**
|
||||
* Delete cart items for user by product IDs
|
||||
*/
|
||||
export async function deleteCartItems(userId: number, productIds: number[]) {
|
||||
return db.delete(cartItems).where(
|
||||
and(
|
||||
eq(cartItems.userId, userId),
|
||||
inArray(cartItems.productId, productIds)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// ============ Payment Info Queries ============
|
||||
|
||||
/**
|
||||
* Create payment info
|
||||
*/
|
||||
export async function createPaymentInfo(data: {
|
||||
status: string
|
||||
gateway: string
|
||||
merchantOrderId: string
|
||||
}) {
|
||||
return db.insert(paymentInfoTable).values(data).returning()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============ Order Queries ============
|
||||
|
||||
/**
|
||||
* Insert multiple orders
|
||||
*/
|
||||
export async function insertOrders(ordersData: any[]) {
|
||||
return db.insert(orders).values(ordersData).returning()
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert multiple order items
|
||||
*/
|
||||
export async function insertOrderItems(itemsData: any[]) {
|
||||
return db.insert(orderItems).values(itemsData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert multiple order statuses
|
||||
*/
|
||||
export async function insertOrderStatuses(statusesData: any[]) {
|
||||
return db.insert(orderStatus).values(statusesData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user orders with all relations
|
||||
*/
|
||||
export async function getUserOrdersWithRelations(userId: number, limit: number, offset: number) {
|
||||
return db.query.orders.findMany({
|
||||
where: eq(orders.userId, userId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
slot: true,
|
||||
paymentInfo: true,
|
||||
orderStatus: true,
|
||||
refunds: true,
|
||||
},
|
||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Count user orders
|
||||
*/
|
||||
export async function countUserOrders(userId: number) {
|
||||
return db.$count(orders, eq(orders.userId, userId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order by ID with all relations
|
||||
*/
|
||||
export async function getOrderByIdWithRelations(orderId: number) {
|
||||
return db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
orderItems: {
|
||||
with: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
slot: true,
|
||||
paymentInfo: true,
|
||||
orderStatus: {
|
||||
with: {
|
||||
refundCoupon: true,
|
||||
},
|
||||
},
|
||||
refunds: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order by ID with order status
|
||||
*/
|
||||
export async function getOrderWithStatus(orderId: number) {
|
||||
return db.query.orders.findFirst({
|
||||
where: eq(orders.id, orderId),
|
||||
with: {
|
||||
orderStatus: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update order status to cancelled
|
||||
*/
|
||||
export async function updateOrderStatusToCancelled(
|
||||
statusId: number,
|
||||
data: {
|
||||
isCancelled: boolean
|
||||
cancelReason: string
|
||||
cancellationUserNotes: string
|
||||
cancellationReviewed: boolean
|
||||
}
|
||||
) {
|
||||
return db
|
||||
.update(orderStatus)
|
||||
.set(data)
|
||||
.where(eq(orderStatus.id, statusId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert refund record
|
||||
*/
|
||||
export async function insertRefund(data: { orderId: number; refundStatus: string }) {
|
||||
return db.insert(refunds).values(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update order notes
|
||||
*/
|
||||
export async function updateOrderNotes(orderId: number, userNotes: string | null) {
|
||||
return db
|
||||
.update(orders)
|
||||
.set({ userNotes })
|
||||
.where(eq(orders.id, orderId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent delivered orders for user
|
||||
*/
|
||||
export async function getRecentDeliveredOrders(
|
||||
userId: number,
|
||||
since: Date,
|
||||
limit: number
|
||||
) {
|
||||
return db
|
||||
.select({ id: orders.id })
|
||||
.from(orders)
|
||||
.innerJoin(orderStatus, eq(orders.id, orderStatus.orderId))
|
||||
.where(
|
||||
and(
|
||||
eq(orders.userId, userId),
|
||||
eq(orderStatus.isDelivered, true),
|
||||
gte(orders.createdAt, since)
|
||||
)
|
||||
)
|
||||
.orderBy(desc(orders.createdAt))
|
||||
.limit(limit)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get order items by order IDs
|
||||
*/
|
||||
export async function getOrderItemsByOrderIds(orderIds: number[]) {
|
||||
return db
|
||||
.select({ productId: orderItems.productId })
|
||||
.from(orderItems)
|
||||
.where(inArray(orderItems.orderId, orderIds))
|
||||
}
|
||||
|
||||
// ============ Transaction Helper ============
|
||||
|
||||
/**
|
||||
* Execute function within a database transaction
|
||||
*/
|
||||
export async function withTransaction<T>(fn: (tx: any) => Promise<T>): Promise<T> {
|
||||
return db.transaction(fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel order with refund record in a transaction
|
||||
*/
|
||||
export async function cancelOrderWithRefund(
|
||||
statusId: number,
|
||||
orderId: number,
|
||||
isCod: boolean,
|
||||
reason: string
|
||||
): Promise<{ orderId: number }> {
|
||||
return db.transaction(async (tx) => {
|
||||
// Update order status
|
||||
await tx
|
||||
.update(orderStatus)
|
||||
.set({
|
||||
isCancelled: true,
|
||||
cancelReason: reason,
|
||||
cancellationUserNotes: reason,
|
||||
cancellationReviewed: false,
|
||||
})
|
||||
.where(eq(orderStatus.id, statusId))
|
||||
|
||||
// Insert refund record
|
||||
const refundStatus = isCod ? "na" : "pending"
|
||||
await tx.insert(refunds).values({
|
||||
orderId,
|
||||
refundStatus,
|
||||
})
|
||||
|
||||
return { orderId }
|
||||
})
|
||||
}
|
||||
|
||||
type Tx = Parameters<Parameters<typeof db.transaction>[0]>[0]
|
||||
|
||||
/**
|
||||
* Create orders with payment info in a transaction
|
||||
*/
|
||||
export async function createOrdersWithPayment(
|
||||
ordersData: any[],
|
||||
paymentMethod: "online" | "cod",
|
||||
totalWithDelivery: number,
|
||||
razorpayOrderCreator?: (paymentInfoId: number, amount: string) => Promise<any>,
|
||||
paymentRecordInserter?: (paymentInfoId: number, razorpayOrder: any, tx: Tx) => Promise<any>
|
||||
): Promise<typeof orders.$inferSelect[]> {
|
||||
return db.transaction(async (tx) => {
|
||||
let sharedPaymentInfoId: number | null = null
|
||||
if (paymentMethod === "online") {
|
||||
const [paymentInfo] = await tx
|
||||
.insert(paymentInfoTable)
|
||||
.values({
|
||||
status: "pending",
|
||||
gateway: "razorpay",
|
||||
merchantOrderId: `multi_order_${Date.now()}`,
|
||||
})
|
||||
.returning()
|
||||
sharedPaymentInfoId = paymentInfo.id
|
||||
}
|
||||
|
||||
const ordersToInsert: Omit<typeof orders.$inferInsert, "id">[] = ordersData.map(
|
||||
(od) => ({
|
||||
...od.order,
|
||||
paymentInfoId: sharedPaymentInfoId,
|
||||
})
|
||||
)
|
||||
|
||||
const insertedOrders = await tx.insert(orders).values(ordersToInsert).returning()
|
||||
|
||||
const allOrderItems: Omit<typeof orderItems.$inferInsert, "id">[] = []
|
||||
const allOrderStatuses: Omit<typeof orderStatus.$inferInsert, "id">[] = []
|
||||
|
||||
insertedOrders.forEach((order: typeof orders.$inferSelect, index: number) => {
|
||||
const od = ordersData[index]
|
||||
od.orderItems.forEach((item: any) => {
|
||||
allOrderItems.push({ ...item, orderId: order.id as number })
|
||||
})
|
||||
allOrderStatuses.push({
|
||||
...od.orderStatus,
|
||||
orderId: order.id as number,
|
||||
})
|
||||
})
|
||||
|
||||
await tx.insert(orderItems).values(allOrderItems)
|
||||
await tx.insert(orderStatus).values(allOrderStatuses)
|
||||
|
||||
if (paymentMethod === "online" && sharedPaymentInfoId && razorpayOrderCreator && paymentRecordInserter) {
|
||||
const razorpayOrder = await razorpayOrderCreator(
|
||||
sharedPaymentInfoId,
|
||||
totalWithDelivery.toString()
|
||||
)
|
||||
await paymentRecordInserter(
|
||||
sharedPaymentInfoId,
|
||||
razorpayOrder,
|
||||
tx
|
||||
)
|
||||
}
|
||||
|
||||
return insertedOrders
|
||||
})
|
||||
}
|
||||
138
apps/backend/src/services/user/product-service.ts
Normal file
138
apps/backend/src/services/user/product-service.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import { db } from '../../db/db_index'
|
||||
import { productInfo, units, productSlots, deliverySlotInfo, specialDeals, storeInfo, productReviews, users } from '../../db/schema'
|
||||
import { eq, and, gt, sql, desc } from 'drizzle-orm'
|
||||
|
||||
/**
|
||||
* Get product basic info with unit
|
||||
*/
|
||||
export async function getProductWithUnit(productId: number) {
|
||||
return db
|
||||
.select({
|
||||
id: productInfo.id,
|
||||
name: productInfo.name,
|
||||
shortDescription: productInfo.shortDescription,
|
||||
longDescription: productInfo.longDescription,
|
||||
price: productInfo.price,
|
||||
marketPrice: productInfo.marketPrice,
|
||||
images: productInfo.images,
|
||||
isOutOfStock: productInfo.isOutOfStock,
|
||||
storeId: productInfo.storeId,
|
||||
unitShortNotation: units.shortNotation,
|
||||
incrementStep: productInfo.incrementStep,
|
||||
productQuantity: productInfo.productQuantity,
|
||||
isFlashAvailable: productInfo.isFlashAvailable,
|
||||
flashPrice: productInfo.flashPrice,
|
||||
})
|
||||
.from(productInfo)
|
||||
.innerJoin(units, eq(productInfo.unitId, units.id))
|
||||
.where(eq(productInfo.id, productId))
|
||||
.limit(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get store info by ID
|
||||
*/
|
||||
export async function getStoreById(storeId: number) {
|
||||
return db.query.storeInfo.findFirst({
|
||||
where: eq(storeInfo.id, storeId),
|
||||
columns: { id: true, name: true, description: true },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delivery slots for product
|
||||
*/
|
||||
export async function getProductDeliverySlots(productId: number) {
|
||||
return db
|
||||
.select({
|
||||
id: deliverySlotInfo.id,
|
||||
deliveryTime: deliverySlotInfo.deliveryTime,
|
||||
freezeTime: deliverySlotInfo.freezeTime,
|
||||
})
|
||||
.from(productSlots)
|
||||
.innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id))
|
||||
.where(
|
||||
and(
|
||||
eq(productSlots.productId, productId),
|
||||
eq(deliverySlotInfo.isActive, true),
|
||||
gt(deliverySlotInfo.deliveryTime, sql`NOW()`),
|
||||
gt(deliverySlotInfo.freezeTime, sql`NOW()`)
|
||||
)
|
||||
)
|
||||
.orderBy(deliverySlotInfo.deliveryTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get special deals for product
|
||||
*/
|
||||
export async function getProductSpecialDeals(productId: number) {
|
||||
return db
|
||||
.select({
|
||||
quantity: specialDeals.quantity,
|
||||
price: specialDeals.price,
|
||||
validTill: specialDeals.validTill,
|
||||
})
|
||||
.from(specialDeals)
|
||||
.where(
|
||||
and(
|
||||
eq(specialDeals.productId, productId),
|
||||
gt(specialDeals.validTill, sql`NOW()`)
|
||||
)
|
||||
)
|
||||
.orderBy(specialDeals.quantity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product reviews with user info
|
||||
*/
|
||||
export async function getProductReviews(productId: number, limit: number, offset: number) {
|
||||
return db
|
||||
.select({
|
||||
id: productReviews.id,
|
||||
reviewBody: productReviews.reviewBody,
|
||||
ratings: productReviews.ratings,
|
||||
imageUrls: productReviews.imageUrls,
|
||||
reviewTime: productReviews.reviewTime,
|
||||
userName: users.name,
|
||||
})
|
||||
.from(productReviews)
|
||||
.innerJoin(users, eq(productReviews.userId, users.id))
|
||||
.where(eq(productReviews.productId, productId))
|
||||
.orderBy(desc(productReviews.reviewTime))
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Count reviews for product
|
||||
*/
|
||||
export async function countProductReviews(productId: number) {
|
||||
const result = await db
|
||||
.select({ count: sql`count(*)` })
|
||||
.from(productReviews)
|
||||
.where(eq(productReviews.productId, productId))
|
||||
|
||||
return Number(result[0].count)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if product exists
|
||||
*/
|
||||
export async function checkProductExists(productId: number) {
|
||||
return db.query.productInfo.findFirst({
|
||||
where: eq(productInfo.id, productId),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new review
|
||||
*/
|
||||
export async function insertReview(data: {
|
||||
userId: number
|
||||
productId: number
|
||||
reviewBody: string
|
||||
ratings: number
|
||||
imageUrls: string[]
|
||||
}) {
|
||||
return db.insert(productReviews).values(data).returning()
|
||||
}
|
||||
|
|
@ -47,9 +47,9 @@ export const slotsRouter = router({
|
|||
getSlotsWithProducts: publicProcedure.query(async () => {
|
||||
const allSlots = await getAllSlotsFromCache();
|
||||
const currentTime = new Date();
|
||||
const validSlots = allSlots.filter((slot) =>
|
||||
dayjs(slot.freezeTime).isAfter(currentTime)
|
||||
);
|
||||
const validSlots = allSlots
|
||||
.filter((slot) => dayjs(slot.freezeTime).isAfter(currentTime))
|
||||
.sort((a, b) => dayjs(a.deliveryTime).valueOf() - dayjs(b.deliveryTime).valueOf());
|
||||
|
||||
return {
|
||||
slots: validSlots,
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
|||
// const BASE_API_URL = API_URL;
|
||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
||||
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
||||
// const BASE_API_URL = 'http://192.168.1.3:4000';
|
||||
let BASE_API_URL = "https://mf.freshyo.in";
|
||||
// let BASE_API_URL = 'http://192.168.100.104:4000';
|
||||
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||
const BASE_API_URL = 'http://192.168.1.3:4000';
|
||||
// let BASE_API_URL = "https://mf.freshyo.in";
|
||||
// let BASE_API_URL = 'http://192.168.100.104:4000';
|
||||
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||
|
||||
// if(isDevMode) {
|
||||
// }
|
||||
|
|
@ -87,11 +87,11 @@ export {
|
|||
Theme,
|
||||
MyTextInput,
|
||||
BottomDialog,
|
||||
LoadingDialog,
|
||||
MyText,
|
||||
MyTouchableOpacity,
|
||||
ConfirmationDialog,
|
||||
RawBottomDialog,
|
||||
LoadingDialog,
|
||||
MyText,
|
||||
MyTouchableOpacity,
|
||||
ConfirmationDialog,
|
||||
RawBottomDialog,
|
||||
DatePicker,
|
||||
BottomDropdown,
|
||||
ImageViewerURI,
|
||||
|
|
@ -105,9 +105,9 @@ export {
|
|||
tw,
|
||||
SearchBar,
|
||||
DataTable,
|
||||
Quantifier,
|
||||
MiniQuantifier,
|
||||
TabViewWrapper,
|
||||
Quantifier,
|
||||
MiniQuantifier,
|
||||
TabViewWrapper,
|
||||
MyFlatList,
|
||||
useFocusCallback,
|
||||
useManualRefresh,
|
||||
|
|
@ -118,10 +118,10 @@ export {
|
|||
useIsDevMode,
|
||||
usePagination,
|
||||
REFUND_STATUS,
|
||||
DateTimePickerMod,
|
||||
RefreshProvider,
|
||||
useRefresh,
|
||||
MyStatusBar,
|
||||
updateStatusBarColor,
|
||||
useStatusBarStore,
|
||||
};
|
||||
DateTimePickerMod,
|
||||
RefreshProvider,
|
||||
useRefresh,
|
||||
MyStatusBar,
|
||||
updateStatusBarColor,
|
||||
useStatusBarStore,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue