Skip to main content

Audit Yêu Cầu Cập Nhật Tính Năng App

Cập nhật: 2026-05-15, rà lại toàn diện ngoài provider messaging và BigFan
Phạm vi: mobileAppgoldenlotus-backend/docker/vendure_backend/apps/server/src/plugins/golden-lotus

Ngoài Phạm Vi Lần Này

Theo yêu cầu rà lại lần này, không audit/không tính ưu tiên cho:

  • Provider messaging: WhatsApp, Messenger, Zalo/Firebase/Expo adapter.
  • BigFan: data contract, CMS/API riêng, voucher/quà sinh nhật theo ngày.

Kết Luận Nhanh

Không tính provider messaging và BigFan, app hiện đạt khá nhiều yêu cầu ở mức UI + API/backend guard. Các phần đã tương đối chắc gồm: đa ngôn ngữ app Nga/Nhật, logic giới hạn ngày/khách booking, dropdown chọn số lượng, hotline khi vượt giới hạn, auto-fill profile, note booking, countdown booking live refresh, event slider/detail, News search/load-more/detail qua API, GPS prompt, profile địa lý và delete account.

Các gap thực tế còn lại:

  • CMS/admin typed cho booking policy vẫn thiếu: maxAdvanceDays, maxPartySize, reminderMinutesBefore, lateAutoCancelEnabled, lateGraceMinutes hiện có backend config nhưng chưa có màn quản trị rõ ràng.
  • QR thành viên đã có màn riêng ở app; nếu cần POS/staff scan xác thực thì vẫn nên bổ sung signed payload API riêng.
  • Location prompt đã có trong app và đã refetch home content theo branchId khớp region khi xác định được vị trí; backend vẫn chưa nhận tọa độ/region thô để tự quyết định khu vực.

Đã xử lý thêm trong lượt này:

  • NewsScreen chuyển sang lotusArticles(input:{languageCode, search, skip, take}) để search/load-more server-side; thêm màn detail /article/[slug] dùng lotusArticle(slug, languageCode).
  • HomeBeautySection và News list mở bài viết vào màn article detail, không còn đi nhầm route /offers/[id].
  • ScheduleScreen refresh countdown mỗi 60 giây khi người dùng đang đứng trên màn.
  • HomeScreen dùng region từ GPS để tìm branch phù hợp và refetch lotusMobileHome(input:{ branchId, languageCode }) khi có branch match.
  • Nút xem tất cả lịch sử điểm mở màn /points/history, gọi lotusMyPointLedger(input:{skip,take}) với load-more.
  • Icon QR trên Home mở /member/qr, hiển thị tên, hạng thành viên ngay trên QR; QR voucher vẫn giữ route riêng /offers/qr.

Đã xử lý thêm luồng Auth/OTP theo feedback khách hàng:

  • Đăng ký không còn cho email đã là thành viên đi tiếp để nhận OTP; backend trả lỗi rõ và mobile gợi ý đăng nhập/quên PIN.
  • Login bằng mã PIN đã nhận cả email hoặc số điện thoại; backend resolve email/phone đã verified trước khi check PIN.
  • OTP đăng nhập và OTP quên PIN đã tự chọn kênh theo identifier: email gửi qua Brevo SMTP, số điện thoại gửi qua eSMS.
  • Mobile chỉ gửi identifier lên OTP/PIN API; backend tự suy ra email hay số điện thoại, không hard-code kênh gửi ở app.
  • Mobile Login có thêm nhánh "Đăng nhập bằng OTP", resend cooldown 60 giây, verify OTP xong vào app như login PIN.
  • .env.example đã có placeholder Brevo/eSMS; .env runtime đang dùng sender Brevo domain azns.vn và eSMS ở sandbox để test SMS an toàn.

Tổng Hợp Trạng Thái Ngoài Scope Bị Loại

