feat: checklists, minibar and deposit modules
- DB migrations 057-059: checklist_templates/items/completions, minibar_items/consumptions, hotel_deposit_settings, booking_deposits - Backend routes: checklists (templates CRUD + task completions), minibar (items + consumptions), deposit (settings, cash/yookassa hold, release/capture) - YooKassa service for hold/capture/cancel payments - Frontend: ChecklistSettingsPage, MinibarSettingsPage, DepositSettingsPage - HousekeepingPage: task cards now show checklist + minibar panel with checkboxes and quantity buttons - BookingModal: minibar charges summary + deposit management (cash/YooKassa/release) for existing bookings - Sidebar + App.tsx: new routes /settings/checklists, /settings/minibar, /settings/deposit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
217
src/lib/api.ts
217
src/lib/api.ts
@@ -640,6 +640,118 @@ export const api = {
|
||||
getCards: (slug: string, bookingId: string) =>
|
||||
req<CardIssuance[]>('GET', `/api/hotels/${slug}/bookings/${bookingId}/cards`),
|
||||
},
|
||||
|
||||
// ── Checklists ────────────────────────────────────────────────────────────
|
||||
checklists: {
|
||||
listTemplates: (slug: string) =>
|
||||
req<ChecklistTemplate[]>('GET', `/api/hotels/${slug}/checklist-templates`),
|
||||
|
||||
createTemplate: (slug: string, data: { name: string; taskType?: string }) =>
|
||||
req<ChecklistTemplate>('POST', `/api/hotels/${slug}/checklist-templates`, {
|
||||
name: data.name,
|
||||
task_type: data.taskType ?? null,
|
||||
}),
|
||||
|
||||
updateTemplate: (slug: string, templateId: string, data: { name?: string; taskType?: string | null; isActive?: boolean; sortOrder?: number }) =>
|
||||
req<ChecklistTemplate>('PATCH', `/api/hotels/${slug}/checklist-templates/${templateId}`, {
|
||||
name: data.name,
|
||||
task_type: data.taskType,
|
||||
is_active: data.isActive,
|
||||
sort_order: data.sortOrder,
|
||||
}),
|
||||
|
||||
deleteTemplate: (slug: string, templateId: string) =>
|
||||
req<void>('DELETE', `/api/hotels/${slug}/checklist-templates/${templateId}`),
|
||||
|
||||
addItem: (slug: string, templateId: string, data: { text: string; sortOrder?: number }) =>
|
||||
req<ChecklistItem>('POST', `/api/hotels/${slug}/checklist-templates/${templateId}/items`, {
|
||||
text: data.text,
|
||||
sort_order: data.sortOrder ?? 0,
|
||||
}),
|
||||
|
||||
updateItem: (slug: string, templateId: string, itemId: string, data: { text?: string; sortOrder?: number }) =>
|
||||
req<ChecklistItem>('PATCH', `/api/hotels/${slug}/checklist-templates/${templateId}/items/${itemId}`, {
|
||||
text: data.text,
|
||||
sort_order: data.sortOrder,
|
||||
}),
|
||||
|
||||
deleteItem: (slug: string, templateId: string, itemId: string) =>
|
||||
req<void>('DELETE', `/api/hotels/${slug}/checklist-templates/${templateId}/items/${itemId}`),
|
||||
|
||||
getTaskChecklist: (slug: string, taskId: string) =>
|
||||
req<TaskChecklist>('GET', `/api/hotels/${slug}/housekeeping/${taskId}/checklist`),
|
||||
|
||||
completeItem: (slug: string, taskId: string, itemId: string) =>
|
||||
req<{ id: string }>('POST', `/api/hotels/${slug}/housekeeping/${taskId}/checklist/${itemId}/complete`),
|
||||
|
||||
uncompleteItem: (slug: string, taskId: string, itemId: string) =>
|
||||
req<void>('DELETE', `/api/hotels/${slug}/housekeeping/${taskId}/checklist/${itemId}/complete`),
|
||||
},
|
||||
|
||||
// ── Minibar ───────────────────────────────────────────────────────────────
|
||||
minibar: {
|
||||
listItems: (slug: string) =>
|
||||
req<MinibarItem[]>('GET', `/api/hotels/${slug}/minibar-items`),
|
||||
|
||||
createItem: (slug: string, data: { name: string; price: number; category?: string; sortOrder?: number }) =>
|
||||
req<MinibarItem>('POST', `/api/hotels/${slug}/minibar-items`, {
|
||||
name: data.name,
|
||||
price: data.price,
|
||||
category: data.category,
|
||||
sort_order: data.sortOrder ?? 0,
|
||||
}),
|
||||
|
||||
updateItem: (slug: string, itemId: string, data: { name?: string; price?: number; category?: string; sortOrder?: number; isActive?: boolean }) =>
|
||||
req<MinibarItem>('PATCH', `/api/hotels/${slug}/minibar-items/${itemId}`, {
|
||||
name: data.name,
|
||||
price: data.price,
|
||||
category: data.category,
|
||||
sort_order: data.sortOrder,
|
||||
is_active: data.isActive,
|
||||
}),
|
||||
|
||||
deleteItem: (slug: string, itemId: string) =>
|
||||
req<void>('DELETE', `/api/hotels/${slug}/minibar-items/${itemId}`),
|
||||
|
||||
getTaskConsumptions: (slug: string, taskId: string) =>
|
||||
req<MinibarConsumption[]>('GET', `/api/hotels/${slug}/housekeeping/${taskId}/minibar`),
|
||||
|
||||
addConsumption: (slug: string, taskId: string, data: { itemId: string; quantity: number }) =>
|
||||
req<MinibarConsumption>('POST', `/api/hotels/${slug}/housekeeping/${taskId}/minibar`, {
|
||||
item_id: data.itemId,
|
||||
quantity: data.quantity,
|
||||
}),
|
||||
|
||||
deleteConsumption: (slug: string, consumptionId: string) =>
|
||||
req<void>('DELETE', `/api/hotels/${slug}/minibar-consumptions/${consumptionId}`),
|
||||
|
||||
getBookingMinibar: (slug: string, bookingId: string) =>
|
||||
req<MinibarBookingCharge[]>('GET', `/api/hotels/${slug}/bookings/${bookingId}/minibar`),
|
||||
},
|
||||
|
||||
// ── Deposits ──────────────────────────────────────────────────────────────
|
||||
deposits: {
|
||||
getSettings: (slug: string) =>
|
||||
req<DepositSettings>('GET', `/api/hotels/${slug}/deposit/settings`),
|
||||
|
||||
updateSettings: (slug: string, data: Partial<DepositSettingsPayload>) =>
|
||||
req<DepositSettings>('PATCH', `/api/hotels/${slug}/deposit/settings`, data),
|
||||
|
||||
getBookingDeposit: (slug: string, bookingId: string) =>
|
||||
req<BookingDeposit>('GET', `/api/hotels/${slug}/bookings/${bookingId}/deposit`),
|
||||
|
||||
payByCash: (slug: string, bookingId: string) =>
|
||||
req<BookingDeposit>('POST', `/api/hotels/${slug}/bookings/${bookingId}/deposit/cash`),
|
||||
|
||||
createYookassaHold: (slug: string, bookingId: string) =>
|
||||
req<BookingDeposit & { confirmationUrl: string | null }>('POST', `/api/hotels/${slug}/bookings/${bookingId}/deposit/yookassa`),
|
||||
|
||||
release: (slug: string, bookingId: string, capturedAmount: number, reason?: string) =>
|
||||
req<BookingDeposit>('POST', `/api/hotels/${slug}/bookings/${bookingId}/deposit/release`, {
|
||||
captured_amount: capturedAmount,
|
||||
reason,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
// ── Schedule ─────────────────────────────────────────────────────────────────
|
||||
@@ -1153,6 +1265,111 @@ export interface WifiSettings {
|
||||
sessionHours: number
|
||||
}
|
||||
|
||||
// ── Checklist types ──────────────────────────────────────────────────────────
|
||||
|
||||
export interface ChecklistItem {
|
||||
id: string
|
||||
templateId: string
|
||||
text: string
|
||||
sortOrder: number
|
||||
}
|
||||
|
||||
export interface ChecklistTemplate {
|
||||
id: string
|
||||
hotelId: string
|
||||
name: string
|
||||
taskType: string | null
|
||||
isActive: boolean
|
||||
sortOrder: number
|
||||
createdAt: string
|
||||
items: ChecklistItem[]
|
||||
}
|
||||
|
||||
export interface TaskChecklistItem {
|
||||
id: string
|
||||
text: string
|
||||
sortOrder: number
|
||||
completedAt: string | null
|
||||
completedBy: string | null
|
||||
}
|
||||
|
||||
export interface TaskChecklist {
|
||||
templateId: string | null
|
||||
templateName: string | null
|
||||
items: TaskChecklistItem[]
|
||||
}
|
||||
|
||||
// ── Minibar types ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface MinibarItem {
|
||||
id: string
|
||||
hotelId: string
|
||||
name: string
|
||||
price: number
|
||||
category: string | null
|
||||
isActive: boolean
|
||||
sortOrder: number
|
||||
}
|
||||
|
||||
export interface MinibarConsumption {
|
||||
id: string
|
||||
hotelId: string
|
||||
roomId: string
|
||||
bookingId: string | null
|
||||
taskId: string | null
|
||||
itemId: string
|
||||
itemName: string
|
||||
quantity: number
|
||||
pricePerUnit: number
|
||||
recordedByName: string | null
|
||||
recordedAt: string
|
||||
}
|
||||
|
||||
export interface MinibarBookingCharge {
|
||||
id: string
|
||||
itemName: string
|
||||
quantity: number
|
||||
pricePerUnit: number
|
||||
total: number
|
||||
recordedAt: string
|
||||
recordedByName: string | null
|
||||
}
|
||||
|
||||
// ── Deposit types ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface DepositSettings {
|
||||
hotelId: string
|
||||
isEnabled: boolean
|
||||
amount: number
|
||||
yookassaShopId: string | null
|
||||
yookassaSecretKey: string | null
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
export interface DepositSettingsPayload {
|
||||
is_enabled?: boolean
|
||||
amount?: number
|
||||
yookassa_shop_id?: string
|
||||
yookassa_secret_key?: string
|
||||
}
|
||||
|
||||
export interface BookingDeposit {
|
||||
id: string
|
||||
hotelId: string
|
||||
bookingId: string
|
||||
amount: number
|
||||
status: string
|
||||
paymentMethod: string | null
|
||||
yookassaPaymentId: string | null
|
||||
yookassaConfirmationUrl: string | null
|
||||
capturedAmount: number | null
|
||||
retentionReason: string | null
|
||||
guestEmailSent: boolean
|
||||
createdAt: string
|
||||
paidAt: string | null
|
||||
releasedAt: string | null
|
||||
}
|
||||
|
||||
function toHotelPayload(h: HotelPayload): Record<string, unknown> {
|
||||
const out: Record<string, unknown> = {}
|
||||
if (h.name !== undefined) out.name = h.name
|
||||
|
||||
Reference in New Issue
Block a user