feat: TTLock — correct DLL flow with buildNo/floorNo per lock
Backend: room-locks now store/return build_no, floor_no (required by CE_WriteCard). issue-card passes buildNo/floorNo to agent instead of cardSectors/username/password. testApi route simplified (no OAuth). Frontend: remove username/password fields (not needed), add buildNo/floorNo fields to lock mapping form, show building/floor in lock list. Migration 056: adds build_no, floor_no columns to room_lock_mappings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -184,7 +184,7 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
|
||||
|
||||
const { rows: cfgRows } = await db.query(
|
||||
'SELECT client_id, client_secret, api_server, ttlock_username, ttlock_password FROM hotel_ttlock_config WHERE hotel_id = $1',
|
||||
'SELECT client_id, client_secret, api_server FROM hotel_ttlock_config WHERE hotel_id = $1',
|
||||
[hotelId],
|
||||
)
|
||||
const cfg = cfgRows[0]
|
||||
@@ -195,16 +195,14 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
|
||||
try {
|
||||
const result = await sendCommandAndWait(workstationId, {
|
||||
type: 'ttlock:test_api',
|
||||
clientId: cfg.client_id,
|
||||
clientSecret: cfg.client_secret,
|
||||
ttlockUsername: cfg.ttlock_username ?? '',
|
||||
ttlockPassword: cfg.ttlock_password ?? '',
|
||||
apiServer: cfg.api_server ?? 'https://euapi.ttlock.com',
|
||||
}, 15_000) as { ok?: boolean; lockCount?: number; error?: string }
|
||||
type: 'ttlock:test_api',
|
||||
clientId: cfg.client_id,
|
||||
clientSecret: cfg.client_secret,
|
||||
apiServer: cfg.api_server ?? 'https://euapi.ttlock.com',
|
||||
}, 15_000) as { ok?: boolean; error?: string }
|
||||
|
||||
if (!result.ok) return reply.code(502).send({ error: result.error ?? 'Ошибка API' })
|
||||
return { ok: true, lockCount: result.lockCount }
|
||||
return { ok: true }
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'Агент не ответил'
|
||||
return reply.code(502).send({ error: msg })
|
||||
@@ -259,7 +257,7 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
|
||||
|
||||
const { rows } = await db.query(
|
||||
`SELECT m.room_id, m.lock_mac, m.lock_name,
|
||||
`SELECT m.room_id, m.lock_mac, m.lock_name, m.build_no, m.floor_no,
|
||||
r.name AS room_name, r.number AS room_number
|
||||
FROM room_lock_mappings m
|
||||
LEFT JOIN rooms r ON r.id = m.room_id
|
||||
@@ -273,11 +271,13 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
roomNumber: r.room_number,
|
||||
lockMac: r.lock_mac,
|
||||
lockName: r.lock_name,
|
||||
buildNo: r.build_no ?? 1,
|
||||
floorNo: r.floor_no ?? 1,
|
||||
}))
|
||||
})
|
||||
|
||||
// ── POST /api/hotels/:slug/ttlock/room-locks ─────────────────────────────────
|
||||
fastify.post<SlugParam & { Body: { room_id?: string; lock_mac: string; lock_name?: string } }>(
|
||||
fastify.post<SlugParam & { Body: { room_id?: string; lock_mac: string; lock_name?: string; build_no?: number; floor_no?: number } }>(
|
||||
'/api/hotels/:slug/ttlock/room-locks',
|
||||
{ onRequest: [fastify.authenticate] },
|
||||
async (req, reply) => {
|
||||
@@ -287,17 +287,17 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
const hotelId = await getHotelId(slug)
|
||||
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
|
||||
|
||||
const { room_id: roomId, lock_mac: lockMac, lock_name: lockName } = req.body
|
||||
const { room_id: roomId, lock_mac: lockMac, lock_name: lockName, build_no: buildNo, floor_no: floorNo } = req.body
|
||||
const mac = (lockMac ?? '').replace(/:/g, '').toUpperCase()
|
||||
if (!/^[0-9A-F]{12}$/.test(mac)) {
|
||||
return reply.code(400).send({ error: 'MAC-адрес должен быть 12 HEX-символов (напр. 42A6BBF5ECE5)' })
|
||||
}
|
||||
|
||||
await db.query(
|
||||
`INSERT INTO room_lock_mappings (hotel_id, room_id, lock_mac, lock_name)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (hotel_id, lock_mac) DO UPDATE SET room_id = $2, lock_name = $4`,
|
||||
[hotelId, roomId ?? null, mac, lockName ?? null],
|
||||
`INSERT INTO room_lock_mappings (hotel_id, room_id, lock_mac, lock_name, build_no, floor_no)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT (hotel_id, lock_mac) DO UPDATE SET room_id = $2, lock_name = $4, build_no = $5, floor_no = $6`,
|
||||
[hotelId, roomId ?? null, mac, lockName ?? null, buildNo ?? 1, floorNo ?? 1],
|
||||
)
|
||||
return { ok: true }
|
||||
},
|
||||
@@ -345,7 +345,7 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
|
||||
// MAC замка для этого номера
|
||||
const { rows: lockRows } = await db.query(
|
||||
'SELECT lock_mac, lock_name FROM room_lock_mappings WHERE hotel_id = $1 AND room_id = $2',
|
||||
'SELECT lock_mac, lock_name, build_no, floor_no FROM room_lock_mappings WHERE hotel_id = $1 AND room_id = $2',
|
||||
[hotelId, booking.room_id],
|
||||
)
|
||||
const lock = lockRows[0]
|
||||
@@ -394,24 +394,21 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
comPort = available[0].ttlock_com_port
|
||||
}
|
||||
|
||||
const checkIn = new Date(booking.check_in).getTime()
|
||||
const checkOut = new Date(booking.check_out).getTime()
|
||||
|
||||
// Отправляем команду агенту — он делает всё остальное через DLL
|
||||
let result: unknown
|
||||
try {
|
||||
result = await sendCommandAndWait(workstationId, {
|
||||
type: 'ttlock:write_card',
|
||||
lockMac: lock.lock_mac,
|
||||
comPort: comPort,
|
||||
cardSectors: cfg.card_sectors,
|
||||
clientId: cfg.client_id,
|
||||
clientSecret: cfg.client_secret,
|
||||
ttlockUsername: cfg.ttlock_username ?? '',
|
||||
ttlockPassword: cfg.ttlock_password ?? '',
|
||||
apiServer: cfg.api_server ?? 'https://euapi.ttlock.com',
|
||||
startDate: checkIn,
|
||||
endDate: checkOut,
|
||||
type: 'ttlock:write_card',
|
||||
lockMac: lock.lock_mac,
|
||||
comPort: comPort,
|
||||
buildNo: lock.build_no ?? 1,
|
||||
floorNo: lock.floor_no ?? 1,
|
||||
clientId: cfg.client_id,
|
||||
clientSecret: cfg.client_secret,
|
||||
apiServer: cfg.api_server ?? 'https://euapi.ttlock.com',
|
||||
endDate: checkOut,
|
||||
}, 30_000)
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'Агент не ответил'
|
||||
|
||||
Reference in New Issue
Block a user