Nhóm yêu cầuTrạng thái hiện tạiMức hoàn thiệnViệc còn lại
Đa ngôn ngữ app Nga/Nhậtru/ja translation bundle, language picker, persist/restore, sync profile preference, query content truyền languageCode.Đã có ở mức appNative QA/copy review trước release; CMS content đa ngôn ngữ để làm sau.
Booking policy ngày/kháchApp đọc policy, UI/validation theo policy, backend reject vượt ngày/khách.Đã có core end-to-endBổ sung CMS UI typed + test boundary timezone/party size.
Booking form UXDropdown guest, hotline over-limit, auto-fill phone/email, note gửi backend.Đã có end-to-endChuẩn hóa hotline bắt buộc/fallback; test dữ liệu branch thiếu phone.
Reminder/auto no-showBackend có scheduled task, reminder window, no-show theo lateAutoCancelEnabled.PartialCần CMS UI typed và chốt automation enabled có tắt reminder hay chỉ no-show.
Membership point UIĐã rút gọn progress, nổi bật điểm sắp hết hạn, có lịch sử ngắn và màn lịch sử đầy đủ.Đã có app-sideExpiry mới summary bucket; nếu cần nhiều đợt hết hạn phải mở rộng API.
QR thành viênCó màn QR thành viên riêng /member/qr, rank nằm ngay phía trên QR.Đã có UI appPayload hiện tạo local từ member id/rank; nếu quầy/POS cần scan xác thực thì thêm signed payload API riêng.
Location promptApp hỏi quyền GPS, lưu region, prioritize local và refetch home content bằng branchId nếu tìm được branch cùng region.Partial+Backend chưa có input region/latitude/longitude; personalization hiện là branch-based, không phải geo-query thật.
Event UIDotted slider, tab chi nhánh client-side, detail không còn CTA đăng ký.Đã có UIBranch filter server-side và route naming event/offer nên tách rõ hơn.
News UISearch bar/load more/detail dùng lotusArticles/lotusArticle với languageCode.Đã có app-sideTest thực tế dữ liệu CMS đa ngôn ngữ và phân trang lớn.
Profile/settingsProvince/ward picker và delete account đã có end-to-end.Đã cóChốt taxonomy label Quận/Huyện vs Province/Ward; tăng xác nhận delete account nếu cần store policy.

1. Module Đặt Lịch (Booking)

Yêu cầuTrạng tháiBằng chứngGap / việc cần làm
Đa ngôn ngữ Nga/NhậtĐã có ở mức applocales/i18n.ts load vi/en/ru/ja; ru.jsonja.json đã dịch đủ key/placeholder; locales/language.ts định nghĩa language picker + persist; SettingsScreen có Nga/Nhật; _layout.tsx restore language; LanguagePreferenceGate sync lotusMyAppPreferences.languageCode; home/offers/services query truyền languageCode.Nên native QA/copy review bản Nga/Nhật trước release. CMS quản trị nội dung theo ngôn ngữ để làm sau như đã chốt.
Chỉ đặt trước tối đa 30 ngày, Admin chỉnh đượcPartialMobile đọc bookingPolicy.maxAdvanceDays; backend default 30 và validate trong createMyBooking.Admin chỉnh qua provider config JSON, chưa có UI CMS chuyên biệt/field typed. Cần màn cấu hình booking policy rõ ràng.
Giới hạn 5 người/booking, Admin chỉnh đượcPartialMobile dropdown theo maxPartySize; backend default 5 và reject vượt limit.Tương tự: cần UI CMS chuyên biệt.
UX chọn số lượng dạng Select/DropdownĐã cóLocationScreen dùng picker option list, không phải text input.Có thêm option > max mở hotline, đúng hướng.
Nếu > giới hạn hiển thị hotline chi nhánhĐã có UIshowHotlineMessage() lấy selectedBranch.phone và mở tel:.Cần đảm bảo mọi branch CMS có hotline/phone hợp lệ.
Auto-fill phone/email từ ProfileĐã có UIapplyProfileContactPrefill() fill fullName, phone, email nếu user chưa sửa.Phụ thuộc auth profile đã được restore đúng.
Thêm trường NoteĐã có end-to-endMobile có draft.note; mutation gửi note; backend LotusBooking.note.Không thấy gap chính.
Countdown trong quản lý lịch đặtĐã có UI live refreshScheduleScreen.formatCountdown() hiển thị còn ngày/giờ/phút và state nowMs refresh mỗi 60 giây.Không thấy gap chính; nên QA khi app background/foreground.
Push reminder trước giờ hẹn ít nhất 15 phútPartialBackend default reminderMinutesBefore: 15, scheduled task mỗi 5 phút, push qua LotusExpoPushService.Scheduled automation hiện chưa check setting.enabled trước khi chạy reminder; cần xác nhận design bật/tắt. Cần push credentials/device registration production.
Auto-cancel/no-show khi trễ quá số phút, Admin bật/tắtPartialBackend policy có lateAutoCancelEnabled, lateGraceMinutes; markLateBookingsNoShow() chuyển NO_SHOW.Chưa có CMS UI chuyên biệt. Logic đặt tên là NO_SHOW chứ không phải CANCELLED; cần chốt nghiệp vụ có muốn hủy thật hay no-show.

