Fix: add passport_issued_by/issue_date to guests table for full autocomplete

- Migration 014: add passport_issued_by, passport_issue_date to guests table
- guests.ts: include new fields in SELECT, INSERT, PATCH + encrypt/decrypt
- booking-guests.ts: sync issued_by/issue_date back to guests on update
- GuestApiType: add passportIssuedBy, passportIssueDate fields
- selectAcGuest: fill passport_issued_by and passport_issue_date from guest profile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 17:47:12 +03:00
parent 887c362c18
commit babd6e950e
5 changed files with 39 additions and 17 deletions

View File

@@ -0,0 +1,6 @@
-- Migration 014: добавляем поля выдачи паспорта в таблицу guests
-- чтобы при автозаполнении по серии+номеру подтягивались все паспортные данные
ALTER TABLE guests
ADD COLUMN IF NOT EXISTS passport_issued_by TEXT,
ADD COLUMN IF NOT EXISTS passport_issue_date TEXT;

View File

@@ -239,9 +239,11 @@ const bookingGuests: FastifyPluginAsync = async (fastify) => {
} }
addG('first_name', b.first_name) addG('first_name', b.first_name)
addG('last_name', b.last_name) addG('last_name', b.last_name)
if (b.passport_series !== undefined) addG('passport_series', encryptField(b.passport_series)) if (b.passport_series !== undefined) addG('passport_series', encryptField(b.passport_series))
if (b.passport_number !== undefined) addG('passport_number', encryptField(b.passport_number)) if (b.passport_number !== undefined) addG('passport_number', encryptField(b.passport_number))
if (b.birth_date !== undefined) addG('birth_date', encryptField(b.birth_date || null)) if (b.passport_issued_by !== undefined) addG('passport_issued_by', encryptField(b.passport_issued_by))
if (b.passport_issue_date !== undefined) addG('passport_issue_date', encryptField(b.passport_issue_date || null))
if (b.birth_date !== undefined) addG('birth_date', encryptField(b.birth_date || null))
addG('nationality', b.nationality) addG('nationality', b.nationality)
// Обновляем токен в профиле гостя // Обновляем токен в профиле гостя
if (b.passport_series !== undefined || b.passport_number !== undefined) { if (b.passport_series !== undefined || b.passport_number !== undefined) {

View File

@@ -8,6 +8,7 @@ type SlugIdParam = { Params: { slug: string; id: string } }
const GUEST_FIELDS = ` const GUEST_FIELDS = `
g.id, g.hotel_id, g.first_name, g.last_name, g.middle_name, g.email, g.phone, g.id, g.hotel_id, g.first_name, g.last_name, g.middle_name, g.email, g.phone,
g.passport, g.passport_series, g.passport_number, g.passport, g.passport_series, g.passport_number,
g.passport_issued_by, g.passport_issue_date,
g.birth_date, g.nationality, g.gender, g.city, g.notes, g.tags, g.birth_date, g.nationality, g.gender, g.city, g.notes, g.tags,
g.loyalty_tier, g.loyalty_points, g.rating, g.loyalty_tier, g.loyalty_points, g.rating,
g.created_at, g.updated_at` g.created_at, g.updated_at`
@@ -28,10 +29,12 @@ const STATS_JOIN = `
function decryptGuest(row: Record<string, unknown>) { function decryptGuest(row: Record<string, unknown>) {
return { return {
...row, ...row,
passport: decryptField(row.passport as string | null), passport: decryptField(row.passport as string | null),
passport_series: decryptField(row.passport_series as string | null), passport_series: decryptField(row.passport_series as string | null),
passport_number: decryptField(row.passport_number as string | null), passport_number: decryptField(row.passport_number as string | null),
birth_date: decryptField(row.birth_date as string | null), passport_issued_by: decryptField(row.passport_issued_by as string | null),
passport_issue_date: decryptField(row.passport_issue_date as string | null),
birth_date: decryptField(row.birth_date as string | null),
} }
} }
@@ -129,6 +132,7 @@ const guests: FastifyPluginAsync = async (fastify) => {
first_name: string; last_name: string; middle_name?: string first_name: string; last_name: string; middle_name?: string
email?: string; phone?: string email?: string; phone?: string
passport?: string; passport_series?: string; passport_number?: string passport?: string; passport_series?: string; passport_number?: string
passport_issued_by?: string; passport_issue_date?: string
birth_date?: string; nationality?: string; gender?: string; city?: string birth_date?: string; nationality?: string; gender?: string; city?: string
notes?: string; tags?: string[]; rating?: number notes?: string; tags?: string[]; rating?: number
} }>( } }>(
@@ -179,8 +183,9 @@ const guests: FastifyPluginAsync = async (fastify) => {
`INSERT INTO guests `INSERT INTO guests
(hotel_id, first_name, last_name, middle_name, email, phone, (hotel_id, first_name, last_name, middle_name, email, phone,
passport, passport_series, passport_number, passport_search_token, passport, passport_series, passport_number, passport_search_token,
passport_issued_by, passport_issue_date,
birth_date, nationality, gender, city, notes, tags, rating) birth_date, nationality, gender, city, notes, tags, rating)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19)
RETURNING *`, RETURNING *`,
[ [
hotelId, b.first_name, b.last_name, b.middle_name ?? null, hotelId, b.first_name, b.last_name, b.middle_name ?? null,
@@ -189,6 +194,8 @@ const guests: FastifyPluginAsync = async (fastify) => {
encryptField(b.passport_series ?? null), encryptField(b.passport_series ?? null),
encryptField(b.passport_number ?? null), encryptField(b.passport_number ?? null),
token, token,
encryptField(b.passport_issued_by ?? null),
encryptField(b.passport_issue_date ?? null),
encryptField(b.birth_date ?? null), encryptField(b.birth_date ?? null),
b.nationality ?? null, b.gender ?? null, b.city ?? null, b.nationality ?? null, b.gender ?? null, b.city ?? null,
b.notes ?? '', b.tags ?? [], b.rating ?? 3, b.notes ?? '', b.tags ?? [], b.rating ?? 3,
@@ -203,6 +210,7 @@ const guests: FastifyPluginAsync = async (fastify) => {
first_name: string; last_name: string; middle_name: string first_name: string; last_name: string; middle_name: string
email: string; phone: string email: string; phone: string
passport: string; passport_series: string; passport_number: string passport: string; passport_series: string; passport_number: string
passport_issued_by: string; passport_issue_date: string
birth_date: string; nationality: string; gender: string; city: string birth_date: string; nationality: string; gender: string; city: string
notes: string; tags: string[]; rating: number notes: string; tags: string[]; rating: number
}> }>( }> }>(
@@ -237,10 +245,12 @@ const guests: FastifyPluginAsync = async (fastify) => {
add('tags', b.tags) add('tags', b.tags)
add('rating', b.rating) add('rating', b.rating)
// Шифруем чувствительные поля // Шифруем чувствительные поля
if (b.passport !== undefined) add('passport', encryptField(b.passport)) if (b.passport !== undefined) add('passport', encryptField(b.passport))
if (b.passport_series !== undefined) add('passport_series', encryptField(b.passport_series)) if (b.passport_series !== undefined) add('passport_series', encryptField(b.passport_series))
if (b.passport_number !== undefined) add('passport_number', encryptField(b.passport_number)) if (b.passport_number !== undefined) add('passport_number', encryptField(b.passport_number))
if (b.birth_date !== undefined) add('birth_date', encryptField(b.birth_date || null)) if (b.passport_issued_by !== undefined) add('passport_issued_by', encryptField(b.passport_issued_by))
if (b.passport_issue_date !== undefined) add('passport_issue_date', encryptField(b.passport_issue_date || null))
if (b.birth_date !== undefined) add('birth_date', encryptField(b.birth_date || null))
// Обновляем токен если поменялся паспорт // Обновляем токен если поменялся паспорт
if (b.passport_series !== undefined || b.passport_number !== undefined) { if (b.passport_series !== undefined || b.passport_number !== undefined) {
// Читаем текущие значения если нужно // Читаем текущие значения если нужно

View File

@@ -141,11 +141,13 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
...f, ...f,
first_name: g.firstName, first_name: g.firstName,
last_name: g.lastName, last_name: g.lastName,
middle_name: g.middleName ?? undefined, middle_name: g.middleName ?? undefined,
birth_date: g.birthDate ?? undefined, birth_date: g.birthDate ?? undefined,
passport_series: g.passportSeries ?? undefined, passport_series: g.passportSeries ?? undefined,
passport_number: g.passportNumber ?? undefined, passport_number: g.passportNumber ?? undefined,
nationality: g.nationality ?? undefined, passport_issued_by: g.passportIssuedBy ?? undefined,
passport_issue_date: g.passportIssueDate ?? undefined,
nationality: g.nationality ?? undefined,
})) }))
setAcQuery(g.lastName) setAcQuery(g.lastName)
setAcResults([]) setAcResults([])

View File

@@ -469,6 +469,8 @@ export interface GuestApiType {
passport: string | null passport: string | null
passportSeries: string | null passportSeries: string | null
passportNumber: string | null passportNumber: string | null
passportIssuedBy: string | null
passportIssueDate: string | null
birthDate: string | null birthDate: string | null
nationality: string | null nationality: string | null
gender: string | null gender: string | null