fix: reviews — correct pending/archive logic, rejected_at field, stats, reply email

- Migration 085: add rejected_at to reviews
- pending = !isPublic && !reply && !rejectedAt
- archived = isPublic || reply || rejectedAt
- Stats: avgRating/NPS from all reviews; 'В архиве' = archived count
- sendReply: no longer sets isPublic=true on negative reviews
- reject: sets rejected_at=NOW() (distinct from pending)
- restore: clears rejected_at (now actually works)
- Badge 'Отклонён' for rejected reviews, opacity-60

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 10:33:01 +03:00
parent 89e919bd8d
commit 47215c7d64
4 changed files with 37 additions and 26 deletions

View File

@@ -37,7 +37,7 @@ const reviewsRoutes: FastifyPluginAsync = async (fastify) => {
const { rows } = await db.query(
`SELECT r.id, r.booking_id, r.guest_id, r.guest_name, r.source,
r.rating, r.text, r.reply, r.replied_at, r.is_public,
r.rating, r.text, r.reply, r.replied_at, r.is_public, r.rejected_at,
r.created_at, r.updated_at,
b.room_id,
rm.number AS room_number,
@@ -54,7 +54,7 @@ const reviewsRoutes: FastifyPluginAsync = async (fastify) => {
)
// ── PATCH /api/hotels/:slug/reviews/:id ───────────────────────────────────
fastify.patch<SlugIdParam & { Body: { reply?: string; isPublic?: boolean } }>(
fastify.patch<SlugIdParam & { Body: { reply?: string; isPublic?: boolean; rejected?: boolean } }>(
'/api/hotels/:slug/reviews/:id',
{ onRequest: [fastify.authenticate] },
async (request, reply) => {
@@ -65,7 +65,7 @@ const reviewsRoutes: FastifyPluginAsync = async (fastify) => {
const hotelId = await getHotelId(slug)
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
const { reply: replyText, isPublic } = request.body
const { reply: replyText, isPublic, rejected } = request.body
const updates: string[] = []
const vals: unknown[] = [id, hotelId]
@@ -77,6 +77,9 @@ const reviewsRoutes: FastifyPluginAsync = async (fastify) => {
vals.push(isPublic)
updates.push(`is_public = $${vals.length}`)
}
if (rejected === true) updates.push(`rejected_at = NOW()`)
if (rejected === false) updates.push(`rejected_at = NULL`)
if (updates.length === 0) return reply.code(400).send({ error: 'Nothing to update' })
const { rows } = await db.query(