feat: TTLock encoder per workstation (Option A)

- Migration 051: add ttlock_com_port to workstations, drop com_port/workstation_id from hotel_ttlock_config
- Backend workstations PATCH: accepts ttlock_com_port alongside name
- Backend ttlock: test/issue-card use per-workstation com_port; auto-select online encoder if workstationId not specified
- TTLockPage Step 2: per-workstation table with COM port input, Save, Test buttons per row
- BookingDetailPanel: auto-issue if 1 encoder online, picker modal if multiple
- api.ts: TTLockConfig/Update cleaned up, Workstation gets ttlockComPort, issueCard/testEncoder updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-31 23:40:29 +03:00
parent 45697b56c3
commit f837a84db5
7 changed files with 263 additions and 175 deletions

View File

@@ -345,8 +345,11 @@ export const api = {
req<Workstation[]>('GET', `/api/hotels/${slug}/workstations`),
create: (slug: string, name: string) =>
req<Workstation>('POST', `/api/hotels/${slug}/workstations`, { name }),
update: (slug: string, id: string, name: string) =>
req<Workstation>('PATCH', `/api/hotels/${slug}/workstations/${id}`, { name }),
update: (slug: string, id: string, data: { name?: string; ttlockComPort?: string | null }) =>
req<Workstation>('PATCH', `/api/hotels/${slug}/workstations/${id}`, {
name: data.name,
ttlock_com_port: data.ttlockComPort,
}),
remove: (slug: string, id: string) =>
req<void>('DELETE', `/api/hotels/${slug}/workstations/${id}`),
pairCode: (slug: string, id: string) =>
@@ -610,8 +613,8 @@ export const api = {
updateConfig: (slug: string, data: TTLockConfigUpdate) =>
req<{ ok: boolean }>('PATCH', `/api/hotels/${slug}/ttlock/config`, data),
testEncoder: (slug: string) =>
req<{ ok: boolean }>('POST', `/api/hotels/${slug}/ttlock/test`),
testEncoder: (slug: string, workstationId: string) =>
req<{ ok: boolean }>('POST', `/api/hotels/${slug}/ttlock/test`, { workstationId }),
getRoomLocks: (slug: string) =>
req<RoomLockMapping[]>('GET', `/api/hotels/${slug}/ttlock/room-locks`),
@@ -622,8 +625,8 @@ export const api = {
unmapRoom: (slug: string, roomId: string) =>
req<{ ok: boolean }>('DELETE', `/api/hotels/${slug}/ttlock/room-locks/${roomId}`),
issueCard: (slug: string, bookingId: string) =>
req<CardIssuanceResult>('POST', `/api/hotels/${slug}/bookings/${bookingId}/issue-card`),
issueCard: (slug: string, bookingId: string, workstationId?: string) =>
req<CardIssuanceResult>('POST', `/api/hotels/${slug}/bookings/${bookingId}/issue-card`, workstationId ? { workstationId } : undefined),
getCards: (slug: string, bookingId: string) =>
req<CardIssuance[]>('GET', `/api/hotels/${slug}/bookings/${bookingId}/cards`),
@@ -1076,25 +1079,22 @@ export interface Workstation {
isOnline: boolean
lastSeen: string | null
agentVersion?: string
ttlockComPort?: string | null
createdAt: string
devices: WorkstationDevice[] | null
}
export interface TTLockConfig {
isEnabled: boolean
clientId: string
cardSectors: string
comPort: string
workstationId: string | null
isEnabled: boolean
clientId: string
cardSectors: string
}
export interface TTLockConfigUpdate {
isEnabled?: boolean
clientId?: string
clientSecret?: string
cardSectors?: string
comPort?: string
workstationId?: string | null
isEnabled?: boolean
clientId?: string
clientSecret?: string
cardSectors?: string
}
export interface RoomLockMapping {