diff --git a/backend/migrations/053_ttlock_api_server.sql b/backend/migrations/053_ttlock_api_server.sql new file mode 100644 index 0000000..9be7aab --- /dev/null +++ b/backend/migrations/053_ttlock_api_server.sql @@ -0,0 +1,3 @@ +-- TTLock: выбор сервера API (EU или China) +ALTER TABLE hotel_ttlock_config + ADD COLUMN IF NOT EXISTS api_server TEXT NOT NULL DEFAULT 'https://euopen.ttlock.com'; diff --git a/backend/src/routes/ttlock.ts b/backend/src/routes/ttlock.ts index e0457f2..3f3d192 100644 --- a/backend/src/routes/ttlock.ts +++ b/backend/src/routes/ttlock.ts @@ -47,17 +47,18 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => { if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' }) const { rows } = await db.query( - `SELECT is_enabled, client_id, card_sectors FROM hotel_ttlock_config WHERE hotel_id = $1`, + `SELECT is_enabled, client_id, card_sectors, api_server FROM hotel_ttlock_config WHERE hotel_id = $1`, [hotelId], ) if (!rows[0]) { - return { isEnabled: false, clientId: '', cardSectors: '1,2,3,4,5,6,7,8,9,10' } + return { isEnabled: false, clientId: '', cardSectors: '1,2,3,4,5,6,7,8,9,10', apiServer: 'https://euopen.ttlock.com' } } const r = rows[0] return { isEnabled: r.is_enabled, clientId: r.client_id, cardSectors: r.card_sectors, + apiServer: r.api_server ?? 'https://euopen.ttlock.com', } }) @@ -68,6 +69,7 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => { client_id?: string client_secret?: string card_sectors?: string + api_server?: string } }>('/api/hotels/:slug/ttlock/config', { onRequest: [fastify.authenticate] }, async (req, reply) => { const { slug } = req.params @@ -76,16 +78,17 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => { const hotelId = await getHotelId(slug) if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' }) - const { is_enabled: isEnabled, client_id: clientId, client_secret: clientSecret, card_sectors: cardSectors } = req.body + const { is_enabled: isEnabled, client_id: clientId, client_secret: clientSecret, card_sectors: cardSectors, api_server: apiServer } = req.body await db.query( - `INSERT INTO hotel_ttlock_config (hotel_id, is_enabled, client_id, client_secret, card_sectors) - VALUES ($1, $2, $3, $4, $5) + `INSERT INTO hotel_ttlock_config (hotel_id, is_enabled, client_id, client_secret, card_sectors, api_server) + VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (hotel_id) DO UPDATE SET is_enabled = COALESCE($2, hotel_ttlock_config.is_enabled), client_id = COALESCE($3, hotel_ttlock_config.client_id), client_secret = COALESCE(NULLIF($4, ''), hotel_ttlock_config.client_secret), card_sectors = COALESCE($5, hotel_ttlock_config.card_sectors), + api_server = COALESCE($6, hotel_ttlock_config.api_server), updated_at = NOW()`, [ hotelId, @@ -93,6 +96,7 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => { clientId ?? null, clientSecret ?? '', cardSectors ?? null, + apiServer ?? null, ], ) return { ok: true } @@ -322,6 +326,7 @@ const ttlockRoutes: FastifyPluginAsync = async (fastify) => { cardSectors: cfg.card_sectors, clientId: cfg.client_id, clientSecret: cfg.client_secret, + apiServer: cfg.api_server ?? 'https://euopen.ttlock.com', startDate: checkIn, endDate: checkOut, }, 30_000) diff --git a/src/lib/api.ts b/src/lib/api.ts index db1e663..8b04f03 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1091,6 +1091,7 @@ export interface TTLockConfig { isEnabled: boolean clientId: string cardSectors: string + apiServer: string } export interface TTLockConfigUpdate { @@ -1098,6 +1099,7 @@ export interface TTLockConfigUpdate { clientId?: string clientSecret?: string cardSectors?: string + apiServer?: string } export interface RoomLockMapping { diff --git a/src/pages/TTLockPage.tsx b/src/pages/TTLockPage.tsx index 365a943..88b7551 100644 --- a/src/pages/TTLockPage.tsx +++ b/src/pages/TTLockPage.tsx @@ -142,6 +142,7 @@ export function TTLockPage() { const [clientId, setClientId] = useState('') const [clientSecret, setClientSecret] = useState('') const [cardSectors, setCardSectors] = useState('1,2,3,4,5,6,7,8,9,10') + const [apiServer, setApiServer] = useState('https://euopen.ttlock.com') // Per-workstation COM port state const [wsComPorts, setWsComPorts] = useState>({}) @@ -241,6 +242,7 @@ export function TTLockPage() { setIsEnabled(cfg.isEnabled) setClientId(cfg.clientId) setCardSectors(cfg.cardSectors || '1,2,3,4,5,6,7,8,9,10') + setApiServer(cfg.apiServer || 'https://euopen.ttlock.com') setMappings(maps) setRooms(roomList) setWorkstations(wsList) @@ -263,6 +265,7 @@ export function TTLockPage() { clientId: clientId.trim(), clientSecret: clientSecret.trim() || undefined, cardSectors: cardSectors.trim(), + apiServer: apiServer, }) showSuccess('Настройки сохранены') setClientSecret('') @@ -391,6 +394,41 @@ export function TTLockPage() { + {/* Сервер TTLock API */} +
+ +
+ {[ + { value: 'https://euopen.ttlock.com', label: 'EU', hint: 'euopen.ttlock.com' }, + { value: 'https://open.ttlock.com', label: 'China', hint: 'open.ttlock.com' }, + ].map(opt => ( + + ))} +
+

+ Если получаете ошибку 404 при выдаче карты — попробуйте другой сервер. +

+
+ {/* Секторы карты — визуальный пикер */}