Audit Chi Tiết Logic Chọn Số Lượng

Hạng mụcTrạng tháiBằng chứng kỹ thuậtNhận xét / gap
Chỉ đặt lịch trước tối đa 30 ngày tớiĐã có end-to-endMobile default defaultBookingPolicy.maxAdvanceDays = 30; app đọc policy từ lotusMobileBootstrap.bookingPolicy; TimeScreen.buildDays(maxAdvanceDays) chỉ render số ngày theo policy; validateBookingDraft() chặn thời gian vượt policy.maxAdvanceDays; backend assertBookingPolicy() cũng chặn startsAt > now + maxAdvanceDays.Đã có guard cả UI và backend. Cần test thực tế boundary ngày thứ 30 với timezone/giờ cuối ngày để tránh lệch kỳ vọng nghiệp vụ.
Admin điều chỉnh số ngày đặt trướcPartialBackend LotusProviderConfigService.normalizeBookingPolicy() đọc maxAdvanceDays từ provider config và clamp 1-365; mobile tự nhận qua bootstrap.Có contract/config backend nhưng chưa thấy CMS UI typed/chuyên biệt cho admin chỉnh; hiện vẫn là gap CMS.
Giới hạn tối đa 5 người/bookingĐã có end-to-endMobile default defaultBookingPolicy.maxPartySize = 5; LocationScreen sinh guestOptions từ bookingPolicy.maxPartySize; validateBookingDraft() chặn guests > policy.maxPartySize; backend assertBookingPolicy() reject partySize > policy.maxPartySize.Đã chặn client và server. Nên bổ sung test regression cho partySize = max, partySize = max + 1.
Admin điều chỉnh giới hạn khách trên CMSPartialBackend đọc maxPartySize từ provider config và clamp 1-50; mobile dropdown tự đổi theo giá trị policy trả về.Logic app/API đã sẵn sàng, nhưng thiếu UI CMS typed để admin tự chỉnh số này rõ ràng.
UX chọn số lượng là Select/Dropdown, không nhập tayĐã có UILocationScreen dùng TouchableOpacity mở optionList; các option được generate từ 1..maxPartySize; không dùng TextInput cho guests.Đúng yêu cầu. Nếu maxPartySize lớn, dropdown có thể dài; backend đang cho tối đa 50 nên nên cân nhắc bottom sheet/scroll picker nếu admin tăng cao.
Nếu khách muốn đặt hơn giới hạn thì hiện thông báo hotline chi nhánhĐã có UILocationScreen thêm option moreThanGuests; chọn option gọi showHotlineMessage(), hiển thị Alert với selectedBranch.phone; nếu có phone thì có nút Call hotline mở tel:.Phụ thuộc dữ liệu branch có phone. Nếu branch thiếu phone, app vẫn báo cần liên hệ hotline nhưng không có nút gọi; cần bắt buộc hotline trong CMS hoặc thêm hotline fallback toàn hệ thống.

2. UI/UX Membership

Yêu cầuTrạng tháiBằng chứngGap / việc cần làm
QR thành viên hiển thị Rank phía trên QRĐã có UI appMemberQrScreen route /member/qr hiển thị name, rankLabel, rankValue ngay trước QR; Home QR icon trỏ sang route này.Cần signed payload API/POS scan nếu nghiệp vụ cần xác thực QR thật tại quầy.
Rút gọn chặng xét hạng, xóa biểu đồ trònĐã có theo app hiện tạiPointScreen không còn biểu đồ tròn; dùng rank card + expiring card + history.PointSummary vẫn còn các field route* legacy trong type/API mapping; có thể dọn sau.
Làm nổi bật điểm sắp hết hạn + ngày kết thúcĐã có UI/APIPointScreenexpireCard; backend lotusMyPointSummary trả expiringSoonPoints, nextExpiryAt.Nếu cần nhiều đợt hết hạn, API hiện gom thành 1 summary/next expiry, chưa list nhiều bucket.
Lịch sử dùng điểm dễ nhận thấy hơnĐã có UI + full listhistoryCard có header/icon/list; nút "see all" điều hướng /points/history; màn full list dùng lotusMyPointLedger(input:{skip,take}) và load-more.Không thấy gap chính; nên QA data lớn.
Popup xin quyền GPS khi mở appĐã có UI + branch-based queryHomeScreen show Alert một lần khi locationPromptEnabled, gọi Expo Location, lưu region, sort local và refetch lotusMobileHome bằng branchId match region.Backend chưa nhận region/latitude/longitude; nếu cần đúng bán kính GPS thì phải thêm contract geo-query.

