351 lines
9.9 KiB
TypeScript
351 lines
9.9 KiB
TypeScript
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'}`,
|
|
}
|
|
}
|