feat: enforce checklist/minibar completion before marking task done

- ApiError now carries errors[] array from 422 responses
- updateStatus re-throws 422 so TaskCard can surface errors
- TaskCard: minibarCheckedLocal state + 'Подтвердить проверку минибара' button
- TaskCard: async handleComplete with completionErrors display
- Backend validation errors shown inline in completion panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 23:09:03 +03:00
parent 3c1a6ccebf
commit d98389e2bc
8 changed files with 289 additions and 24 deletions

View File

@@ -83,7 +83,7 @@ const checklists: FastifyPluginAsync = async (fastify) => {
const hotelId = await getHotelId(slug)
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
const allowed = ['name', 'task_type', 'is_active', 'sort_order']
const allowed = ['name', 'task_type', 'is_active', 'sort_order', 'require_before_complete']
const updates: string[] = []
const values: unknown[] = []
let idx = 1
@@ -369,4 +369,48 @@ const checklists: FastifyPluginAsync = async (fastify) => {
)
}
// ── GET /api/hotels/:slug/housekeeping-rules ──────────────────────────────
fastify.get<SlugParam>(
'/api/hotels/:slug/housekeeping-rules',
{ onRequest: [fastify.authenticate] },
async (request, reply) => {
const { slug } = request.params
if (!canAccess(request.user.hotelSlug, request.user.role, slug)) {
return reply.code(403).send({ error: 'Forbidden' })
}
const hotelId = await getHotelId(slug)
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
const { rows } = await db.query(
'SELECT require_minibar_check FROM hotel_housekeeping_rules WHERE hotel_id = $1',
[hotelId],
)
return { requireMinibarCheck: rows[0]?.require_minibar_check ?? false }
},
)
// ── PATCH /api/hotels/:slug/housekeeping-rules ─────────────────────────────
fastify.patch<SlugParam & { Body: { require_minibar_check?: boolean } }>(
'/api/hotels/:slug/housekeeping-rules',
{ onRequest: [fastify.authenticate] },
async (request, reply) => {
const { slug } = request.params
if (!canAccess(request.user.hotelSlug, request.user.role, slug) || !isManager(request.user.role)) {
return reply.code(403).send({ error: 'Forbidden' })
}
const hotelId = await getHotelId(slug)
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
const { require_minibar_check } = request.body
await db.query(
`INSERT INTO hotel_housekeeping_rules (hotel_id, require_minibar_check)
VALUES ($1, $2)
ON CONFLICT (hotel_id) DO UPDATE SET require_minibar_check = $2`,
[hotelId, require_minibar_check ?? false],
)
return { requireMinibarCheck: require_minibar_check ?? false }
},
)
}
export default checklists