3. Sự Kiện (Event) & Tin Tức (News)

Yêu cầuTrạng tháiBằng chứngGap / việc cần làm
Event dotted sliderĐã có UIHomeEventSection horizontal scroll + dots.Cần kiểm tra pixel/gesture thật trên thiết bị.
Bỏ nút Đăng ký ở chi tiết EventĐã có UIOfferDetailScreen chỉ render bottom CTA khi offer.kind === 'voucher'; event không có nút đăng ký.Backend vẫn còn registerLotusEvent; nếu business bỏ hẳn đăng ký, cần ẩn/remove flow entry khác.
Nhấn banner để xem chi tiếtĐã có UIEvent card push sang /offers/[id] detail.Route dùng chung offers/events, naming hơi gây nhầm.
Tab filter event theo chi nhánhĐã có UIHomeEventSection tạo branch tabs từ event.location.Backend có branchId/branchCode filter nhưng app home đang filter client-side theo text location, chưa gọi server theo branch.
News detail/category nhiều bài hơn, load more/paginationĐã có app-sideNewsScreen gọi lotusArticles(input:{ languageCode, search, skip, take }); nút load more tăng take; có màn /article/[slug] gọi lotusArticle(slug, languageCode).Cần QA dữ liệu CMS đa ngôn ngữ và trường hợp slug thiếu bản dịch theo ngôn ngữ.
Search bar NewsĐã có app-sideSearch debounced 300ms và truyền vào lotusArticles.input.search.Backend search đang filter trong memory sau khi lấy active rows; nếu dữ liệu lớn nên tối ưu DB query sau.

4. Profile & Settings

Yêu cầuTrạng tháiBằng chứngGap / việc cần làm
Thêm Quận/Huyện, Khu phố/Phường chọn sẵnĐã có end-to-endEditProfileScreen chọn province/ward; backend có lotusVietnamProvinces, lotusVietnamWards, updateLotusMyProfile.Label hiện gọi district nhưng dữ liệu là province/city + ward theo bảng 2026; cần chốt taxonomy theo yêu cầu mới.
Delete Account dưới About/Giới thiệuĐã có end-to-endSettingsScreen đặt delete row sau Terms/Privacy; backend deleteLotusMyAccount soft delete customer.Nên thêm xác nhận mạnh hơn, lý do xóa, và copy App Store/Google Play nếu cần.

Rủi Ro Kỹ Thuật Nên Chốt Sớm

  1. useMock mặc định đang là true trong mobile env, nên nhiều màn vẫn có thể chạy với dữ liệu mock dù API đã có.
  2. Gói ru/ja đã dịch đủ cấu trúc app, nhưng vẫn nên có native QA/copy review trước khi release cho khách Nga/Nhật.
  3. Booking automation scheduled task được đăng ký ở plugin config; phần reminder không kiểm tra LotusAutomationSetting.enabled, trong khi no-show lại kiểm theo booking policy. Cần chốt "tắt automation" nghĩa là tắt tất cả hay chỉ tắt auto no-show.
  4. QR thành viên và QR voucher đã tách UI route; payload thành viên vẫn là local, cần signed payload/thời hạn/POS scan flow nếu dùng vận hành thật.
  5. Timezone boundary cho maxAdvanceDays đang dùng now + N*24h; nếu nghiệp vụ hiểu là "đến hết ngày thứ 30" thì cần chỉnh rule.
  6. Location personalization đã refetch theo branchId khi match region, nhưng chưa phải lọc tọa độ/bán kính ở backend.

Ưu Tiên Đề Xuất

P0:

  • Thêm CMS UI typed cho booking policy: maxAdvanceDays, maxPartySize, reminderMinutesBefore, lateAutoCancelEnabled, lateGraceMinutes.
  • Nếu dùng QR thành viên để quét tại quầy: thêm signed payload API, thời hạn QR và POS/staff scan flow.

P1:

  • Native QA/copy review bản dịch ru/ja trước release.
  • Chốt timezone rule cho giới hạn đặt trước 30 ngày.

P2:

  • Thêm contract geo-query nếu cần location đúng theo tọa độ/bán kính thay vì branchId theo region.
  • Chuyển event branch filter từ client-side text location sang query server-side branchId/branchCode nếu dữ liệu nhiều.