175 lines
6.5 KiB
JavaScript
175 lines
6.5 KiB
JavaScript
import { NOTIFICATION_EVENTS } from '../../../../shared/constants/notification-events.js'
|
|
import { canTransitionAdminOrderStatus } from '../../lib/order-status.js'
|
|
import { prisma } from '../../lib/prisma.js'
|
|
|
|
export async function registerAdminOrderRoutes(fastify) {
|
|
fastify.get('/api/admin/orders/summary', { preHandler: [fastify.verifyAdmin] }, async () => {
|
|
const attentionCount = await prisma.order.count({
|
|
where: {
|
|
status: 'PENDING_PAYMENT',
|
|
},
|
|
})
|
|
return { attentionCount }
|
|
})
|
|
|
|
fastify.get('/api/admin/orders', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
|
|
const status = typeof request.query?.status === 'string' ? request.query.status.trim() : ''
|
|
const q = typeof request.query?.q === 'string' ? request.query.q.trim() : ''
|
|
const deliveryTypeRaw = request.query?.deliveryType
|
|
const deliveryType = typeof deliveryTypeRaw === 'string' ? deliveryTypeRaw.trim() : ''
|
|
|
|
const pageRaw = request.query?.page
|
|
const pageParsed = typeof pageRaw === 'string' ? Number(pageRaw) : Number(pageRaw)
|
|
const page = Number.isFinite(pageParsed) && pageParsed > 0 ? Math.floor(pageParsed) : 1
|
|
|
|
const pageSizeRaw = request.query?.pageSize
|
|
const pageSizeParsed = typeof pageSizeRaw === 'string' ? Number(pageSizeRaw) : Number(pageSizeRaw)
|
|
const pageSize = Number.isFinite(pageSizeParsed) && pageSizeParsed > 0 ? Math.floor(pageSizeParsed) : 20
|
|
if (pageSize > 100) return reply.code(400).send({ error: 'pageSize должен быть ≤ 100' })
|
|
|
|
const where = {}
|
|
if (status) where.status = status
|
|
if (deliveryType) {
|
|
if (deliveryType !== 'delivery' && deliveryType !== 'pickup') {
|
|
return reply.code(400).send({ error: 'deliveryType должен быть delivery | pickup' })
|
|
}
|
|
where.deliveryType = deliveryType
|
|
}
|
|
if (q) {
|
|
where.OR = [{ id: { contains: q } }, { user: { email: { contains: q } } }]
|
|
}
|
|
|
|
const total = await prisma.order.count({ where })
|
|
const items = await prisma.order.findMany({
|
|
where,
|
|
include: { user: { select: { id: true, email: true } }, items: true },
|
|
orderBy: { createdAt: 'desc' },
|
|
skip: (page - 1) * pageSize,
|
|
take: pageSize,
|
|
})
|
|
|
|
return {
|
|
items: items.map((o) => ({
|
|
id: o.id,
|
|
status: o.status,
|
|
deliveryType: o.deliveryType,
|
|
deliveryCarrier: o.deliveryCarrier,
|
|
paymentMethod: o.paymentMethod,
|
|
totalCents: o.totalCents,
|
|
currency: o.currency,
|
|
createdAt: o.createdAt,
|
|
updatedAt: o.updatedAt,
|
|
user: o.user,
|
|
itemsCount: o.items.reduce((s, i) => s + i.qty, 0),
|
|
})),
|
|
total,
|
|
page,
|
|
pageSize,
|
|
}
|
|
})
|
|
|
|
fastify.get('/api/admin/orders/:id', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
|
|
const { id } = request.params
|
|
const order = await prisma.order.findUnique({
|
|
where: { id },
|
|
include: {
|
|
user: {
|
|
select: { id: true, email: true, displayName: true, avatar: true, avatarStyle: true },
|
|
},
|
|
items: true,
|
|
messages: { orderBy: { createdAt: 'asc' } },
|
|
},
|
|
})
|
|
if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
|
|
return { item: order }
|
|
})
|
|
|
|
fastify.patch('/api/admin/orders/:id/status', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
|
|
const { id } = request.params
|
|
const next = String(request.body?.status || '').trim()
|
|
if (!next) return reply.code(400).send({ error: 'status обязателен' })
|
|
|
|
const existing = await prisma.order.findUnique({ where: { id } })
|
|
if (!existing) return reply.code(404).send({ error: 'Заказ не найден' })
|
|
if (!canTransitionAdminOrderStatus(existing, next)) {
|
|
return reply.code(409).send({
|
|
error: `Нельзя сменить статус ${existing.status} → ${next}`,
|
|
})
|
|
}
|
|
|
|
const updated = await prisma.order.update({
|
|
where: { id },
|
|
data: { status: next },
|
|
})
|
|
|
|
request.server.eventBus.emit(NOTIFICATION_EVENTS.ORDER_STATUS_CHANGED, {
|
|
orderId: updated.id,
|
|
userId: existing.userId,
|
|
oldStatus: existing.status,
|
|
newStatus: next,
|
|
})
|
|
|
|
return { item: updated }
|
|
})
|
|
|
|
fastify.patch('/api/admin/orders/:id/delivery-fee', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
|
|
const { id } = request.params
|
|
const feeRaw = request.body?.deliveryFeeCents
|
|
const parsed = typeof feeRaw === 'string' ? Number.parseInt(feeRaw, 10) : typeof feeRaw === 'number' ? feeRaw : NaN
|
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
return reply.code(400).send({
|
|
error: 'deliveryFeeCents должно быть целым числом ≥ 0 (копейки)',
|
|
})
|
|
}
|
|
|
|
const existing = await prisma.order.findUnique({ where: { id } })
|
|
if (!existing) return reply.code(404).send({ error: 'Заказ не найден' })
|
|
if (existing.status !== 'PENDING_PAYMENT' || existing.deliveryFeeLocked !== false) {
|
|
return reply.code(409).send({
|
|
error: 'Корректировка доставки доступна только пока стоимость не утверждена',
|
|
})
|
|
}
|
|
|
|
const totalCents = existing.itemsSubtotalCents + parsed
|
|
const updated = await prisma.order.update({
|
|
where: { id },
|
|
data: {
|
|
deliveryFeeCents: parsed,
|
|
totalCents,
|
|
deliveryFeeLocked: true,
|
|
},
|
|
})
|
|
|
|
request.server.eventBus.emit(NOTIFICATION_EVENTS.DELIVERY_FEE_ADJUSTED, {
|
|
orderId: updated.id,
|
|
userId: existing.userId,
|
|
totalCents: updated.totalCents,
|
|
})
|
|
|
|
return { item: updated }
|
|
})
|
|
|
|
fastify.post('/api/admin/orders/:id/messages', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => {
|
|
const { id } = request.params
|
|
const text = String(request.body?.text || '').trim()
|
|
if (!text) return reply.code(400).send({ error: 'Сообщение пустое' })
|
|
if (text.length > 2000) return reply.code(400).send({ error: 'Сообщение слишком длинное' })
|
|
|
|
const order = await prisma.order.findUnique({ where: { id } })
|
|
if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
|
|
|
|
const msg = await prisma.orderMessage.create({
|
|
data: { orderId: id, authorType: 'admin', text },
|
|
})
|
|
|
|
request.server.eventBus.emit(NOTIFICATION_EVENTS.ORDER_MESSAGE_ADMIN_REPLY, {
|
|
orderId: id,
|
|
userId: order.userId,
|
|
messageId: msg.id,
|
|
preview: text,
|
|
})
|
|
|
|
return reply.code(201).send({ item: msg })
|
|
})
|
|
}
|