Admin Settings Inventory — Mobile Planning
Mục đích: liệt kê toàn bộ cấu hình / dữ liệu mà salon owner đang quản lý trên
booking-web/adminđể lên plan mobile app (owner + staff). Mỗi mục ghi rõ: nguồn dữ liệu, API endpoint, đang/không enforce ở đâu, mức ưu tiên khi đưa lên mobile.Đọc cùng:
docs/flows/booking-flow.md— settings nào enforce ở đâubooking-api/prisma/schema.prisma— DB source of truthbooking-api/src/core/tenant/tenant.dto.ts— DTO contractdocs/mobile/feature-plan.md— plan feature mobile
Quy ước ưu tiên:
- P0 — MVP mobile (phải có trong bản đầu tiên)
- P1 — V1 (có sau 1–2 sprint, sau khi MVP chạy)
- P2 — V2+ (để sau, desktop admin vẫn phục vụ được)
- ❌ Desktop-only (không nên đưa lên mobile, quá phức tạp hoặc ít dùng)
0. Tổng quan tầng cài đặt
Hệ thống có 4 tầng cấu hình, tất cả thuộc booking-web/admin:
| Tầng | Scope | Model DB | Ai quản lý |
|---|---|---|---|
| Tenant-level settings | Toàn salon | Tenant.settings (JSON), Tenant.branding (JSON), Tenant.address (JSON) |
Owner |
| Catalog | Dịch vụ, thuế, kế toán | Service, ServiceCategory, Tax, AccountingAccount |
Owner |
| Resources | Staff + lịch làm việc + time-off | Resource, ResourceSchedule, ResourceScheduleOverride, TimeOff, ResourceSkill |
Owner |
| Payment config | Gateway integration | TenantPaymentConfig |
Owner (cần trợ giúp kỹ thuật) |
| Loyalty | Chương trình khách hàng trung thành | LoyaltyCard |
Owner |
UI entry: /admin/settings (8 tabs), /admin/staff, /admin/services, /admin/loyalty, /admin/work-schedule.
1. Settings — General tab
Route: /admin/settings?tab=general
Component: booking-web/src/components/settings/GeneralSection.tsx
API: GET /api/tenants/me, PATCH /api/tenants/:id
| Field | Type | Mặc định | Enforce | Mobile |
|---|---|---|---|---|
name |
string | — | — | P0 |
slug |
string (kebab-case) | — | unique, chỉ đổi được qua superadmin | ❌ Read-only |
industryType |
enum (beauty|nail|barbershop) | beauty |
quyết định default settings + labels | P1 (read-only trước, edit có confirm) |
settings.organizationName |
string? | null | — | P1 |
settings.currency |
ISO 4217 (3 chữ) | NOK |
toàn bộ money format | P0 (read-only) |
settings.timezone |
IANA tz | Europe/Oslo |
mọi so sánh giờ làm việc | P0 (read-only) |
💡 Lưu ý mobile:
slug,currency,timezonelà critical data — không nên cho đổi trên mobile vì breaking change với bookings cũ. Chỉ cho xem.
2. Settings — Booking tab (Booking Policy)
Route: /admin/settings?tab=booking
Component: booking-web/src/components/settings/BookingSection.tsx + BookingPolicyEditor
API: PATCH /api/tenants/:id (nested trong settings)
| Field | Type | Mặc định | Ý nghĩa | Mobile |
|---|---|---|---|---|
walkInEnabled |
boolean | true | Cho phép tạo booking walk-in trên admin | P0 |
autoConfirm |
boolean | false (beauty/nail), true (barbershop) | Booking online tự CONFIRMED thay vì PENDING | P0 |
bookingMode |
assigned_only | allow_unassigned |
allow_unassigned |
Khách book không chọn staff được không | P0 |
allowDoubleBooking |
boolean | false | Cho phép 2 booking trùng giờ cùng 1 staff | P1 |
cancellationHours |
int 0–168 | 24 (beauty), 2 (barbershop) | Số giờ trước giờ hẹn được cancel miễn phí | P0 |
maxBookingDaysInAdvance |
int 1–365 | 30 | Khách book trước tối đa bao nhiêu ngày | P1 |
posEnabled |
boolean | false | Bật POS mode (chưa dùng) | ❌ Không triển khai MVP |
depositEnabled |
boolean | false | Yêu cầu đặt cọc khi book | P1 |
depositType |
percentage | fixed |
percentage |
Cọc theo % tổng hay số cố định | P1 |
depositValue |
int | 0 | Giá trị cọc (0–100 nếu %, øre nếu fixed) | P1 |
Enforce matrix (đã document chi tiết trong docs/flows/booking-flow.md):
autoConfirm→ public booking API set statusbookingMode→ public booking validate resourceIdcancellationHours→ customer cancel endpointmaxBookingDaysInAdvance→ availability API + create bookingdepositEnabled/type/value→ payment flow (dispatch deposit command)
⚠️ Cảnh báo chéo: Nếu
depositEnabled=truenhưng chưa cóTenantPaymentConfigactive → onboarding đã cảnh báo. Mobile cũng phải show cảnh báo này.
3. Settings — Business Hours tab
Route: /admin/settings?tab=businessHours
Component: booking-web/src/components/settings/BusinessHoursSection.tsx + shared business-hours editor
API: PATCH /api/tenants/:id (settings.businessHours)
Schema (lưu trong Tenant.settings.businessHours):
[
{
"dayOfWeek": 1, // 0=Sunday, 6=Saturday
"isOpen": true,
"slots": [
{ "startTime": "09:00", "endTime": "12:00" },
{ "startTime": "13:00", "endTime": "17:00" } // multi-slot, break giữa trưa
]
}
]
| Feature | Desktop đã có | Mobile |
|---|---|---|
| 7 ngày × toggle mở/đóng | ✅ | P0 |
| Multi-slot / ngày | ✅ | P0 |
| Copy 1 ngày → ngày khác | ✅ | P1 |
Copy all → staff schedules (applyToStaff: true in UpdateTenantDto) |
✅ | P1 (cần confirm dialog) |
💡 Editor pattern: đã có shared editor ở
booking-web/src/components/business-hours/. Mobile nên thiết kế native picker tối ưu cho ngón tay (tap & hold range slider).
4. Settings — Branding tab
Route: /admin/settings?tab=branding
Component: booking-web/src/components/settings/BrandingSection.tsx
API: PATCH /api/tenants/:id (branding)
| Field | Type | Mobile |
|---|---|---|
branding.primaryColor |
hex (#RRGGBB) | P1 |
branding.secondaryColor |
hex | P1 |
branding.logoUrl |
URL (MinIO upload) | P1 (cần expo-image-picker + upload) |
branding.coverUrl |
URL (MinIO upload) | P1 |
💡 Upload: API đã có upload endpoint qua MinIO. Mobile cần
expo-image-picker+ resize trước khi POST.
5. Settings — About tab
Route: /admin/settings?tab=about
Component: booking-web/src/components/settings/AboutSection.tsx
API: PATCH /api/tenants/:id (description, HTML)
description(HTML, rich text via Tiptap, sanitize-html ở backend).- Mobile: P2. Rich text editor trên mobile phức tạp — tạm thời cho nhập plain text + markdown hoặc defer.
6. Settings — Location tab
Route: /admin/settings?tab=location
Component: booking-web/src/components/settings/LocationSection.tsx + AddressSearch, LocationMap
API: PATCH /api/tenants/:id (address)
| Field | Type | Mobile |
|---|---|---|
address.street |
string | P0 (read-only trước, edit sau) |
address.city |
string | P0 |
address.postalCode |
string | P0 |
address.country |
string | P0 |
address.latitude |
number | P1 |
address.longitude |
number | P1 |
- Desktop dùng Nominatim (OpenStreetMap) geocoding + pigeon-maps interactive map.
- Mobile: có thể tận dụng native map (
react-native-maps) + location picker. P1 cho edit, P0 cho hiển thị.
7. Settings — Tax & Accounting tab
Route: /admin/settings?tab=tax
Components: TaxSettings.tsx, AccountingSettings.tsx, TaxFormModal.tsx, AccountFormModal.tsx
API: GET/POST/PATCH/DELETE /api/taxes, /api/accounting-accounts
7.1 Tax rates
| Field | Type | Mobile |
|---|---|---|
name |
string ("25%") |
P2 |
rate |
float (percent) | P2 |
isDefault |
boolean | P2 |
isActive |
boolean | P2 |
Seed mặc định (Na Uy): 25% (default), 15%, 0% — xem booking-api/src/core/tenant/tenant-defaults.ts.
7.2 Accounting accounts
| Field | Type | Mobile |
|---|---|---|
code |
string ("3000") |
P2 |
name |
string | P2 |
taxId |
FK → Tax | P2 |
isDefault |
boolean | P2 |
❌ Đánh giá: Tax + Accounting là accountant territory, owner thường setup 1 lần rồi quên. Mobile không cần trừ khi đưa vào flow "tạo service" (cần chọn accounting account).
8. Settings — Payment tab
Route: /admin/settings?tab=payment
Components: PaymentSettings.tsx, ProviderCard, ProviderConfigDrawer, BamboraConfigForm, HealthCheckBadge
API: GET/POST/PATCH/DELETE /api/admin/payment-configs, POST .../:id/rotate, POST .../:id/activate, POST .../:id/health-check
| Feature | Desktop | Mobile |
|---|---|---|
| List provider configs (Bambora, Worldline, Stripe, Vipps, Nets, Adyen) | ✅ | P1 (list + toggle active) |
| Create/edit credentials (encrypted) | ✅ | ❌ Desktop-only (nhập API key phức tạp, an toàn) |
| Rotate encryption key | ✅ | ❌ |
| Activate / deactivate provider | ✅ | P1 |
| Health check | ✅ | P1 (quan trọng khi on-site đi xem provider có chạy không) |
| Display name, test/prod mode | ✅ | P1 |
⚠️ Security: credentials chỉ nhập từ desktop (đã có khuyến nghị trong memory "Payment Implementation"). Mobile chỉ nên: xem status, toggle active, health check.
9. Services & Categories (Catalog)
Route: /admin/services
Components: ServicesContent.tsx, ServiceList.tsx, ServiceFormModal.tsx, CategoryManager.tsx, AddCategoryModal.tsx
API: /api/services, /api/service-categories
9.1 Service
| Field | Type | Mobile |
|---|---|---|
name |
string | P0 |
description |
string? | P0 |
duration |
int (phút) | P0 |
price |
int (øre, minor unit) | P0 |
currency |
ISO 4217 | P0 (auto từ tenant) |
categoryId |
FK? | P0 |
accountingAccountId |
FK? | P1 |
isActive |
boolean | P0 |
sortOrder |
int | P1 |
metadata |
JSON (industry-specific) | P1 |
9.2 ServiceCategory
| Field | Mobile |
|---|---|
name |
P0 |
sortOrder |
P1 |
💡 Mobile nên có quick-create service ngay từ booking drawer (khi staff/owner tạo booking và chưa có dịch vụ trong catalog).
10. Staff / Resources
Route: /admin/staff
Components: StaffContent.tsx, StaffList.tsx, StaffFormModal.tsx, MetadataFields.tsx
API: /api/resources, /api/resources/:id/skills
| Field | Type | Mobile |
|---|---|---|
type |
string (staff/room/equipment) | P0 (default staff) |
name |
string | P0 |
color |
hex? | P0 |
imageUrl |
string? | P1 (camera upload) |
description |
string? | P1 |
isActive |
boolean | P0 |
isBookableOnline |
boolean | P0 |
sortOrder |
int | P1 |
metadata.jobTitle, metadata.phone, metadata.email, metadata.commission |
JSON | P1 |
userId |
FK User? | P1 (link staff → login user) |
| Skills (assigned services) | ResourceSkill[] |
P0 |
💡 Industry-aware label: hiển thị "Barbers" / "Therapists" / "Staff" theo
industryType. DùnguseResourceLabel()hook ở web — mobile cần port logic tương đương.
11. Work Schedule (Staff lịch làm việc)
Route: /admin/work-schedule
Components: WorkScheduleContent.tsx, ScheduleGrid.tsx, ScheduleCellEditor.tsx, RecurringScheduleModal.tsx, TimeOffModal.tsx
API: /api/resources/:id/schedules, /api/resources/:id/schedule-overrides, /api/time-offs
11.1 ResourceSchedule (weekly recurring)
- 1 staff × 7 ngày × multi-slot (
{ dayOfWeek, startTime, endTime, isActive }). - Mobile P0 — staff cần xem & owner cần sửa nhanh.
11.2 ResourceScheduleOverride (override 1 ngày cụ thể)
{ resourceId, date, startTime?, endTime?, reason? }—null= ngày nghỉ đặc biệt.- Mobile P0 — ví dụ owner duyệt nghỉ cho 1 staff trong ngày.
11.3 TimeOff (vacation / sick)
| Field | Type | Mobile |
|---|---|---|
type |
vacation/sick/personal/other | P0 |
startDate / endDate |
date | P0 |
startTime / endTime |
string? (null = full day) | P0 |
description |
string? (max 100) | P1 |
isApproved |
boolean | P1 (owner approval flow) |
💡 Staff request → Owner approve là flow rất tự nhiên trên mobile. Nên làm sớm.
12. Loyalty Programs
Route: /admin/loyalty
Components: LoyaltyContent.tsx, LoyaltyList.tsx, LoyaltyFormModal.tsx
API: /api/loyalty/cards, /api/loyalty/stamps, /api/loyalty/redemptions, /api/loyalty/points
| Field | Type | Mobile |
|---|---|---|
name |
string | P2 |
type |
VISIT_BASED | POINTS_BASED |
P2 |
requiredVisits, rewardType, rewardValue (visit-based) |
int/enum | P2 |
earnRate, redeemRate, expiryMonths, minRedemption (points) |
int | P2 |
serviceIds |
string[] (empty = all) | P2 |
isActive |
boolean | P2 |
❌ Setup loyalty chỉ cần làm 1 lần — defer mobile. Nhưng xem tình trạng stamps/points của khách khi tra profile → P0.
13. Customers
Route: /admin/customers
Components: CustomersContent.tsx, CustomerList.tsx, CustomerFormModal.tsx
API: /api/customers, /api/tenant-customers
| Field | Mobile |
|---|---|
name, email, phone, notes |
P0 |
TenantCustomer.tags[], notes, visitCount, lastVisit, totalSpent |
P0 |
| Booking history per customer | P0 |
| Loyalty stamps / points balance | P0 |
💡 Customer profile trên mobile = màn hình quan trọng cho staff (check lịch sử trước cuộc hẹn, ghi note sau).
14. Bookings
Route: /admin/bookings
Components: Calendar (BookingCalendar.tsx, StaffColumn.tsx, BookingBlock.tsx, useDragBooking.ts), List (BookingList.tsx), Drawer (BookingDrawer.tsx, ServicePicker.tsx, AddedServiceCard.tsx)
API: /api/bookings, /api/availability, /api/bookings/:id/status/:status
Tính năng đã có (desktop)
- Day/Week calendar view, drag-drop reschedule, staff column layout.
- Multi-service booking (1 booking nhiều items với staff riêng, thời gian riêng).
- Status machine: PENDING → CONFIRMED → ARRIVED → IN_PROGRESS → COMPLETED / CANCELLED / NO_SHOW.
- Conflict detection (409 on overlap).
- Audit log (ai làm gì khi nào).
- Payment summary card (deposit, remaining, refund).
- Loyalty redemption apply.
📱 Chi tiết phân rã cho mobile ở
feature-plan.md.
15. Payments (Transactions)
Route: /admin/payments
Components: PaymentsContent.tsx, PaymentList.tsx, PaymentDetailDrawer.tsx, CaptureDialog.tsx, CollectRemainingModal.tsx, RefundDialog.tsx, VoidDialog.tsx
API: /api/admin/payments/*
- List + filter by status (INITIATED, AUTHORIZED, CAPTURED, ...).
- Detail drawer: events, state machine, provider refs.
- Actions: capture, void, refund (partial), collect remaining.
- Mobile: P1 (xem list + capture/refund common cases), action chi tiết ưu tiên desktop.
16. Onboarding Wizard
Route: /admin/onboarding (full-page, guards OWNER nếu tenant.onboardedAt == null)
Spec: 7 bước — Welcome → Salon info → Business hours → Services → Staff → Booking policy → Review & Launch.
💡 Mobile: mobile app cũng cần support onboarding cho owner lần đầu đăng ký qua app (ví dụ download app trên store, tạo tài khoản). P1 — MVP có thể force onboard qua web.
17. Dashboard
Route: /admin (default)
Components: DashboardContent.tsx, DashboardMetrics.tsx, MetricCard.tsx, TodayTimeline.tsx, UpcomingBookings.tsx, QuickActions.tsx.
| Widget | Mobile |
|---|---|
| Metrics (booking today, revenue today, ...) | P0 |
| Today timeline (gantt-like timeline booking hôm nay) | P0 |
| Upcoming bookings (next 5) | P0 |
| Quick actions (new booking, walk-in, ...) | P0 |
18. Profile (Admin user settings)
Route: /admin/profile
API: /api/auth/me, /api/auth/change-password
- Email, phone, password. P0 cho mobile.
19. Ma trận ưu tiên — cheatsheet
| Area | P0 (MVP) | P1 (V1) | P2+ / Desktop-only |
|---|---|---|---|
| General info | name, currency/tz read-only | organizationName | slug, industryType edit |
| Booking policy | walkIn, autoConfirm, bookingMode, cancellationHours | deposit*, maxBookingDays, allowDoubleBooking | posEnabled |
| Business hours | 7-day × multi-slot | copy day, apply to staff | — |
| Branding | — | colors, logo, cover upload | — |
| About | — | — | rich text editor |
| Location | read-only display | edit address + map | — |
| Tax/Accounting | — | — | full CRUD |
| Payment config | — | view + activate + health-check | edit credentials |
| Services | CRUD + category + active toggle | accounting account, sortOrder, image, metadata | — |
| Staff | CRUD + skills + active + color | avatar upload, metadata, userId link | bulk ops |
| Work schedule | weekly + overrides + TimeOff | approve TimeOff flow, copy week | bulk grid editor |
| Customers | CRUD + history + notes + tags | advanced filter | bulk import |
| Bookings | list + calendar day + create + status + walk-in | drag-drop, multi-service, refund | analytics |
| Payments | list + basic capture/refund | drawer detail, event log | admin overrides |
| Loyalty | view customer balance | — | card CRUD |
| Dashboard | metrics + timeline + upcoming | — | — |
| Profile | email/phone/password | 2FA | — |
| Onboarding | — | full 7-step wizard | — |
20. Gaps / câu hỏi cần trả lời trước khi build mobile
- Owner vs Staff UI split — cùng app hay 2 app? Hiện tại README nói "role-based UI switching within a single app" → đi theo hướng này.
- Offline-first scope — WatermelonDB sync cho những entity nào? Đề xuất:
Booking,Resource,Service,Customer,TenantSettingssnapshot.PaymentKHÔNG offline (phải online để gọi provider). - Push notification — Expo push hay FCM trực tiếp? PRD nêu Expo push (US-7.3). Cần chuẩn bị
expo-notifications+ device token endpoint ở API. - Subdomain routing — web dùng
{slug}.app.no, mobile dùng API trực tiếp → không lệ thuộc, ok. - Deep link từ QR code —
/b/:slug/bookings/:idđã gen sẵn. Mobile staff cần scan QR → open booking detail. Cầnexpo-linking+ deep link scheme. - Payment credential — confirm dứt khoát: nhập credentials chỉ qua web admin, mobile chỉ view + toggle.
- Rich text (description, service description) — dùng markdown thay vì HTML trên mobile để tránh Tiptap phức tạp.
- Language — Norwegian Bokmål (nb-NO) primary, chuẩn bị i18n từ đầu (expo-localization + i18n-js hoặc react-intl).
- Admin "login as tenant" (impersonate) — chưa có, cả web cũng chưa. Mobile có cần không? → Không cho MVP, dùng desktop.
21. Sync với existing docs
Khi thêm/sửa setting mới trên mobile, phải update đồng thời:
docs/flows/booking-flow.md— nếu setting ảnh hưởng booking logic.docs/flows/payment-flow.md— nếu liên quan payment.booking-api/src/core/tenant/tenant.dto.ts— DTO contract.booking-api/src/core/tenant/tenant-settings.constants.ts— default values per industry.booking-web/src/types/api.generated.ts— regen sau khi đổi DTO.booking-mobile/src/types/api.generated.ts— regen qua OpenAPI codegen.
Quy tắc "KHÔNG fallback settings" áp dụng cho mobile như backend — data phải complete trong DB, UI không được silent default.