Compare commits
2 commits
038733c14a
...
3c836e274d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c836e274d | ||
|
|
306244e8df |
36 changed files with 5429 additions and 600 deletions
|
|
@ -1,197 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { View, ScrollView, Dimensions } from 'react-native';
|
|
||||||
import { Image } from 'expo-image';
|
|
||||||
import { MyText, tw } from 'common-ui';
|
|
||||||
import { trpc } from '../src/trpc-client';
|
|
||||||
|
|
||||||
interface FullOrderViewProps {
|
|
||||||
orderId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FullOrderView: React.FC<FullOrderViewProps> = ({ orderId }) => {
|
|
||||||
const { data: order, isLoading, error } = trpc.admin.order.getFullOrder.useQuery({ orderId });
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<View style={tw`p-6`}>
|
|
||||||
<MyText style={tw`text-center text-gray-600`}>Loading order details...</MyText>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error || !order) {
|
|
||||||
return (
|
|
||||||
<View style={tw`p-6`}>
|
|
||||||
<MyText style={tw`text-center text-red-600`}>Failed to load order details</MyText>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalAmount = order.items.reduce((sum, item) => sum + item.amount, 0);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView
|
|
||||||
style={[tw`flex-1`, { maxHeight: Dimensions.get('window').height * 0.8 }]}
|
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
>
|
|
||||||
<View style={tw`p-6`}>
|
|
||||||
<MyText style={tw`text-2xl font-bold text-gray-800 mb-6`}>Order #{order.readableId}</MyText>
|
|
||||||
|
|
||||||
{/* Customer Information */}
|
|
||||||
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
|
|
||||||
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Customer Details</MyText>
|
|
||||||
<View style={tw`space-y-2`}>
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Name:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>{order.customerName}</MyText>
|
|
||||||
</View>
|
|
||||||
{order.customerEmail && (
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Email:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>{order.customerEmail}</MyText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Mobile:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>{order.customerMobile}</MyText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Delivery Address */}
|
|
||||||
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
|
|
||||||
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Delivery Address</MyText>
|
|
||||||
<View style={tw`space-y-1`}>
|
|
||||||
<MyText style={tw`text-gray-800`}>{order.address.line1}</MyText>
|
|
||||||
{order.address.line2 && <MyText style={tw`text-gray-800`}>{order.address.line2}</MyText>}
|
|
||||||
<MyText style={tw`text-gray-800`}>
|
|
||||||
{order.address.city}, {order.address.state} - {order.address.pincode}
|
|
||||||
</MyText>
|
|
||||||
<MyText style={tw`text-gray-800`}>Phone: {order.address.phone}</MyText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Order Details */}
|
|
||||||
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
|
|
||||||
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Order Details</MyText>
|
|
||||||
<View style={tw`space-y-2`}>
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Order Date:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>
|
|
||||||
{new Date(order.createdAt).toLocaleDateString('en-IN', {
|
|
||||||
day: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
})}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Payment Method:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>
|
|
||||||
{order.isCod ? 'Cash on Delivery' : 'Online Payment'}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
{order.slotInfo && (
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Delivery Slot:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>
|
|
||||||
{new Date(order.slotInfo.time).toLocaleDateString('en-IN', {
|
|
||||||
day: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
})}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Items */}
|
|
||||||
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
|
|
||||||
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Items ({order.items.length})</MyText>
|
|
||||||
{order.items.map((item, index) => (
|
|
||||||
<View key={item.id} style={tw`flex-row items-center py-3 ${index !== order.items.length - 1 ? 'border-b border-gray-100' : ''}`}>
|
|
||||||
<View style={tw`flex-1`}>
|
|
||||||
<MyText style={tw`font-medium text-gray-800`} numberOfLines={2}>
|
|
||||||
{item.productName}
|
|
||||||
</MyText>
|
|
||||||
<MyText style={tw`text-sm text-gray-600`}>
|
|
||||||
Qty: {item.quantity} {item.unit} × ₹{parseFloat(item.price.toString()).toFixed(2)}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
<MyText style={tw`font-semibold text-gray-800`}>₹{item.amount.toFixed(2)}</MyText>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Payment Information */}
|
|
||||||
{(order.payment || order.paymentInfo) && (
|
|
||||||
<View style={tw`bg-white rounded-xl p-4 mb-4 shadow-sm`}>
|
|
||||||
<MyText style={tw`text-lg font-semibold text-gray-800 mb-3`}>Payment Information</MyText>
|
|
||||||
{order.payment && (
|
|
||||||
<View style={tw`space-y-2 mb-3`}>
|
|
||||||
<MyText style={tw`text-sm font-medium text-gray-700`}>Payment Details:</MyText>
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Status:</MyText>
|
|
||||||
<MyText style={tw`font-medium capitalize`}>{order.payment.status}</MyText>
|
|
||||||
</View>
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Gateway:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>{order.payment.gateway}</MyText>
|
|
||||||
</View>
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Order ID:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>{order.payment.merchantOrderId}</MyText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{order.paymentInfo && (
|
|
||||||
<View style={tw`space-y-2`}>
|
|
||||||
<MyText style={tw`text-sm font-medium text-gray-700`}>Payment Info:</MyText>
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Status:</MyText>
|
|
||||||
<MyText style={tw`font-medium capitalize`}>{order.paymentInfo.status}</MyText>
|
|
||||||
</View>
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Gateway:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>{order.paymentInfo.gateway}</MyText>
|
|
||||||
</View>
|
|
||||||
<View style={tw`flex-row justify-between`}>
|
|
||||||
<MyText style={tw`text-gray-600`}>Order ID:</MyText>
|
|
||||||
<MyText style={tw`font-medium`}>{order.paymentInfo.merchantOrderId}</MyText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* User Notes */}
|
|
||||||
{order.userNotes && (
|
|
||||||
<View style={tw`bg-blue-50 rounded-xl p-4 mb-4`}>
|
|
||||||
<MyText style={tw`text-lg font-semibold text-gray-800 mb-2`}>Customer Notes</MyText>
|
|
||||||
<MyText style={tw`text-gray-700`}>{order.userNotes}</MyText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Admin Notes */}
|
|
||||||
{order.adminNotes && (
|
|
||||||
<View style={tw`bg-yellow-50 rounded-xl p-4 mb-4`}>
|
|
||||||
<MyText style={tw`text-lg font-semibold text-gray-800 mb-2`}>Admin Notes</MyText>
|
|
||||||
<MyText style={tw`text-gray-700`}>{order.adminNotes}</MyText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Total */}
|
|
||||||
<View style={tw`bg-blue-50 rounded-xl p-4`}>
|
|
||||||
<View style={tw`flex-row justify-between items-center`}>
|
|
||||||
<MyText style={tw`text-xl font-bold text-gray-800`}>Total Amount</MyText>
|
|
||||||
<MyText style={tw`text-2xl font-bold text-blue-600`}>₹{parseFloat(order.totalAmount.toString()).toFixed(2)}</MyText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -2,13 +2,17 @@
|
||||||
// This file re-exports everything from postgresService to provide a clean abstraction layer
|
// This file re-exports everything from postgresService to provide a clean abstraction layer
|
||||||
// Implementation is the responsibility of postgresService package
|
// Implementation is the responsibility of postgresService package
|
||||||
|
|
||||||
|
import { getOrderDetails as getOrderDetailsFromDb } from 'postgresService'
|
||||||
|
import type { AdminOrderDetails } from '@packages/shared'
|
||||||
|
|
||||||
// Re-export database connection
|
// Re-export database connection
|
||||||
export { db } from 'postgresService';
|
export { db } from 'postgresService'
|
||||||
|
|
||||||
// Re-export all schema exports
|
// Re-export all schema exports
|
||||||
export * from 'postgresService';
|
export * from 'postgresService'
|
||||||
|
|
||||||
// Re-export methods from postgresService (implementation lives there)
|
// Re-export methods from postgresService (implementation lives there)
|
||||||
|
|
||||||
export {
|
export {
|
||||||
// Banner methods
|
// Banner methods
|
||||||
getBanners,
|
getBanners,
|
||||||
|
|
@ -22,14 +26,13 @@ export {
|
||||||
// Constants methods
|
// Constants methods
|
||||||
getAllConstants,
|
getAllConstants,
|
||||||
upsertConstants,
|
upsertConstants,
|
||||||
// Coupon methods (batch 1 - non-transaction)
|
// Coupon methods
|
||||||
getAllCoupons,
|
getAllCoupons,
|
||||||
getCouponById,
|
getCouponById,
|
||||||
invalidateCoupon,
|
invalidateCoupon,
|
||||||
validateCoupon,
|
validateCoupon,
|
||||||
getReservedCoupons,
|
getReservedCoupons,
|
||||||
getUsersForCoupon,
|
getUsersForCoupon,
|
||||||
// Coupon methods (batch 2 - transactions)
|
|
||||||
createCouponWithRelations,
|
createCouponWithRelations,
|
||||||
updateCouponWithRelations,
|
updateCouponWithRelations,
|
||||||
generateCancellationCoupon,
|
generateCancellationCoupon,
|
||||||
|
|
@ -48,10 +51,9 @@ export {
|
||||||
// Staff-user methods
|
// Staff-user methods
|
||||||
getStaffUserByName,
|
getStaffUserByName,
|
||||||
getAllStaff,
|
getAllStaff,
|
||||||
getStaffByName,
|
|
||||||
getAllUsers,
|
getAllUsers,
|
||||||
getUserWithDetails,
|
getUserWithDetails,
|
||||||
updateUserSuspension,
|
updateUserSuspensionStatus,
|
||||||
checkStaffUserExists,
|
checkStaffUserExists,
|
||||||
checkStaffRoleExists,
|
checkStaffRoleExists,
|
||||||
createStaffUser,
|
createStaffUser,
|
||||||
|
|
@ -90,12 +92,17 @@ export {
|
||||||
getOrderItemsByOrderIds,
|
getOrderItemsByOrderIds,
|
||||||
getOrderStatusByOrderIds,
|
getOrderStatusByOrderIds,
|
||||||
updateVendorOrderItemPackaging,
|
updateVendorOrderItemPackaging,
|
||||||
|
getVendorOrders,
|
||||||
// Product methods
|
// Product methods
|
||||||
getAllProducts,
|
getAllProducts,
|
||||||
getProductById,
|
getProductById,
|
||||||
|
deleteProduct,
|
||||||
createProduct,
|
createProduct,
|
||||||
updateProduct,
|
updateProduct,
|
||||||
toggleProductOutOfStock,
|
toggleProductOutOfStock,
|
||||||
|
updateSlotProducts,
|
||||||
|
getSlotProductIds,
|
||||||
|
getSlotsProductIds,
|
||||||
getAllUnits,
|
getAllUnits,
|
||||||
getAllProductTags,
|
getAllProductTags,
|
||||||
getProductReviews,
|
getProductReviews,
|
||||||
|
|
@ -106,35 +113,123 @@ export {
|
||||||
deleteProductGroup,
|
deleteProductGroup,
|
||||||
addProductToGroup,
|
addProductToGroup,
|
||||||
removeProductFromGroup,
|
removeProductFromGroup,
|
||||||
|
updateProductPrices,
|
||||||
// Slots methods
|
// Slots methods
|
||||||
getAllSlots,
|
getActiveSlotsWithProducts,
|
||||||
getSlotById,
|
getActiveSlots,
|
||||||
createSlot,
|
getSlotsAfterDate,
|
||||||
updateSlot,
|
getSlotByIdWithRelations,
|
||||||
deleteSlot,
|
createSlotWithRelations,
|
||||||
getSlotProducts,
|
updateSlotWithRelations,
|
||||||
addProductToSlot,
|
deleteSlotById,
|
||||||
removeProductFromSlot,
|
|
||||||
clearSlotProducts,
|
|
||||||
updateSlotCapacity,
|
updateSlotCapacity,
|
||||||
getSlotDeliverySequence,
|
getSlotDeliverySequence,
|
||||||
updateSlotDeliverySequence,
|
updateSlotDeliverySequence,
|
||||||
// Order methods
|
// Order methods
|
||||||
updateOrderNotes,
|
updateOrderNotes,
|
||||||
getOrderWithDetails,
|
|
||||||
getFullOrder,
|
|
||||||
getOrderDetails,
|
|
||||||
getAllOrders,
|
|
||||||
getOrdersBySlotId,
|
|
||||||
updateOrderPackaged,
|
updateOrderPackaged,
|
||||||
updateOrderDelivered,
|
updateOrderDelivered,
|
||||||
updateOrderItemPackaging,
|
updateOrderItemPackaging,
|
||||||
updateAddressCoords,
|
|
||||||
getOrderStatus,
|
|
||||||
cancelOrder,
|
|
||||||
getTodaysOrders,
|
|
||||||
removeDeliveryCharge,
|
removeDeliveryCharge,
|
||||||
} from 'postgresService';
|
getSlotOrders,
|
||||||
|
updateAddressCoords,
|
||||||
|
getAllOrders,
|
||||||
|
rebalanceSlots,
|
||||||
|
cancelOrder,
|
||||||
|
deleteOrderById,
|
||||||
|
} from 'postgresService'
|
||||||
|
|
||||||
// Re-export types from local types file (to avoid circular dependencies)
|
export async function getOrderDetails(orderId: number): Promise<AdminOrderDetails | null> {
|
||||||
export type { Banner } from './types/db.types';
|
return getOrderDetailsFromDb(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-export all types from shared package
|
||||||
|
export type {
|
||||||
|
// Admin types
|
||||||
|
Banner,
|
||||||
|
Complaint,
|
||||||
|
ComplaintWithUser,
|
||||||
|
Constant,
|
||||||
|
ConstantUpdateResult,
|
||||||
|
Coupon,
|
||||||
|
CouponValidationResult,
|
||||||
|
UserMiniInfo,
|
||||||
|
Store,
|
||||||
|
StaffUser,
|
||||||
|
StaffRole,
|
||||||
|
AdminOrderRow,
|
||||||
|
AdminOrderDetails,
|
||||||
|
AdminOrderUpdateResult,
|
||||||
|
AdminOrderItemPackagingResult,
|
||||||
|
AdminOrderMessageResult,
|
||||||
|
AdminOrderBasicResult,
|
||||||
|
AdminGetSlotOrdersResult,
|
||||||
|
AdminGetAllOrdersResult,
|
||||||
|
AdminGetAllOrdersResultWithUserId,
|
||||||
|
AdminRebalanceSlotsResult,
|
||||||
|
AdminCancelOrderResult,
|
||||||
|
AdminUnit,
|
||||||
|
AdminProduct,
|
||||||
|
AdminProductWithRelations,
|
||||||
|
AdminProductWithDetails,
|
||||||
|
AdminProductTagInfo,
|
||||||
|
AdminProductTagWithProducts,
|
||||||
|
AdminProductListResponse,
|
||||||
|
AdminProductResponse,
|
||||||
|
AdminDeleteProductResult,
|
||||||
|
AdminToggleOutOfStockResult,
|
||||||
|
AdminUpdateSlotProductsResult,
|
||||||
|
AdminSlotProductIdsResult,
|
||||||
|
AdminSlotsProductIdsResult,
|
||||||
|
AdminProductReview,
|
||||||
|
AdminProductReviewWithSignedUrls,
|
||||||
|
AdminProductReviewsResult,
|
||||||
|
AdminProductReviewResponse,
|
||||||
|
AdminProductGroup,
|
||||||
|
AdminProductGroupsResult,
|
||||||
|
AdminProductGroupResponse,
|
||||||
|
AdminProductGroupInfo,
|
||||||
|
AdminUpdateProductPricesResult,
|
||||||
|
AdminDeliverySlot,
|
||||||
|
AdminSlotProductSummary,
|
||||||
|
AdminSlotWithProducts,
|
||||||
|
AdminSlotWithProductsAndSnippets,
|
||||||
|
AdminSlotWithProductsAndSnippetsBase,
|
||||||
|
AdminSlotsResult,
|
||||||
|
AdminSlotsListResult,
|
||||||
|
AdminSlotResult,
|
||||||
|
AdminSlotCreateResult,
|
||||||
|
AdminSlotUpdateResult,
|
||||||
|
AdminSlotDeleteResult,
|
||||||
|
AdminDeliverySequence,
|
||||||
|
AdminDeliverySequenceResult,
|
||||||
|
AdminUpdateDeliverySequenceResult,
|
||||||
|
AdminUpdateSlotCapacityResult,
|
||||||
|
AdminVendorSnippet,
|
||||||
|
AdminVendorSnippetWithAccess,
|
||||||
|
AdminVendorSnippetWithSlot,
|
||||||
|
AdminVendorSnippetProduct,
|
||||||
|
AdminVendorSnippetWithProducts,
|
||||||
|
AdminVendorSnippetCreateInput,
|
||||||
|
AdminVendorSnippetUpdateInput,
|
||||||
|
AdminVendorSnippetDeleteResult,
|
||||||
|
AdminVendorSnippetOrderProduct,
|
||||||
|
AdminVendorSnippetOrderSummary,
|
||||||
|
AdminVendorSnippetOrdersResult,
|
||||||
|
AdminVendorSnippetOrdersWithSlotResult,
|
||||||
|
AdminVendorOrderSummary,
|
||||||
|
AdminUpcomingSlotsResult,
|
||||||
|
AdminVendorUpdatePackagingResult,
|
||||||
|
} from '@packages/shared';
|
||||||
|
|
||||||
|
export type {
|
||||||
|
// User types
|
||||||
|
User,
|
||||||
|
UserDetails,
|
||||||
|
Address,
|
||||||
|
Product,
|
||||||
|
CartItem,
|
||||||
|
Order,
|
||||||
|
OrderItem,
|
||||||
|
Payment,
|
||||||
|
} from '@packages/shared';
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,5 @@
|
||||||
import { router, protectedProcedure } from "@/src/trpc/trpc-index"
|
import { router, protectedProcedure } from "@/src/trpc/trpc-index"
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@/src/db/db_index"
|
|
||||||
import {
|
|
||||||
orders,
|
|
||||||
orderItems,
|
|
||||||
orderStatus,
|
|
||||||
users,
|
|
||||||
addresses,
|
|
||||||
refunds,
|
|
||||||
coupons,
|
|
||||||
couponUsage,
|
|
||||||
complaints,
|
|
||||||
payments,
|
|
||||||
} from "@/src/db/schema";
|
|
||||||
import { eq, and, gte, lt, desc, SQL, inArray } from "drizzle-orm";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import utc from "dayjs/plugin/utc";
|
|
||||||
import { ApiError } from "@/src/lib/api-error"
|
import { ApiError } from "@/src/lib/api-error"
|
||||||
import {
|
import {
|
||||||
sendOrderPackagedNotification,
|
sendOrderPackagedNotification,
|
||||||
|
|
@ -23,16 +7,38 @@ import {
|
||||||
} from "@/src/lib/notif-job";
|
} from "@/src/lib/notif-job";
|
||||||
import { publishCancellation } from "@/src/lib/post-order-handler"
|
import { publishCancellation } from "@/src/lib/post-order-handler"
|
||||||
import { getMultipleUserNegativityScores } from "@/src/stores/user-negativity-store"
|
import { getMultipleUserNegativityScores } from "@/src/stores/user-negativity-store"
|
||||||
|
import {
|
||||||
|
updateOrderNotes as updateOrderNotesInDb,
|
||||||
|
getOrderDetails as getOrderDetailsInDb,
|
||||||
|
updateOrderPackaged as updateOrderPackagedInDb,
|
||||||
|
updateOrderDelivered as updateOrderDeliveredInDb,
|
||||||
|
updateOrderItemPackaging as updateOrderItemPackagingInDb,
|
||||||
|
removeDeliveryCharge as removeDeliveryChargeInDb,
|
||||||
|
getSlotOrders as getSlotOrdersInDb,
|
||||||
|
updateAddressCoords as updateAddressCoordsInDb,
|
||||||
|
getAllOrders as getAllOrdersInDb,
|
||||||
|
rebalanceSlots as rebalanceSlotsInDb,
|
||||||
|
cancelOrder as cancelOrderInDb,
|
||||||
|
deleteOrderById as deleteOrderByIdInDb,
|
||||||
|
} from '@/src/dbService'
|
||||||
|
import type {
|
||||||
|
AdminCancelOrderResult,
|
||||||
|
AdminGetAllOrdersResult,
|
||||||
|
AdminGetSlotOrdersResult,
|
||||||
|
AdminOrderBasicResult,
|
||||||
|
AdminOrderDetails,
|
||||||
|
AdminOrderItemPackagingResult,
|
||||||
|
AdminOrderMessageResult,
|
||||||
|
AdminOrderRow,
|
||||||
|
AdminOrderUpdateResult,
|
||||||
|
AdminRebalanceSlotsResult,
|
||||||
|
} from "@packages/shared"
|
||||||
|
|
||||||
const updateOrderNotesSchema = z.object({
|
const updateOrderNotesSchema = z.object({
|
||||||
orderId: z.number(),
|
orderId: z.number(),
|
||||||
adminNotes: z.string(),
|
adminNotes: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const getFullOrderSchema = z.object({
|
|
||||||
orderId: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const getOrderDetailsSchema = z.object({
|
const getOrderDetailsSchema = z.object({
|
||||||
orderId: z.number(),
|
orderId: z.number(),
|
||||||
});
|
});
|
||||||
|
|
@ -57,10 +63,6 @@ const getSlotOrdersSchema = z.object({
|
||||||
slotId: z.string(),
|
slotId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const getTodaysOrdersSchema = z.object({
|
|
||||||
slotId: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const getAllOrdersSchema = z.object({
|
const getAllOrdersSchema = z.object({
|
||||||
cursor: z.number().optional(),
|
cursor: z.number().optional(),
|
||||||
limit: z.number().default(20),
|
limit: z.number().default(20),
|
||||||
|
|
@ -86,9 +88,13 @@ const getAllOrdersSchema = z.object({
|
||||||
export const orderRouter = router({
|
export const orderRouter = router({
|
||||||
updateNotes: protectedProcedure
|
updateNotes: protectedProcedure
|
||||||
.input(updateOrderNotesSchema)
|
.input(updateOrderNotesSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminOrderRow> => {
|
||||||
const { orderId, adminNotes } = input;
|
const { orderId, adminNotes } = input;
|
||||||
|
|
||||||
|
const result = await updateOrderNotesInDb(orderId, adminNotes || null)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB query:
|
||||||
const result = await db
|
const result = await db
|
||||||
.update(orders)
|
.update(orders)
|
||||||
.set({
|
.set({
|
||||||
|
|
@ -100,125 +106,24 @@ export const orderRouter = router({
|
||||||
if (result.length === 0) {
|
if (result.length === 0) {
|
||||||
throw new Error("Order not found");
|
throw new Error("Order not found");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
return result[0];
|
if (!result) {
|
||||||
}),
|
throw new Error("Order not found")
|
||||||
|
|
||||||
getFullOrder: protectedProcedure
|
|
||||||
.input(getFullOrderSchema)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
const { orderId } = input;
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!orderData) {
|
|
||||||
throw new Error("Order not found");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get order status separately
|
return result as AdminOrderRow;
|
||||||
const statusRecord = await db.query.orderStatus.findFirst({
|
|
||||||
where: eq(orderStatus.orderId, orderId),
|
|
||||||
});
|
|
||||||
|
|
||||||
let status: "pending" | "delivered" | "cancelled" = "pending";
|
|
||||||
if (statusRecord?.isCancelled) {
|
|
||||||
status = "cancelled";
|
|
||||||
} else if (statusRecord?.isDelivered) {
|
|
||||||
status = "delivered";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get refund details if order is cancelled
|
|
||||||
let refund = null;
|
|
||||||
if (status === "cancelled") {
|
|
||||||
refund = await db.query.refunds.findFirst({
|
|
||||||
where: eq(refunds.orderId, orderId),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: orderData.id,
|
|
||||||
readableId: orderData.id,
|
|
||||||
customerName: `${orderData.user.name}`,
|
|
||||||
customerEmail: orderData.user.email,
|
|
||||||
customerMobile: orderData.user.mobile,
|
|
||||||
address: {
|
|
||||||
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: orderData.totalAmount,
|
|
||||||
adminNotes: orderData.adminNotes,
|
|
||||||
userNotes: orderData.userNotes,
|
|
||||||
createdAt: orderData.createdAt,
|
|
||||||
status,
|
|
||||||
isPackaged:
|
|
||||||
orderData.orderItems.every((item) => item.is_packaged) || false,
|
|
||||||
isDelivered: statusRecord?.isDelivered || false,
|
|
||||||
items: orderData.orderItems.map((item) => ({
|
|
||||||
id: item.id,
|
|
||||||
name: item.product.name,
|
|
||||||
quantity: item.quantity,
|
|
||||||
price: item.price,
|
|
||||||
unit: item.product.unit?.shortNotation,
|
|
||||||
amount:
|
|
||||||
parseFloat(item.price.toString()) *
|
|
||||||
parseFloat(item.quantity || "0"),
|
|
||||||
})),
|
|
||||||
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,
|
|
||||||
// Cancellation details (only present for cancelled orders)
|
|
||||||
cancelReason: statusRecord?.cancelReason || null,
|
|
||||||
cancellationReviewed: statusRecord?.cancellationReviewed || false,
|
|
||||||
isRefundDone: refund?.refundStatus === "processed" || false,
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getOrderDetails: protectedProcedure
|
getOrderDetails: protectedProcedure
|
||||||
.input(getOrderDetailsSchema)
|
.input(getOrderDetailsSchema)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }): Promise<AdminOrderDetails> => {
|
||||||
const { orderId } = input;
|
const { orderId } = input;
|
||||||
|
|
||||||
|
const orderDetails = await getOrderDetailsInDb(orderId)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Single optimized query with all relations
|
// Single optimized query with all relations
|
||||||
const orderData = await db.query.orders.findFirst({
|
const orderData = await db.query.orders.findFirst({
|
||||||
where: eq(orders.id, orderId),
|
where: eq(orders.id, orderId),
|
||||||
|
|
@ -237,8 +142,8 @@ export const orderRouter = router({
|
||||||
},
|
},
|
||||||
payment: true,
|
payment: true,
|
||||||
paymentInfo: true,
|
paymentInfo: true,
|
||||||
orderStatus: true, // Include in main query
|
orderStatus: true,
|
||||||
refunds: true, // Include in main query
|
refunds: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -248,7 +153,7 @@ export const orderRouter = router({
|
||||||
|
|
||||||
// Get coupon usage for this specific order using new orderId field
|
// Get coupon usage for this specific order using new orderId field
|
||||||
const couponUsageData = await db.query.couponUsage.findMany({
|
const couponUsageData = await db.query.couponUsage.findMany({
|
||||||
where: eq(couponUsage.orderId, orderData.id), // Use new orderId field
|
where: eq(couponUsage.orderId, orderData.id),
|
||||||
with: {
|
with: {
|
||||||
coupon: true,
|
coupon: true,
|
||||||
},
|
},
|
||||||
|
|
@ -380,13 +285,24 @@ export const orderRouter = router({
|
||||||
refundRecord: refund,
|
refundRecord: refund,
|
||||||
isFlashDelivery: orderData.isFlashDelivery,
|
isFlashDelivery: orderData.isFlashDelivery,
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!orderDetails) {
|
||||||
|
throw new Error('Order not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
return orderDetails
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updatePackaged: protectedProcedure
|
updatePackaged: protectedProcedure
|
||||||
.input(updatePackagedSchema)
|
.input(updatePackagedSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminOrderUpdateResult> => {
|
||||||
const { orderId, isPackaged } = input;
|
const { orderId, isPackaged } = input;
|
||||||
|
|
||||||
|
const result = await updateOrderPackagedInDb(orderId, isPackaged)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Update all order items to the specified packaged state
|
// Update all order items to the specified packaged state
|
||||||
await db
|
await db
|
||||||
.update(orderItems)
|
.update(orderItems)
|
||||||
|
|
@ -412,13 +328,22 @@ export const orderRouter = router({
|
||||||
if (order) await sendOrderPackagedNotification(order.userId, orderId);
|
if (order) await sendOrderPackagedNotification(order.userId, orderId);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (result.userId) await sendOrderPackagedNotification(result.userId, orderId)
|
||||||
|
|
||||||
|
return { success: true, userId: result.userId }
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateDelivered: protectedProcedure
|
updateDelivered: protectedProcedure
|
||||||
.input(updateDeliveredSchema)
|
.input(updateDeliveredSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminOrderUpdateResult> => {
|
||||||
const { orderId, isDelivered } = input;
|
const { orderId, isDelivered } = input;
|
||||||
|
|
||||||
|
const result = await updateOrderDeliveredInDb(orderId, isDelivered)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
await db
|
await db
|
||||||
.update(orderStatus)
|
.update(orderStatus)
|
||||||
.set({ isDelivered })
|
.set({ isDelivered })
|
||||||
|
|
@ -430,13 +355,22 @@ export const orderRouter = router({
|
||||||
if (order) await sendOrderDeliveredNotification(order.userId, orderId);
|
if (order) await sendOrderDeliveredNotification(order.userId, orderId);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (result.userId) await sendOrderDeliveredNotification(result.userId, orderId)
|
||||||
|
|
||||||
|
return { success: true, userId: result.userId }
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateOrderItemPackaging: protectedProcedure
|
updateOrderItemPackaging: protectedProcedure
|
||||||
.input(updateOrderItemPackagingSchema)
|
.input(updateOrderItemPackagingSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminOrderItemPackagingResult> => {
|
||||||
const { orderItemId, isPackaged, isPackageVerified } = input;
|
const { orderItemId, isPackaged, isPackageVerified } = input;
|
||||||
|
|
||||||
|
const result = await updateOrderItemPackagingInDb(orderItemId, isPackaged, isPackageVerified)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Validate that orderItem exists
|
// Validate that orderItem exists
|
||||||
const orderItem = await db.query.orderItems.findFirst({
|
const orderItem = await db.query.orderItems.findFirst({
|
||||||
where: eq(orderItems.id, orderItemId),
|
where: eq(orderItems.id, orderItemId),
|
||||||
|
|
@ -462,13 +396,24 @@ export const orderRouter = router({
|
||||||
.where(eq(orderItems.id, orderItemId));
|
.where(eq(orderItems.id, orderItemId));
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result.updated) {
|
||||||
|
throw new ApiError('Order item not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
removeDeliveryCharge: protectedProcedure
|
removeDeliveryCharge: protectedProcedure
|
||||||
.input(z.object({ orderId: z.number() }))
|
.input(z.object({ orderId: z.number() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminOrderMessageResult> => {
|
||||||
const { orderId } = input;
|
const { orderId } = input;
|
||||||
|
|
||||||
|
const result = await removeDeliveryChargeInDb(orderId)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const order = await db.query.orders.findFirst({
|
const order = await db.query.orders.findFirst({
|
||||||
where: eq(orders.id, orderId),
|
where: eq(orders.id, orderId),
|
||||||
});
|
});
|
||||||
|
|
@ -490,13 +435,24 @@ export const orderRouter = router({
|
||||||
.where(eq(orders.id, orderId));
|
.where(eq(orders.id, orderId));
|
||||||
|
|
||||||
return { success: true, message: 'Delivery charge removed' };
|
return { success: true, message: 'Delivery charge removed' };
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('Order not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getSlotOrders: protectedProcedure
|
getSlotOrders: protectedProcedure
|
||||||
.input(getSlotOrdersSchema)
|
.input(getSlotOrdersSchema)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }): Promise<AdminGetSlotOrdersResult> => {
|
||||||
const { slotId } = input;
|
const { slotId } = input;
|
||||||
|
|
||||||
|
const result = await getSlotOrdersInDb(slotId)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const slotOrders = await db.query.orders.findMany({
|
const slotOrders = await db.query.orders.findMany({
|
||||||
where: eq(orders.slotId, parseInt(slotId)),
|
where: eq(orders.slotId, parseInt(slotId)),
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -573,97 +529,9 @@ export const orderRouter = router({
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, data: formattedOrders };
|
return { success: true, data: formattedOrders };
|
||||||
}),
|
*/
|
||||||
|
|
||||||
getTodaysOrders: protectedProcedure
|
return result
|
||||||
.input(getTodaysOrdersSchema)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
const { slotId } = input;
|
|
||||||
const start = dayjs().startOf("day").toDate();
|
|
||||||
const end = dayjs().endOf("day").toDate();
|
|
||||||
|
|
||||||
let whereCondition = and(
|
|
||||||
gte(orders.createdAt, start),
|
|
||||||
lt(orders.createdAt, end)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (slotId) {
|
|
||||||
whereCondition = and(
|
|
||||||
whereCondition,
|
|
||||||
eq(orders.slotId, parseInt(slotId))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const todaysOrders = await db.query.orders.findMany({
|
|
||||||
where: whereCondition,
|
|
||||||
with: {
|
|
||||||
user: true,
|
|
||||||
address: true,
|
|
||||||
slot: true,
|
|
||||||
orderItems: {
|
|
||||||
with: {
|
|
||||||
product: {
|
|
||||||
with: {
|
|
||||||
unit: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderStatus: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredOrders = todaysOrders.filter((order) => {
|
|
||||||
const statusRecord = order.orderStatus[0];
|
|
||||||
return (
|
|
||||||
order.isCod ||
|
|
||||||
(statusRecord && statusRecord.paymentStatus === "success")
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const formattedOrders = filteredOrders.map((order) => {
|
|
||||||
const statusRecord = order.orderStatus[0]; // assuming one status per order
|
|
||||||
let status: "pending" | "delivered" | "cancelled" = "pending";
|
|
||||||
if (statusRecord?.isCancelled) {
|
|
||||||
status = "cancelled";
|
|
||||||
} else if (statusRecord?.isDelivered) {
|
|
||||||
status = "delivered";
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = order.orderItems.map((item) => ({
|
|
||||||
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 || "",
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
orderId: order.id.toString(),
|
|
||||||
readableId: order.id,
|
|
||||||
customerName: order.user.name,
|
|
||||||
address: `${order.address.addressLine1}${
|
|
||||||
order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
|
|
||||||
}, ${order.address.city}, ${order.address.state} - ${
|
|
||||||
order.address.pincode
|
|
||||||
}`,
|
|
||||||
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: order.isCod ? "COD" : "Online",
|
|
||||||
paymentStatus: statusRecord?.paymentStatus || "pending",
|
|
||||||
slotId: order.slotId,
|
|
||||||
adminNotes: order.adminNotes,
|
|
||||||
userNotes: order.userNotes,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, data: formattedOrders };
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateAddressCoords: protectedProcedure
|
updateAddressCoords: protectedProcedure
|
||||||
|
|
@ -674,9 +542,13 @@ export const orderRouter = router({
|
||||||
longitude: z.number(),
|
longitude: z.number(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminOrderBasicResult> => {
|
||||||
const { addressId, latitude, longitude } = input;
|
const { addressId, latitude, longitude } = input;
|
||||||
|
|
||||||
|
const result = await updateAddressCoordsInDb(addressId, latitude, longitude)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const result = await db
|
const result = await db
|
||||||
.update(addresses)
|
.update(addresses)
|
||||||
.set({
|
.set({
|
||||||
|
|
@ -691,12 +563,33 @@ export const orderRouter = router({
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new ApiError('Address not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getAll: protectedProcedure
|
getAll: protectedProcedure
|
||||||
.input(getAllOrdersSchema)
|
.input(getAllOrdersSchema)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }): Promise<AdminGetAllOrdersResult | undefined> => {
|
||||||
try {
|
try {
|
||||||
|
const result = await getAllOrdersInDb(input)
|
||||||
|
const userIds = [...new Set(result.orders.map((order) => order.userId))]
|
||||||
|
const negativityScores = await getMultipleUserNegativityScores(userIds)
|
||||||
|
|
||||||
|
const orders = result.orders.map((order) => {
|
||||||
|
const { userId, userNegativityScore, ...rest } = order
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
userNegativityScore: negativityScores[userId] || 0,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const {
|
const {
|
||||||
cursor,
|
cursor,
|
||||||
limit,
|
limit,
|
||||||
|
|
@ -858,6 +751,12 @@ export const orderRouter = router({
|
||||||
? ordersToReturn[ordersToReturn.length - 1].id
|
? ordersToReturn[ordersToReturn.length - 1].id
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
return {
|
||||||
|
orders,
|
||||||
|
nextCursor: result.nextCursor,
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log({ e });
|
console.log({ e });
|
||||||
}
|
}
|
||||||
|
|
@ -865,9 +764,13 @@ export const orderRouter = router({
|
||||||
|
|
||||||
rebalanceSlots: protectedProcedure
|
rebalanceSlots: protectedProcedure
|
||||||
.input(z.object({ slotIds: z.array(z.number()).min(1).max(50) }))
|
.input(z.object({ slotIds: z.array(z.number()).min(1).max(50) }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminRebalanceSlotsResult> => {
|
||||||
const slotIds = input.slotIds;
|
const slotIds = input.slotIds;
|
||||||
|
|
||||||
|
const result = await rebalanceSlotsInDb(slotIds)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const ordersList = await db.query.orders.findMany({
|
const ordersList = await db.query.orders.findMany({
|
||||||
where: inArray(orders.slotId, slotIds),
|
where: inArray(orders.slotId, slotIds),
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -936,6 +839,9 @@ export const orderRouter = router({
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, updatedOrders: updatedOrderIds, message: `Rebalanced ${updatedOrderIds.length} orders.` };
|
return { success: true, updatedOrders: updatedOrderIds, message: `Rebalanced ${updatedOrderIds.length} orders.` };
|
||||||
|
*/
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
cancelOrder: protectedProcedure
|
cancelOrder: protectedProcedure
|
||||||
|
|
@ -943,9 +849,13 @@ export const orderRouter = router({
|
||||||
orderId: z.number(),
|
orderId: z.number(),
|
||||||
reason: z.string().min(1, "Cancellation reason is required"),
|
reason: z.string().min(1, "Cancellation reason is required"),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminCancelOrderResult> => {
|
||||||
const { orderId, reason } = input;
|
const { orderId, reason } = input;
|
||||||
|
|
||||||
|
const result = await cancelOrderInDb(orderId, reason)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const order = await db.query.orders.findFirst({
|
const order = await db.query.orders.findFirst({
|
||||||
where: eq(orders.id, orderId),
|
where: eq(orders.id, orderId),
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -997,14 +907,40 @@ export const orderRouter = router({
|
||||||
await publishCancellation(result.orderId, 'admin', reason);
|
await publishCancellation(result.orderId, 'admin', reason);
|
||||||
|
|
||||||
return { success: true, message: "Order cancelled successfully" };
|
return { success: true, message: "Order cancelled successfully" };
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
if (result.error === 'order_not_found') {
|
||||||
|
throw new ApiError(result.message, 404)
|
||||||
|
}
|
||||||
|
if (result.error === 'status_not_found') {
|
||||||
|
throw new ApiError(result.message, 400)
|
||||||
|
}
|
||||||
|
if (result.error === 'already_cancelled') {
|
||||||
|
throw new ApiError(result.message, 400)
|
||||||
|
}
|
||||||
|
if (result.error === 'already_delivered') {
|
||||||
|
throw new ApiError(result.message, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ApiError(result.message, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.orderId) {
|
||||||
|
await publishCancellation(result.orderId, 'admin', reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, message: result.message }
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// {"id": "order_Rhh00qJNdjUp8o", "notes": {"retry": "true", "customerOrderId": "14"}, "amount": 21000, "entity": "order", "status": "created", "receipt": "order_14_retry", "attempts": 0, "currency": "INR", "offer_id": null, "signature": "6df20655021f1d6841340f2a2ef2ef9378cb3d43495ab09e85f08aea1a851583", "amount_due": 21000, "created_at": 1763575791, "payment_id": "pay_Rhh15cLL28YM7j", "amount_paid": 0}
|
// {"id": "order_Rhh00qJNdjUp8o", "notes": {"retry": "true", "customerOrderId": "14"}, "amount": 21000, "entity": "order", "status": "created", "receipt": "order_14_retry", "attempts": 0, "currency": "INR", "offer_id": null, "signature": "6df20655021f1d6841340f2a2ef2ef9378cb3d43495ab09e85f08aea1a851583", "amount_due": 21000, "created_at": 1763575791, "payment_id": "pay_Rhh15cLL28YM7j", "amount_paid": 0}
|
||||||
|
|
||||||
type RefundStatus = "success" | "pending" | "failed" | "none" | "na";
|
|
||||||
|
|
||||||
export async function deleteOrderById(orderId: number): Promise<void> {
|
export async function deleteOrderById(orderId: number): Promise<void> {
|
||||||
|
await deleteOrderByIdInDb(orderId)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
await db.transaction(async (tx) => {
|
await db.transaction(async (tx) => {
|
||||||
await tx.delete(orderItems).where(eq(orderItems.orderId, orderId));
|
await tx.delete(orderItems).where(eq(orderItems.orderId, orderId));
|
||||||
await tx.delete(orderStatus).where(eq(orderStatus.orderId, orderId));
|
await tx.delete(orderStatus).where(eq(orderStatus.orderId, orderId));
|
||||||
|
|
@ -1014,5 +950,5 @@ export async function deleteOrderById(orderId: number): Promise<void> {
|
||||||
await tx.delete(complaints).where(eq(complaints.orderId, orderId));
|
await tx.delete(complaints).where(eq(complaints.orderId, orderId));
|
||||||
await tx.delete(orders).where(eq(orders.id, orderId));
|
await tx.delete(orders).where(eq(orders.id, orderId));
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,47 @@
|
||||||
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
import { router, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||||
import { z } from 'zod';
|
import { z } from 'zod'
|
||||||
import { db } from '@/src/db/db_index'
|
|
||||||
import { productInfo, units, specialDeals, productSlots, productTags, productReviews, users, productGroupInfo, productGroupMembership } from '@/src/db/schema'
|
|
||||||
import { eq, and, inArray, desc, sql } from 'drizzle-orm';
|
|
||||||
import { ApiError } from '@/src/lib/api-error'
|
import { ApiError } from '@/src/lib/api-error'
|
||||||
import { imageUploadS3, generateSignedUrlsFromS3Urls, getOriginalUrlFromSignedUrl, claimUploadUrl } from '@/src/lib/s3-client'
|
import { generateSignedUrlsFromS3Urls, claimUploadUrl } from '@/src/lib/s3-client'
|
||||||
import { deleteS3Image } from '@/src/lib/delete-image'
|
|
||||||
import type { SpecialDeal } from '@/src/db/types'
|
|
||||||
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
||||||
|
import {
|
||||||
|
getAllProducts as getAllProductsInDb,
|
||||||
|
getProductById as getProductByIdInDb,
|
||||||
|
deleteProduct as deleteProductInDb,
|
||||||
|
toggleProductOutOfStock as toggleProductOutOfStockInDb,
|
||||||
|
updateSlotProducts as updateSlotProductsInDb,
|
||||||
|
getSlotProductIds as getSlotProductIdsInDb,
|
||||||
|
getSlotsProductIds as getSlotsProductIdsInDb,
|
||||||
|
getProductReviews as getProductReviewsInDb,
|
||||||
|
respondToReview as respondToReviewInDb,
|
||||||
|
getAllProductGroups as getAllProductGroupsInDb,
|
||||||
|
createProductGroup as createProductGroupInDb,
|
||||||
|
updateProductGroup as updateProductGroupInDb,
|
||||||
|
deleteProductGroup as deleteProductGroupInDb,
|
||||||
|
updateProductPrices as updateProductPricesInDb,
|
||||||
|
} from '@/src/dbService'
|
||||||
|
import type {
|
||||||
|
AdminProductGroupsResult,
|
||||||
|
AdminProductGroupResponse,
|
||||||
|
AdminProductReviewsResult,
|
||||||
|
AdminProductReviewResponse,
|
||||||
|
AdminProductListResponse,
|
||||||
|
AdminProductResponse,
|
||||||
|
AdminDeleteProductResult,
|
||||||
|
AdminToggleOutOfStockResult,
|
||||||
|
AdminUpdateSlotProductsResult,
|
||||||
|
AdminSlotProductIdsResult,
|
||||||
|
AdminSlotsProductIdsResult,
|
||||||
|
AdminUpdateProductPricesResult,
|
||||||
|
} from '@packages/shared'
|
||||||
|
|
||||||
|
|
||||||
type CreateDeal = {
|
|
||||||
quantity: number;
|
|
||||||
price: number;
|
|
||||||
validTill: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const productRouter = router({
|
export const productRouter = router({
|
||||||
getProducts: protectedProcedure
|
getProducts: protectedProcedure
|
||||||
.query(async ({ ctx }) => {
|
.query(async (): Promise<AdminProductListResponse> => {
|
||||||
|
const products = await getAllProductsInDb()
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB query:
|
||||||
const products = await db.query.productInfo.findMany({
|
const products = await db.query.productInfo.findMany({
|
||||||
orderBy: productInfo.name,
|
orderBy: productInfo.name,
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -26,28 +49,32 @@ export const productRouter = router({
|
||||||
store: true,
|
store: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
// Generate signed URLs for all product images
|
|
||||||
const productsWithSignedUrls = await Promise.all(
|
const productsWithSignedUrls = await Promise.all(
|
||||||
products.map(async (product) => ({
|
products.map(async (product) => ({
|
||||||
...product,
|
...product,
|
||||||
images: await generateSignedUrlsFromS3Urls((product.images as string[]) || []),
|
images: await generateSignedUrlsFromS3Urls((product.images as string[]) || []),
|
||||||
}))
|
}))
|
||||||
);
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
products: productsWithSignedUrls,
|
products: productsWithSignedUrls,
|
||||||
count: productsWithSignedUrls.length,
|
count: productsWithSignedUrls.length,
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getProductById: protectedProcedure
|
getProductById: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input }): Promise<AdminProductResponse> => {
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
|
const product = await getProductByIdInDb(id)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const product = await db.query.productInfo.findFirst({
|
const product = await db.query.productInfo.findFirst({
|
||||||
where: eq(productInfo.id, id),
|
where: eq(productInfo.id, id),
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -84,15 +111,33 @@ export const productRouter = router({
|
||||||
return {
|
return {
|
||||||
product: productWithSignedUrls,
|
product: productWithSignedUrls,
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
throw new ApiError('Product not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
const productWithSignedUrls = {
|
||||||
|
...product,
|
||||||
|
images: await generateSignedUrlsFromS3Urls((product.images as string[]) || []),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
product: productWithSignedUrls,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
deleteProduct: protectedProcedure
|
deleteProduct: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }): Promise<AdminDeleteProductResult> => {
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
|
const deletedProduct = await deleteProductInDb(id)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB query:
|
||||||
const [deletedProduct] = await db
|
const [deletedProduct] = await db
|
||||||
.delete(productInfo)
|
.delete(productInfo)
|
||||||
.where(eq(productInfo.id, id))
|
.where(eq(productInfo.id, id))
|
||||||
|
|
@ -101,22 +146,31 @@ export const productRouter = router({
|
||||||
if (!deletedProduct) {
|
if (!deletedProduct) {
|
||||||
throw new ApiError("Product not found", 404);
|
throw new ApiError("Product not found", 404);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!deletedProduct) {
|
||||||
|
throw new ApiError('Product not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
scheduleStoreInitialization()
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "Product deleted successfully",
|
message: 'Product deleted successfully',
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
toggleOutOfStock: protectedProcedure
|
toggleOutOfStock: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }): Promise<AdminToggleOutOfStockResult> => {
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
|
const updatedProduct = await toggleProductOutOfStockInDb(id)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const product = await db.query.productInfo.findFirst({
|
const product = await db.query.productInfo.findFirst({
|
||||||
where: eq(productInfo.id, id),
|
where: eq(productInfo.id, id),
|
||||||
});
|
});
|
||||||
|
|
@ -132,14 +186,18 @@ export const productRouter = router({
|
||||||
})
|
})
|
||||||
.where(eq(productInfo.id, id))
|
.where(eq(productInfo.id, id))
|
||||||
.returning();
|
.returning();
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!updatedProduct) {
|
||||||
|
throw new ApiError('Product not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes
|
|
||||||
scheduleStoreInitialization()
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
product: updatedProduct,
|
product: updatedProduct,
|
||||||
message: `Product marked as ${updatedProduct.isOutOfStock ? 'out of stock' : 'in stock'}`,
|
message: `Product marked as ${updatedProduct.isOutOfStock ? 'out of stock' : 'in stock'}`,
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateSlotProducts: protectedProcedure
|
updateSlotProducts: protectedProcedure
|
||||||
|
|
@ -147,13 +205,17 @@ export const productRouter = router({
|
||||||
slotId: z.string(),
|
slotId: z.string(),
|
||||||
productIds: z.array(z.string()),
|
productIds: z.array(z.string()),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }): Promise<AdminUpdateSlotProductsResult> => {
|
||||||
const { slotId, productIds } = input;
|
const { slotId, productIds } = input;
|
||||||
|
|
||||||
if (!Array.isArray(productIds)) {
|
if (!Array.isArray(productIds)) {
|
||||||
throw new ApiError("productIds must be an array", 400);
|
throw new ApiError("productIds must be an array", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = await updateSlotProductsInDb(slotId, productIds)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Get current associations
|
// Get current associations
|
||||||
const currentAssociations = await db.query.productSlots.findMany({
|
const currentAssociations = await db.query.productSlots.findMany({
|
||||||
where: eq(productSlots.slotId, parseInt(slotId)),
|
where: eq(productSlots.slotId, parseInt(slotId)),
|
||||||
|
|
@ -197,15 +259,28 @@ export const productRouter = router({
|
||||||
added: productsToAdd.length,
|
added: productsToAdd.length,
|
||||||
removed: productsToRemove.length,
|
removed: productsToRemove.length,
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: 'Slot products updated successfully',
|
||||||
|
added: result.added,
|
||||||
|
removed: result.removed,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getSlotProductIds: protectedProcedure
|
getSlotProductIds: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
slotId: z.string(),
|
slotId: z.string(),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input }): Promise<AdminSlotProductIdsResult> => {
|
||||||
const { slotId } = input;
|
const { slotId } = input;
|
||||||
|
|
||||||
|
const productIds = await getSlotProductIdsInDb(slotId)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const associations = await db.query.productSlots.findMany({
|
const associations = await db.query.productSlots.findMany({
|
||||||
where: eq(productSlots.slotId, parseInt(slotId)),
|
where: eq(productSlots.slotId, parseInt(slotId)),
|
||||||
columns: {
|
columns: {
|
||||||
|
|
@ -218,19 +293,28 @@ export const productRouter = router({
|
||||||
return {
|
return {
|
||||||
productIds,
|
productIds,
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
return {
|
||||||
|
productIds,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getSlotsProductIds: protectedProcedure
|
getSlotsProductIds: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
slotIds: z.array(z.number()),
|
slotIds: z.array(z.number()),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input }): Promise<AdminSlotsProductIdsResult> => {
|
||||||
const { slotIds } = input;
|
const { slotIds } = input;
|
||||||
|
|
||||||
if (!Array.isArray(slotIds)) {
|
if (!Array.isArray(slotIds)) {
|
||||||
throw new ApiError("slotIds must be an array", 400);
|
throw new ApiError("slotIds must be an array", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = await getSlotsProductIdsInDb(slotIds)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
if (slotIds.length === 0) {
|
if (slotIds.length === 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -261,6 +345,9 @@ export const productRouter = router({
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
*/
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getProductReviews: protectedProcedure
|
getProductReviews: protectedProcedure
|
||||||
|
|
@ -269,9 +356,13 @@ export const productRouter = router({
|
||||||
limit: z.number().int().min(1).max(50).optional().default(10),
|
limit: z.number().int().min(1).max(50).optional().default(10),
|
||||||
offset: z.number().int().min(0).optional().default(0),
|
offset: z.number().int().min(0).optional().default(0),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }): Promise<AdminProductReviewsResult> => {
|
||||||
const { productId, limit, offset } = input;
|
const { productId, limit, offset } = input;
|
||||||
|
|
||||||
|
const { reviews, totalCount } = await getProductReviewsInDb(productId, limit, offset)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const reviews = await db
|
const reviews = await db
|
||||||
.select({
|
.select({
|
||||||
id: productReviews.id,
|
id: productReviews.id,
|
||||||
|
|
@ -309,6 +400,19 @@ export const productRouter = router({
|
||||||
const hasMore = offset + limit < totalCount;
|
const hasMore = offset + limit < totalCount;
|
||||||
|
|
||||||
return { reviews: reviewsWithSignedUrls, hasMore };
|
return { reviews: reviewsWithSignedUrls, hasMore };
|
||||||
|
*/
|
||||||
|
|
||||||
|
const reviewsWithSignedUrls = await Promise.all(
|
||||||
|
reviews.map(async (review) => ({
|
||||||
|
...review,
|
||||||
|
signedImageUrls: await generateSignedUrlsFromS3Urls((review.imageUrls as string[]) || []),
|
||||||
|
signedAdminImageUrls: await generateSignedUrlsFromS3Urls((review.adminResponseImages as string[]) || []),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasMore = offset + limit < totalCount
|
||||||
|
|
||||||
|
return { reviews: reviewsWithSignedUrls, hasMore }
|
||||||
}),
|
}),
|
||||||
|
|
||||||
respondToReview: protectedProcedure
|
respondToReview: protectedProcedure
|
||||||
|
|
@ -318,9 +422,13 @@ export const productRouter = router({
|
||||||
adminResponseImages: z.array(z.string()).optional().default([]),
|
adminResponseImages: z.array(z.string()).optional().default([]),
|
||||||
uploadUrls: z.array(z.string()).optional().default([]),
|
uploadUrls: z.array(z.string()).optional().default([]),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminProductReviewResponse> => {
|
||||||
const { reviewId, adminResponse, adminResponseImages, uploadUrls } = input;
|
const { reviewId, adminResponse, adminResponseImages, uploadUrls } = input;
|
||||||
|
|
||||||
|
const updatedReview = await respondToReviewInDb(reviewId, adminResponse, adminResponseImages)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const [updatedReview] = await db
|
const [updatedReview] = await db
|
||||||
.update(productReviews)
|
.update(productReviews)
|
||||||
.set({
|
.set({
|
||||||
|
|
@ -341,10 +449,25 @@ export const productRouter = router({
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, review: updatedReview };
|
return { success: true, review: updatedReview };
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!updatedReview) {
|
||||||
|
throw new ApiError('Review not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadUrls && uploadUrls.length > 0) {
|
||||||
|
await Promise.all(uploadUrls.map(url => claimUploadUrl(url)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, review: updatedReview }
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getGroups: protectedProcedure
|
getGroups: protectedProcedure
|
||||||
.query(async ({ ctx }) => {
|
.query(async (): Promise<AdminProductGroupsResult> => {
|
||||||
|
const groups = await getAllProductGroupsInDb()
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const groups = await db.query.productGroupInfo.findMany({
|
const groups = await db.query.productGroupInfo.findMany({
|
||||||
with: {
|
with: {
|
||||||
memberships: {
|
memberships: {
|
||||||
|
|
@ -355,14 +478,18 @@ export const productRouter = router({
|
||||||
},
|
},
|
||||||
orderBy: desc(productGroupInfo.createdAt),
|
orderBy: desc(productGroupInfo.createdAt),
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groups: groups.map(group => ({
|
groups: groups.map(group => ({
|
||||||
...group,
|
...group,
|
||||||
products: group.memberships.map(m => m.product),
|
products: group.memberships.map(m => ({
|
||||||
|
...m.product,
|
||||||
|
images: (m.product.images as string[]) || null,
|
||||||
|
})),
|
||||||
productCount: group.memberships.length,
|
productCount: group.memberships.length,
|
||||||
})),
|
})),
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
createGroup: protectedProcedure
|
createGroup: protectedProcedure
|
||||||
|
|
@ -371,9 +498,13 @@ export const productRouter = router({
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
product_ids: z.array(z.number()).default([]),
|
product_ids: z.array(z.number()).default([]),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }): Promise<AdminProductGroupResponse> => {
|
||||||
const { group_name, description, product_ids } = input;
|
const { group_name, description, product_ids } = input;
|
||||||
|
|
||||||
|
const newGroup = await createProductGroupInDb(group_name, description, product_ids)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const [newGroup] = await db
|
const [newGroup] = await db
|
||||||
.insert(productGroupInfo)
|
.insert(productGroupInfo)
|
||||||
.values({
|
.values({
|
||||||
|
|
@ -398,6 +529,14 @@ export const productRouter = router({
|
||||||
group: newGroup,
|
group: newGroup,
|
||||||
message: 'Group created successfully',
|
message: 'Group created successfully',
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
|
return {
|
||||||
|
group: newGroup,
|
||||||
|
message: 'Group created successfully',
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateGroup: protectedProcedure
|
updateGroup: protectedProcedure
|
||||||
|
|
@ -407,9 +546,13 @@ export const productRouter = router({
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
product_ids: z.array(z.number()).optional(),
|
product_ids: z.array(z.number()).optional(),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }): Promise<AdminProductGroupResponse> => {
|
||||||
const { id, group_name, description, product_ids } = input;
|
const { id, group_name, description, product_ids } = input;
|
||||||
|
|
||||||
|
const updatedGroup = await updateProductGroupInDb(id, group_name, description, product_ids)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const updateData: any = {};
|
const updateData: any = {};
|
||||||
if (group_name !== undefined) updateData.groupName = group_name;
|
if (group_name !== undefined) updateData.groupName = group_name;
|
||||||
if (description !== undefined) updateData.description = description;
|
if (description !== undefined) updateData.description = description;
|
||||||
|
|
@ -446,15 +589,31 @@ export const productRouter = router({
|
||||||
group: updatedGroup,
|
group: updatedGroup,
|
||||||
message: 'Group updated successfully',
|
message: 'Group updated successfully',
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!updatedGroup) {
|
||||||
|
throw new ApiError('Group not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
|
return {
|
||||||
|
group: updatedGroup,
|
||||||
|
message: 'Group updated successfully',
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
deleteGroup: protectedProcedure
|
deleteGroup: protectedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }): Promise<AdminDeleteProductResult> => {
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
|
const deletedGroup = await deleteProductGroupInDb(id)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Delete memberships first
|
// Delete memberships first
|
||||||
await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, id));
|
await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, id));
|
||||||
|
|
||||||
|
|
@ -474,6 +633,17 @@ export const productRouter = router({
|
||||||
return {
|
return {
|
||||||
message: 'Group deleted successfully',
|
message: 'Group deleted successfully',
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!deletedGroup) {
|
||||||
|
throw new ApiError('Group not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: 'Group deleted successfully',
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateProductPrices: protectedProcedure
|
updateProductPrices: protectedProcedure
|
||||||
|
|
@ -486,9 +656,17 @@ export const productRouter = router({
|
||||||
isFlashAvailable: z.boolean().optional(),
|
isFlashAvailable: z.boolean().optional(),
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }): Promise<AdminUpdateProductPricesResult> => {
|
||||||
const { updates } = input;
|
const { updates } = input;
|
||||||
|
|
||||||
|
if (updates.length === 0) {
|
||||||
|
throw new ApiError('No updates provided', 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateProductPricesInDb(updates)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
if (updates.length === 0) {
|
if (updates.length === 0) {
|
||||||
throw new ApiError('No updates provided', 400);
|
throw new ApiError('No updates provided', 400);
|
||||||
}
|
}
|
||||||
|
|
@ -531,5 +709,17 @@ export const productRouter = router({
|
||||||
message: `Updated prices for ${updates.length} product(s)`,
|
message: `Updated prices for ${updates.length} product(s)`,
|
||||||
updatedCount: updates.length,
|
updatedCount: updates.length,
|
||||||
};
|
};
|
||||||
}),
|
*/
|
||||||
|
|
||||||
|
if (result.invalidIds.length > 0) {
|
||||||
|
throw new ApiError(`Invalid product IDs: ${result.invalidIds.join(', ')}`, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: `Updated prices for ${result.updatedCount} product(s)`,
|
||||||
|
updatedCount: result.updatedCount,
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,38 @@
|
||||||
import { router, protectedProcedure } from "@/src/trpc/trpc-index"
|
import { router, protectedProcedure } from "@/src/trpc/trpc-index"
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@/src/db/db_index"
|
|
||||||
import { deliverySlotInfo, productSlots, productInfo, vendorSnippets, productGroupInfo } from "@/src/db/schema"
|
|
||||||
import { eq, inArray, and, desc } from "drizzle-orm";
|
|
||||||
import { ApiError } from "@/src/lib/api-error"
|
import { ApiError } from "@/src/lib/api-error"
|
||||||
import { appUrl } from "@/src/lib/env-exporter"
|
import { appUrl } from "@/src/lib/env-exporter"
|
||||||
import redisClient from "@/src/lib/redis-client"
|
import redisClient from "@/src/lib/redis-client"
|
||||||
import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters"
|
import { getSlotSequenceKey } from "@/src/lib/redisKeyGetters"
|
||||||
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
import { scheduleStoreInitialization } from '@/src/stores/store-initializer'
|
||||||
|
import {
|
||||||
|
getActiveSlotsWithProducts as getActiveSlotsWithProductsInDb,
|
||||||
|
getActiveSlots as getActiveSlotsInDb,
|
||||||
|
getSlotsAfterDate as getSlotsAfterDateInDb,
|
||||||
|
getSlotByIdWithRelations as getSlotByIdWithRelationsInDb,
|
||||||
|
createSlotWithRelations as createSlotWithRelationsInDb,
|
||||||
|
updateSlotWithRelations as updateSlotWithRelationsInDb,
|
||||||
|
deleteSlotById as deleteSlotByIdInDb,
|
||||||
|
updateSlotCapacity as updateSlotCapacityInDb,
|
||||||
|
getSlotDeliverySequence as getSlotDeliverySequenceInDb,
|
||||||
|
updateSlotDeliverySequence as updateSlotDeliverySequenceInDb,
|
||||||
|
updateSlotProducts as updateSlotProductsInDb,
|
||||||
|
getSlotsProductIds as getSlotsProductIdsInDb,
|
||||||
|
} from '@/src/dbService'
|
||||||
|
import type {
|
||||||
|
AdminDeliverySequenceResult,
|
||||||
|
AdminSlotResult,
|
||||||
|
AdminSlotsResult,
|
||||||
|
AdminSlotsListResult,
|
||||||
|
AdminSlotCreateResult,
|
||||||
|
AdminSlotUpdateResult,
|
||||||
|
AdminSlotDeleteResult,
|
||||||
|
AdminUpdateDeliverySequenceResult,
|
||||||
|
AdminUpdateSlotCapacityResult,
|
||||||
|
AdminSlotsProductIdsResult,
|
||||||
|
AdminUpdateSlotProductsResult,
|
||||||
|
} from '@packages/shared'
|
||||||
|
|
||||||
|
|
||||||
interface CachedDeliverySequence {
|
interface CachedDeliverySequence {
|
||||||
|
|
@ -64,11 +88,15 @@ const updateDeliverySequenceSchema = z.object({
|
||||||
|
|
||||||
export const slotsRouter = router({
|
export const slotsRouter = router({
|
||||||
// Exact replica of GET /av/slots
|
// Exact replica of GET /av/slots
|
||||||
getAll: protectedProcedure.query(async ({ ctx }) => {
|
getAll: protectedProcedure.query(async ({ ctx }): Promise<AdminSlotsResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const slots = await getActiveSlotsWithProductsInDb()
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const slots = await db.query.deliverySlotInfo
|
const slots = await db.query.deliverySlotInfo
|
||||||
.findMany({
|
.findMany({
|
||||||
where: eq(deliverySlotInfo.isActive, true),
|
where: eq(deliverySlotInfo.isActive, true),
|
||||||
|
|
@ -94,17 +122,18 @@ export const slotsRouter = router({
|
||||||
products: slot.productSlots.map((ps) => ps.product),
|
products: slot.productSlots.map((ps) => ps.product),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slots,
|
slots,
|
||||||
count: slots.length,
|
count: slots.length,
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Exact replica of POST /av/products/slots/product-ids
|
// Exact replica of POST /av/products/slots/product-ids
|
||||||
getSlotsProductIds: protectedProcedure
|
getSlotsProductIds: protectedProcedure
|
||||||
.input(z.object({ slotIds: z.array(z.number()) }))
|
.input(z.object({ slotIds: z.array(z.number()) }))
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }): Promise<AdminSlotsProductIdsResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
@ -118,6 +147,10 @@ export const slotsRouter = router({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = await getSlotsProductIdsInDb(slotIds)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
if (slotIds.length === 0) {
|
if (slotIds.length === 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -148,6 +181,9 @@ export const slotsRouter = router({
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
*/
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Exact replica of PUT /av/products/slots/:slotId/products
|
// Exact replica of PUT /av/products/slots/:slotId/products
|
||||||
|
|
@ -158,7 +194,7 @@ export const slotsRouter = router({
|
||||||
productIds: z.array(z.number()),
|
productIds: z.array(z.number()),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }): Promise<AdminUpdateSlotProductsResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +208,10 @@ export const slotsRouter = router({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = await updateSlotProductsInDb(String(slotId), productIds.map(String))
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Get current associations
|
// Get current associations
|
||||||
const currentAssociations = await db.query.productSlots.findMany({
|
const currentAssociations = await db.query.productSlots.findMany({
|
||||||
where: eq(productSlots.slotId, slotId),
|
where: eq(productSlots.slotId, slotId),
|
||||||
|
|
@ -223,11 +263,20 @@ export const slotsRouter = router({
|
||||||
added: productsToAdd.length,
|
added: productsToAdd.length,
|
||||||
removed: productsToRemove.length,
|
removed: productsToRemove.length,
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: result.message,
|
||||||
|
added: result.added,
|
||||||
|
removed: result.removed,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
createSlot: protectedProcedure
|
createSlot: protectedProcedure
|
||||||
.input(createSlotSchema)
|
.input(createSlotSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }): Promise<AdminSlotCreateResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
@ -239,6 +288,17 @@ export const slotsRouter = router({
|
||||||
throw new ApiError("Delivery time and orders close time are required", 400);
|
throw new ApiError("Delivery time and orders close time are required", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = await createSlotWithRelationsInDb({
|
||||||
|
deliveryTime,
|
||||||
|
freezeTime,
|
||||||
|
isActive,
|
||||||
|
productIds,
|
||||||
|
vendorSnippets: snippets,
|
||||||
|
groupIds,
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const result = await db.transaction(async (tx) => {
|
const result = await db.transaction(async (tx) => {
|
||||||
// Create slot
|
// Create slot
|
||||||
const [newSlot] = await tx
|
const [newSlot] = await tx
|
||||||
|
|
@ -297,76 +357,84 @@ export const slotsRouter = router({
|
||||||
message: "Slot created successfully",
|
message: "Slot created successfully",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes (outside transaction)
|
// Reinitialize stores to reflect changes (outside transaction)
|
||||||
scheduleStoreInitialization()
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
return result;
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getSlots: protectedProcedure.query(async ({ ctx }) => {
|
getSlots: protectedProcedure.query(async ({ ctx }): Promise<AdminSlotsListResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const slots = await getActiveSlotsInDb()
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const slots = await db.query.deliverySlotInfo.findMany({
|
const slots = await db.query.deliverySlotInfo.findMany({
|
||||||
where: eq(deliverySlotInfo.isActive, true),
|
where: eq(deliverySlotInfo.isActive, true),
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slots,
|
slots,
|
||||||
count: slots.length,
|
count: slots.length,
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getSlotById: protectedProcedure
|
getSlotById: protectedProcedure
|
||||||
.input(getSlotByIdSchema)
|
.input(getSlotByIdSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }): Promise<AdminSlotResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
const slot = await getSlotByIdWithRelationsInDb(id)
|
||||||
where: eq(deliverySlotInfo.id, id),
|
|
||||||
with: {
|
/*
|
||||||
productSlots: {
|
// Old implementation - direct DB queries:
|
||||||
with: {
|
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||||
product: {
|
where: eq(deliverySlotInfo.id, id),
|
||||||
columns: {
|
with: {
|
||||||
id: true,
|
productSlots: {
|
||||||
name: true,
|
with: {
|
||||||
images: true,
|
product: {
|
||||||
},
|
columns: {
|
||||||
},
|
id: true,
|
||||||
},
|
name: true,
|
||||||
},
|
images: true,
|
||||||
vendorSnippets: true,
|
},
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
vendorSnippets: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
if (!slot) {
|
if (!slot) {
|
||||||
throw new ApiError("Slot not found", 404);
|
throw new ApiError('Slot not found', 404)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slot: {
|
slot: {
|
||||||
...slot,
|
...slot,
|
||||||
deliverySequence: slot.deliverySequence as number[],
|
vendorSnippets: slot.vendorSnippets.map(snippet => ({
|
||||||
groupIds: slot.groupIds as number[],
|
|
||||||
products: slot.productSlots.map((ps) => ps.product),
|
|
||||||
vendorSnippets: slot.vendorSnippets?.map(snippet => ({
|
|
||||||
...snippet,
|
...snippet,
|
||||||
accessUrl: `${appUrl}/vendor-order-list?id=${snippet.snippetCode}`
|
accessUrl: `${appUrl}/vendor-order-list?id=${snippet.snippetCode}`,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateSlot: protectedProcedure
|
updateSlot: protectedProcedure
|
||||||
.input(updateSlotSchema)
|
.input(updateSlotSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }): Promise<AdminSlotUpdateResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
@ -377,6 +445,18 @@ export const slotsRouter = router({
|
||||||
throw new ApiError("Delivery time and orders close time are required", 400);
|
throw new ApiError("Delivery time and orders close time are required", 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = await updateSlotWithRelationsInDb({
|
||||||
|
id,
|
||||||
|
deliveryTime,
|
||||||
|
freezeTime,
|
||||||
|
isActive,
|
||||||
|
productIds,
|
||||||
|
vendorSnippets: snippets,
|
||||||
|
groupIds,
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Filter groupIds to only include valid (existing) groups
|
// Filter groupIds to only include valid (existing) groups
|
||||||
let validGroupIds = groupIds;
|
let validGroupIds = groupIds;
|
||||||
if (groupIds && groupIds.length > 0) {
|
if (groupIds && groupIds.length > 0) {
|
||||||
|
|
@ -456,11 +536,16 @@ export const slotsRouter = router({
|
||||||
message: "Slot updated successfully",
|
message: "Slot updated successfully",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new ApiError('Slot not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes (outside transaction)
|
// Reinitialize stores to reflect changes (outside transaction)
|
||||||
scheduleStoreInitialization()
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
|
@ -470,13 +555,17 @@ export const slotsRouter = router({
|
||||||
|
|
||||||
deleteSlot: protectedProcedure
|
deleteSlot: protectedProcedure
|
||||||
.input(deleteSlotSchema)
|
.input(deleteSlotSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }): Promise<AdminSlotDeleteResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
|
const deletedSlot = await deleteSlotByIdInDb(id)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const [deletedSlot] = await db
|
const [deletedSlot] = await db
|
||||||
.update(deliverySlotInfo)
|
.update(deliverySlotInfo)
|
||||||
.set({ isActive: false })
|
.set({ isActive: false })
|
||||||
|
|
@ -486,18 +575,23 @@ export const slotsRouter = router({
|
||||||
if (!deletedSlot) {
|
if (!deletedSlot) {
|
||||||
throw new ApiError("Slot not found", 404);
|
throw new ApiError("Slot not found", 404);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!deletedSlot) {
|
||||||
|
throw new ApiError('Slot not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
// Reinitialize stores to reflect changes
|
// Reinitialize stores to reflect changes
|
||||||
scheduleStoreInitialization()
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "Slot deleted successfully",
|
message: 'Slot deleted successfully',
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getDeliverySequence: protectedProcedure
|
getDeliverySequence: protectedProcedure
|
||||||
.input(getDeliverySequenceSchema)
|
.input(getDeliverySequenceSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }): Promise<AdminDeliverySequenceResult> => {
|
||||||
|
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
const slotId = parseInt(id);
|
const slotId = parseInt(id);
|
||||||
|
|
@ -507,7 +601,7 @@ export const slotsRouter = router({
|
||||||
const cached = await redisClient.get(cacheKey);
|
const cached = await redisClient.get(cacheKey);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
const parsed = JSON.parse(cached);
|
const parsed = JSON.parse(cached);
|
||||||
const validated = cachedSequenceSchema.parse(parsed) as CachedDeliverySequence;
|
const validated = cachedSequenceSchema.parse(parsed);
|
||||||
console.log('sending cached response')
|
console.log('sending cached response')
|
||||||
|
|
||||||
return { deliverySequence: validated };
|
return { deliverySequence: validated };
|
||||||
|
|
@ -518,6 +612,10 @@ export const slotsRouter = router({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to DB
|
// Fallback to DB
|
||||||
|
const slot = await getSlotDeliverySequenceInDb(slotId)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||||
where: eq(deliverySlotInfo.id, slotId),
|
where: eq(deliverySlotInfo.id, slotId),
|
||||||
});
|
});
|
||||||
|
|
@ -526,6 +624,13 @@ export const slotsRouter = router({
|
||||||
throw new ApiError("Slot not found", 404);
|
throw new ApiError("Slot not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sequence = cachedSequenceSchema.parse(slot.deliverySequence || {});
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!slot) {
|
||||||
|
throw new ApiError('Slot not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
const sequence = (slot.deliverySequence || {}) as CachedDeliverySequence;
|
const sequence = (slot.deliverySequence || {}) as CachedDeliverySequence;
|
||||||
|
|
||||||
// Cache the validated result
|
// Cache the validated result
|
||||||
|
|
@ -536,18 +641,22 @@ export const slotsRouter = router({
|
||||||
console.warn('Redis cache write failed:', cacheError);
|
console.warn('Redis cache write failed:', cacheError);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { deliverySequence: sequence };
|
return { deliverySequence: sequence }
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateDeliverySequence: protectedProcedure
|
updateDeliverySequence: protectedProcedure
|
||||||
.input(updateDeliverySequenceSchema)
|
.input(updateDeliverySequenceSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }): Promise<AdminUpdateDeliverySequenceResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id, deliverySequence } = input;
|
const { id, deliverySequence } = input;
|
||||||
|
|
||||||
|
const updatedSlot = await updateSlotDeliverySequenceInDb(id, deliverySequence)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const [updatedSlot] = await db
|
const [updatedSlot] = await db
|
||||||
.update(deliverySlotInfo)
|
.update(deliverySlotInfo)
|
||||||
.set({ deliverySequence })
|
.set({ deliverySequence })
|
||||||
|
|
@ -560,6 +669,11 @@ export const slotsRouter = router({
|
||||||
if (!updatedSlot) {
|
if (!updatedSlot) {
|
||||||
throw new ApiError("Slot not found", 404);
|
throw new ApiError("Slot not found", 404);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!updatedSlot) {
|
||||||
|
throw new ApiError('Slot not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
// Cache the updated sequence
|
// Cache the updated sequence
|
||||||
const cacheKey = getSlotSequenceKey(id);
|
const cacheKey = getSlotSequenceKey(id);
|
||||||
|
|
@ -572,8 +686,8 @@ export const slotsRouter = router({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slot: updatedSlot,
|
slot: updatedSlot,
|
||||||
message: "Delivery sequence updated successfully",
|
message: 'Delivery sequence updated successfully',
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateSlotCapacity: protectedProcedure
|
updateSlotCapacity: protectedProcedure
|
||||||
|
|
@ -581,13 +695,17 @@ export const slotsRouter = router({
|
||||||
slotId: z.number(),
|
slotId: z.number(),
|
||||||
isCapacityFull: z.boolean(),
|
isCapacityFull: z.boolean(),
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }): Promise<AdminUpdateSlotCapacityResult> => {
|
||||||
if (!ctx.staffUser?.id) {
|
if (!ctx.staffUser?.id) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { slotId, isCapacityFull } = input;
|
const { slotId, isCapacityFull } = input;
|
||||||
|
|
||||||
|
const result = await updateSlotCapacityInDb(slotId, isCapacityFull)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const [updatedSlot] = await db
|
const [updatedSlot] = await db
|
||||||
.update(deliverySlotInfo)
|
.update(deliverySlotInfo)
|
||||||
.set({ isCapacityFull })
|
.set({ isCapacityFull })
|
||||||
|
|
@ -606,5 +724,14 @@ export const slotsRouter = router({
|
||||||
slot: updatedSlot,
|
slot: updatedSlot,
|
||||||
message: `Slot ${isCapacityFull ? 'marked as full capacity' : 'capacity reset'}`,
|
message: `Slot ${isCapacityFull ? 'marked as full capacity' : 'capacity reset'}`,
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new ApiError('Slot not found', 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleStoreInitialization()
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
getAllStaff,
|
getAllStaff,
|
||||||
getAllUsers,
|
getAllUsers,
|
||||||
getUserWithDetails,
|
getUserWithDetails,
|
||||||
updateUserSuspension,
|
upsertUserSuspension,
|
||||||
checkStaffUserExists,
|
checkStaffUserExists,
|
||||||
checkStaffRoleExists,
|
checkStaffRoleExists,
|
||||||
createStaffUser,
|
createStaffUser,
|
||||||
|
|
@ -130,7 +130,7 @@ export const staffUserRouter = router({
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const { userId, isSuspended } = input;
|
const { userId, isSuspended } = input;
|
||||||
|
|
||||||
await updateUserSuspension(userId, isSuspended);
|
await upsertUserSuspension(userId, isSuspended);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,33 @@
|
||||||
import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index'
|
import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index'
|
||||||
import { z } from 'zod';
|
import { z } from 'zod'
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs'
|
||||||
import { db } from '@/src/db/db_index'
|
|
||||||
import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, users, orderStatus } from '@/src/db/schema'
|
|
||||||
import { eq, and, inArray, isNotNull, gt, sql, asc, ne } from 'drizzle-orm';
|
|
||||||
import { appUrl } from '@/src/lib/env-exporter'
|
import { appUrl } from '@/src/lib/env-exporter'
|
||||||
|
import {
|
||||||
|
checkVendorSnippetExists as checkVendorSnippetExistsInDb,
|
||||||
|
getVendorSnippetById as getVendorSnippetByIdInDb,
|
||||||
|
getVendorSnippetByCode as getVendorSnippetByCodeInDb,
|
||||||
|
getAllVendorSnippets as getAllVendorSnippetsInDb,
|
||||||
|
createVendorSnippet as createVendorSnippetInDb,
|
||||||
|
updateVendorSnippet as updateVendorSnippetInDb,
|
||||||
|
deleteVendorSnippet as deleteVendorSnippetInDb,
|
||||||
|
getProductsByIds as getProductsByIdsInDb,
|
||||||
|
getVendorSlotById as getVendorSlotByIdInDb,
|
||||||
|
getVendorOrdersBySlotId as getVendorOrdersBySlotIdInDb,
|
||||||
|
getVendorOrders as getVendorOrdersInDb,
|
||||||
|
updateVendorOrderItemPackaging as updateVendorOrderItemPackagingInDb,
|
||||||
|
getSlotsAfterDate as getSlotsAfterDateInDb,
|
||||||
|
} from '@/src/dbService'
|
||||||
|
import type {
|
||||||
|
AdminVendorSnippet,
|
||||||
|
AdminVendorSnippetWithProducts,
|
||||||
|
AdminVendorSnippetWithSlot,
|
||||||
|
AdminVendorSnippetDeleteResult,
|
||||||
|
AdminVendorSnippetOrdersResult,
|
||||||
|
AdminVendorSnippetOrdersWithSlotResult,
|
||||||
|
AdminVendorOrderSummary,
|
||||||
|
AdminUpcomingSlotsResult,
|
||||||
|
AdminVendorUpdatePackagingResult,
|
||||||
|
} from '@packages/shared'
|
||||||
|
|
||||||
const createSnippetSchema = z.object({
|
const createSnippetSchema = z.object({
|
||||||
snippetCode: z.string().min(1, "Snippet code is required"),
|
snippetCode: z.string().min(1, "Snippet code is required"),
|
||||||
|
|
@ -26,7 +49,7 @@ const updateSnippetSchema = z.object({
|
||||||
export const vendorSnippetsRouter = router({
|
export const vendorSnippetsRouter = router({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
.input(createSnippetSchema)
|
.input(createSnippetSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }): Promise<AdminVendorSnippet> => {
|
||||||
const { snippetCode, slotId, productIds, validTill, isPermanent } = input;
|
const { snippetCode, slotId, productIds, validTill, isPermanent } = input;
|
||||||
|
|
||||||
// Get staff user ID from auth middleware
|
// Get staff user ID from auth middleware
|
||||||
|
|
@ -35,6 +58,33 @@ export const vendorSnippetsRouter = router({
|
||||||
throw new Error("Unauthorized");
|
throw new Error("Unauthorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(slotId) {
|
||||||
|
const slot = await getVendorSlotByIdInDb(slotId)
|
||||||
|
if (!slot) {
|
||||||
|
throw new Error("Invalid slot ID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const products = await getProductsByIdsInDb(productIds)
|
||||||
|
if (products.length !== productIds.length) {
|
||||||
|
throw new Error("One or more invalid product IDs")
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSnippet = await checkVendorSnippetExistsInDb(snippetCode)
|
||||||
|
if (existingSnippet) {
|
||||||
|
throw new Error("Snippet code already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await createVendorSnippetInDb({
|
||||||
|
snippetCode,
|
||||||
|
slotId,
|
||||||
|
productIds,
|
||||||
|
isPermanent,
|
||||||
|
validTill: validTill ? new Date(validTill) : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Validate slot exists
|
// Validate slot exists
|
||||||
if(slotId) {
|
if(slotId) {
|
||||||
const slot = await db.query.deliverySlotInfo.findFirst({
|
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||||
|
|
@ -70,13 +120,32 @@ export const vendorSnippetsRouter = router({
|
||||||
}).returning();
|
}).returning();
|
||||||
|
|
||||||
return result[0];
|
return result[0];
|
||||||
|
*/
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getAll: protectedProcedure
|
getAll: protectedProcedure
|
||||||
.query(async () => {
|
.query(async (): Promise<AdminVendorSnippetWithProducts[]> => {
|
||||||
console.log('from the vendor snipptes methods')
|
console.log('from the vendor snipptes methods')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const result = await getAllVendorSnippetsInDb()
|
||||||
|
|
||||||
|
const snippetsWithProducts = await Promise.all(
|
||||||
|
result.map(async (snippet) => {
|
||||||
|
const products = await getProductsByIdsInDb(snippet.productIds)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...snippet,
|
||||||
|
accessUrl: `${appUrl}/vendor-order-list?id=${snippet.snippetCode}`,
|
||||||
|
products,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const result = await db.query.vendorSnippets.findMany({
|
const result = await db.query.vendorSnippets.findMany({
|
||||||
with: {
|
with: {
|
||||||
slot: true,
|
slot: true,
|
||||||
|
|
@ -100,18 +169,25 @@ export const vendorSnippetsRouter = router({
|
||||||
);
|
);
|
||||||
|
|
||||||
return snippetsWithProducts;
|
return snippetsWithProducts;
|
||||||
|
*/
|
||||||
|
|
||||||
|
return snippetsWithProducts
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
return [];
|
return []
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getById: protectedProcedure
|
getById: protectedProcedure
|
||||||
.input(z.object({ id: z.number().int().positive() }))
|
.input(z.object({ id: z.number().int().positive() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }): Promise<AdminVendorSnippetWithSlot> => {
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
|
const result = await getVendorSnippetByIdInDb(id)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const result = await db.query.vendorSnippets.findFirst({
|
const result = await db.query.vendorSnippets.findFirst({
|
||||||
where: eq(vendorSnippets.id, id),
|
where: eq(vendorSnippets.id, id),
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -124,14 +200,57 @@ export const vendorSnippetsRouter = router({
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('Vendor snippet not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(updateSnippetSchema)
|
.input(updateSnippetSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminVendorSnippet> => {
|
||||||
const { id, updates } = input;
|
const { id, updates } = input;
|
||||||
|
|
||||||
// Check if snippet exists
|
const existingSnippet = await getVendorSnippetByIdInDb(id)
|
||||||
|
if (!existingSnippet) {
|
||||||
|
throw new Error('Vendor snippet not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.slotId) {
|
||||||
|
const slot = await getVendorSlotByIdInDb(updates.slotId)
|
||||||
|
if (!slot) {
|
||||||
|
throw new Error('Invalid slot ID')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.productIds) {
|
||||||
|
const products = await getProductsByIdsInDb(updates.productIds)
|
||||||
|
if (products.length !== updates.productIds.length) {
|
||||||
|
throw new Error('One or more invalid product IDs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.snippetCode && updates.snippetCode !== existingSnippet.snippetCode) {
|
||||||
|
const duplicateSnippet = await checkVendorSnippetExistsInDb(updates.snippetCode)
|
||||||
|
if (duplicateSnippet) {
|
||||||
|
throw new Error('Snippet code already exists')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateData = {
|
||||||
|
...updates,
|
||||||
|
validTill: updates.validTill !== undefined
|
||||||
|
? (updates.validTill ? new Date(updates.validTill) : null)
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateVendorSnippetInDb(id, updateData)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const existingSnippet = await db.query.vendorSnippets.findFirst({
|
const existingSnippet = await db.query.vendorSnippets.findFirst({
|
||||||
where: eq(vendorSnippets.id, id),
|
where: eq(vendorSnippets.id, id),
|
||||||
});
|
});
|
||||||
|
|
@ -184,13 +303,24 @@ export const vendorSnippetsRouter = router({
|
||||||
}
|
}
|
||||||
|
|
||||||
return result[0];
|
return result[0];
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('Failed to update vendor snippet')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
|
|
||||||
delete: protectedProcedure
|
delete: protectedProcedure
|
||||||
.input(z.object({ id: z.number().int().positive() }))
|
.input(z.object({ id: z.number().int().positive() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }): Promise<AdminVendorSnippetDeleteResult> => {
|
||||||
const { id } = input;
|
const { id } = input;
|
||||||
|
|
||||||
|
const result = await deleteVendorSnippetInDb(id)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const result = await db.delete(vendorSnippets)
|
const result = await db.delete(vendorSnippets)
|
||||||
.where(eq(vendorSnippets.id, id))
|
.where(eq(vendorSnippets.id, id))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
@ -200,15 +330,26 @@ export const vendorSnippetsRouter = router({
|
||||||
}
|
}
|
||||||
|
|
||||||
return { message: "Vendor snippet deleted successfully" };
|
return { message: "Vendor snippet deleted successfully" };
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('Vendor snippet not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
return { message: 'Vendor snippet deleted successfully' }
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getOrdersBySnippet: publicProcedure
|
getOrdersBySnippet: publicProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
snippetCode: z.string().min(1, "Snippet code is required")
|
snippetCode: z.string().min(1, "Snippet code is required")
|
||||||
}))
|
}))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }): Promise<AdminVendorSnippetOrdersResult> => {
|
||||||
const { snippetCode } = input;
|
const { snippetCode } = input;
|
||||||
|
|
||||||
|
const snippet = await getVendorSnippetByCodeInDb(snippetCode)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Find the snippet
|
// Find the snippet
|
||||||
const snippet = await db.query.vendorSnippets.findFirst({
|
const snippet = await db.query.vendorSnippets.findFirst({
|
||||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||||
|
|
@ -242,6 +383,21 @@ export const vendorSnippetsRouter = router({
|
||||||
},
|
},
|
||||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!snippet) {
|
||||||
|
throw new Error('Vendor snippet not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snippet.validTill && new Date(snippet.validTill) < new Date()) {
|
||||||
|
throw new Error('Vendor snippet has expired')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!snippet.slotId) {
|
||||||
|
throw new Error('Vendor snippet not associated with a slot')
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingOrders = await getVendorOrdersBySlotIdInDb(snippet.slotId)
|
||||||
|
|
||||||
// Filter orders that contain at least one of the snippet's products
|
// Filter orders that contain at least one of the snippet's products
|
||||||
const filteredOrders = matchingOrders.filter(order => {
|
const filteredOrders = matchingOrders.filter(order => {
|
||||||
|
|
@ -273,11 +429,11 @@ export const vendorSnippetsRouter = router({
|
||||||
|
|
||||||
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orderId: `ORD${order.id}`,
|
orderId: `ORD${order.id}`,
|
||||||
orderDate: order.createdAt.toISOString(),
|
orderDate: order.createdAt.toISOString(),
|
||||||
customerName: order.user.name,
|
customerName: order.user.name || '',
|
||||||
totalAmount: orderTotal,
|
totalAmount: orderTotal,
|
||||||
slotInfo: order.slot ? {
|
slotInfo: order.slot ? {
|
||||||
time: order.slot.deliveryTime.toISOString(),
|
time: order.slot.deliveryTime.toISOString(),
|
||||||
sequence: order.slot.deliverySequence,
|
sequence: order.slot.deliverySequence,
|
||||||
|
|
@ -300,11 +456,15 @@ export const vendorSnippetsRouter = router({
|
||||||
createdAt: snippet.createdAt.toISOString(),
|
createdAt: snippet.createdAt.toISOString(),
|
||||||
isPermanent: snippet.isPermanent,
|
isPermanent: snippet.isPermanent,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getVendorOrders: protectedProcedure
|
getVendorOrders: protectedProcedure
|
||||||
.query(async () => {
|
.query(async (): Promise<AdminVendorOrderSummary[]> => {
|
||||||
|
const vendorOrders = await getVendorOrdersInDb()
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const vendorOrders = await db.query.orders.findMany({
|
const vendorOrders = await db.query.orders.findMany({
|
||||||
with: {
|
with: {
|
||||||
user: true,
|
user: true,
|
||||||
|
|
@ -320,10 +480,11 @@ export const vendorSnippetsRouter = router({
|
||||||
},
|
},
|
||||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
return vendorOrders.map(order => ({
|
return vendorOrders.map(order => ({
|
||||||
id: order.id,
|
id: order.id,
|
||||||
status: 'pending', // Default status since orders table may not have status field
|
status: 'pending',
|
||||||
orderDate: order.createdAt.toISOString(),
|
orderDate: order.createdAt.toISOString(),
|
||||||
totalQuantity: order.orderItems.reduce((sum, item) => sum + parseFloat(item.quantity || '0'), 0),
|
totalQuantity: order.orderItems.reduce((sum, item) => sum + parseFloat(item.quantity || '0'), 0),
|
||||||
products: order.orderItems.map(item => ({
|
products: order.orderItems.map(item => ({
|
||||||
|
|
@ -331,12 +492,16 @@ export const vendorSnippetsRouter = router({
|
||||||
quantity: parseFloat(item.quantity || '0'),
|
quantity: parseFloat(item.quantity || '0'),
|
||||||
unit: item.product.unit?.shortNotation || 'unit',
|
unit: item.product.unit?.shortNotation || 'unit',
|
||||||
})),
|
})),
|
||||||
}));
|
}))
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getUpcomingSlots: publicProcedure
|
getUpcomingSlots: publicProcedure
|
||||||
.query(async () => {
|
.query(async (): Promise<AdminUpcomingSlotsResult> => {
|
||||||
const threeHoursAgo = dayjs().subtract(3, 'hour').toDate();
|
const threeHoursAgo = dayjs().subtract(3, 'hour').toDate();
|
||||||
|
const slots = await getSlotsAfterDateInDb(threeHoursAgo)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
const slots = await db.query.deliverySlotInfo.findMany({
|
const slots = await db.query.deliverySlotInfo.findMany({
|
||||||
where: and(
|
where: and(
|
||||||
eq(deliverySlotInfo.isActive, true),
|
eq(deliverySlotInfo.isActive, true),
|
||||||
|
|
@ -344,6 +509,7 @@ export const vendorSnippetsRouter = router({
|
||||||
),
|
),
|
||||||
orderBy: asc(deliverySlotInfo.deliveryTime),
|
orderBy: asc(deliverySlotInfo.deliveryTime),
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -353,7 +519,7 @@ export const vendorSnippetsRouter = router({
|
||||||
freezeTime: slot.freezeTime.toISOString(),
|
freezeTime: slot.freezeTime.toISOString(),
|
||||||
deliverySequence: slot.deliverySequence,
|
deliverySequence: slot.deliverySequence,
|
||||||
})),
|
})),
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getOrdersBySnippetAndSlot: publicProcedure
|
getOrdersBySnippetAndSlot: publicProcedure
|
||||||
|
|
@ -361,9 +527,14 @@ export const vendorSnippetsRouter = router({
|
||||||
snippetCode: z.string().min(1, "Snippet code is required"),
|
snippetCode: z.string().min(1, "Snippet code is required"),
|
||||||
slotId: z.number().int().positive("Valid slot ID is required"),
|
slotId: z.number().int().positive("Valid slot ID is required"),
|
||||||
}))
|
}))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }): Promise<AdminVendorSnippetOrdersWithSlotResult> => {
|
||||||
const { snippetCode, slotId } = input;
|
const { snippetCode, slotId } = input;
|
||||||
|
|
||||||
|
const snippet = await getVendorSnippetByCodeInDb(snippetCode)
|
||||||
|
const slot = await getVendorSlotByIdInDb(slotId)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Find the snippet
|
// Find the snippet
|
||||||
const snippet = await db.query.vendorSnippets.findFirst({
|
const snippet = await db.query.vendorSnippets.findFirst({
|
||||||
where: eq(vendorSnippets.snippetCode, snippetCode),
|
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||||
|
|
@ -401,6 +572,17 @@ export const vendorSnippetsRouter = router({
|
||||||
},
|
},
|
||||||
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!snippet) {
|
||||||
|
throw new Error('Vendor snippet not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!slot) {
|
||||||
|
throw new Error('Slot not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingOrders = await getVendorOrdersBySlotIdInDb(slotId)
|
||||||
|
|
||||||
// Filter orders that contain at least one of the snippet's products
|
// Filter orders that contain at least one of the snippet's products
|
||||||
const filteredOrders = matchingOrders.filter(order => {
|
const filteredOrders = matchingOrders.filter(order => {
|
||||||
|
|
@ -435,7 +617,7 @@ export const vendorSnippetsRouter = router({
|
||||||
return {
|
return {
|
||||||
orderId: `ORD${order.id}`,
|
orderId: `ORD${order.id}`,
|
||||||
orderDate: order.createdAt.toISOString(),
|
orderDate: order.createdAt.toISOString(),
|
||||||
customerName: order.user.name,
|
customerName: order.user.name || '',
|
||||||
totalAmount: orderTotal,
|
totalAmount: orderTotal,
|
||||||
slotInfo: order.slot ? {
|
slotInfo: order.slot ? {
|
||||||
time: order.slot.deliveryTime.toISOString(),
|
time: order.slot.deliveryTime.toISOString(),
|
||||||
|
|
@ -465,7 +647,7 @@ export const vendorSnippetsRouter = router({
|
||||||
freezeTime: slot.freezeTime.toISOString(),
|
freezeTime: slot.freezeTime.toISOString(),
|
||||||
deliverySequence: slot.deliverySequence,
|
deliverySequence: slot.deliverySequence,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateOrderItemPackaging: publicProcedure
|
updateOrderItemPackaging: publicProcedure
|
||||||
|
|
@ -473,7 +655,7 @@ export const vendorSnippetsRouter = router({
|
||||||
orderItemId: z.number().int().positive("Valid order item ID required"),
|
orderItemId: z.number().int().positive("Valid order item ID required"),
|
||||||
is_packaged: z.boolean()
|
is_packaged: z.boolean()
|
||||||
}))
|
}))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }): Promise<AdminVendorUpdatePackagingResult> => {
|
||||||
const { orderItemId, is_packaged } = input;
|
const { orderItemId, is_packaged } = input;
|
||||||
|
|
||||||
// Get staff user ID from auth middleware
|
// Get staff user ID from auth middleware
|
||||||
|
|
@ -482,6 +664,10 @@ export const vendorSnippetsRouter = router({
|
||||||
// throw new Error("Unauthorized");
|
// throw new Error("Unauthorized");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
const result = await updateVendorOrderItemPackagingInDb(orderItemId, is_packaged)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Old implementation - direct DB queries:
|
||||||
// Check if order item exists and get related data
|
// Check if order item exists and get related data
|
||||||
const orderItem = await db.query.orderItems.findFirst({
|
const orderItem = await db.query.orderItems.findFirst({
|
||||||
where: eq(orderItems.id, orderItemId),
|
where: eq(orderItems.id, orderItemId),
|
||||||
|
|
@ -527,5 +713,12 @@ export const vendorSnippetsRouter = router({
|
||||||
orderItemId,
|
orderItemId,
|
||||||
is_packaged
|
is_packaged
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,165 @@ export { db } from './src/db/db_index';
|
||||||
// Re-export schema
|
// Re-export schema
|
||||||
export * from './src/db/schema';
|
export * from './src/db/schema';
|
||||||
|
|
||||||
// Re-export helper methods
|
// Admin API helpers - explicitly namespaced exports to avoid duplicates
|
||||||
export * from './src/helper_methods/banner';
|
export {
|
||||||
export * from './src/helper_methods/complaint';
|
// Banner
|
||||||
export * from './src/helper_methods/const';
|
getBanners,
|
||||||
export * from './src/helper_methods/coupon';
|
getBannerById,
|
||||||
export * from './src/helper_methods/store';
|
createBanner,
|
||||||
export * from './src/helper_methods/staff-user';
|
updateBanner,
|
||||||
export * from './src/helper_methods/user';
|
deleteBanner,
|
||||||
export * from './src/helper_methods/vendor-snippets';
|
} from './src/admin-apis/banner';
|
||||||
export * from './src/helper_methods/product';
|
|
||||||
export * from './src/helper_methods/slots';
|
export {
|
||||||
export * from './src/helper_methods/order';
|
// Complaint
|
||||||
|
getComplaints,
|
||||||
|
resolveComplaint,
|
||||||
|
} from './src/admin-apis/complaint';
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Constants
|
||||||
|
getAllConstants,
|
||||||
|
upsertConstants,
|
||||||
|
} from './src/admin-apis/const';
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Coupon
|
||||||
|
getAllCoupons,
|
||||||
|
getCouponById,
|
||||||
|
invalidateCoupon,
|
||||||
|
validateCoupon,
|
||||||
|
getReservedCoupons,
|
||||||
|
getUsersForCoupon,
|
||||||
|
createCouponWithRelations,
|
||||||
|
updateCouponWithRelations,
|
||||||
|
generateCancellationCoupon,
|
||||||
|
createReservedCouponWithProducts,
|
||||||
|
createCouponForUser,
|
||||||
|
checkUsersExist,
|
||||||
|
checkCouponExists,
|
||||||
|
checkReservedCouponExists,
|
||||||
|
getOrderWithUser,
|
||||||
|
} from './src/admin-apis/coupon';
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Order
|
||||||
|
updateOrderNotes,
|
||||||
|
getOrderDetails,
|
||||||
|
updateOrderPackaged,
|
||||||
|
updateOrderDelivered,
|
||||||
|
updateOrderItemPackaging,
|
||||||
|
removeDeliveryCharge,
|
||||||
|
getSlotOrders,
|
||||||
|
updateAddressCoords,
|
||||||
|
getAllOrders,
|
||||||
|
rebalanceSlots,
|
||||||
|
cancelOrder,
|
||||||
|
deleteOrderById,
|
||||||
|
} from './src/admin-apis/order';
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Product
|
||||||
|
getAllProducts,
|
||||||
|
getProductById,
|
||||||
|
deleteProduct,
|
||||||
|
createProduct,
|
||||||
|
updateProduct,
|
||||||
|
toggleProductOutOfStock,
|
||||||
|
updateSlotProducts,
|
||||||
|
getSlotProductIds,
|
||||||
|
getSlotsProductIds,
|
||||||
|
getAllUnits,
|
||||||
|
getAllProductTags,
|
||||||
|
getProductReviews,
|
||||||
|
respondToReview,
|
||||||
|
getAllProductGroups,
|
||||||
|
createProductGroup,
|
||||||
|
updateProductGroup,
|
||||||
|
deleteProductGroup,
|
||||||
|
addProductToGroup,
|
||||||
|
removeProductFromGroup,
|
||||||
|
updateProductPrices,
|
||||||
|
} from './src/admin-apis/product';
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Slots
|
||||||
|
getActiveSlotsWithProducts,
|
||||||
|
getActiveSlots,
|
||||||
|
getSlotsAfterDate,
|
||||||
|
getSlotByIdWithRelations,
|
||||||
|
createSlotWithRelations,
|
||||||
|
updateSlotWithRelations,
|
||||||
|
deleteSlotById,
|
||||||
|
updateSlotCapacity,
|
||||||
|
getSlotDeliverySequence,
|
||||||
|
updateSlotDeliverySequence,
|
||||||
|
} from './src/admin-apis/slots';
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Staff User
|
||||||
|
getStaffUserByName,
|
||||||
|
getAllStaff,
|
||||||
|
getAllUsers,
|
||||||
|
getUserWithDetails,
|
||||||
|
updateUserSuspensionStatus,
|
||||||
|
checkStaffUserExists,
|
||||||
|
checkStaffRoleExists,
|
||||||
|
createStaffUser,
|
||||||
|
getAllRoles,
|
||||||
|
} from './src/admin-apis/staff-user';
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Store
|
||||||
|
getAllStores,
|
||||||
|
getStoreById,
|
||||||
|
createStore,
|
||||||
|
updateStore,
|
||||||
|
deleteStore,
|
||||||
|
} from './src/admin-apis/store';
|
||||||
|
|
||||||
|
export {
|
||||||
|
// User
|
||||||
|
createUserByMobile,
|
||||||
|
getUserByMobile,
|
||||||
|
getUnresolvedComplaintsCount,
|
||||||
|
getAllUsersWithFilters,
|
||||||
|
getOrderCountsByUserIds,
|
||||||
|
getLastOrdersByUserIds,
|
||||||
|
getSuspensionStatusesByUserIds,
|
||||||
|
getUserBasicInfo,
|
||||||
|
getUserSuspensionStatus,
|
||||||
|
getUserOrders,
|
||||||
|
getOrderStatusesByOrderIds,
|
||||||
|
getItemCountsByOrderIds,
|
||||||
|
upsertUserSuspension,
|
||||||
|
searchUsers,
|
||||||
|
getAllNotifCreds,
|
||||||
|
getAllUnloggedTokens,
|
||||||
|
getNotifTokensByUserIds,
|
||||||
|
getUserIncidentsWithRelations,
|
||||||
|
createUserIncident,
|
||||||
|
} from './src/admin-apis/user';
|
||||||
|
|
||||||
|
export {
|
||||||
|
// Vendor Snippets
|
||||||
|
checkVendorSnippetExists,
|
||||||
|
getVendorSnippetById,
|
||||||
|
getVendorSnippetByCode,
|
||||||
|
getAllVendorSnippets,
|
||||||
|
createVendorSnippet,
|
||||||
|
updateVendorSnippet,
|
||||||
|
deleteVendorSnippet,
|
||||||
|
getProductsByIds,
|
||||||
|
getVendorSlotById,
|
||||||
|
getVendorOrdersBySlotId,
|
||||||
|
getOrderItemsByOrderIds,
|
||||||
|
getOrderStatusByOrderIds,
|
||||||
|
updateVendorOrderItemPackaging,
|
||||||
|
getVendorOrders,
|
||||||
|
} from './src/admin-apis/vendor-snippets';
|
||||||
|
|
||||||
|
// Note: User API helpers are available in their respective files
|
||||||
|
// but not exported from main index to avoid naming conflicts
|
||||||
|
// Import them directly from the file paths if needed:
|
||||||
|
// import { getAllProducts } from '@packages/db_helper_postgres/src/user-apis/product'
|
||||||
|
|
|
||||||
112
packages/db_helper_postgres/src/admin-apis/banner.ts
Normal file
112
packages/db_helper_postgres/src/admin-apis/banner.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { homeBanners } from '../db/schema';
|
||||||
|
import { eq, desc } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export interface Banner {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
imageUrl: string;
|
||||||
|
description: string | null;
|
||||||
|
productIds: number[] | null;
|
||||||
|
redirectUrl: string | null;
|
||||||
|
serialNum: number | null;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
lastUpdated: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBanners(): Promise<Banner[]> {
|
||||||
|
const banners = await db.query.homeBanners.findMany({
|
||||||
|
orderBy: desc(homeBanners.createdAt),
|
||||||
|
});
|
||||||
|
|
||||||
|
return banners.map((banner) => ({
|
||||||
|
id: banner.id,
|
||||||
|
name: banner.name,
|
||||||
|
imageUrl: banner.imageUrl,
|
||||||
|
description: banner.description,
|
||||||
|
productIds: banner.productIds || [],
|
||||||
|
redirectUrl: banner.redirectUrl,
|
||||||
|
serialNum: banner.serialNum,
|
||||||
|
isActive: banner.isActive,
|
||||||
|
createdAt: banner.createdAt,
|
||||||
|
lastUpdated: banner.lastUpdated,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBannerById(id: number): Promise<Banner | null> {
|
||||||
|
const banner = await db.query.homeBanners.findFirst({
|
||||||
|
where: eq(homeBanners.id, id),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!banner) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: banner.id,
|
||||||
|
name: banner.name,
|
||||||
|
imageUrl: banner.imageUrl,
|
||||||
|
description: banner.description,
|
||||||
|
productIds: banner.productIds || [],
|
||||||
|
redirectUrl: banner.redirectUrl,
|
||||||
|
serialNum: banner.serialNum,
|
||||||
|
isActive: banner.isActive,
|
||||||
|
createdAt: banner.createdAt,
|
||||||
|
lastUpdated: banner.lastUpdated,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateBannerInput = Omit<Banner, 'id' | 'createdAt' | 'lastUpdated'>;
|
||||||
|
|
||||||
|
export async function createBanner(input: CreateBannerInput): Promise<Banner> {
|
||||||
|
const [banner] = await db.insert(homeBanners).values({
|
||||||
|
name: input.name,
|
||||||
|
imageUrl: input.imageUrl,
|
||||||
|
description: input.description,
|
||||||
|
productIds: input.productIds || [],
|
||||||
|
redirectUrl: input.redirectUrl,
|
||||||
|
serialNum: input.serialNum,
|
||||||
|
isActive: input.isActive,
|
||||||
|
}).returning();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: banner.id,
|
||||||
|
name: banner.name,
|
||||||
|
imageUrl: banner.imageUrl,
|
||||||
|
description: banner.description,
|
||||||
|
productIds: banner.productIds || [],
|
||||||
|
redirectUrl: banner.redirectUrl,
|
||||||
|
serialNum: banner.serialNum,
|
||||||
|
isActive: banner.isActive,
|
||||||
|
createdAt: banner.createdAt,
|
||||||
|
lastUpdated: banner.lastUpdated,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateBannerInput = Partial<Omit<Banner, 'id' | 'createdAt'>>;
|
||||||
|
|
||||||
|
export async function updateBanner(id: number, input: UpdateBannerInput): Promise<Banner> {
|
||||||
|
const [banner] = await db.update(homeBanners)
|
||||||
|
.set({
|
||||||
|
...input,
|
||||||
|
lastUpdated: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(homeBanners.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: banner.id,
|
||||||
|
name: banner.name,
|
||||||
|
imageUrl: banner.imageUrl,
|
||||||
|
description: banner.description,
|
||||||
|
productIds: banner.productIds || [],
|
||||||
|
redirectUrl: banner.redirectUrl,
|
||||||
|
serialNum: banner.serialNum,
|
||||||
|
isActive: banner.isActive,
|
||||||
|
createdAt: banner.createdAt,
|
||||||
|
lastUpdated: banner.lastUpdated,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteBanner(id: number): Promise<void> {
|
||||||
|
await db.delete(homeBanners).where(eq(homeBanners.id, id));
|
||||||
|
}
|
||||||
74
packages/db_helper_postgres/src/admin-apis/complaint.ts
Normal file
74
packages/db_helper_postgres/src/admin-apis/complaint.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { complaints, users } from '../db/schema';
|
||||||
|
import { eq, desc, lt } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export interface Complaint {
|
||||||
|
id: number;
|
||||||
|
complaintBody: string;
|
||||||
|
userId: number;
|
||||||
|
orderId: number | null;
|
||||||
|
isResolved: boolean;
|
||||||
|
response: string | null;
|
||||||
|
createdAt: Date;
|
||||||
|
images: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComplaintWithUser extends Complaint {
|
||||||
|
userName: string | null;
|
||||||
|
userMobile: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getComplaints(
|
||||||
|
cursor?: number,
|
||||||
|
limit: number = 20
|
||||||
|
): Promise<{ complaints: ComplaintWithUser[]; hasMore: boolean }> {
|
||||||
|
let whereCondition = cursor ? lt(complaints.id, cursor) : undefined;
|
||||||
|
|
||||||
|
const complaintsData = await db
|
||||||
|
.select({
|
||||||
|
id: complaints.id,
|
||||||
|
complaintBody: complaints.complaintBody,
|
||||||
|
userId: complaints.userId,
|
||||||
|
orderId: complaints.orderId,
|
||||||
|
isResolved: complaints.isResolved,
|
||||||
|
response: complaints.response,
|
||||||
|
createdAt: complaints.createdAt,
|
||||||
|
images: complaints.images,
|
||||||
|
userName: users.name,
|
||||||
|
userMobile: users.mobile,
|
||||||
|
})
|
||||||
|
.from(complaints)
|
||||||
|
.leftJoin(users, eq(complaints.userId, users.id))
|
||||||
|
.where(whereCondition)
|
||||||
|
.orderBy(desc(complaints.id))
|
||||||
|
.limit(limit + 1);
|
||||||
|
|
||||||
|
const hasMore = complaintsData.length > limit;
|
||||||
|
const complaintsToReturn = hasMore ? complaintsData.slice(0, limit) : complaintsData;
|
||||||
|
|
||||||
|
return {
|
||||||
|
complaints: complaintsToReturn.map((c) => ({
|
||||||
|
id: c.id,
|
||||||
|
complaintBody: c.complaintBody,
|
||||||
|
userId: c.userId,
|
||||||
|
orderId: c.orderId,
|
||||||
|
isResolved: c.isResolved,
|
||||||
|
response: c.response,
|
||||||
|
createdAt: c.createdAt,
|
||||||
|
images: c.images as string[],
|
||||||
|
userName: c.userName,
|
||||||
|
userMobile: c.userMobile,
|
||||||
|
})),
|
||||||
|
hasMore,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveComplaint(
|
||||||
|
id: number,
|
||||||
|
response?: string
|
||||||
|
): Promise<void> {
|
||||||
|
await db
|
||||||
|
.update(complaints)
|
||||||
|
.set({ isResolved: true, response })
|
||||||
|
.where(eq(complaints.id, id));
|
||||||
|
}
|
||||||
29
packages/db_helper_postgres/src/admin-apis/const.ts
Normal file
29
packages/db_helper_postgres/src/admin-apis/const.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { keyValStore } from '../db/schema';
|
||||||
|
|
||||||
|
export interface Constant {
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllConstants(): Promise<Constant[]> {
|
||||||
|
const constants = await db.select().from(keyValStore);
|
||||||
|
|
||||||
|
return constants.map(c => ({
|
||||||
|
key: c.key,
|
||||||
|
value: c.value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertConstants(constants: Constant[]): Promise<void> {
|
||||||
|
await db.transaction(async (tx) => {
|
||||||
|
for (const { key, value } of constants) {
|
||||||
|
await tx.insert(keyValStore)
|
||||||
|
.values({ key, value })
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: keyValStore.key,
|
||||||
|
set: { value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
496
packages/db_helper_postgres/src/admin-apis/coupon.ts
Normal file
496
packages/db_helper_postgres/src/admin-apis/coupon.ts
Normal file
|
|
@ -0,0 +1,496 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { coupons, reservedCoupons, users, orders, orderStatus, couponApplicableUsers, couponApplicableProducts } from '../db/schema';
|
||||||
|
import { eq, and, like, or, inArray, lt, desc } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export interface Coupon {
|
||||||
|
id: number;
|
||||||
|
couponCode: string;
|
||||||
|
isUserBased: boolean;
|
||||||
|
discountPercent: string | null;
|
||||||
|
flatDiscount: string | null;
|
||||||
|
minOrder: string | null;
|
||||||
|
productIds: number[] | null;
|
||||||
|
maxValue: string | null;
|
||||||
|
isApplyForAll: boolean;
|
||||||
|
validTill: Date | null;
|
||||||
|
maxLimitForUser: number | null;
|
||||||
|
exclusiveApply: boolean;
|
||||||
|
isInvalidated: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
createdBy: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllCoupons(
|
||||||
|
cursor?: number,
|
||||||
|
limit: number = 50,
|
||||||
|
search?: string
|
||||||
|
): Promise<{ coupons: any[]; hasMore: boolean }> {
|
||||||
|
let whereCondition = undefined;
|
||||||
|
const conditions = [];
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
conditions.push(lt(coupons.id, cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search && search.trim()) {
|
||||||
|
conditions.push(like(coupons.couponCode, `%${search}%`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditions.length > 0) {
|
||||||
|
whereCondition = and(...conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db.query.coupons.findMany({
|
||||||
|
where: whereCondition,
|
||||||
|
with: {
|
||||||
|
creator: true,
|
||||||
|
applicableUsers: {
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
applicableProducts: {
|
||||||
|
with: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: (coupons, { desc }) => [desc(coupons.createdAt)],
|
||||||
|
limit: limit + 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasMore = result.length > limit;
|
||||||
|
const couponsList = hasMore ? result.slice(0, limit) : result;
|
||||||
|
|
||||||
|
return { coupons: couponsList, hasMore };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCouponById(id: number): Promise<any | null> {
|
||||||
|
return await db.query.coupons.findFirst({
|
||||||
|
where: eq(coupons.id, id),
|
||||||
|
with: {
|
||||||
|
creator: true,
|
||||||
|
applicableUsers: {
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
applicableProducts: {
|
||||||
|
with: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateCouponInput {
|
||||||
|
couponCode: string;
|
||||||
|
isUserBased: boolean;
|
||||||
|
discountPercent?: string;
|
||||||
|
flatDiscount?: string;
|
||||||
|
minOrder?: string;
|
||||||
|
productIds?: number[] | null;
|
||||||
|
maxValue?: string;
|
||||||
|
isApplyForAll: boolean;
|
||||||
|
validTill?: Date;
|
||||||
|
maxLimitForUser?: number;
|
||||||
|
exclusiveApply: boolean;
|
||||||
|
createdBy: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCouponWithRelations(
|
||||||
|
input: CreateCouponInput,
|
||||||
|
applicableUsers?: number[],
|
||||||
|
applicableProducts?: number[]
|
||||||
|
): Promise<Coupon> {
|
||||||
|
return await db.transaction(async (tx) => {
|
||||||
|
const [coupon] = await tx.insert(coupons).values({
|
||||||
|
couponCode: input.couponCode,
|
||||||
|
isUserBased: input.isUserBased,
|
||||||
|
discountPercent: input.discountPercent,
|
||||||
|
flatDiscount: input.flatDiscount,
|
||||||
|
minOrder: input.minOrder,
|
||||||
|
productIds: input.productIds,
|
||||||
|
createdBy: input.createdBy,
|
||||||
|
maxValue: input.maxValue,
|
||||||
|
isApplyForAll: input.isApplyForAll,
|
||||||
|
validTill: input.validTill,
|
||||||
|
maxLimitForUser: input.maxLimitForUser,
|
||||||
|
exclusiveApply: input.exclusiveApply,
|
||||||
|
}).returning();
|
||||||
|
|
||||||
|
if (applicableUsers && applicableUsers.length > 0) {
|
||||||
|
await tx.insert(couponApplicableUsers).values(
|
||||||
|
applicableUsers.map(userId => ({
|
||||||
|
couponId: coupon.id,
|
||||||
|
userId,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicableProducts && applicableProducts.length > 0) {
|
||||||
|
await tx.insert(couponApplicableProducts).values(
|
||||||
|
applicableProducts.map(productId => ({
|
||||||
|
couponId: coupon.id,
|
||||||
|
productId,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coupon as Coupon;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateCouponInput {
|
||||||
|
couponCode?: string;
|
||||||
|
isUserBased?: boolean;
|
||||||
|
discountPercent?: string;
|
||||||
|
flatDiscount?: string;
|
||||||
|
minOrder?: string;
|
||||||
|
productIds?: number[] | null;
|
||||||
|
maxValue?: string;
|
||||||
|
isApplyForAll?: boolean;
|
||||||
|
validTill?: Date | null;
|
||||||
|
maxLimitForUser?: number;
|
||||||
|
exclusiveApply?: boolean;
|
||||||
|
isInvalidated?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateCouponWithRelations(
|
||||||
|
id: number,
|
||||||
|
input: UpdateCouponInput,
|
||||||
|
applicableUsers?: number[],
|
||||||
|
applicableProducts?: number[]
|
||||||
|
): Promise<Coupon> {
|
||||||
|
return await db.transaction(async (tx) => {
|
||||||
|
const [coupon] = await tx.update(coupons)
|
||||||
|
.set({
|
||||||
|
...input,
|
||||||
|
})
|
||||||
|
.where(eq(coupons.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (applicableUsers !== undefined) {
|
||||||
|
await tx.delete(couponApplicableUsers).where(eq(couponApplicableUsers.couponId, id));
|
||||||
|
if (applicableUsers.length > 0) {
|
||||||
|
await tx.insert(couponApplicableUsers).values(
|
||||||
|
applicableUsers.map(userId => ({
|
||||||
|
couponId: id,
|
||||||
|
userId,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicableProducts !== undefined) {
|
||||||
|
await tx.delete(couponApplicableProducts).where(eq(couponApplicableProducts.couponId, id));
|
||||||
|
if (applicableProducts.length > 0) {
|
||||||
|
await tx.insert(couponApplicableProducts).values(
|
||||||
|
applicableProducts.map(productId => ({
|
||||||
|
couponId: id,
|
||||||
|
productId,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return coupon as Coupon;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function invalidateCoupon(id: number): Promise<Coupon> {
|
||||||
|
const result = await db.update(coupons)
|
||||||
|
.set({ isInvalidated: true })
|
||||||
|
.where(eq(coupons.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return result[0] as Coupon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CouponValidationResult {
|
||||||
|
valid: boolean;
|
||||||
|
message?: string;
|
||||||
|
discountAmount?: number;
|
||||||
|
coupon?: Partial<Coupon>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateCoupon(
|
||||||
|
code: string,
|
||||||
|
userId: number,
|
||||||
|
orderAmount: number
|
||||||
|
): Promise<CouponValidationResult> {
|
||||||
|
const coupon = await db.query.coupons.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(coupons.couponCode, code.toUpperCase()),
|
||||||
|
eq(coupons.isInvalidated, false)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!coupon) {
|
||||||
|
return { valid: false, message: "Coupon not found or invalidated" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coupon.validTill && new Date(coupon.validTill) < new Date()) {
|
||||||
|
return { valid: false, message: "Coupon has expired" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!coupon.isApplyForAll && !coupon.isUserBased) {
|
||||||
|
return { valid: false, message: "Coupon is not available for use" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const minOrderValue = coupon.minOrder ? parseFloat(coupon.minOrder) : 0;
|
||||||
|
if (minOrderValue > 0 && orderAmount < minOrderValue) {
|
||||||
|
return { valid: false, message: `Minimum order amount is ${minOrderValue}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
let discountAmount = 0;
|
||||||
|
if (coupon.discountPercent) {
|
||||||
|
const percent = parseFloat(coupon.discountPercent);
|
||||||
|
discountAmount = (orderAmount * percent) / 100;
|
||||||
|
} else if (coupon.flatDiscount) {
|
||||||
|
discountAmount = parseFloat(coupon.flatDiscount);
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxValueLimit = coupon.maxValue ? parseFloat(coupon.maxValue) : 0;
|
||||||
|
if (maxValueLimit > 0 && discountAmount > maxValueLimit) {
|
||||||
|
discountAmount = maxValueLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
discountAmount,
|
||||||
|
coupon: {
|
||||||
|
id: coupon.id,
|
||||||
|
discountPercent: coupon.discountPercent,
|
||||||
|
flatDiscount: coupon.flatDiscount,
|
||||||
|
maxValue: coupon.maxValue,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReservedCoupons(
|
||||||
|
cursor?: number,
|
||||||
|
limit: number = 50,
|
||||||
|
search?: string
|
||||||
|
): Promise<{ coupons: any[]; hasMore: boolean }> {
|
||||||
|
let whereCondition = undefined;
|
||||||
|
const conditions = [];
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
conditions.push(lt(reservedCoupons.id, cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search && search.trim()) {
|
||||||
|
conditions.push(or(
|
||||||
|
like(reservedCoupons.secretCode, `%${search}%`),
|
||||||
|
like(reservedCoupons.couponCode, `%${search}%`)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditions.length > 0) {
|
||||||
|
whereCondition = and(...conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db.query.reservedCoupons.findMany({
|
||||||
|
where: whereCondition,
|
||||||
|
with: {
|
||||||
|
redeemedUser: true,
|
||||||
|
creator: true,
|
||||||
|
},
|
||||||
|
orderBy: (reservedCoupons, { desc }) => [desc(reservedCoupons.createdAt)],
|
||||||
|
limit: limit + 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasMore = result.length > limit;
|
||||||
|
const couponsList = hasMore ? result.slice(0, limit) : result;
|
||||||
|
|
||||||
|
return { coupons: couponsList, hasMore };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createReservedCouponWithProducts(
|
||||||
|
input: any,
|
||||||
|
applicableProducts?: number[]
|
||||||
|
): Promise<any> {
|
||||||
|
return await db.transaction(async (tx) => {
|
||||||
|
const [coupon] = await tx.insert(reservedCoupons).values({
|
||||||
|
secretCode: input.secretCode,
|
||||||
|
couponCode: input.couponCode,
|
||||||
|
discountPercent: input.discountPercent,
|
||||||
|
flatDiscount: input.flatDiscount,
|
||||||
|
minOrder: input.minOrder,
|
||||||
|
productIds: input.productIds,
|
||||||
|
maxValue: input.maxValue,
|
||||||
|
validTill: input.validTill,
|
||||||
|
maxLimitForUser: input.maxLimitForUser,
|
||||||
|
exclusiveApply: input.exclusiveApply,
|
||||||
|
createdBy: input.createdBy,
|
||||||
|
}).returning();
|
||||||
|
|
||||||
|
if (applicableProducts && applicableProducts.length > 0) {
|
||||||
|
await tx.insert(couponApplicableProducts).values(
|
||||||
|
applicableProducts.map(productId => ({
|
||||||
|
couponId: coupon.id,
|
||||||
|
productId,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coupon;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkUsersExist(userIds: number[]): Promise<boolean> {
|
||||||
|
const existingUsers = await db.query.users.findMany({
|
||||||
|
where: inArray(users.id, userIds),
|
||||||
|
columns: { id: true },
|
||||||
|
});
|
||||||
|
return existingUsers.length === userIds.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkCouponExists(couponCode: string): Promise<boolean> {
|
||||||
|
const existing = await db.query.coupons.findFirst({
|
||||||
|
where: eq(coupons.couponCode, couponCode),
|
||||||
|
});
|
||||||
|
return !!existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkReservedCouponExists(secretCode: string): Promise<boolean> {
|
||||||
|
const existing = await db.query.reservedCoupons.findFirst({
|
||||||
|
where: eq(reservedCoupons.secretCode, secretCode),
|
||||||
|
});
|
||||||
|
return !!existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateCancellationCoupon(
|
||||||
|
orderId: number,
|
||||||
|
staffUserId: number,
|
||||||
|
userId: number,
|
||||||
|
orderAmount: number,
|
||||||
|
couponCode: string
|
||||||
|
): Promise<Coupon> {
|
||||||
|
return await db.transaction(async (tx) => {
|
||||||
|
const expiryDate = new Date();
|
||||||
|
expiryDate.setDate(expiryDate.getDate() + 30);
|
||||||
|
|
||||||
|
const [coupon] = await tx.insert(coupons).values({
|
||||||
|
couponCode,
|
||||||
|
isUserBased: true,
|
||||||
|
flatDiscount: orderAmount.toString(),
|
||||||
|
minOrder: orderAmount.toString(),
|
||||||
|
maxValue: orderAmount.toString(),
|
||||||
|
validTill: expiryDate,
|
||||||
|
maxLimitForUser: 1,
|
||||||
|
createdBy: staffUserId,
|
||||||
|
isApplyForAll: false,
|
||||||
|
}).returning();
|
||||||
|
|
||||||
|
await tx.insert(couponApplicableUsers).values({
|
||||||
|
couponId: coupon.id,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.update(orderStatus)
|
||||||
|
.set({ refundCouponId: coupon.id })
|
||||||
|
.where(eq(orderStatus.orderId, orderId));
|
||||||
|
|
||||||
|
return coupon as Coupon;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrderWithUser(orderId: number): Promise<any | null> {
|
||||||
|
return await db.query.orders.findFirst({
|
||||||
|
where: eq(orders.id, orderId),
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCouponForUser(
|
||||||
|
mobile: string,
|
||||||
|
couponCode: string,
|
||||||
|
staffUserId: number
|
||||||
|
): Promise<{ coupon: Coupon; user: { id: number; mobile: string; name: string | null } }> {
|
||||||
|
return await db.transaction(async (tx) => {
|
||||||
|
let user = await tx.query.users.findFirst({
|
||||||
|
where: eq(users.mobile, mobile),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
const [newUser] = await tx.insert(users).values({
|
||||||
|
name: null,
|
||||||
|
email: null,
|
||||||
|
mobile,
|
||||||
|
}).returning();
|
||||||
|
user = newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [coupon] = await tx.insert(coupons).values({
|
||||||
|
couponCode,
|
||||||
|
isUserBased: true,
|
||||||
|
discountPercent: "20",
|
||||||
|
minOrder: "1000",
|
||||||
|
maxValue: "500",
|
||||||
|
maxLimitForUser: 1,
|
||||||
|
isApplyForAll: false,
|
||||||
|
exclusiveApply: false,
|
||||||
|
createdBy: staffUserId,
|
||||||
|
validTill: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
|
||||||
|
}).returning();
|
||||||
|
|
||||||
|
await tx.insert(couponApplicableUsers).values({
|
||||||
|
couponId: coupon.id,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
coupon: coupon as Coupon,
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
mobile: user.mobile as string,
|
||||||
|
name: user.name,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserMiniInfo {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
mobile: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUsersForCoupon(
|
||||||
|
search?: string,
|
||||||
|
limit: number = 20,
|
||||||
|
offset: number = 0
|
||||||
|
): Promise<{ users: UserMiniInfo[] }> {
|
||||||
|
let whereCondition = undefined;
|
||||||
|
if (search && search.trim()) {
|
||||||
|
whereCondition = or(
|
||||||
|
like(users.name, `%${search}%`),
|
||||||
|
like(users.mobile, `%${search}%`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userList = await db.query.users.findMany({
|
||||||
|
where: whereCondition,
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
mobile: true,
|
||||||
|
},
|
||||||
|
limit: limit,
|
||||||
|
offset: offset,
|
||||||
|
orderBy: (users, { asc }) => [asc(users.name)],
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
users: userList.map(user => ({
|
||||||
|
id: user.id,
|
||||||
|
name: user.name || 'Unknown',
|
||||||
|
mobile: user.mobile,
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
710
packages/db_helper_postgres/src/admin-apis/order.ts
Normal file
710
packages/db_helper_postgres/src/admin-apis/order.ts
Normal file
|
|
@ -0,0 +1,710 @@
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
558
packages/db_helper_postgres/src/admin-apis/product.ts
Normal file
558
packages/db_helper_postgres/src/admin-apis/product.ts
Normal file
|
|
@ -0,0 +1,558 @@
|
||||||
|
import { db } from '../db/db_index'
|
||||||
|
import {
|
||||||
|
productInfo,
|
||||||
|
units,
|
||||||
|
specialDeals,
|
||||||
|
productSlots,
|
||||||
|
productTags,
|
||||||
|
productReviews,
|
||||||
|
productGroupInfo,
|
||||||
|
productGroupMembership,
|
||||||
|
productTagInfo,
|
||||||
|
users,
|
||||||
|
storeInfo,
|
||||||
|
} from '../db/schema'
|
||||||
|
import { and, desc, eq, inArray, sql } from 'drizzle-orm'
|
||||||
|
import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'
|
||||||
|
import type {
|
||||||
|
AdminProduct,
|
||||||
|
AdminProductGroupInfo,
|
||||||
|
AdminProductTagWithProducts,
|
||||||
|
AdminProductReview,
|
||||||
|
AdminProductWithDetails,
|
||||||
|
AdminProductWithRelations,
|
||||||
|
AdminSpecialDeal,
|
||||||
|
AdminUnit,
|
||||||
|
AdminUpdateSlotProductsResult,
|
||||||
|
Store,
|
||||||
|
} from '@packages/shared'
|
||||||
|
|
||||||
|
type ProductRow = InferSelectModel<typeof productInfo>
|
||||||
|
type UnitRow = InferSelectModel<typeof units>
|
||||||
|
type StoreRow = InferSelectModel<typeof storeInfo>
|
||||||
|
type SpecialDealRow = InferSelectModel<typeof specialDeals>
|
||||||
|
type ProductTagInfoRow = InferSelectModel<typeof productTagInfo>
|
||||||
|
type ProductTagRow = InferSelectModel<typeof productTags>
|
||||||
|
type ProductGroupRow = InferSelectModel<typeof productGroupInfo>
|
||||||
|
type ProductGroupMembershipRow = InferSelectModel<typeof productGroupMembership>
|
||||||
|
type ProductReviewRow = InferSelectModel<typeof productReviews>
|
||||||
|
|
||||||
|
const getStringArray = (value: unknown): string[] | null => {
|
||||||
|
if (!Array.isArray(value)) return null
|
||||||
|
return value.map((item) => String(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapUnit = (unit: UnitRow): AdminUnit => ({
|
||||||
|
id: unit.id,
|
||||||
|
shortNotation: unit.shortNotation,
|
||||||
|
fullName: unit.fullName,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStore = (store: StoreRow): Store => ({
|
||||||
|
id: store.id,
|
||||||
|
name: store.name,
|
||||||
|
description: store.description,
|
||||||
|
imageUrl: store.imageUrl,
|
||||||
|
owner: store.owner,
|
||||||
|
createdAt: store.createdAt,
|
||||||
|
// updatedAt: store.createdAt,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapProduct = (product: ProductRow): AdminProduct => ({
|
||||||
|
id: product.id,
|
||||||
|
name: product.name,
|
||||||
|
shortDescription: product.shortDescription ?? null,
|
||||||
|
longDescription: product.longDescription ?? null,
|
||||||
|
unitId: product.unitId,
|
||||||
|
price: product.price.toString(),
|
||||||
|
marketPrice: product.marketPrice ? product.marketPrice.toString() : null,
|
||||||
|
images: getStringArray(product.images),
|
||||||
|
isOutOfStock: product.isOutOfStock,
|
||||||
|
isSuspended: product.isSuspended,
|
||||||
|
isFlashAvailable: product.isFlashAvailable,
|
||||||
|
flashPrice: product.flashPrice ? product.flashPrice.toString() : null,
|
||||||
|
createdAt: product.createdAt,
|
||||||
|
incrementStep: product.incrementStep,
|
||||||
|
productQuantity: product.productQuantity,
|
||||||
|
storeId: product.storeId,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapSpecialDeal = (deal: SpecialDealRow): AdminSpecialDeal => ({
|
||||||
|
id: deal.id,
|
||||||
|
productId: deal.productId,
|
||||||
|
quantity: deal.quantity.toString(),
|
||||||
|
price: deal.price.toString(),
|
||||||
|
validTill: deal.validTill,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapTagInfo = (tag: ProductTagInfoRow) => ({
|
||||||
|
id: tag.id,
|
||||||
|
tagName: tag.tagName,
|
||||||
|
tagDescription: tag.tagDescription ?? null,
|
||||||
|
imageUrl: tag.imageUrl ?? null,
|
||||||
|
isDashboardTag: tag.isDashboardTag,
|
||||||
|
relatedStores: tag.relatedStores,
|
||||||
|
createdAt: tag.createdAt,
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function getAllProducts(): Promise<AdminProductWithRelations[]> {
|
||||||
|
const products = await db.query.productInfo.findMany({
|
||||||
|
orderBy: productInfo.name,
|
||||||
|
with: {
|
||||||
|
unit: true,
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return products.map((product) => ({
|
||||||
|
...mapProduct(product),
|
||||||
|
unit: mapUnit(product.unit),
|
||||||
|
store: product.store ? mapStore(product.store) : null,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProductById(id: number): Promise<AdminProductWithDetails | null> {
|
||||||
|
const product = await db.query.productInfo.findFirst({
|
||||||
|
where: eq(productInfo.id, id),
|
||||||
|
with: {
|
||||||
|
unit: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const deals = await db.query.specialDeals.findMany({
|
||||||
|
where: eq(specialDeals.productId, id),
|
||||||
|
orderBy: specialDeals.quantity,
|
||||||
|
})
|
||||||
|
|
||||||
|
const productTagsData = await db.query.productTags.findMany({
|
||||||
|
where: eq(productTags.productId, id),
|
||||||
|
with: {
|
||||||
|
tag: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...mapProduct(product),
|
||||||
|
unit: mapUnit(product.unit),
|
||||||
|
deals: deals.map(mapSpecialDeal),
|
||||||
|
tags: productTagsData.map((tag) => mapTagInfo(tag.tag)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteProduct(id: number): Promise<AdminProduct | null> {
|
||||||
|
const [deletedProduct] = await db
|
||||||
|
.delete(productInfo)
|
||||||
|
.where(eq(productInfo.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (!deletedProduct) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapProduct(deletedProduct)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductInfoInsert = InferInsertModel<typeof productInfo>
|
||||||
|
type ProductInfoUpdate = Partial<ProductInfoInsert>
|
||||||
|
|
||||||
|
export async function createProduct(input: ProductInfoInsert): Promise<AdminProduct> {
|
||||||
|
const [product] = await db.insert(productInfo).values(input).returning()
|
||||||
|
return mapProduct(product)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProduct(id: number, updates: ProductInfoUpdate): Promise<AdminProduct | null> {
|
||||||
|
const [product] = await db.update(productInfo)
|
||||||
|
.set(updates)
|
||||||
|
.where(eq(productInfo.id, id))
|
||||||
|
.returning()
|
||||||
|
if (!product) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapProduct(product)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function toggleProductOutOfStock(id: number): Promise<AdminProduct | null> {
|
||||||
|
const product = await db.query.productInfo.findFirst({
|
||||||
|
where: eq(productInfo.id, id),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updatedProduct] = await db
|
||||||
|
.update(productInfo)
|
||||||
|
.set({
|
||||||
|
isOutOfStock: !product.isOutOfStock,
|
||||||
|
})
|
||||||
|
.where(eq(productInfo.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (!updatedProduct) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapProduct(updatedProduct)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSlotProducts(slotId: string, productIds: string[]): Promise<AdminUpdateSlotProductsResult> {
|
||||||
|
const currentAssociations = await db.query.productSlots.findMany({
|
||||||
|
where: eq(productSlots.slotId, parseInt(slotId)),
|
||||||
|
columns: {
|
||||||
|
productId: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentProductIds = currentAssociations.map((assoc) => assoc.productId)
|
||||||
|
const newProductIds = productIds.map((id) => parseInt(id))
|
||||||
|
|
||||||
|
const productsToAdd = newProductIds.filter((id) => !currentProductIds.includes(id))
|
||||||
|
const productsToRemove = currentProductIds.filter((id) => !newProductIds.includes(id))
|
||||||
|
|
||||||
|
if (productsToRemove.length > 0) {
|
||||||
|
await db.delete(productSlots).where(
|
||||||
|
and(
|
||||||
|
eq(productSlots.slotId, parseInt(slotId)),
|
||||||
|
inArray(productSlots.productId, productsToRemove)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (productsToAdd.length > 0) {
|
||||||
|
const newAssociations = productsToAdd.map((productId) => ({
|
||||||
|
productId,
|
||||||
|
slotId: parseInt(slotId),
|
||||||
|
}))
|
||||||
|
|
||||||
|
await db.insert(productSlots).values(newAssociations)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: 'Slot products updated successfully',
|
||||||
|
added: productsToAdd.length,
|
||||||
|
removed: productsToRemove.length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSlotProductIds(slotId: string): Promise<number[]> {
|
||||||
|
const associations = await db.query.productSlots.findMany({
|
||||||
|
where: eq(productSlots.slotId, parseInt(slotId)),
|
||||||
|
columns: {
|
||||||
|
productId: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return associations.map((assoc) => assoc.productId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllUnits(): Promise<AdminUnit[]> {
|
||||||
|
const allUnits = await db.query.units.findMany({
|
||||||
|
orderBy: units.shortNotation,
|
||||||
|
})
|
||||||
|
|
||||||
|
return allUnits.map(mapUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllProductTags(): Promise<AdminProductTagWithProducts[]> {
|
||||||
|
const tags = await db.query.productTagInfo.findMany({
|
||||||
|
with: {
|
||||||
|
products: {
|
||||||
|
with: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return tags.map((tag) => ({
|
||||||
|
...mapTagInfo(tag),
|
||||||
|
products: tag.products.map((assignment) => ({
|
||||||
|
productId: assignment.productId,
|
||||||
|
tagId: assignment.tagId,
|
||||||
|
assignedAt: assignment.assignedAt,
|
||||||
|
product: mapProduct(assignment.product),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSlotsProductIds(slotIds: number[]): Promise<Record<number, number[]>> {
|
||||||
|
if (slotIds.length === 0) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const associations = await db.query.productSlots.findMany({
|
||||||
|
where: inArray(productSlots.slotId, slotIds),
|
||||||
|
columns: {
|
||||||
|
slotId: true,
|
||||||
|
productId: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const result: Record<number, number[]> = {}
|
||||||
|
for (const assoc of associations) {
|
||||||
|
if (!result[assoc.slotId]) {
|
||||||
|
result[assoc.slotId] = []
|
||||||
|
}
|
||||||
|
result[assoc.slotId].push(assoc.productId)
|
||||||
|
}
|
||||||
|
|
||||||
|
slotIds.forEach((slotId) => {
|
||||||
|
if (!result[slotId]) {
|
||||||
|
result[slotId] = []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProductReviews(productId: number, limit: number, offset: number) {
|
||||||
|
const reviews = await db
|
||||||
|
.select({
|
||||||
|
id: productReviews.id,
|
||||||
|
reviewBody: productReviews.reviewBody,
|
||||||
|
ratings: productReviews.ratings,
|
||||||
|
imageUrls: productReviews.imageUrls,
|
||||||
|
reviewTime: productReviews.reviewTime,
|
||||||
|
adminResponse: productReviews.adminResponse,
|
||||||
|
adminResponseImages: productReviews.adminResponseImages,
|
||||||
|
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)
|
||||||
|
|
||||||
|
const totalCountResult = await db
|
||||||
|
.select({ count: sql`count(*)` })
|
||||||
|
.from(productReviews)
|
||||||
|
.where(eq(productReviews.productId, productId))
|
||||||
|
|
||||||
|
const totalCount = Number(totalCountResult[0].count)
|
||||||
|
|
||||||
|
const mappedReviews: AdminProductReview[] = reviews.map((review) => ({
|
||||||
|
id: review.id,
|
||||||
|
reviewBody: review.reviewBody,
|
||||||
|
ratings: review.ratings,
|
||||||
|
imageUrls: review.imageUrls,
|
||||||
|
reviewTime: review.reviewTime,
|
||||||
|
adminResponse: review.adminResponse ?? null,
|
||||||
|
adminResponseImages: review.adminResponseImages,
|
||||||
|
userName: review.userName ?? null,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
reviews: mappedReviews,
|
||||||
|
totalCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function respondToReview(
|
||||||
|
reviewId: number,
|
||||||
|
adminResponse: string | undefined,
|
||||||
|
adminResponseImages: string[]
|
||||||
|
): Promise<AdminProductReview | null> {
|
||||||
|
const [updatedReview] = await db
|
||||||
|
.update(productReviews)
|
||||||
|
.set({
|
||||||
|
adminResponse,
|
||||||
|
adminResponseImages,
|
||||||
|
})
|
||||||
|
.where(eq(productReviews.id, reviewId))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (!updatedReview) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: updatedReview.id,
|
||||||
|
reviewBody: updatedReview.reviewBody,
|
||||||
|
ratings: updatedReview.ratings,
|
||||||
|
imageUrls: updatedReview.imageUrls,
|
||||||
|
reviewTime: updatedReview.reviewTime,
|
||||||
|
adminResponse: updatedReview.adminResponse ?? null,
|
||||||
|
adminResponseImages: updatedReview.adminResponseImages,
|
||||||
|
userName: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllProductGroups() {
|
||||||
|
const groups = await db.query.productGroupInfo.findMany({
|
||||||
|
with: {
|
||||||
|
memberships: {
|
||||||
|
with: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: desc(productGroupInfo.createdAt),
|
||||||
|
})
|
||||||
|
|
||||||
|
return groups.map((group) => ({
|
||||||
|
id: group.id,
|
||||||
|
groupName: group.groupName,
|
||||||
|
description: group.description ?? null,
|
||||||
|
createdAt: group.createdAt,
|
||||||
|
products: group.memberships.map((membership) => mapProduct(membership.product)),
|
||||||
|
productCount: group.memberships.length,
|
||||||
|
memberships: group.memberships
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createProductGroup(
|
||||||
|
groupName: string,
|
||||||
|
description: string | undefined,
|
||||||
|
productIds: number[]
|
||||||
|
): Promise<AdminProductGroupInfo> {
|
||||||
|
const [newGroup] = await db
|
||||||
|
.insert(productGroupInfo)
|
||||||
|
.values({
|
||||||
|
groupName,
|
||||||
|
description,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (productIds.length > 0) {
|
||||||
|
const memberships = productIds.map((productId) => ({
|
||||||
|
productId,
|
||||||
|
groupId: newGroup.id,
|
||||||
|
}))
|
||||||
|
|
||||||
|
await db.insert(productGroupMembership).values(memberships)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: newGroup.id,
|
||||||
|
groupName: newGroup.groupName,
|
||||||
|
description: newGroup.description ?? null,
|
||||||
|
createdAt: newGroup.createdAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProductGroup(
|
||||||
|
id: number,
|
||||||
|
groupName: string | undefined,
|
||||||
|
description: string | undefined,
|
||||||
|
productIds: number[] | undefined
|
||||||
|
): Promise<AdminProductGroupInfo | null> {
|
||||||
|
const updateData: Partial<{
|
||||||
|
groupName: string
|
||||||
|
description: string | null
|
||||||
|
}> = {}
|
||||||
|
|
||||||
|
if (groupName !== undefined) updateData.groupName = groupName
|
||||||
|
if (description !== undefined) updateData.description = description
|
||||||
|
|
||||||
|
const [updatedGroup] = await db
|
||||||
|
.update(productGroupInfo)
|
||||||
|
.set(updateData)
|
||||||
|
.where(eq(productGroupInfo.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (!updatedGroup) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (productIds !== undefined) {
|
||||||
|
await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, id))
|
||||||
|
|
||||||
|
if (productIds.length > 0) {
|
||||||
|
const memberships = productIds.map((productId) => ({
|
||||||
|
productId,
|
||||||
|
groupId: id,
|
||||||
|
}))
|
||||||
|
|
||||||
|
await db.insert(productGroupMembership).values(memberships)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: updatedGroup.id,
|
||||||
|
groupName: updatedGroup.groupName,
|
||||||
|
description: updatedGroup.description ?? null,
|
||||||
|
createdAt: updatedGroup.createdAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteProductGroup(id: number): Promise<AdminProductGroupInfo | null> {
|
||||||
|
await db.delete(productGroupMembership).where(eq(productGroupMembership.groupId, id))
|
||||||
|
|
||||||
|
const [deletedGroup] = await db
|
||||||
|
.delete(productGroupInfo)
|
||||||
|
.where(eq(productGroupInfo.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (!deletedGroup) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: deletedGroup.id,
|
||||||
|
groupName: deletedGroup.groupName,
|
||||||
|
description: deletedGroup.description ?? null,
|
||||||
|
createdAt: deletedGroup.createdAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
|
||||||
|
await db.insert(productGroupMembership).values({ groupId, productId })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
|
||||||
|
await db.delete(productGroupMembership)
|
||||||
|
.where(and(
|
||||||
|
eq(productGroupMembership.groupId, groupId),
|
||||||
|
eq(productGroupMembership.productId, productId)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProductPrices(updates: Array<{
|
||||||
|
productId: number
|
||||||
|
price?: number
|
||||||
|
marketPrice?: number | null
|
||||||
|
flashPrice?: number | null
|
||||||
|
isFlashAvailable?: boolean
|
||||||
|
}>) {
|
||||||
|
if (updates.length === 0) {
|
||||||
|
return { updatedCount: 0, invalidIds: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const productIds = updates.map((update) => update.productId)
|
||||||
|
const existingProducts = await db.query.productInfo.findMany({
|
||||||
|
where: inArray(productInfo.id, productIds),
|
||||||
|
columns: { id: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
const existingIds = new Set(existingProducts.map((product) => product.id))
|
||||||
|
const invalidIds = productIds.filter((id) => !existingIds.has(id))
|
||||||
|
|
||||||
|
if (invalidIds.length > 0) {
|
||||||
|
return { updatedCount: 0, invalidIds }
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePromises = updates.map((update) => {
|
||||||
|
const { productId, price, marketPrice, flashPrice, isFlashAvailable } = update
|
||||||
|
const updateData: Partial<Pick<ProductInfoInsert, 'price' | 'marketPrice' | 'flashPrice' | 'isFlashAvailable'>> = {}
|
||||||
|
|
||||||
|
if (price !== undefined) updateData.price = price.toString()
|
||||||
|
if (marketPrice !== undefined) updateData.marketPrice = marketPrice === null ? null : marketPrice.toString()
|
||||||
|
if (flashPrice !== undefined) updateData.flashPrice = flashPrice === null ? null : flashPrice.toString()
|
||||||
|
if (isFlashAvailable !== undefined) updateData.isFlashAvailable = isFlashAvailable
|
||||||
|
|
||||||
|
return db
|
||||||
|
.update(productInfo)
|
||||||
|
.set(updateData)
|
||||||
|
.where(eq(productInfo.id, productId))
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(updatePromises)
|
||||||
|
|
||||||
|
return { updatedCount: updates.length, invalidIds: [] }
|
||||||
|
}
|
||||||
351
packages/db_helper_postgres/src/admin-apis/slots.ts
Normal file
351
packages/db_helper_postgres/src/admin-apis/slots.ts
Normal file
|
|
@ -0,0 +1,351 @@
|
||||||
|
import { db } from '../db/db_index'
|
||||||
|
import {
|
||||||
|
deliverySlotInfo,
|
||||||
|
productSlots,
|
||||||
|
productInfo,
|
||||||
|
vendorSnippets,
|
||||||
|
productGroupInfo,
|
||||||
|
} from '../db/schema'
|
||||||
|
import { and, asc, desc, eq, gt, inArray } from 'drizzle-orm'
|
||||||
|
import type {
|
||||||
|
AdminDeliverySlot,
|
||||||
|
AdminSlotWithProducts,
|
||||||
|
AdminSlotWithProductsAndSnippetsBase,
|
||||||
|
AdminSlotCreateResult,
|
||||||
|
AdminSlotUpdateResult,
|
||||||
|
AdminVendorSnippet,
|
||||||
|
AdminSlotProductSummary,
|
||||||
|
AdminUpdateSlotCapacityResult,
|
||||||
|
} from '@packages/shared'
|
||||||
|
|
||||||
|
type SlotSnippetInput = {
|
||||||
|
name: string
|
||||||
|
productIds: number[]
|
||||||
|
validTill?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStringArray = (value: unknown): string[] | null => {
|
||||||
|
if (!Array.isArray(value)) return null
|
||||||
|
return value.map((item) => String(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNumberArray = (value: unknown): number[] => {
|
||||||
|
if (!Array.isArray(value)) return []
|
||||||
|
return value.map((item) => Number(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDeliverySlot = (slot: typeof deliverySlotInfo.$inferSelect): AdminDeliverySlot => ({
|
||||||
|
id: slot.id,
|
||||||
|
deliveryTime: slot.deliveryTime,
|
||||||
|
freezeTime: slot.freezeTime,
|
||||||
|
isActive: slot.isActive,
|
||||||
|
isFlash: slot.isFlash,
|
||||||
|
isCapacityFull: slot.isCapacityFull,
|
||||||
|
deliverySequence: slot.deliverySequence,
|
||||||
|
groupIds: slot.groupIds,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapSlotProductSummary = (product: { id: number; name: string; images: unknown }): AdminSlotProductSummary => ({
|
||||||
|
id: product.id,
|
||||||
|
name: product.name,
|
||||||
|
images: getStringArray(product.images),
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapVendorSnippet = (snippet: typeof vendorSnippets.$inferSelect): AdminVendorSnippet => ({
|
||||||
|
id: snippet.id,
|
||||||
|
snippetCode: snippet.snippetCode,
|
||||||
|
slotId: snippet.slotId ?? null,
|
||||||
|
productIds: snippet.productIds,
|
||||||
|
isPermanent: snippet.isPermanent,
|
||||||
|
validTill: snippet.validTill ?? null,
|
||||||
|
createdAt: snippet.createdAt,
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function getActiveSlotsWithProducts(): Promise<AdminSlotWithProducts[]> {
|
||||||
|
const slots = await db.query.deliverySlotInfo
|
||||||
|
.findMany({
|
||||||
|
where: eq(deliverySlotInfo.isActive, true),
|
||||||
|
orderBy: desc(deliverySlotInfo.deliveryTime),
|
||||||
|
with: {
|
||||||
|
productSlots: {
|
||||||
|
with: {
|
||||||
|
product: {
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
images: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return slots.map((slot) => ({
|
||||||
|
...mapDeliverySlot(slot),
|
||||||
|
deliverySequence: getNumberArray(slot.deliverySequence),
|
||||||
|
products: slot.productSlots.map((ps) => mapSlotProductSummary(ps.product)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getActiveSlots(): Promise<AdminDeliverySlot[]> {
|
||||||
|
const slots = await db.query.deliverySlotInfo.findMany({
|
||||||
|
where: eq(deliverySlotInfo.isActive, true),
|
||||||
|
})
|
||||||
|
|
||||||
|
return slots.map(mapDeliverySlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSlotsAfterDate(afterDate: Date): Promise<AdminDeliverySlot[]> {
|
||||||
|
const slots = await db.query.deliverySlotInfo.findMany({
|
||||||
|
where: and(
|
||||||
|
eq(deliverySlotInfo.isActive, true),
|
||||||
|
gt(deliverySlotInfo.deliveryTime, afterDate)
|
||||||
|
),
|
||||||
|
orderBy: asc(deliverySlotInfo.deliveryTime),
|
||||||
|
})
|
||||||
|
|
||||||
|
return slots.map(mapDeliverySlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSlotByIdWithRelations(id: number): Promise<AdminSlotWithProductsAndSnippetsBase | null> {
|
||||||
|
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||||
|
where: eq(deliverySlotInfo.id, id),
|
||||||
|
with: {
|
||||||
|
productSlots: {
|
||||||
|
with: {
|
||||||
|
product: {
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
images: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vendorSnippets: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!slot) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...mapDeliverySlot(slot),
|
||||||
|
deliverySequence: getNumberArray(slot.deliverySequence),
|
||||||
|
groupIds: getNumberArray(slot.groupIds),
|
||||||
|
products: slot.productSlots.map((ps) => mapSlotProductSummary(ps.product)),
|
||||||
|
vendorSnippets: slot.vendorSnippets.map(mapVendorSnippet),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSlotWithRelations(input: {
|
||||||
|
deliveryTime: string
|
||||||
|
freezeTime: string
|
||||||
|
isActive?: boolean
|
||||||
|
productIds?: number[]
|
||||||
|
vendorSnippets?: SlotSnippetInput[]
|
||||||
|
groupIds?: number[]
|
||||||
|
}): Promise<AdminSlotCreateResult> {
|
||||||
|
const { deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets, groupIds } = input
|
||||||
|
|
||||||
|
const result = await db.transaction(async (tx) => {
|
||||||
|
const [newSlot] = await tx
|
||||||
|
.insert(deliverySlotInfo)
|
||||||
|
.values({
|
||||||
|
deliveryTime: new Date(deliveryTime),
|
||||||
|
freezeTime: new Date(freezeTime),
|
||||||
|
isActive: isActive !== undefined ? isActive : true,
|
||||||
|
groupIds: groupIds !== undefined ? groupIds : [],
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (productIds && productIds.length > 0) {
|
||||||
|
const associations = productIds.map((productId) => ({
|
||||||
|
productId,
|
||||||
|
slotId: newSlot.id,
|
||||||
|
}))
|
||||||
|
await tx.insert(productSlots).values(associations)
|
||||||
|
}
|
||||||
|
|
||||||
|
let createdSnippets: AdminVendorSnippet[] = []
|
||||||
|
if (snippets && snippets.length > 0) {
|
||||||
|
for (const snippet of snippets) {
|
||||||
|
const products = await tx.query.productInfo.findMany({
|
||||||
|
where: inArray(productInfo.id, snippet.productIds),
|
||||||
|
})
|
||||||
|
if (products.length !== snippet.productIds.length) {
|
||||||
|
throw new Error(`One or more invalid product IDs in snippet "${snippet.name}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSnippet = await tx.query.vendorSnippets.findFirst({
|
||||||
|
where: eq(vendorSnippets.snippetCode, snippet.name),
|
||||||
|
})
|
||||||
|
if (existingSnippet) {
|
||||||
|
throw new Error(`Snippet name "${snippet.name}" already exists`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [createdSnippet] = await tx.insert(vendorSnippets).values({
|
||||||
|
snippetCode: snippet.name,
|
||||||
|
slotId: newSlot.id,
|
||||||
|
productIds: snippet.productIds,
|
||||||
|
validTill: snippet.validTill ? new Date(snippet.validTill) : undefined,
|
||||||
|
}).returning()
|
||||||
|
|
||||||
|
createdSnippets.push(mapVendorSnippet(createdSnippet))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
slot: mapDeliverySlot(newSlot),
|
||||||
|
createdSnippets,
|
||||||
|
message: 'Slot created successfully',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSlotWithRelations(input: {
|
||||||
|
id: number
|
||||||
|
deliveryTime: string
|
||||||
|
freezeTime: string
|
||||||
|
isActive?: boolean
|
||||||
|
productIds?: number[]
|
||||||
|
vendorSnippets?: SlotSnippetInput[]
|
||||||
|
groupIds?: number[]
|
||||||
|
}): Promise<AdminSlotUpdateResult | null> {
|
||||||
|
const { id, deliveryTime, freezeTime, isActive, productIds, vendorSnippets: snippets, groupIds } = input
|
||||||
|
|
||||||
|
let validGroupIds = groupIds
|
||||||
|
if (groupIds && groupIds.length > 0) {
|
||||||
|
const existingGroups = await db.query.productGroupInfo.findMany({
|
||||||
|
where: inArray(productGroupInfo.id, groupIds),
|
||||||
|
columns: { id: true },
|
||||||
|
})
|
||||||
|
validGroupIds = existingGroups.map((group) => group.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db.transaction(async (tx) => {
|
||||||
|
const [updatedSlot] = await tx
|
||||||
|
.update(deliverySlotInfo)
|
||||||
|
.set({
|
||||||
|
deliveryTime: new Date(deliveryTime),
|
||||||
|
freezeTime: new Date(freezeTime),
|
||||||
|
isActive: isActive !== undefined ? isActive : true,
|
||||||
|
groupIds: validGroupIds !== undefined ? validGroupIds : [],
|
||||||
|
})
|
||||||
|
.where(eq(deliverySlotInfo.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (!updatedSlot) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (productIds !== undefined) {
|
||||||
|
await tx.delete(productSlots).where(eq(productSlots.slotId, id))
|
||||||
|
|
||||||
|
if (productIds.length > 0) {
|
||||||
|
const associations = productIds.map((productId) => ({
|
||||||
|
productId,
|
||||||
|
slotId: id,
|
||||||
|
}))
|
||||||
|
await tx.insert(productSlots).values(associations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let createdSnippets: AdminVendorSnippet[] = []
|
||||||
|
if (snippets && snippets.length > 0) {
|
||||||
|
for (const snippet of snippets) {
|
||||||
|
const products = await tx.query.productInfo.findMany({
|
||||||
|
where: inArray(productInfo.id, snippet.productIds),
|
||||||
|
})
|
||||||
|
if (products.length !== snippet.productIds.length) {
|
||||||
|
throw new Error(`One or more invalid product IDs in snippet "${snippet.name}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSnippet = await tx.query.vendorSnippets.findFirst({
|
||||||
|
where: eq(vendorSnippets.snippetCode, snippet.name),
|
||||||
|
})
|
||||||
|
if (existingSnippet) {
|
||||||
|
throw new Error(`Snippet name "${snippet.name}" already exists`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [createdSnippet] = await tx.insert(vendorSnippets).values({
|
||||||
|
snippetCode: snippet.name,
|
||||||
|
slotId: id,
|
||||||
|
productIds: snippet.productIds,
|
||||||
|
validTill: snippet.validTill ? new Date(snippet.validTill) : undefined,
|
||||||
|
}).returning()
|
||||||
|
|
||||||
|
createdSnippets.push(mapVendorSnippet(createdSnippet))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
slot: mapDeliverySlot(updatedSlot),
|
||||||
|
createdSnippets,
|
||||||
|
message: 'Slot updated successfully',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteSlotById(id: number): Promise<AdminDeliverySlot | null> {
|
||||||
|
const [deletedSlot] = await db
|
||||||
|
.update(deliverySlotInfo)
|
||||||
|
.set({ isActive: false })
|
||||||
|
.where(eq(deliverySlotInfo.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (!deletedSlot) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapDeliverySlot(deletedSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSlotDeliverySequence(slotId: number): Promise<AdminDeliverySlot | null> {
|
||||||
|
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||||
|
where: eq(deliverySlotInfo.id, slotId),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!slot) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapDeliverySlot(slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSlotDeliverySequence(slotId: number, sequence: unknown) {
|
||||||
|
const [updatedSlot] = await db
|
||||||
|
.update(deliverySlotInfo)
|
||||||
|
.set({ deliverySequence: sequence })
|
||||||
|
.where(eq(deliverySlotInfo.id, slotId))
|
||||||
|
.returning({
|
||||||
|
id: deliverySlotInfo.id,
|
||||||
|
deliverySequence: deliverySlotInfo.deliverySequence,
|
||||||
|
})
|
||||||
|
|
||||||
|
return updatedSlot || null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSlotCapacity(slotId: number, isCapacityFull: boolean): Promise<AdminUpdateSlotCapacityResult | null> {
|
||||||
|
const [updatedSlot] = await db
|
||||||
|
.update(deliverySlotInfo)
|
||||||
|
.set({ isCapacityFull })
|
||||||
|
.where(eq(deliverySlotInfo.id, slotId))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
if (!updatedSlot) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
slot: mapDeliverySlot(updatedSlot),
|
||||||
|
message: `Slot ${isCapacityFull ? 'marked as full capacity' : 'capacity reset'}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
146
packages/db_helper_postgres/src/admin-apis/staff-user.ts
Normal file
146
packages/db_helper_postgres/src/admin-apis/staff-user.ts
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { staffUsers, staffRoles, users, userDetails, orders } from '../db/schema';
|
||||||
|
import { eq, or, ilike, and, lt, desc } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export interface StaffUser {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
staffRoleId: number | null;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaffUserByName(name: string): Promise<StaffUser | null> {
|
||||||
|
const staff = await db.query.staffUsers.findFirst({
|
||||||
|
where: eq(staffUsers.name, name),
|
||||||
|
});
|
||||||
|
|
||||||
|
return staff || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllStaff(): Promise<any[]> {
|
||||||
|
const staff = await db.query.staffUsers.findMany({
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
with: {
|
||||||
|
role: {
|
||||||
|
with: {
|
||||||
|
rolePermissions: {
|
||||||
|
with: {
|
||||||
|
permission: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return staff;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllUsers(
|
||||||
|
cursor?: number,
|
||||||
|
limit: number = 20,
|
||||||
|
search?: string
|
||||||
|
): Promise<{ users: any[]; hasMore: boolean }> {
|
||||||
|
let whereCondition = undefined;
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
whereCondition = or(
|
||||||
|
ilike(users.name, `%${search}%`),
|
||||||
|
ilike(users.email, `%${search}%`),
|
||||||
|
ilike(users.mobile, `%${search}%`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
const cursorCondition = lt(users.id, cursor);
|
||||||
|
whereCondition = whereCondition ? and(whereCondition, cursorCondition) : cursorCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allUsers = await db.query.users.findMany({
|
||||||
|
where: whereCondition,
|
||||||
|
with: {
|
||||||
|
userDetails: true,
|
||||||
|
},
|
||||||
|
orderBy: desc(users.id),
|
||||||
|
limit: limit + 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasMore = allUsers.length > limit;
|
||||||
|
const usersToReturn = hasMore ? allUsers.slice(0, limit) : allUsers;
|
||||||
|
|
||||||
|
return { users: usersToReturn, hasMore };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserWithDetails(userId: number): Promise<any | null> {
|
||||||
|
const user = await db.query.users.findFirst({
|
||||||
|
where: eq(users.id, userId),
|
||||||
|
with: {
|
||||||
|
userDetails: true,
|
||||||
|
orders: {
|
||||||
|
orderBy: desc(orders.createdAt),
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return user || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUserSuspensionStatus(userId: number, isSuspended: boolean): Promise<void> {
|
||||||
|
await db
|
||||||
|
.insert(userDetails)
|
||||||
|
.values({ userId, isSuspended })
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: userDetails.userId,
|
||||||
|
set: { isSuspended },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkStaffUserExists(name: string): Promise<boolean> {
|
||||||
|
const existingUser = await db.query.staffUsers.findFirst({
|
||||||
|
where: eq(staffUsers.name, name),
|
||||||
|
});
|
||||||
|
return !!existingUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkStaffRoleExists(roleId: number): Promise<boolean> {
|
||||||
|
const role = await db.query.staffRoles.findFirst({
|
||||||
|
where: eq(staffRoles.id, roleId),
|
||||||
|
});
|
||||||
|
return !!role;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createStaffUser(
|
||||||
|
name: string,
|
||||||
|
password: string,
|
||||||
|
roleId: number
|
||||||
|
): Promise<StaffUser> {
|
||||||
|
const [newUser] = await db.insert(staffUsers).values({
|
||||||
|
name: name.trim(),
|
||||||
|
password,
|
||||||
|
staffRoleId: roleId,
|
||||||
|
}).returning();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: newUser.id,
|
||||||
|
name: newUser.name,
|
||||||
|
password: newUser.password,
|
||||||
|
staffRoleId: newUser.staffRoleId,
|
||||||
|
createdAt: newUser.createdAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllRoles(): Promise<any[]> {
|
||||||
|
const roles = await db.query.staffRoles.findMany({
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
roleName: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
145
packages/db_helper_postgres/src/admin-apis/store.ts
Normal file
145
packages/db_helper_postgres/src/admin-apis/store.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { storeInfo, productInfo } from '../db/schema';
|
||||||
|
import { eq, inArray } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export interface Store {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
imageUrl: string | null;
|
||||||
|
owner: number;
|
||||||
|
createdAt: Date;
|
||||||
|
// updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllStores(): Promise<any[]> {
|
||||||
|
const stores = await db.query.storeInfo.findMany({
|
||||||
|
with: {
|
||||||
|
owner: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return stores;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStoreById(id: number): Promise<any | null> {
|
||||||
|
const store = await db.query.storeInfo.findFirst({
|
||||||
|
where: eq(storeInfo.id, id),
|
||||||
|
with: {
|
||||||
|
owner: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return store || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateStoreInput {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
owner: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createStore(
|
||||||
|
input: CreateStoreInput,
|
||||||
|
products?: number[]
|
||||||
|
): Promise<Store> {
|
||||||
|
const [newStore] = await db
|
||||||
|
.insert(storeInfo)
|
||||||
|
.values({
|
||||||
|
name: input.name,
|
||||||
|
description: input.description,
|
||||||
|
imageUrl: input.imageUrl,
|
||||||
|
owner: input.owner,
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (products && products.length > 0) {
|
||||||
|
await db
|
||||||
|
.update(productInfo)
|
||||||
|
.set({ storeId: newStore.id })
|
||||||
|
.where(inArray(productInfo.id, products));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: newStore.id,
|
||||||
|
name: newStore.name,
|
||||||
|
description: newStore.description,
|
||||||
|
imageUrl: newStore.imageUrl,
|
||||||
|
owner: newStore.owner,
|
||||||
|
createdAt: newStore.createdAt,
|
||||||
|
// updatedAt: newStore.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateStoreInput {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
owner?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateStore(
|
||||||
|
id: number,
|
||||||
|
input: UpdateStoreInput,
|
||||||
|
products?: number[]
|
||||||
|
): Promise<Store> {
|
||||||
|
const [updatedStore] = await db
|
||||||
|
.update(storeInfo)
|
||||||
|
.set({
|
||||||
|
...input,
|
||||||
|
// updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(storeInfo.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!updatedStore) {
|
||||||
|
throw new Error("Store not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (products !== undefined) {
|
||||||
|
await db
|
||||||
|
.update(productInfo)
|
||||||
|
.set({ storeId: null })
|
||||||
|
.where(eq(productInfo.storeId, id));
|
||||||
|
|
||||||
|
if (products.length > 0) {
|
||||||
|
await db
|
||||||
|
.update(productInfo)
|
||||||
|
.set({ storeId: id })
|
||||||
|
.where(inArray(productInfo.id, products));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: updatedStore.id,
|
||||||
|
name: updatedStore.name,
|
||||||
|
description: updatedStore.description,
|
||||||
|
imageUrl: updatedStore.imageUrl,
|
||||||
|
owner: updatedStore.owner,
|
||||||
|
createdAt: updatedStore.createdAt,
|
||||||
|
// updatedAt: updatedStore.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteStore(id: number): Promise<{ message: string }> {
|
||||||
|
return await db.transaction(async (tx) => {
|
||||||
|
await tx
|
||||||
|
.update(productInfo)
|
||||||
|
.set({ storeId: null })
|
||||||
|
.where(eq(productInfo.storeId, id));
|
||||||
|
|
||||||
|
const [deletedStore] = await tx
|
||||||
|
.delete(storeInfo)
|
||||||
|
.where(eq(storeInfo.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!deletedStore) {
|
||||||
|
throw new Error("Store not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: "Store deleted successfully",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
270
packages/db_helper_postgres/src/admin-apis/user.ts
Normal file
270
packages/db_helper_postgres/src/admin-apis/user.ts
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { users, userDetails, orders, orderItems, complaints, notifCreds, unloggedUserTokens, userIncidents, orderStatus } from '../db/schema';
|
||||||
|
import { eq, sql, desc, asc, count, max, inArray } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function createUserByMobile(mobile: string): Promise<any> {
|
||||||
|
const [newUser] = await db
|
||||||
|
.insert(users)
|
||||||
|
.values({
|
||||||
|
name: null,
|
||||||
|
email: null,
|
||||||
|
mobile,
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserByMobile(mobile: string): Promise<any | null> {
|
||||||
|
const [existingUser] = await db
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.mobile, mobile))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return existingUser || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUnresolvedComplaintsCount(): Promise<number> {
|
||||||
|
const result = await db
|
||||||
|
.select({ count: count(complaints.id) })
|
||||||
|
.from(complaints)
|
||||||
|
.where(eq(complaints.isResolved, false));
|
||||||
|
|
||||||
|
return result[0]?.count || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllUsersWithFilters(
|
||||||
|
limit: number,
|
||||||
|
cursor?: number,
|
||||||
|
search?: string
|
||||||
|
): Promise<{ users: any[]; hasMore: boolean }> {
|
||||||
|
const whereConditions = [];
|
||||||
|
|
||||||
|
if (search && search.trim()) {
|
||||||
|
whereConditions.push(sql`${users.mobile} ILIKE ${`%${search.trim()}%`}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
whereConditions.push(sql`${users.id} > ${cursor}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const usersList = await db
|
||||||
|
.select({
|
||||||
|
id: users.id,
|
||||||
|
name: users.name,
|
||||||
|
mobile: users.mobile,
|
||||||
|
createdAt: users.createdAt,
|
||||||
|
})
|
||||||
|
.from(users)
|
||||||
|
.where(whereConditions.length > 0 ? sql.join(whereConditions, sql` AND `) : undefined)
|
||||||
|
.orderBy(asc(users.id))
|
||||||
|
.limit(limit + 1);
|
||||||
|
|
||||||
|
const hasMore = usersList.length > limit;
|
||||||
|
const usersToReturn = hasMore ? usersList.slice(0, limit) : usersList;
|
||||||
|
|
||||||
|
return { users: usersToReturn, hasMore };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrderCountsByUserIds(userIds: number[]): Promise<{ userId: number; totalOrders: number }[]> {
|
||||||
|
if (userIds.length === 0) return [];
|
||||||
|
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
userId: orders.userId,
|
||||||
|
totalOrders: count(orders.id),
|
||||||
|
})
|
||||||
|
.from(orders)
|
||||||
|
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
|
||||||
|
.groupBy(orders.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLastOrdersByUserIds(userIds: number[]): Promise<{ userId: number; lastOrderDate: Date | null }[]> {
|
||||||
|
if (userIds.length === 0) return [];
|
||||||
|
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
userId: orders.userId,
|
||||||
|
lastOrderDate: max(orders.createdAt),
|
||||||
|
})
|
||||||
|
.from(orders)
|
||||||
|
.where(sql`${orders.userId} IN (${sql.join(userIds, sql`, `)})`)
|
||||||
|
.groupBy(orders.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSuspensionStatusesByUserIds(userIds: number[]): Promise<{ userId: number; isSuspended: boolean }[]> {
|
||||||
|
if (userIds.length === 0) return [];
|
||||||
|
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
userId: userDetails.userId,
|
||||||
|
isSuspended: userDetails.isSuspended,
|
||||||
|
})
|
||||||
|
.from(userDetails)
|
||||||
|
.where(sql`${userDetails.userId} IN (${sql.join(userIds, sql`, `)})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserBasicInfo(userId: number): Promise<any | null> {
|
||||||
|
const user = await db
|
||||||
|
.select({
|
||||||
|
id: users.id,
|
||||||
|
name: users.name,
|
||||||
|
mobile: users.mobile,
|
||||||
|
createdAt: users.createdAt,
|
||||||
|
})
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.id, userId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return user[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserSuspensionStatus(userId: number): Promise<boolean> {
|
||||||
|
const userDetail = await db
|
||||||
|
.select({
|
||||||
|
isSuspended: userDetails.isSuspended,
|
||||||
|
})
|
||||||
|
.from(userDetails)
|
||||||
|
.where(eq(userDetails.userId, userId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return userDetail[0]?.isSuspended ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserOrders(userId: number): Promise<any[]> {
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
id: orders.id,
|
||||||
|
readableId: orders.readableId,
|
||||||
|
totalAmount: orders.totalAmount,
|
||||||
|
createdAt: orders.createdAt,
|
||||||
|
isFlashDelivery: orders.isFlashDelivery,
|
||||||
|
})
|
||||||
|
.from(orders)
|
||||||
|
.where(eq(orders.userId, userId))
|
||||||
|
.orderBy(desc(orders.createdAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrderStatusesByOrderIds(orderIds: number[]): Promise<{ orderId: number; isDelivered: boolean; isCancelled: boolean }[]> {
|
||||||
|
if (orderIds.length === 0) return [];
|
||||||
|
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
orderId: orderStatus.orderId,
|
||||||
|
isDelivered: orderStatus.isDelivered,
|
||||||
|
isCancelled: orderStatus.isCancelled,
|
||||||
|
})
|
||||||
|
.from(orderStatus)
|
||||||
|
.where(sql`${orderStatus.orderId} IN (${sql.join(orderIds, sql`, `)})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getItemCountsByOrderIds(orderIds: number[]): Promise<{ orderId: number; itemCount: number }[]> {
|
||||||
|
if (orderIds.length === 0) return [];
|
||||||
|
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
orderId: orderItems.orderId,
|
||||||
|
itemCount: count(orderItems.id),
|
||||||
|
})
|
||||||
|
.from(orderItems)
|
||||||
|
.where(sql`${orderItems.orderId} IN (${sql.join(orderIds, sql`, `)})`)
|
||||||
|
.groupBy(orderItems.orderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertUserSuspension(userId: number, isSuspended: boolean): Promise<void> {
|
||||||
|
const existingDetail = await db
|
||||||
|
.select({ id: userDetails.id })
|
||||||
|
.from(userDetails)
|
||||||
|
.where(eq(userDetails.userId, userId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (existingDetail.length > 0) {
|
||||||
|
await db
|
||||||
|
.update(userDetails)
|
||||||
|
.set({ isSuspended })
|
||||||
|
.where(eq(userDetails.userId, userId));
|
||||||
|
} else {
|
||||||
|
await db
|
||||||
|
.insert(userDetails)
|
||||||
|
.values({
|
||||||
|
userId,
|
||||||
|
isSuspended,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchUsers(search?: string): Promise<any[]> {
|
||||||
|
if (search && search.trim()) {
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
id: users.id,
|
||||||
|
name: users.name,
|
||||||
|
mobile: users.mobile,
|
||||||
|
})
|
||||||
|
.from(users)
|
||||||
|
.where(sql`${users.mobile} ILIKE ${`%${search.trim()}%`} OR ${users.name} ILIKE ${`%${search.trim()}%`}`);
|
||||||
|
} else {
|
||||||
|
return await db
|
||||||
|
.select({
|
||||||
|
id: users.id,
|
||||||
|
name: users.name,
|
||||||
|
mobile: users.mobile,
|
||||||
|
})
|
||||||
|
.from(users);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllNotifCreds(): Promise<{ userId: number, token: string }[]> {
|
||||||
|
return await db
|
||||||
|
.select({ userId: notifCreds.userId, token: notifCreds.token })
|
||||||
|
.from(notifCreds);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllUnloggedTokens(): Promise<{ token: string }[]> {
|
||||||
|
return await db
|
||||||
|
.select({ token: unloggedUserTokens.token })
|
||||||
|
.from(unloggedUserTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNotifTokensByUserIds(userIds: number[]): Promise<{ token: string }[]> {
|
||||||
|
return await db
|
||||||
|
.select({ token: notifCreds.token })
|
||||||
|
.from(notifCreds)
|
||||||
|
.where(inArray(notifCreds.userId, userIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserIncidentsWithRelations(userId: number): Promise<any[]> {
|
||||||
|
return await db.query.userIncidents.findMany({
|
||||||
|
where: eq(userIncidents.userId, userId),
|
||||||
|
with: {
|
||||||
|
order: {
|
||||||
|
with: {
|
||||||
|
orderStatus: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addedBy: true,
|
||||||
|
},
|
||||||
|
orderBy: desc(userIncidents.dateAdded),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createUserIncident(
|
||||||
|
userId: number,
|
||||||
|
orderId: number | undefined,
|
||||||
|
adminComment: string | undefined,
|
||||||
|
adminUserId: number,
|
||||||
|
negativityScore: number | undefined
|
||||||
|
): Promise<any> {
|
||||||
|
const [incident] = await db.insert(userIncidents)
|
||||||
|
.values({
|
||||||
|
userId,
|
||||||
|
orderId,
|
||||||
|
adminComment,
|
||||||
|
addedBy: adminUserId,
|
||||||
|
negativityScore,
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return incident;
|
||||||
|
}
|
||||||
250
packages/db_helper_postgres/src/admin-apis/vendor-snippets.ts
Normal file
250
packages/db_helper_postgres/src/admin-apis/vendor-snippets.ts
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
import { db } from '../db/db_index'
|
||||||
|
import { vendorSnippets, deliverySlotInfo, productInfo, orders, orderItems, orderStatus } from '../db/schema'
|
||||||
|
import { desc, eq, inArray } from 'drizzle-orm'
|
||||||
|
import type { InferSelectModel } from 'drizzle-orm'
|
||||||
|
import type {
|
||||||
|
AdminDeliverySlot,
|
||||||
|
AdminVendorSnippet,
|
||||||
|
AdminVendorSnippetWithSlot,
|
||||||
|
AdminVendorSnippetProduct,
|
||||||
|
AdminVendorUpdatePackagingResult,
|
||||||
|
} from '@packages/shared'
|
||||||
|
|
||||||
|
type VendorSnippetRow = InferSelectModel<typeof vendorSnippets>
|
||||||
|
type DeliverySlotRow = InferSelectModel<typeof deliverySlotInfo>
|
||||||
|
type ProductRow = InferSelectModel<typeof productInfo>
|
||||||
|
|
||||||
|
const mapVendorSnippet = (snippet: VendorSnippetRow): AdminVendorSnippet => ({
|
||||||
|
id: snippet.id,
|
||||||
|
snippetCode: snippet.snippetCode,
|
||||||
|
slotId: snippet.slotId ?? null,
|
||||||
|
productIds: snippet.productIds,
|
||||||
|
isPermanent: snippet.isPermanent,
|
||||||
|
validTill: snippet.validTill ?? null,
|
||||||
|
createdAt: snippet.createdAt,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDeliverySlot = (slot: DeliverySlotRow): AdminDeliverySlot => ({
|
||||||
|
id: slot.id,
|
||||||
|
deliveryTime: slot.deliveryTime,
|
||||||
|
freezeTime: slot.freezeTime,
|
||||||
|
isActive: slot.isActive,
|
||||||
|
isFlash: slot.isFlash,
|
||||||
|
isCapacityFull: slot.isCapacityFull,
|
||||||
|
deliverySequence: slot.deliverySequence,
|
||||||
|
groupIds: slot.groupIds,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapProductSummary = (product:{id:number, name: string}): AdminVendorSnippetProduct => ({
|
||||||
|
id: product.id,
|
||||||
|
name: product.name,
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function checkVendorSnippetExists(snippetCode: string): Promise<boolean> {
|
||||||
|
const existingSnippet = await db.query.vendorSnippets.findFirst({
|
||||||
|
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||||
|
})
|
||||||
|
return !!existingSnippet
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVendorSnippetById(id: number): Promise<AdminVendorSnippetWithSlot | null> {
|
||||||
|
const snippet = await db.query.vendorSnippets.findFirst({
|
||||||
|
where: eq(vendorSnippets.id, id),
|
||||||
|
with: {
|
||||||
|
slot: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!snippet) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...mapVendorSnippet(snippet),
|
||||||
|
slot: snippet.slot ? mapDeliverySlot(snippet.slot) : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVendorSnippetByCode(snippetCode: string): Promise<AdminVendorSnippet | null> {
|
||||||
|
const snippet = await db.query.vendorSnippets.findFirst({
|
||||||
|
where: eq(vendorSnippets.snippetCode, snippetCode),
|
||||||
|
})
|
||||||
|
|
||||||
|
return snippet ? mapVendorSnippet(snippet) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllVendorSnippets(): Promise<AdminVendorSnippetWithSlot[]> {
|
||||||
|
const snippets = await db.query.vendorSnippets.findMany({
|
||||||
|
with: {
|
||||||
|
slot: true,
|
||||||
|
},
|
||||||
|
orderBy: (vendorSnippets, { desc }) => [desc(vendorSnippets.createdAt)],
|
||||||
|
})
|
||||||
|
|
||||||
|
return snippets.map((snippet) => ({
|
||||||
|
...mapVendorSnippet(snippet),
|
||||||
|
slot: snippet.slot ? mapDeliverySlot(snippet.slot) : null,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createVendorSnippet(input: {
|
||||||
|
snippetCode: string
|
||||||
|
slotId?: number
|
||||||
|
productIds: number[]
|
||||||
|
isPermanent: boolean
|
||||||
|
validTill?: Date
|
||||||
|
}): Promise<AdminVendorSnippet> {
|
||||||
|
const [result] = await db.insert(vendorSnippets).values({
|
||||||
|
snippetCode: input.snippetCode,
|
||||||
|
slotId: input.slotId,
|
||||||
|
productIds: input.productIds,
|
||||||
|
isPermanent: input.isPermanent,
|
||||||
|
validTill: input.validTill,
|
||||||
|
}).returning()
|
||||||
|
|
||||||
|
return mapVendorSnippet(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateVendorSnippet(id: number, updates: {
|
||||||
|
snippetCode?: string
|
||||||
|
slotId?: number | null
|
||||||
|
productIds?: number[]
|
||||||
|
isPermanent?: boolean
|
||||||
|
validTill?: Date | null
|
||||||
|
}): Promise<AdminVendorSnippet | null> {
|
||||||
|
const [result] = await db.update(vendorSnippets)
|
||||||
|
.set(updates)
|
||||||
|
.where(eq(vendorSnippets.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
return result ? mapVendorSnippet(result) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteVendorSnippet(id: number): Promise<AdminVendorSnippet | null> {
|
||||||
|
const [result] = await db.delete(vendorSnippets)
|
||||||
|
.where(eq(vendorSnippets.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
return result ? mapVendorSnippet(result) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProductsByIds(productIds: number[]): Promise<AdminVendorSnippetProduct[]> {
|
||||||
|
const products = await db.query.productInfo.findMany({
|
||||||
|
where: inArray(productInfo.id, productIds),
|
||||||
|
columns: { id: true, name: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
const prods = products.map(mapProductSummary)
|
||||||
|
return prods;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVendorSlotById(slotId: number): Promise<AdminDeliverySlot | null> {
|
||||||
|
const slot = await db.query.deliverySlotInfo.findFirst({
|
||||||
|
where: eq(deliverySlotInfo.id, slotId),
|
||||||
|
})
|
||||||
|
|
||||||
|
return slot ? mapDeliverySlot(slot) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVendorOrdersBySlotId(slotId: number) {
|
||||||
|
return await db.query.orders.findMany({
|
||||||
|
where: eq(orders.slotId, slotId),
|
||||||
|
with: {
|
||||||
|
orderItems: {
|
||||||
|
with: {
|
||||||
|
product: {
|
||||||
|
with: {
|
||||||
|
unit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderStatus: true,
|
||||||
|
user: true,
|
||||||
|
slot: true,
|
||||||
|
},
|
||||||
|
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVendorOrders() {
|
||||||
|
return await db.query.orders.findMany({
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
orderItems: {
|
||||||
|
with: {
|
||||||
|
product: {
|
||||||
|
with: {
|
||||||
|
unit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: (orders, { desc }) => [desc(orders.createdAt)],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrderItemsByOrderIds(orderIds: number[]) {
|
||||||
|
return await db.query.orderItems.findMany({
|
||||||
|
where: inArray(orderItems.orderId, orderIds),
|
||||||
|
with: {
|
||||||
|
product: {
|
||||||
|
with: {
|
||||||
|
unit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrderStatusByOrderIds(orderIds: number[]) {
|
||||||
|
return await db.query.orderStatus.findMany({
|
||||||
|
where: inArray(orderStatus.orderId, orderIds),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateVendorOrderItemPackaging(
|
||||||
|
orderItemId: number,
|
||||||
|
isPackaged: boolean
|
||||||
|
): Promise<AdminVendorUpdatePackagingResult> {
|
||||||
|
const orderItem = await db.query.orderItems.findFirst({
|
||||||
|
where: eq(orderItems.id, orderItemId),
|
||||||
|
with: {
|
||||||
|
order: {
|
||||||
|
with: {
|
||||||
|
slot: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!orderItem) {
|
||||||
|
return { success: false, message: 'Order item not found' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!orderItem.order.slotId) {
|
||||||
|
return { success: false, message: 'Order item not associated with a vendor slot' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const snippetExists = await db.query.vendorSnippets.findFirst({
|
||||||
|
where: eq(vendorSnippets.slotId, orderItem.order.slotId),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!snippetExists) {
|
||||||
|
return { success: false, message: "No vendor snippet found for this order's slot" }
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updatedItem] = await db.update(orderItems)
|
||||||
|
.set({
|
||||||
|
is_packaged: isPackaged,
|
||||||
|
})
|
||||||
|
.where(eq(orderItems.id, orderItemId))
|
||||||
|
.returning({ id: orderItems.id })
|
||||||
|
|
||||||
|
if (!updatedItem) {
|
||||||
|
return { success: false, message: 'Failed to update packaging status' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, orderItemId, is_packaged: isPackaged }
|
||||||
|
}
|
||||||
19
packages/db_helper_postgres/src/common-apis/utils.ts
Normal file
19
packages/db_helper_postgres/src/common-apis/utils.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Common utility functions that can be used by both admin and user APIs
|
||||||
|
|
||||||
|
export function formatDate(date: Date): string {
|
||||||
|
return date.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateCode(prefix: string, length: number = 6): string {
|
||||||
|
const timestamp = Date.now().toString().slice(-length);
|
||||||
|
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||||
|
return `${prefix}${timestamp}${random}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateDiscount(amount: number, percent: number, maxDiscount?: number): number {
|
||||||
|
let discount = (amount * percent) / 100;
|
||||||
|
if (maxDiscount && discount > maxDiscount) {
|
||||||
|
discount = maxDiscount;
|
||||||
|
}
|
||||||
|
return discount;
|
||||||
|
}
|
||||||
23
packages/db_helper_postgres/src/user-apis/address.ts
Normal file
23
packages/db_helper_postgres/src/user-apis/address.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { addresses, addressAreas, addressZones } from '../db/schema';
|
||||||
|
import { eq, desc } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getZones(): Promise<any[]> {
|
||||||
|
const zones = await db.select().from(addressZones).orderBy(desc(addressZones.addedAt));
|
||||||
|
return zones;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAreas(): Promise<any[]> {
|
||||||
|
const areas = await db.select().from(addressAreas).orderBy(desc(addressAreas.createdAt));
|
||||||
|
return areas;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createZone(zoneName: string): Promise<any> {
|
||||||
|
const [zone] = await db.insert(addressZones).values({ zoneName }).returning();
|
||||||
|
return zone;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createArea(placeName: string, zoneId: number | null): Promise<any> {
|
||||||
|
const [area] = await db.insert(addressAreas).values({ placeName, zoneId }).returning();
|
||||||
|
return area;
|
||||||
|
}
|
||||||
14
packages/db_helper_postgres/src/user-apis/auth.ts
Normal file
14
packages/db_helper_postgres/src/user-apis/auth.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { users } from '../db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getUserByMobile(mobile: string): Promise<any | null> {
|
||||||
|
return await db.query.users.findFirst({
|
||||||
|
where: eq(users.mobile, mobile),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createUser(userData: any): Promise<any> {
|
||||||
|
const [user] = await db.insert(users).values(userData).returning();
|
||||||
|
return user;
|
||||||
|
}
|
||||||
11
packages/db_helper_postgres/src/user-apis/banners.ts
Normal file
11
packages/db_helper_postgres/src/user-apis/banners.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { homeBanners } from '../db/schema';
|
||||||
|
import { eq, and, desc, sql } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getActiveBanners(): Promise<any[]> {
|
||||||
|
const banners = await db.query.homeBanners.findMany({
|
||||||
|
where: eq(homeBanners.isActive, true),
|
||||||
|
orderBy: desc(homeBanners.createdAt),
|
||||||
|
});
|
||||||
|
return banners;
|
||||||
|
}
|
||||||
41
packages/db_helper_postgres/src/user-apis/cart.ts
Normal file
41
packages/db_helper_postgres/src/user-apis/cart.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { cartItems, productInfo } from '../db/schema';
|
||||||
|
import { eq, and } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getCartItems(userId: number): Promise<any[]> {
|
||||||
|
return await db.query.cartItems.findMany({
|
||||||
|
where: eq(cartItems.userId, userId),
|
||||||
|
with: {
|
||||||
|
product: {
|
||||||
|
with: {
|
||||||
|
unit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addToCart(userId: number, productId: number, quantity: number): Promise<any> {
|
||||||
|
const [item] = await db.insert(cartItems).values({
|
||||||
|
userId,
|
||||||
|
productId,
|
||||||
|
quantity,
|
||||||
|
}).returning();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateCartItem(itemId: number, quantity: number): Promise<any> {
|
||||||
|
const [item] = await db.update(cartItems)
|
||||||
|
.set({ quantity })
|
||||||
|
.where(eq(cartItems.id, itemId))
|
||||||
|
.returning();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeFromCart(itemId: number): Promise<void> {
|
||||||
|
await db.delete(cartItems).where(eq(cartItems.id, itemId));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function clearCart(userId: number): Promise<void> {
|
||||||
|
await db.delete(cartItems).where(eq(cartItems.userId, userId));
|
||||||
|
}
|
||||||
21
packages/db_helper_postgres/src/user-apis/complaint.ts
Normal file
21
packages/db_helper_postgres/src/user-apis/complaint.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { complaints } from '../db/schema';
|
||||||
|
import { eq, desc } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getUserComplaints(userId: number): Promise<any[]> {
|
||||||
|
return await db.query.complaints.findMany({
|
||||||
|
where: eq(complaints.userId, userId),
|
||||||
|
orderBy: desc(complaints.createdAt),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createComplaint(userId: number, orderId: number | null, complaintBody: string, images?: string[]): Promise<any> {
|
||||||
|
const [complaint] = await db.insert(complaints).values({
|
||||||
|
userId,
|
||||||
|
orderId,
|
||||||
|
complaintBody,
|
||||||
|
images,
|
||||||
|
isResolved: false,
|
||||||
|
}).returning();
|
||||||
|
return complaint;
|
||||||
|
}
|
||||||
43
packages/db_helper_postgres/src/user-apis/coupon.ts
Normal file
43
packages/db_helper_postgres/src/user-apis/coupon.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { coupons, couponUsage } from '../db/schema';
|
||||||
|
import { eq, and } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function validateUserCoupon(code: string, userId: number, orderAmount: number): Promise<any> {
|
||||||
|
const coupon = await db.query.coupons.findFirst({
|
||||||
|
where: eq(coupons.couponCode, code.toUpperCase()),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!coupon || coupon.isInvalidated) {
|
||||||
|
return { valid: false, message: 'Invalid coupon' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coupon.validTill && new Date(coupon.validTill) < new Date()) {
|
||||||
|
return { valid: false, message: 'Coupon expired' };
|
||||||
|
}
|
||||||
|
|
||||||
|
let discountAmount = 0;
|
||||||
|
if (coupon.discountPercent) {
|
||||||
|
discountAmount = (orderAmount * parseFloat(coupon.discountPercent)) / 100;
|
||||||
|
} else if (coupon.flatDiscount) {
|
||||||
|
discountAmount = parseFloat(coupon.flatDiscount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coupon.maxValue) {
|
||||||
|
const maxDiscount = parseFloat(coupon.maxValue);
|
||||||
|
if (discountAmount > maxDiscount) {
|
||||||
|
discountAmount = maxDiscount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
discountAmount,
|
||||||
|
couponId: coupon.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserCoupons(userId: number): Promise<any[]> {
|
||||||
|
return await db.query.coupons.findMany({
|
||||||
|
where: eq(coupons.userId, userId),
|
||||||
|
});
|
||||||
|
}
|
||||||
59
packages/db_helper_postgres/src/user-apis/order.ts
Normal file
59
packages/db_helper_postgres/src/user-apis/order.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { orders, orderItems, orderStatus } from '../db/schema';
|
||||||
|
import { eq, desc } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getUserOrders(userId: number): Promise<any[]> {
|
||||||
|
return await db.query.orders.findMany({
|
||||||
|
where: eq(orders.userId, userId),
|
||||||
|
with: {
|
||||||
|
orderItems: {
|
||||||
|
with: {
|
||||||
|
product: {
|
||||||
|
with: {
|
||||||
|
unit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderStatus: true,
|
||||||
|
slot: true,
|
||||||
|
},
|
||||||
|
orderBy: desc(orders.createdAt),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrderById(orderId: number, userId: number): Promise<any | null> {
|
||||||
|
return await db.query.orders.findFirst({
|
||||||
|
where: eq(orders.id, orderId),
|
||||||
|
with: {
|
||||||
|
orderItems: {
|
||||||
|
with: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderStatus: true,
|
||||||
|
address: true,
|
||||||
|
slot: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createOrder(orderData: any, orderItemsData: any[]): Promise<any> {
|
||||||
|
return await db.transaction(async (tx) => {
|
||||||
|
const [order] = await tx.insert(orders).values(orderData).returning();
|
||||||
|
|
||||||
|
for (const item of orderItemsData) {
|
||||||
|
await tx.insert(orderItems).values({
|
||||||
|
...item,
|
||||||
|
orderId: order.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.insert(orderStatus).values({
|
||||||
|
orderId: order.id,
|
||||||
|
paymentStatus: 'pending',
|
||||||
|
});
|
||||||
|
|
||||||
|
return order;
|
||||||
|
});
|
||||||
|
}
|
||||||
22
packages/db_helper_postgres/src/user-apis/payments.ts
Normal file
22
packages/db_helper_postgres/src/user-apis/payments.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { payments, orders, orderStatus } from '../db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function createPayment(paymentData: any): Promise<any> {
|
||||||
|
const [payment] = await db.insert(payments).values(paymentData).returning();
|
||||||
|
return payment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updatePaymentStatus(paymentId: number, status: string): Promise<any> {
|
||||||
|
const [payment] = await db.update(payments)
|
||||||
|
.set({ paymentStatus: status })
|
||||||
|
.where(eq(payments.id, paymentId))
|
||||||
|
.returning();
|
||||||
|
return payment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPaymentByOrderId(orderId: number): Promise<any | null> {
|
||||||
|
return await db.query.payments.findFirst({
|
||||||
|
where: eq(payments.orderId, orderId),
|
||||||
|
});
|
||||||
|
}
|
||||||
41
packages/db_helper_postgres/src/user-apis/product.ts
Normal file
41
packages/db_helper_postgres/src/user-apis/product.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { productInfo, productReviews } from '../db/schema';
|
||||||
|
import { eq, desc } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getAllProducts(): Promise<any[]> {
|
||||||
|
return await db.query.productInfo.findMany({
|
||||||
|
with: {
|
||||||
|
unit: true,
|
||||||
|
store: true,
|
||||||
|
specialDeals: true,
|
||||||
|
},
|
||||||
|
orderBy: productInfo.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProductById(id: number): Promise<any | null> {
|
||||||
|
return await db.query.productInfo.findFirst({
|
||||||
|
where: eq(productInfo.id, id),
|
||||||
|
with: {
|
||||||
|
unit: true,
|
||||||
|
store: true,
|
||||||
|
specialDeals: true,
|
||||||
|
productReviews: {
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
orderBy: desc(productReviews.createdAt),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createProductReview(userId: number, productId: number, rating: number, comment?: string): Promise<any> {
|
||||||
|
const [review] = await db.insert(productReviews).values({
|
||||||
|
userId,
|
||||||
|
productId,
|
||||||
|
rating,
|
||||||
|
comment,
|
||||||
|
}).returning();
|
||||||
|
return review;
|
||||||
|
}
|
||||||
17
packages/db_helper_postgres/src/user-apis/slots.ts
Normal file
17
packages/db_helper_postgres/src/user-apis/slots.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { deliverySlotInfo } from '../db/schema';
|
||||||
|
import { eq, gte, and } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getAvailableSlots(): Promise<any[]> {
|
||||||
|
const now = new Date();
|
||||||
|
return await db.query.deliverySlotInfo.findMany({
|
||||||
|
where: gte(deliverySlotInfo.freezeTime, now),
|
||||||
|
orderBy: deliverySlotInfo.deliveryTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSlotById(id: number): Promise<any | null> {
|
||||||
|
return await db.query.deliverySlotInfo.findFirst({
|
||||||
|
where: eq(deliverySlotInfo.id, id),
|
||||||
|
});
|
||||||
|
}
|
||||||
20
packages/db_helper_postgres/src/user-apis/stores.ts
Normal file
20
packages/db_helper_postgres/src/user-apis/stores.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { storeInfo } from '../db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getAllStores(): Promise<any[]> {
|
||||||
|
return await db.query.storeInfo.findMany({
|
||||||
|
with: {
|
||||||
|
owner: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStoreById(id: number): Promise<any | null> {
|
||||||
|
return await db.query.storeInfo.findFirst({
|
||||||
|
where: eq(storeInfo.id, id),
|
||||||
|
with: {
|
||||||
|
owner: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
28
packages/db_helper_postgres/src/user-apis/tags.ts
Normal file
28
packages/db_helper_postgres/src/user-apis/tags.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { productTags } from '../db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getAllTags(): Promise<any[]> {
|
||||||
|
return await db.query.productTags.findMany({
|
||||||
|
with: {
|
||||||
|
products: {
|
||||||
|
with: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTagById(id: number): Promise<any | null> {
|
||||||
|
return await db.query.productTags.findFirst({
|
||||||
|
where: eq(productTags.id, id),
|
||||||
|
with: {
|
||||||
|
products: {
|
||||||
|
with: {
|
||||||
|
product: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
44
packages/db_helper_postgres/src/user-apis/user.ts
Normal file
44
packages/db_helper_postgres/src/user-apis/user.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { db } from '../db/db_index';
|
||||||
|
import { users, userDetails, addresses } from '../db/schema';
|
||||||
|
import { eq, desc } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function getCurrentUser(userId: number): Promise<any | null> {
|
||||||
|
return await db.query.users.findFirst({
|
||||||
|
where: eq(users.id, userId),
|
||||||
|
with: {
|
||||||
|
userDetails: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUser(userId: number, updates: any): Promise<any> {
|
||||||
|
const [user] = await db.update(users)
|
||||||
|
.set(updates)
|
||||||
|
.where(eq(users.id, userId))
|
||||||
|
.returning();
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserAddresses(userId: number): Promise<any[]> {
|
||||||
|
return await db.query.addresses.findMany({
|
||||||
|
where: eq(addresses.userId, userId),
|
||||||
|
orderBy: desc(addresses.isDefault),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createAddress(addressData: any): Promise<any> {
|
||||||
|
const [address] = await db.insert(addresses).values(addressData).returning();
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateAddress(addressId: number, updates: any): Promise<any> {
|
||||||
|
const [address] = await db.update(addresses)
|
||||||
|
.set(updates)
|
||||||
|
.where(eq(addresses.id, addressId))
|
||||||
|
.returning();
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteAddress(addressId: number): Promise<void> {
|
||||||
|
await db.delete(addresses).where(eq(addresses.id, addressId));
|
||||||
|
}
|
||||||
713
packages/shared/types/admin.ts
Normal file
713
packages/shared/types/admin.ts
Normal file
|
|
@ -0,0 +1,713 @@
|
||||||
|
// Admin-related types
|
||||||
|
|
||||||
|
export interface Banner {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
imageUrl: string;
|
||||||
|
description: string | null;
|
||||||
|
productIds: number[] | null;
|
||||||
|
redirectUrl: string | null;
|
||||||
|
serialNum: number | null;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
lastUpdated: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Complaint {
|
||||||
|
id: number;
|
||||||
|
complaintBody: string;
|
||||||
|
userId: number;
|
||||||
|
orderId: number | null;
|
||||||
|
isResolved: boolean;
|
||||||
|
response: string | null;
|
||||||
|
createdAt: Date;
|
||||||
|
images: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComplaintWithUser extends Complaint {
|
||||||
|
userName: string | null;
|
||||||
|
userMobile: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Constant {
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConstantUpdateResult {
|
||||||
|
success: boolean;
|
||||||
|
updatedCount: number;
|
||||||
|
keys: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Coupon {
|
||||||
|
id: number;
|
||||||
|
couponCode: string;
|
||||||
|
isUserBased: boolean;
|
||||||
|
discountPercent: string | null;
|
||||||
|
flatDiscount: string | null;
|
||||||
|
minOrder: string | null;
|
||||||
|
productIds: number[] | null;
|
||||||
|
maxValue: string | null;
|
||||||
|
isApplyForAll: boolean;
|
||||||
|
validTill: Date | null;
|
||||||
|
maxLimitForUser: number | null;
|
||||||
|
exclusiveApply: boolean;
|
||||||
|
isInvalidated: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
createdBy: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CouponValidationResult {
|
||||||
|
valid: boolean;
|
||||||
|
message?: string;
|
||||||
|
discountAmount?: number;
|
||||||
|
coupon?: Partial<Coupon>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserMiniInfo {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
mobile: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Store {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
imageUrl: string | null;
|
||||||
|
owner: number;
|
||||||
|
createdAt: Date;
|
||||||
|
// updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StaffUser {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
staffRoleId: number;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StaffRole {
|
||||||
|
id: number;
|
||||||
|
roleName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderRow {
|
||||||
|
id: number;
|
||||||
|
userId: number;
|
||||||
|
addressId: number;
|
||||||
|
slotId: number | null;
|
||||||
|
isCod: boolean;
|
||||||
|
isOnlinePayment: boolean;
|
||||||
|
paymentInfoId: number | null;
|
||||||
|
totalAmount: string;
|
||||||
|
deliveryCharge: string;
|
||||||
|
readableId: number;
|
||||||
|
adminNotes: string | null;
|
||||||
|
userNotes: string | null;
|
||||||
|
orderGroupId: string | null;
|
||||||
|
orderGroupProportion: string | null;
|
||||||
|
isFlashDelivery: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaymentStatus = 'pending' | 'success' | 'cod' | 'failed'
|
||||||
|
|
||||||
|
export type RefundStatus = 'success' | 'pending' | 'failed' | 'none' | 'na' | 'processed'
|
||||||
|
|
||||||
|
export interface AdminOrderStatusRecord {
|
||||||
|
id: number;
|
||||||
|
orderTime: Date;
|
||||||
|
userId: number;
|
||||||
|
orderId: number;
|
||||||
|
isPackaged: boolean;
|
||||||
|
isDelivered: boolean;
|
||||||
|
isCancelled: boolean;
|
||||||
|
cancelReason: string | null;
|
||||||
|
isCancelledByAdmin: boolean | null;
|
||||||
|
paymentStatus: PaymentStatus;
|
||||||
|
cancellationUserNotes: string | null;
|
||||||
|
cancellationAdminNotes: string | null;
|
||||||
|
cancellationReviewed: boolean;
|
||||||
|
cancellationReviewedAt: Date | null;
|
||||||
|
refundCouponId: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminRefundRecord {
|
||||||
|
id: number;
|
||||||
|
orderId: number;
|
||||||
|
refundAmount: string | null;
|
||||||
|
refundStatus: RefundStatus | null;
|
||||||
|
merchantRefundId: string | null;
|
||||||
|
refundProcessedAt: Date | null;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderDetailsAddress {
|
||||||
|
name: string;
|
||||||
|
line1: string;
|
||||||
|
line2: string | null;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
pincode: string;
|
||||||
|
phone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderDetailsSlotInfo {
|
||||||
|
time: string;
|
||||||
|
sequence: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderDetailsItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
quantity: string;
|
||||||
|
productSize: number;
|
||||||
|
price: string;
|
||||||
|
unit?: string;
|
||||||
|
amount: number;
|
||||||
|
isPackaged: boolean;
|
||||||
|
isPackageVerified: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderDetailsPayment {
|
||||||
|
status: string;
|
||||||
|
gateway: string;
|
||||||
|
merchantOrderId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderDetailsCouponData {
|
||||||
|
couponCode: string;
|
||||||
|
couponDescription: string;
|
||||||
|
discountAmount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderDetails {
|
||||||
|
id: number;
|
||||||
|
readableId: number;
|
||||||
|
userId: number;
|
||||||
|
customerName: string;
|
||||||
|
customerEmail: string | null;
|
||||||
|
customerMobile: string | null;
|
||||||
|
address: AdminOrderDetailsAddress;
|
||||||
|
slotInfo: AdminOrderDetailsSlotInfo | null;
|
||||||
|
isCod: boolean;
|
||||||
|
isOnlinePayment: boolean;
|
||||||
|
totalAmount: number;
|
||||||
|
deliveryCharge: number;
|
||||||
|
adminNotes: string | null;
|
||||||
|
userNotes: string | null;
|
||||||
|
createdAt: Date;
|
||||||
|
status: 'pending' | 'delivered' | 'cancelled';
|
||||||
|
isPackaged: boolean;
|
||||||
|
isDelivered: boolean;
|
||||||
|
items: AdminOrderDetailsItem[];
|
||||||
|
payment: AdminOrderDetailsPayment | null;
|
||||||
|
paymentInfo: AdminOrderDetailsPayment | null;
|
||||||
|
cancelReason: string | null;
|
||||||
|
cancellationReviewed: boolean;
|
||||||
|
isRefundDone: boolean;
|
||||||
|
refundStatus: RefundStatus | null;
|
||||||
|
refundAmount: number | null;
|
||||||
|
couponData: AdminOrderDetailsCouponData | null;
|
||||||
|
couponCode: string | null;
|
||||||
|
couponDescription: string | null;
|
||||||
|
discountAmount: number | null;
|
||||||
|
orderStatus: AdminOrderStatusRecord | null;
|
||||||
|
refundRecord: AdminRefundRecord | null;
|
||||||
|
isFlashDelivery: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderUpdateResult {
|
||||||
|
success: boolean;
|
||||||
|
userId: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderItemPackagingResult {
|
||||||
|
success: boolean;
|
||||||
|
updated: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderMessageResult {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderBasicResult {
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotOrderItem {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
quantity: number;
|
||||||
|
price: number;
|
||||||
|
amount: number;
|
||||||
|
unit: string;
|
||||||
|
isPackaged: boolean;
|
||||||
|
isPackageVerified: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotOrder {
|
||||||
|
id: number;
|
||||||
|
readableId: number;
|
||||||
|
customerName: string;
|
||||||
|
address: string;
|
||||||
|
addressId: number;
|
||||||
|
latitude: number | null;
|
||||||
|
longitude: number | null;
|
||||||
|
totalAmount: number;
|
||||||
|
items: AdminSlotOrderItem[];
|
||||||
|
deliveryTime: string | null;
|
||||||
|
status: 'pending' | 'delivered' | 'cancelled';
|
||||||
|
isPackaged: boolean;
|
||||||
|
isDelivered: boolean;
|
||||||
|
isCod: boolean;
|
||||||
|
paymentMode: 'COD' | 'Online';
|
||||||
|
paymentStatus: PaymentStatus;
|
||||||
|
slotId: number | null;
|
||||||
|
adminNotes: string | null;
|
||||||
|
userNotes: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminGetSlotOrdersResult {
|
||||||
|
success: boolean;
|
||||||
|
data: AdminSlotOrder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderListItemProduct {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
quantity: number;
|
||||||
|
price: number;
|
||||||
|
amount: number;
|
||||||
|
unit: string;
|
||||||
|
productSize: number;
|
||||||
|
isPackaged: boolean;
|
||||||
|
isPackageVerified: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderListItem {
|
||||||
|
id: number;
|
||||||
|
orderId: string;
|
||||||
|
readableId: number;
|
||||||
|
customerName: string;
|
||||||
|
customerMobile: string | null;
|
||||||
|
address: string;
|
||||||
|
addressId: number;
|
||||||
|
latitude: number | null;
|
||||||
|
longitude: number | null;
|
||||||
|
totalAmount: number;
|
||||||
|
deliveryCharge: number;
|
||||||
|
items: AdminOrderListItemProduct[];
|
||||||
|
createdAt: Date;
|
||||||
|
deliveryTime: string | null;
|
||||||
|
status: 'pending' | 'delivered' | 'cancelled';
|
||||||
|
isPackaged: boolean;
|
||||||
|
isDelivered: boolean;
|
||||||
|
isCod: boolean;
|
||||||
|
isFlashDelivery: boolean;
|
||||||
|
userNotes: string | null;
|
||||||
|
adminNotes: string | null;
|
||||||
|
userNegativityScore: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminOrderListItemWithUserId extends AdminOrderListItem {
|
||||||
|
userId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminGetAllOrdersResult {
|
||||||
|
orders: AdminOrderListItem[];
|
||||||
|
nextCursor?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminGetAllOrdersResultWithUserId {
|
||||||
|
orders: AdminOrderListItemWithUserId[];
|
||||||
|
nextCursor?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminRebalanceSlotsResult {
|
||||||
|
success: boolean;
|
||||||
|
updatedOrders: number[];
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AdminCancelOrderError =
|
||||||
|
| 'order_not_found'
|
||||||
|
| 'status_not_found'
|
||||||
|
| 'already_cancelled'
|
||||||
|
| 'already_delivered'
|
||||||
|
|
||||||
|
export interface AdminCancelOrderResult {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
orderId?: number;
|
||||||
|
userId?: number;
|
||||||
|
error?: AdminCancelOrderError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminUnit {
|
||||||
|
id: number;
|
||||||
|
shortNotation: string;
|
||||||
|
fullName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProduct {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
shortDescription: string | null;
|
||||||
|
longDescription: string | null;
|
||||||
|
unitId: number;
|
||||||
|
price: string;
|
||||||
|
marketPrice: string | null;
|
||||||
|
images: string[] | null;
|
||||||
|
isOutOfStock: boolean;
|
||||||
|
isSuspended: boolean;
|
||||||
|
isFlashAvailable: boolean;
|
||||||
|
flashPrice: string | null;
|
||||||
|
createdAt: Date;
|
||||||
|
incrementStep: number;
|
||||||
|
productQuantity: number;
|
||||||
|
storeId: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductWithRelations extends AdminProduct {
|
||||||
|
unit: AdminUnit;
|
||||||
|
store: Store | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductTagInfo {
|
||||||
|
id: number;
|
||||||
|
tagName: string;
|
||||||
|
tagDescription: string | null;
|
||||||
|
imageUrl: string | null;
|
||||||
|
isDashboardTag: boolean;
|
||||||
|
relatedStores: unknown;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductTagAssignment {
|
||||||
|
productId: number;
|
||||||
|
tagId: number;
|
||||||
|
assignedAt: Date;
|
||||||
|
product: AdminProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductTagWithProducts extends AdminProductTagInfo {
|
||||||
|
products: AdminProductTagAssignment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSpecialDeal {
|
||||||
|
id: number;
|
||||||
|
productId: number;
|
||||||
|
quantity: string;
|
||||||
|
price: string;
|
||||||
|
validTill: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductWithDetails extends AdminProduct {
|
||||||
|
unit: AdminUnit;
|
||||||
|
deals: AdminSpecialDeal[];
|
||||||
|
tags: AdminProductTagInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductListResponse {
|
||||||
|
products: AdminProductWithRelations[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductResponse {
|
||||||
|
product: AdminProductWithDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminDeleteProductResult {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminToggleOutOfStockResult {
|
||||||
|
product: AdminProduct;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminUpdateSlotProductsResult {
|
||||||
|
message: string;
|
||||||
|
added: number;
|
||||||
|
removed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotProductIdsResult {
|
||||||
|
productIds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AdminSlotsProductIdsResult = Record<number, number[]>;
|
||||||
|
|
||||||
|
export interface AdminProductReview {
|
||||||
|
id: number;
|
||||||
|
reviewBody: string;
|
||||||
|
ratings: number;
|
||||||
|
imageUrls: unknown;
|
||||||
|
reviewTime: Date;
|
||||||
|
adminResponse: string | null;
|
||||||
|
adminResponseImages: unknown;
|
||||||
|
userName: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductReviewWithSignedUrls extends AdminProductReview {
|
||||||
|
signedImageUrls: string[];
|
||||||
|
signedAdminImageUrls: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductReviewsResult {
|
||||||
|
reviews: AdminProductReviewWithSignedUrls[];
|
||||||
|
hasMore: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductReviewResponse {
|
||||||
|
success: boolean;
|
||||||
|
review: AdminProductReview;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductGroup {
|
||||||
|
id: number;
|
||||||
|
groupName: string;
|
||||||
|
description: string | null;
|
||||||
|
createdAt: Date;
|
||||||
|
products: AdminProduct[];
|
||||||
|
productCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductGroupsResult {
|
||||||
|
groups: AdminProductGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductGroupResponse {
|
||||||
|
group: AdminProductGroupInfo;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminProductGroupInfo {
|
||||||
|
id: number;
|
||||||
|
groupName: string;
|
||||||
|
description: string | null;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminUpdateProductPricesResult {
|
||||||
|
message: string;
|
||||||
|
updatedCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminDeliverySlot {
|
||||||
|
id: number;
|
||||||
|
deliveryTime: Date;
|
||||||
|
freezeTime: Date;
|
||||||
|
isActive: boolean;
|
||||||
|
isFlash: boolean;
|
||||||
|
isCapacityFull: boolean;
|
||||||
|
deliverySequence: unknown;
|
||||||
|
groupIds: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotProductSummary {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
images: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippet {
|
||||||
|
id: number;
|
||||||
|
snippetCode: string;
|
||||||
|
slotId: number | null;
|
||||||
|
productIds: number[];
|
||||||
|
isPermanent: boolean;
|
||||||
|
validTill: Date | null;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetWithAccess extends AdminVendorSnippet {
|
||||||
|
accessUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetWithSlot extends AdminVendorSnippet {
|
||||||
|
slot: AdminDeliverySlot | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetProduct {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetWithProducts extends AdminVendorSnippetWithSlot {
|
||||||
|
accessUrl: string;
|
||||||
|
products: AdminVendorSnippetProduct[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotWithProducts extends AdminDeliverySlot {
|
||||||
|
deliverySequence: number[];
|
||||||
|
products: AdminSlotProductSummary[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotWithProductsAndSnippets extends AdminSlotWithProducts {
|
||||||
|
groupIds: number[];
|
||||||
|
vendorSnippets: AdminVendorSnippetWithAccess[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotWithProductsAndSnippetsBase extends AdminSlotWithProducts {
|
||||||
|
groupIds: number[];
|
||||||
|
vendorSnippets: AdminVendorSnippet[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotsResult {
|
||||||
|
slots: AdminSlotWithProducts[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotsListResult {
|
||||||
|
slots: AdminDeliverySlot[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotResult {
|
||||||
|
slot: AdminSlotWithProductsAndSnippets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotCreateResult {
|
||||||
|
slot: AdminDeliverySlot;
|
||||||
|
createdSnippets: AdminVendorSnippet[];
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotUpdateResult {
|
||||||
|
slot: AdminDeliverySlot;
|
||||||
|
createdSnippets: AdminVendorSnippet[];
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminSlotDeleteResult {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AdminDeliverySequence = Record<string, number[]>;
|
||||||
|
|
||||||
|
export interface AdminDeliverySequenceResult {
|
||||||
|
deliverySequence: AdminDeliverySequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminUpdateDeliverySequenceResult {
|
||||||
|
slot: {
|
||||||
|
id: number;
|
||||||
|
deliverySequence: unknown;
|
||||||
|
};
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminUpdateSlotCapacityResult {
|
||||||
|
success: boolean;
|
||||||
|
slot: AdminDeliverySlot;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetCreateInput {
|
||||||
|
snippetCode: string;
|
||||||
|
slotId?: number;
|
||||||
|
productIds: number[];
|
||||||
|
validTill?: string;
|
||||||
|
isPermanent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetUpdateInput {
|
||||||
|
snippetCode?: string;
|
||||||
|
slotId?: number;
|
||||||
|
productIds?: number[];
|
||||||
|
validTill?: string | null;
|
||||||
|
isPermanent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetDeleteResult {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetOrderProduct {
|
||||||
|
orderItemId: number;
|
||||||
|
productId: number;
|
||||||
|
productName: string;
|
||||||
|
quantity: number;
|
||||||
|
productSize: number;
|
||||||
|
price: number;
|
||||||
|
unit: string;
|
||||||
|
subtotal: number;
|
||||||
|
is_packaged: boolean;
|
||||||
|
is_package_verified: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetOrderSummary {
|
||||||
|
orderId: string;
|
||||||
|
orderDate: string;
|
||||||
|
customerName: string;
|
||||||
|
totalAmount: number;
|
||||||
|
slotInfo: {
|
||||||
|
time: string;
|
||||||
|
sequence: unknown;
|
||||||
|
} | null;
|
||||||
|
products: AdminVendorSnippetOrderProduct[];
|
||||||
|
matchedProducts: number[];
|
||||||
|
snippetCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetOrdersResult {
|
||||||
|
success: boolean;
|
||||||
|
data: AdminVendorSnippetOrderSummary[];
|
||||||
|
snippet: {
|
||||||
|
id: number;
|
||||||
|
snippetCode: string;
|
||||||
|
slotId: number | null;
|
||||||
|
productIds: number[];
|
||||||
|
validTill?: string;
|
||||||
|
createdAt: string;
|
||||||
|
isPermanent: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorSnippetOrdersWithSlotResult extends AdminVendorSnippetOrdersResult {
|
||||||
|
selectedSlot: {
|
||||||
|
id: number;
|
||||||
|
deliveryTime: string;
|
||||||
|
freezeTime: string;
|
||||||
|
deliverySequence: unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminVendorOrderSummary {
|
||||||
|
id: number;
|
||||||
|
status: string;
|
||||||
|
orderDate: string;
|
||||||
|
totalQuantity: number;
|
||||||
|
products: {
|
||||||
|
name: string;
|
||||||
|
quantity: number;
|
||||||
|
unit: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminUpcomingSlotsResult {
|
||||||
|
success: boolean;
|
||||||
|
data: {
|
||||||
|
id: number;
|
||||||
|
deliveryTime: string;
|
||||||
|
freezeTime: string;
|
||||||
|
deliverySequence: unknown;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AdminVendorUpdatePackagingResult =
|
||||||
|
| {
|
||||||
|
success: true;
|
||||||
|
orderItemId: number;
|
||||||
|
is_packaged: boolean;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
// Central types export file
|
// Central types export file
|
||||||
// Re-export all types from the types folder
|
// Re-export all types from the types folder
|
||||||
|
|
||||||
export type { Banner } from './banner.types';
|
export type * from './admin';
|
||||||
export type { Complaint, ComplaintWithUser } from './complaint.types';
|
export type * from './user';
|
||||||
export type { Constant, ConstantUpdateResult } from './const.types';
|
|
||||||
export type { Coupon, ReservedCoupon, CouponValidationResult, UserMiniInfo } from './coupon.types';
|
|
||||||
export type { Store } from './store.types';
|
|
||||||
export type { StaffUser, StaffRole } from './staff-user.types';
|
|
||||||
|
|
|
||||||
82
packages/shared/types/user.ts
Normal file
82
packages/shared/types/user.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
// User-related types
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
name: string | null;
|
||||||
|
email: string | null;
|
||||||
|
mobile: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserDetails {
|
||||||
|
id: number;
|
||||||
|
userId: number;
|
||||||
|
isSuspended: boolean;
|
||||||
|
profileImage: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Address {
|
||||||
|
id: number;
|
||||||
|
userId: number;
|
||||||
|
addressLine1: string;
|
||||||
|
addressLine2: string | null;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
pincode: string;
|
||||||
|
lat: number | null;
|
||||||
|
lng: number | null;
|
||||||
|
isDefault: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Product {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
price: string;
|
||||||
|
productQuantity: string;
|
||||||
|
images: string[] | null;
|
||||||
|
isOutOfStock: boolean;
|
||||||
|
storeId: number | null;
|
||||||
|
unitId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CartItem {
|
||||||
|
id: number;
|
||||||
|
userId: number;
|
||||||
|
productId: number;
|
||||||
|
quantity: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Order {
|
||||||
|
id: number;
|
||||||
|
userId: number;
|
||||||
|
addressId: number;
|
||||||
|
slotId: number | null;
|
||||||
|
totalAmount: string;
|
||||||
|
deliveryCharge: string;
|
||||||
|
isFlashDelivery: boolean;
|
||||||
|
adminNotes: string | null;
|
||||||
|
isPackaged: boolean;
|
||||||
|
isDelivered: boolean;
|
||||||
|
isCancelled: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderItem {
|
||||||
|
id: number;
|
||||||
|
orderId: number;
|
||||||
|
productId: number;
|
||||||
|
quantity: string;
|
||||||
|
price: string;
|
||||||
|
is_packaged: boolean;
|
||||||
|
is_package_verified: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Payment {
|
||||||
|
id: number;
|
||||||
|
orderId: number;
|
||||||
|
paymentStatus: string;
|
||||||
|
paymentMethod: string;
|
||||||
|
amount: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue