test commit

This commit is contained in:
Kirill
2026-05-18 13:54:05 +05:00
parent 7421384161
commit 2f67c37502
10 changed files with 875 additions and 627 deletions
+128 -79
View File
@@ -1,59 +1,80 @@
import { prisma } from '../../lib/prisma.js'
import { canTransitionAdminOrderStatus } from '../../lib/order-status.js'
import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js'
import { prisma } from "../../lib/prisma.js";
import { canTransitionAdminOrderStatus } from "../../lib/order-status.js";
import { NOTIFICATION_EVENTS } from "../../../../shared/constants/notification-events.js";
export async function registerAdminOrderRoutes(fastify) {
fastify.get(
'/api/admin/orders/summary',
"/api/admin/orders/summary",
{ preHandler: [fastify.verifyAdmin] },
async () => {
const attentionCount = await prisma.order.count({
where: {
status: 'PENDING_PAYMENT',
status: "PENDING_PAYMENT",
},
})
return { attentionCount }
});
return { attentionCount };
},
)
);
fastify.get(
'/api/admin/orders',
"/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 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 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 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
const where = {};
if (status) where.status = status;
if (deliveryType) {
if (deliveryType !== 'delivery' && deliveryType !== 'pickup') {
return reply.code(400).send({ error: 'deliveryType должен быть delivery | pickup' })
if (deliveryType !== "delivery" && deliveryType !== "pickup") {
return reply
.code(400)
.send({ error: "deliveryType должен быть delivery | pickup" });
}
where.deliveryType = deliveryType
where.deliveryType = deliveryType;
}
if (q) {
where.OR = [{ id: { contains: q } }, { user: { email: { contains: q } } }]
where.OR = [
{ id: { contains: q } },
{ user: { email: { contains: q } } },
];
}
const total = await prisma.order.count({ where })
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' },
orderBy: { createdAt: "desc" },
skip: (page - 1) * pageSize,
take: pageSize,
})
});
return {
items: items.map((o) => ({
@@ -72,74 +93,97 @@ export async function registerAdminOrderRoutes(fastify) {
total,
page,
pageSize,
}
};
},
)
);
fastify.get(
'/api/admin/orders/:id',
"/api/admin/orders/:id",
{ preHandler: [fastify.verifyAdmin] },
async (request, reply) => {
const { id } = request.params
const { id } = request.params;
const order = await prisma.order.findUnique({
where: { id },
include: {
user: { select: { id: true, email: true, name: true, phone: true } },
items: true,
messages: { orderBy: { createdAt: 'asc' } },
messages: { orderBy: { createdAt: "asc" } },
},
})
if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
return { item: order }
});
if (!order) return reply.code(404).send({ error: "Заказ не найден" });
return { item: order };
},
)
);
fastify.patch(
'/api/admin/orders/:id/status',
"/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 { 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: 'Заказ не найден' })
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}` })
return reply
.code(409)
.send({
error: `Нельзя сменить статус ${existing.status}${next}`,
});
}
const updated = await prisma.order.update({ where: { id }, data: { 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 }
return { item: updated };
},
)
);
fastify.patch(
'/api/admin/orders/:id/delivery-fee',
"/api/admin/orders/:id/delivery-fee",
{ preHandler: [fastify.verifyAdmin] },
async (request, reply) => {
const { id } = request.params
const feeRaw = request.body?.deliveryFeeCents
const { id } = request.params;
const feeRaw = request.body?.deliveryFeeCents;
const parsed =
typeof feeRaw === 'string' ? Number.parseInt(feeRaw, 10) : typeof feeRaw === 'number' ? feeRaw : NaN
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 (копейки)' })
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 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 totalCents = existing.itemsSubtotalCents + parsed;
const updated = await prisma.order.update({
where: { id },
data: {
@@ -147,34 +191,39 @@ export async function registerAdminOrderRoutes(fastify) {
totalCents,
deliveryFeeLocked: true,
},
})
return { item: updated }
});
return { item: updated };
},
)
);
fastify.post(
'/api/admin/orders/:id/messages',
"/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 { 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 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 } })
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,
})
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 })
return reply.code(201).send({ item: msg });
},
)
);
}