enh
This commit is contained in:
parent
b2a35176dd
commit
a875e63751
16 changed files with 3958 additions and 38 deletions
|
|
@ -82,6 +82,16 @@ export default function OrderDetails() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const removeDeliveryChargeMutation = trpc.admin.order.removeDeliveryCharge.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
Alert.alert("Success", "Delivery charge has been removed");
|
||||||
|
refetch();
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
Alert.alert("Error", error.message || "Failed to remove delivery charge");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||||
|
|
@ -508,6 +518,40 @@ export default function OrderDetails() {
|
||||||
-₹{discountAmount}
|
-₹{discountAmount}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
|
)}
|
||||||
|
{order.deliveryCharge > 0 && (
|
||||||
|
<View style={tw`flex-row justify-between items-center mb-2`}>
|
||||||
|
<View style={tw`flex-row items-center`}>
|
||||||
|
<MyText style={tw`text-gray-600 font-medium`}>
|
||||||
|
Delivery Charge
|
||||||
|
</MyText>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
Alert.alert(
|
||||||
|
'Remove Delivery Cost',
|
||||||
|
'Are you sure you want to remove the delivery cost from this order?',
|
||||||
|
[
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Remove',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: () => removeDeliveryChargeMutation.mutate({ orderId: order.id }),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
disabled={removeDeliveryChargeMutation.isPending}
|
||||||
|
style={tw`ml-2 px-2 py-1 bg-red-100 rounded-md`}
|
||||||
|
>
|
||||||
|
<MyText style={tw`text-xs font-bold text-red-600`}>
|
||||||
|
{removeDeliveryChargeMutation.isPending ? 'Removing...' : 'Remove'}
|
||||||
|
</MyText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<MyText style={tw`text-gray-600 font-medium`}>
|
||||||
|
₹{order.deliveryCharge}
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
<View style={tw`flex-row justify-between items-center pt-2 border-t border-gray-200`}>
|
<View style={tw`flex-row justify-between items-center pt-2 border-t border-gray-200`}>
|
||||||
<View style={tw`flex-row items-center`}>
|
<View style={tw`flex-row items-center`}>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { MaterialCommunityIcons, Entypo } from '@expo/vector-icons';
|
import { MaterialCommunityIcons, Entypo } from '@expo/vector-icons';
|
||||||
import { View, TouchableOpacity, FlatList, Alert } from 'react-native';
|
import { View, TouchableOpacity, FlatList, Alert, ActivityIndicator } from 'react-native';
|
||||||
import { AppContainer, MyText, tw, MyFlatList , BottomDialog, MyTouchableOpacity } from 'common-ui';
|
import { AppContainer, MyText, tw, MyFlatList , BottomDialog, MyTouchableOpacity, Checkbox } from 'common-ui';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import { useRouter } from 'expo-router';
|
import { useRouter } from 'expo-router';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
@ -12,6 +12,7 @@ interface SlotItemProps {
|
||||||
router: any;
|
router: any;
|
||||||
setDialogProducts: React.Dispatch<React.SetStateAction<any[]>>;
|
setDialogProducts: React.Dispatch<React.SetStateAction<any[]>>;
|
||||||
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
refetch: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SlotItemComponent: React.FC<SlotItemProps> = ({
|
const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
|
|
@ -19,6 +20,7 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
router,
|
router,
|
||||||
setDialogProducts,
|
setDialogProducts,
|
||||||
setDialogOpen,
|
setDialogOpen,
|
||||||
|
refetch,
|
||||||
}) => {
|
}) => {
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const slotProducts = slot.products?.map((p: any) => p.name).filter(Boolean) || [];
|
const slotProducts = slot.products?.map((p: any) => p.name).filter(Boolean) || [];
|
||||||
|
|
@ -28,6 +30,29 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
const statusColor = isActive ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';
|
const statusColor = isActive ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';
|
||||||
const statusText = isActive ? 'Active' : 'Inactive';
|
const statusText = isActive ? 'Active' : 'Inactive';
|
||||||
|
|
||||||
|
const updateSlotCapacity = trpc.admin.slots.updateSlotCapacity.useMutation();
|
||||||
|
|
||||||
|
const handleCapacityToggle = () => {
|
||||||
|
updateSlotCapacity.mutate(
|
||||||
|
{ slotId: slot.id, isCapacityFull: !slot.isCapacityFull },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
refetch();
|
||||||
|
Alert.alert(
|
||||||
|
'Success',
|
||||||
|
slot.isCapacityFull
|
||||||
|
? 'Slot capacity reset. It will now be visible to users.'
|
||||||
|
: 'Slot marked as full capacity. It will be hidden from users.'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
Alert.alert('Error', error.message || 'Failed to update slot capacity');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => router.push(`/(drawer)/slots/slot-details?slotId=${slot.id}`)}
|
onPress={() => router.push(`/(drawer)/slots/slot-details?slotId=${slot.id}`)}
|
||||||
|
|
@ -55,10 +80,15 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
<MyText style={tw`text-xs font-bold text-pink1`}>Edit</MyText>
|
<MyText style={tw`text-xs font-bold text-pink1`}>Edit</MyText>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<View style={tw`px-3 py-1 rounded-full ${statusColor.split(' ')[0]}`}>
|
<View style={tw`px-3 py-1 rounded-full ${statusColor.split(' ')[0]}`}>
|
||||||
<MyText style={tw`text-xs font-bold ${statusColor.split(' ')[1]}`}>{statusText}</MyText>
|
<MyText style={tw`text-xs font-bold ${statusColor.split(' ')[1]}`}>{statusText}</MyText>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity
|
{slot.isCapacityFull && (
|
||||||
|
<View style={tw`px-2 py-1 rounded-full bg-red-500 ml-2`}>
|
||||||
|
<MyText style={tw`text-xs font-bold text-white`}>FULL</MyText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<TouchableOpacity
|
||||||
onPress={() => setMenuOpen(true)}
|
onPress={() => setMenuOpen(true)}
|
||||||
style={tw`ml-2 p-1`}
|
style={tw`ml-2 p-1`}
|
||||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
|
|
@ -68,33 +98,75 @@ const SlotItemComponent: React.FC<SlotItemProps> = ({
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Replicate Menu Dialog */}
|
{/* Replicate Menu Dialog */}
|
||||||
<BottomDialog open={menuOpen} onClose={() => setMenuOpen(false)}>
|
<BottomDialog open={menuOpen} onClose={() => setMenuOpen(false)}>
|
||||||
<View style={tw`p-4`}>
|
<View style={tw`p-4`}>
|
||||||
<MyText style={tw`text-lg font-bold mb-4`}>Slot #{slot.id} Actions</MyText>
|
<MyText style={tw`text-lg font-bold mb-4`}>Slot #{slot.id} Actions</MyText>
|
||||||
|
|
||||||
|
{/* Capacity Toggle */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={handleCapacityToggle}
|
||||||
setMenuOpen(false);
|
disabled={updateSlotCapacity.isPending}
|
||||||
router.push(`/slots/add?baseslot=${slot.id}` as any);
|
|
||||||
}}
|
|
||||||
style={tw`py-4 border-b border-gray-200`}
|
style={tw`py-4 border-b border-gray-200`}
|
||||||
>
|
>
|
||||||
<View style={tw`flex-row items-center`}>
|
<View style={tw`flex-row items-center justify-between`}>
|
||||||
<MaterialCommunityIcons name="content-copy" size={20} color="#4B5563" style={tw`mr-3`} />
|
<View style={tw`flex-row items-center flex-1`}>
|
||||||
<MyText style={tw`text-base text-gray-800`}>Replicate Slot</MyText>
|
{updateSlotCapacity.isPending ? (
|
||||||
|
<ActivityIndicator size="small" color="#EF4444" style={tw`mr-3`} />
|
||||||
|
) : (
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name={slot.isCapacityFull ? "package-variant-closed" : "package-variant"}
|
||||||
|
size={20}
|
||||||
|
color={slot.isCapacityFull ? "#EF4444" : "#4B5563"}
|
||||||
|
style={tw`mr-3`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<View>
|
||||||
|
<MyText style={tw`text-base text-gray-800`}>Mark as Full Capacity</MyText>
|
||||||
|
<MyText style={tw`text-xs text-gray-500 mt-0.5`}>
|
||||||
|
{slot.isCapacityFull
|
||||||
|
? "Slot is hidden from users"
|
||||||
|
: "Hidden from users when full"}
|
||||||
|
</MyText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{updateSlotCapacity.isPending ? (
|
||||||
|
<ActivityIndicator size="small" color="#EF4444" />
|
||||||
|
) : (
|
||||||
|
<Checkbox
|
||||||
|
checked={slot.isCapacityFull}
|
||||||
|
onPress={handleCapacityToggle}
|
||||||
|
size={22}
|
||||||
|
fillColor="#EF4444"
|
||||||
|
checkColor="#FFFFFF"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => setMenuOpen(false)}
|
<TouchableOpacity
|
||||||
style={tw`py-4 mt-2`}
|
onPress={() => {
|
||||||
>
|
setMenuOpen(false);
|
||||||
<View style={tw`flex-row items-center`}>
|
router.push(`/slots/add?baseslot=${slot.id}` as any);
|
||||||
<MaterialCommunityIcons name="close" size={20} color="#EF4444" style={tw`mr-3`} />
|
}}
|
||||||
<MyText style={tw`text-base text-red-500`}>Cancel</MyText>
|
style={tw`py-4 border-b border-gray-200`}
|
||||||
</View>
|
>
|
||||||
</TouchableOpacity>
|
<View style={tw`flex-row items-center`}>
|
||||||
</View>
|
<MaterialCommunityIcons name="content-copy" size={20} color="#4B5563" style={tw`mr-3`} />
|
||||||
</BottomDialog>
|
<MyText style={tw`text-base text-gray-800`}>Replicate Slot</MyText>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => setMenuOpen(false)}
|
||||||
|
style={tw`py-4 mt-2`}
|
||||||
|
>
|
||||||
|
<View style={tw`flex-row items-center`}>
|
||||||
|
<MaterialCommunityIcons name="close" size={20} color="#EF4444" style={tw`mr-3`} />
|
||||||
|
<MyText style={tw`text-base text-red-500`}>Cancel</MyText>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</BottomDialog>
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<View style={tw`h-[1px] bg-gray-100 mb-4`} />
|
<View style={tw`h-[1px] bg-gray-100 mb-4`} />
|
||||||
|
|
@ -193,6 +265,7 @@ export default function Slots() {
|
||||||
router={router}
|
router={router}
|
||||||
setDialogProducts={setDialogProducts}
|
setDialogProducts={setDialogProducts}
|
||||||
setDialogOpen={setDialogOpen}
|
setDialogOpen={setDialogOpen}
|
||||||
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
contentContainerStyle={tw`p-4`}
|
contentContainerStyle={tw`p-4`}
|
||||||
|
|
|
||||||
1
apps/backend/drizzle/0074_outgoing_black_cat.sql
Normal file
1
apps/backend/drizzle/0074_outgoing_black_cat.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE "mf"."delivery_slot_info" ADD COLUMN "is_capacity_full" boolean DEFAULT false NOT NULL;
|
||||||
3698
apps/backend/drizzle/meta/0074_snapshot.json
Normal file
3698
apps/backend/drizzle/meta/0074_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -519,6 +519,13 @@
|
||||||
"when": 1770561175889,
|
"when": 1770561175889,
|
||||||
"tag": "0073_faithful_gravity",
|
"tag": "0073_faithful_gravity",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 74,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1771674555093,
|
||||||
|
"tag": "0074_outgoing_black_cat",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -192,6 +192,7 @@ export const deliverySlotInfo = mf.table('delivery_slot_info', {
|
||||||
freezeTime: timestamp('freeze_time').notNull(),
|
freezeTime: timestamp('freeze_time').notNull(),
|
||||||
isActive: boolean('is_active').notNull().default(true),
|
isActive: boolean('is_active').notNull().default(true),
|
||||||
isFlash: boolean('is_flash').notNull().default(false),
|
isFlash: boolean('is_flash').notNull().default(false),
|
||||||
|
isCapacityFull: boolean('is_capacity_full').notNull().default(false),
|
||||||
deliverySequence: jsonb('delivery_sequence').$defaultFn(() => {}),
|
deliverySequence: jsonb('delivery_sequence').$defaultFn(() => {}),
|
||||||
groupIds: jsonb('group_ids').$defaultFn(() => []),
|
groupIds: jsonb('group_ids').$defaultFn(() => []),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ interface Product {
|
||||||
productQuantity: number;
|
productQuantity: number;
|
||||||
isFlashAvailable: boolean;
|
isFlashAvailable: boolean;
|
||||||
flashPrice: string | null;
|
flashPrice: string | null;
|
||||||
deliverySlots: Array<{ id: number; deliveryTime: Date; freezeTime: Date }>;
|
deliverySlots: Array<{ id: number; deliveryTime: Date; freezeTime: Date; isCapacityFull: boolean }>;
|
||||||
specialDeals: Array<{ quantity: string; price: string; validTill: Date }>;
|
specialDeals: Array<{ quantity: string; price: string; validTill: Date }>;
|
||||||
productTags: string[];
|
productTags: string[];
|
||||||
}
|
}
|
||||||
|
|
@ -57,19 +57,21 @@ export async function initializeProducts(): Promise<void> {
|
||||||
});
|
});
|
||||||
const storeMap = new Map(allStores.map(s => [s.id, s]));
|
const storeMap = new Map(allStores.map(s => [s.id, s]));
|
||||||
|
|
||||||
// Fetch all delivery slots
|
// Fetch all delivery slots (excluding full capacity slots)
|
||||||
const allDeliverySlots = await db
|
const allDeliverySlots = await db
|
||||||
.select({
|
.select({
|
||||||
productId: productSlots.productId,
|
productId: productSlots.productId,
|
||||||
id: deliverySlotInfo.id,
|
id: deliverySlotInfo.id,
|
||||||
deliveryTime: deliverySlotInfo.deliveryTime,
|
deliveryTime: deliverySlotInfo.deliveryTime,
|
||||||
freezeTime: deliverySlotInfo.freezeTime,
|
freezeTime: deliverySlotInfo.freezeTime,
|
||||||
|
isCapacityFull: deliverySlotInfo.isCapacityFull,
|
||||||
})
|
})
|
||||||
.from(productSlots)
|
.from(productSlots)
|
||||||
.innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id))
|
.innerJoin(deliverySlotInfo, eq(productSlots.slotId, deliverySlotInfo.id))
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(deliverySlotInfo.isActive, true),
|
eq(deliverySlotInfo.isActive, true),
|
||||||
|
eq(deliverySlotInfo.isCapacityFull, false),
|
||||||
gt(deliverySlotInfo.deliveryTime, sql`NOW()`)
|
gt(deliverySlotInfo.deliveryTime, sql`NOW()`)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -132,7 +134,7 @@ export async function initializeProducts(): Promise<void> {
|
||||||
productQuantity: product.productQuantity,
|
productQuantity: product.productQuantity,
|
||||||
isFlashAvailable: product.isFlashAvailable,
|
isFlashAvailable: product.isFlashAvailable,
|
||||||
flashPrice: product.flashPrice?.toString() || null,
|
flashPrice: product.flashPrice?.toString() || null,
|
||||||
deliverySlots: deliverySlots.map(s => ({ id: s.id, deliveryTime: s.deliveryTime, freezeTime: s.freezeTime })),
|
deliverySlots: deliverySlots.map(s => ({ id: s.id, deliveryTime: s.deliveryTime, freezeTime: s.freezeTime, isCapacityFull: s.isCapacityFull })),
|
||||||
specialDeals: specialDeals.map(d => ({ quantity: d.quantity.toString(), price: d.price.toString(), validTill: d.validTill })),
|
specialDeals: specialDeals.map(d => ({ quantity: d.quantity.toString(), price: d.price.toString(), validTill: d.validTill })),
|
||||||
productTags: productTags,
|
productTags: productTags,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ interface SlotWithProducts {
|
||||||
deliveryTime: Date;
|
deliveryTime: Date;
|
||||||
freezeTime: Date;
|
freezeTime: Date;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
isCapacityFull: boolean;
|
||||||
products: Array<{
|
products: Array<{
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -30,6 +31,7 @@ interface SlotInfo {
|
||||||
id: number;
|
id: number;
|
||||||
deliveryTime: Date;
|
deliveryTime: Date;
|
||||||
freezeTime: Date;
|
freezeTime: Date;
|
||||||
|
isCapacityFull: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initializeSlotStore(): Promise<void> {
|
export async function initializeSlotStore(): Promise<void> {
|
||||||
|
|
@ -80,6 +82,7 @@ export async function initializeSlotStore(): Promise<void> {
|
||||||
deliveryTime: slot.deliveryTime,
|
deliveryTime: slot.deliveryTime,
|
||||||
freezeTime: slot.freezeTime,
|
freezeTime: slot.freezeTime,
|
||||||
isActive: slot.isActive,
|
isActive: slot.isActive,
|
||||||
|
isCapacityFull: slot.isCapacityFull,
|
||||||
products: await Promise.all(
|
products: await Promise.all(
|
||||||
slot.productSlots.map(async (productSlot) => ({
|
slot.productSlots.map(async (productSlot) => ({
|
||||||
id: productSlot.product.id,
|
id: productSlot.product.id,
|
||||||
|
|
@ -118,6 +121,7 @@ export async function initializeSlotStore(): Promise<void> {
|
||||||
id: slot.id,
|
id: slot.id,
|
||||||
deliveryTime: slot.deliveryTime,
|
deliveryTime: slot.deliveryTime,
|
||||||
freezeTime: slot.freezeTime,
|
freezeTime: slot.freezeTime,
|
||||||
|
isCapacityFull: slot.isCapacityFull,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -225,7 +229,9 @@ export async function getMultipleProductsSlots(productIds: number[]): Promise<Re
|
||||||
for (let i = 0; i < productIds.length; i++) {
|
for (let i = 0; i < productIds.length; i++) {
|
||||||
const data = productsData[i];
|
const data = productsData[i];
|
||||||
if (data) {
|
if (data) {
|
||||||
result[productIds[i]] = JSON.parse(data) as SlotInfo[];
|
const slots = JSON.parse(data) as SlotInfo[];
|
||||||
|
// Filter out slots that are at full capacity
|
||||||
|
result[productIds[i]] = slots.filter(slot => !slot.isCapacityFull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -325,7 +325,8 @@ export const orderRouter = router({
|
||||||
: null,
|
: null,
|
||||||
isCod: orderData.isCod,
|
isCod: orderData.isCod,
|
||||||
isOnlinePayment: orderData.isOnlinePayment,
|
isOnlinePayment: orderData.isOnlinePayment,
|
||||||
totalAmount: orderData.totalAmount,
|
totalAmount: parseFloat(orderData.totalAmount?.toString() || '0') - parseFloat(orderData.deliveryCharge?.toString() || '0'),
|
||||||
|
deliveryCharge: parseFloat(orderData.deliveryCharge?.toString() || '0'),
|
||||||
adminNotes: orderData.adminNotes,
|
adminNotes: orderData.adminNotes,
|
||||||
userNotes: orderData.userNotes,
|
userNotes: orderData.userNotes,
|
||||||
createdAt: orderData.createdAt,
|
createdAt: orderData.createdAt,
|
||||||
|
|
@ -460,6 +461,34 @@ export const orderRouter = router({
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
removeDeliveryCharge: protectedProcedure
|
||||||
|
.input(z.object({ orderId: z.number() }))
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { orderId } = input;
|
||||||
|
|
||||||
|
const order = await db.query.orders.findFirst({
|
||||||
|
where: eq(orders.id, orderId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
throw new Error('Order not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
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' };
|
||||||
|
}),
|
||||||
|
|
||||||
getSlotOrders: protectedProcedure
|
getSlotOrders: protectedProcedure
|
||||||
.input(getSlotOrdersSchema)
|
.input(getSlotOrdersSchema)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
|
|
|
||||||
|
|
@ -574,4 +574,36 @@ export const slotsRouter = router({
|
||||||
message: "Delivery sequence updated successfully",
|
message: "Delivery sequence updated successfully",
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
updateSlotCapacity: protectedProcedure
|
||||||
|
.input(z.object({
|
||||||
|
slotId: z.number(),
|
||||||
|
isCapacityFull: z.boolean(),
|
||||||
|
}))
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
if (!ctx.staffUser?.id) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "Access denied" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { slotId, isCapacityFull } = input;
|
||||||
|
|
||||||
|
const [updatedSlot] = await db
|
||||||
|
.update(deliverySlotInfo)
|
||||||
|
.set({ isCapacityFull })
|
||||||
|
.where(eq(deliverySlotInfo.id, slotId))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!updatedSlot) {
|
||||||
|
throw new ApiError("Slot not found", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinitialize stores to reflect changes
|
||||||
|
await initializeAllStores();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
slot: updatedSlot,
|
||||||
|
message: `Slot ${isCapacityFull ? 'marked as full capacity' : 'capacity reset'}`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export const getNextDeliveryDate = async (productId: number): Promise<Date | nul
|
||||||
and(
|
and(
|
||||||
eq(productSlots.productId, productId),
|
eq(productSlots.productId, productId),
|
||||||
eq(deliverySlotInfo.isActive, true),
|
eq(deliverySlotInfo.isActive, true),
|
||||||
|
eq(deliverySlotInfo.isCapacityFull, false),
|
||||||
gt(deliverySlotInfo.deliveryTime, sql`NOW()`)
|
gt(deliverySlotInfo.deliveryTime, sql`NOW()`)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { RazorpayPaymentService } from "../../lib/payments-utils";
|
||||||
import { getNextDeliveryDate } from "../common-apis/common";
|
import { getNextDeliveryDate } from "../common-apis/common";
|
||||||
import { CONST_KEYS, getConstant, getConstants } from "../../lib/const-store";
|
import { CONST_KEYS, getConstant, getConstants } from "../../lib/const-store";
|
||||||
import { publishFormattedOrder, publishCancellation } from "../../lib/post-order-handler";
|
import { publishFormattedOrder, publishCancellation } from "../../lib/post-order-handler";
|
||||||
|
import { getSlotById } from "../../stores/slot-store";
|
||||||
|
|
||||||
|
|
||||||
const validateAndGetCoupon = async (
|
const validateAndGetCoupon = async (
|
||||||
|
|
@ -404,6 +405,17 @@ export const orderRouter = router({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if any selected slot is at full capacity (only for regular delivery)
|
||||||
|
if (!isFlashDelivery) {
|
||||||
|
const slotIds = [...new Set(selectedItems.filter(i => i.slotId !== null).map(i => i.slotId as number))];
|
||||||
|
for (const slotId of slotIds) {
|
||||||
|
const slot = await getSlotById(slotId);
|
||||||
|
if (slot?.isCapacityFull) {
|
||||||
|
throw new ApiError("Selected delivery slot is at full capacity. Please choose another slot.", 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let processedItems = selectedItems;
|
let processedItems = selectedItems;
|
||||||
|
|
||||||
// Handle flash delivery slot resolution
|
// Handle flash delivery slot resolution
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,10 @@ export const productRouter = router({
|
||||||
const cachedProduct = await getProductByIdFromCache(productId);
|
const cachedProduct = await getProductByIdFromCache(productId);
|
||||||
|
|
||||||
if (cachedProduct) {
|
if (cachedProduct) {
|
||||||
// Filter delivery slots to only include those with future freeze times
|
// Filter delivery slots to only include those with future freeze times and not at full capacity
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
const filteredSlots = cachedProduct.deliverySlots.filter(slot =>
|
const filteredSlots = cachedProduct.deliverySlots.filter(slot =>
|
||||||
dayjs(slot.freezeTime).isAfter(currentTime)
|
dayjs(slot.freezeTime).isAfter(currentTime) && !slot.isCapacityFull
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -107,6 +107,7 @@ export const productRouter = router({
|
||||||
and(
|
and(
|
||||||
eq(productSlots.productId, productId),
|
eq(productSlots.productId, productId),
|
||||||
eq(deliverySlotInfo.isActive, true),
|
eq(deliverySlotInfo.isActive, true),
|
||||||
|
eq(deliverySlotInfo.isCapacityFull, false),
|
||||||
gt(deliverySlotInfo.deliveryTime, sql`NOW()`),
|
gt(deliverySlotInfo.deliveryTime, sql`NOW()`),
|
||||||
gt(deliverySlotInfo.freezeTime, sql`NOW()`)
|
gt(deliverySlotInfo.freezeTime, sql`NOW()`)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export const slotsRouter = router({
|
||||||
const allSlots = await getAllSlotsFromCache();
|
const allSlots = await getAllSlotsFromCache();
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
const validSlots = allSlots
|
const validSlots = allSlots
|
||||||
.filter((slot) => dayjs(slot.freezeTime).isAfter(currentTime))
|
.filter((slot) => dayjs(slot.freezeTime).isAfter(currentTime) && !slot.isCapacityFull)
|
||||||
.sort((a, b) => dayjs(a.deliveryTime).valueOf() - dayjs(b.deliveryTime).valueOf());
|
.sort((a, b) => dayjs(a.deliveryTime).valueOf() - dayjs(b.deliveryTime).valueOf());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
const formatTimeRange = (deliveryTime: string) => {
|
||||||
|
const time = dayjs(deliveryTime);
|
||||||
|
const endTime = time.add(1, 'hour');
|
||||||
|
const startPeriod = time.format('A');
|
||||||
|
const endPeriod = endTime.format('A');
|
||||||
|
|
||||||
|
if (startPeriod === endPeriod) {
|
||||||
|
return `${time.format('h')}-${endTime.format('h')} ${startPeriod}`;
|
||||||
|
} else {
|
||||||
|
return `${time.format('h:mm')} ${startPeriod} - ${endTime.format('h:mm')} ${endPeriod}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default function AddToCartDialog() {
|
export default function AddToCartDialog() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { addedToCartProduct, clearAddedToCartProduct } = useCartStore();
|
const { addedToCartProduct, clearAddedToCartProduct } = useCartStore();
|
||||||
|
|
@ -158,7 +171,7 @@ export default function AddToCartDialog() {
|
||||||
<MaterialIcons name="local-shipping" size={20} color="#3B82F6" style={tw`mt-0.5`} />
|
<MaterialIcons name="local-shipping" size={20} color="#3B82F6" style={tw`mt-0.5`} />
|
||||||
<View style={tw`ml-3 flex-1`}>
|
<View style={tw`ml-3 flex-1`}>
|
||||||
<MyText style={tw`text-gray-900 font-bold text-base`}>
|
<MyText style={tw`text-gray-900 font-bold text-base`}>
|
||||||
{dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A')}
|
{dayjs(slot.deliveryTime).format('ddd, DD MMM • ')}{formatTimeRange(slot.deliveryTime)}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
{selectedSlotId === slot.id ? (
|
{selectedSlotId === slot.id ? (
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
|
||||||
// const BASE_API_URL = API_URL;
|
// const BASE_API_URL = API_URL;
|
||||||
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
// const BASE_API_URL = 'http://10.0.2.2:4000';
|
||||||
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
// const BASE_API_URL = 'http://192.168.100.101:4000';
|
||||||
const BASE_API_URL = 'http://192.168.1.6:4000';
|
const BASE_API_URL = 'http://192.168.100.105:4000';
|
||||||
// let BASE_API_URL = "https://mf.freshyo.in";
|
// let BASE_API_URL = "https://mf.freshyo.in";
|
||||||
// let BASE_API_URL = 'http://192.168.100.104:4000';
|
// let BASE_API_URL = 'http://192.168.100.104:4000';
|
||||||
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
// let BASE_API_URL = 'http://192.168.29.176:4000';
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue