Viettel Academy B2B licensing — local end-to-end verification

Branch demo/viettel-b2b · stack: Vite :3000 + FastAPI :8123 + Postgres (lms_db_va) + Redis · driven by a Codex computer-use walkthrough in Chrome · generated 2026-08-25 13:39 +07

RELEASED Status as of 9 Sep 2026

SP-629 (Viettel Academy B2B course licensing) merged 2026-08-25 (PR #311) and released to production in v0.0.40 (2026-08-30). Follow-ups: the standalone B2B console (#321) and partner course requests (#348) shipped in v0.0.40; further console follow-ups (#424) shipped in v0.0.47 (2026-09-09).

Status checked 2026-09-09 against git tags and Jira. Index of all reports: https://skillpixel.atlassian.net/wiki/x/A4BJCw

24passed
0warnings
0failed
24steps

→ Beta verification report (after merge + deploy)

→ Beta verification in real Chrome (Skill Pixel profile)

Walkthrough summary

# Viettel Academy B2B licensing walkthrough

## Result

- Pass: 24
- Warn: 0
- Fail: 0
- Total: 24

The final refresh cleared the last warning. Viettel now uses its own header wordmark and red Viettel tab icon, Mekong keeps the default SkillPixel mark with its own tenant name, and the default tenant still shows the standard SkillPixel brand. Licensed READING and QUIZ lessons now render read-only previews on Mekong, while the course `/edit` route remains notice-only.

## Issues

- None.

## Could not verify

- Nothing. All 24 walkthrough entries are verified as passing.

## Scratch data left in place

- Added `partner.qa@mekongenergy.vn` as a Mekong partner administrator.
- Updated HD-2026-MKE-001 notes to `QA walkthrough note`.
- Created Song Da Power Academy with slug `song-da-power-academy` and administrator `admin@songda.example`.
- Created active contract HD-2026-SDA-001 for 20 seats and 50,000,000 VND.
- Licensed `An toàn thông tin cho người lao động` to Song Da, then successfully revoked that licence. The row now shows `Đã thu hồi` and no revoke button.
- Deleted the temporary QA-TEST-001 payment, leaving HD-2026-MKE-001 at 90,000,000 VND collected and 30,000,000 VND outstanding.

Fixes applied during verification and final gates

Branch demo/viettel-b2b (local, not pushed) — 37 commits on top of origin/main c4318ca7.

Fixes made during this verification (newest first):
- a265aaf1 fix(frontend): read-only lesson preview for licensed copies
- 5b13aa22 fix(frontend): show tenant wordmark logos unclipped in header and footer
- f3caa9da chore(seed): give Viettel Academy its logo and favicon
- ae6add9a fix(frontend): keep B2B admin tables and dialogs inside the viewport
- 6fa25a0b fix(frontend): hide editing controls on licensed copies
- 031a0b4a fix(frontend): stable B2B revenue bars and full-width partner table
- ba3acbcc fix(b2b): eager-load license relations before building responses

Gates on the final tree: backend ruff lint + yarn test:unit; frontend eslint + yarn build (tsc -b + vite). Results are appended below by the orchestrator.

Final gate results (final tree, after all fixes):
- backend: ruff "All checks passed!"; yarn test:unit → 2429 passed, 0 failed
- frontend: eslint 0 errors (11 pre-existing warnings in untouched files); yarn build (tsc -b + vite) ✓ built in 12.34s; yarn i18n:interface produces no diff
- migration: single alembic head c5d8a3e17f24; seed chain base → viettel → historyclass verified on lms_db_va

Steps

  1. 01 Viettel Academy landing and catalogue PASS
  2. 02 Viettel admin sidebar and B2B licensing navigation PASS
  3. 03 B2B licensing overview PASS
  4. 04 B2B partners list PASS
  5. 05 Mekong Energy Academy partner detail PASS
  6. 06 Add a partner administrator PASS
  7. 07 B2B contracts list and filters PASS
  8. 08 Seeded contract detail, licences, and payments PASS
  9. 09 Add and delete a contract payment PASS
  10. 10 Edit and persist contract notes PASS
  11. 11 Create a provisioned partner PASS
  12. 12 Create an active Song Da contract PASS
  13. 13 Licence a Viettel course to Song Da PASS
  14. 14 Revoke the Song Da course licence PASS
  15. 15 Mekong partner catalogue PASS
  16. 16 Mekong admin navigation gating PASS
  17. 17 Mekong licensed-content dashboard PASS
  18. 18 Licensed course read-only instructor views PASS
  19. 19 Licensed courses in the instructor list PASS
  20. 20 Default SkillPixel admin feature gating PASS
  21. 21 Mobile B2B overview and contract layout PASS
  22. 22 Tenant bootstrap API spot checks PASS
  23. 23 Default-window overflow audit PASS
  24. 24 Default-window contract dialog fit PASS

01 Viettel Academy landing and catalogue PASS

http://viettel.localhost:3000/

The header brand link shows the red "viettel academy" wordmark image by itself, with no SkillPixel "S" mark or separate Viettel Academy text label beside it. The browser tab favicon is the red Viettel V-shaped mark, not the SkillPixel "S". The red Viettel Academy landing page lists exactly "An toàn thông tin cho người lao động" and "Quản trị dự án cho doanh nghiệp viễn thông".

Viettel Academy landing and catalogue
01-viettel-catalogue.webp

02 Viettel admin sidebar and B2B licensing navigation PASS

http://viettel.localhost:3000/admin/users

The admin header again shows the red "viettel academy" wordmark image by itself, with no SkillPixel "S" mark or separate Viettel Academy text label beside it; the signed-in avatar is shown at the far right. The sidebar contains the Vietnamese B2B group "Cấp phép B2B" with "Tổng quan", "Đối tác", and "Hợp đồng" links. The page is signed in as Developer Admin.

Viettel admin sidebar and B2B licensing navigation
02-admin-sidebar.webp

03 B2B licensing overview PASS

http://viettel.localhost:3000/admin/b2b

The current scratch state shows 2 partners, 2 active contracts, 3 licensed courses, 8 partner learners, 8 / 70 seats, 170.000.000 ₫ contracted, 90.000.000 ₫ collected, 80.000.000 ₫ outstanding, and 7.320.000 ₫ of B2C revenue over 12 months. The monthly chart now has 8 visible `.recharts-bar-rectangle path` elements: two B2B bars aligned with June and July 2026 and six B2C bars across recent months. The partner breakdown table is 1,166px wide from x=417 to x=1,583; its final "Đã thu" header and 90.000.000 ₫ cell both end at x=1,583 within the 1,744px viewport, so every column is visible without clipping.

B2B licensing overview
03-b2b-overview.webp

04 B2B partners list PASS

http://viettel.localhost:3000/admin/b2b/partners

The Mekong Energy Academy row is present with slug "mekong-energy", green "Đang hoạt động" status, hostname mekong.localhost:3000, and admins skillpixelad@gmail.com plus skillpixeldeveloper@gmail.com. It reports 1 contract, 2 licences, 8 learners, and 90.000.000 ₫ collected.

B2B partners list
04-partners.webp

05 Mekong Energy Academy partner detail PASS

http://viettel.localhost:3000/admin/b2b/partners/c4d7f2b8-6e19-4a50-8b3c-2f6a9d1e7c40

The info card shows Mekong Energy Academy as "Đang hoạt động", slug mekong-energy, hostname mekong.localhost:3000, 8 learners, and 90.000.000 ₫ collected. Admins are skillpixelad@gmail.com and skillpixeldeveloper@gmail.com. The contract section shows HD-2026-MKE-001 active for 25/05/2026–25/05/2027 with 8 / 50 seats, 2 licences, 120.000.000 ₫ value, 90.000.000 ₫ collected, and 30.000.000 ₫ outstanding; the course section shows both seeded licences with 6/2 and 5/1 learner/completion counts and snapshot dates 25/05/2026 and 16/06/2026.

Mekong Energy Academy partner detail
05-partner-detail.webp

06 Add a partner administrator PASS

http://viettel.localhost:3000/admin/b2b/partners/c4d7f2b8-6e19-4a50-8b3c-2f6a9d1e7c40

The add-admin form accepted partner.qa@mekongenergy.vn. A success toast said the address now has partner administrator access, and the address appeared in the administrator list alongside skillpixelad@gmail.com and skillpixeldeveloper@gmail.com.

Add a partner administrator
06-partner-admin.webp

07 B2B contracts list and filters PASS

http://viettel.localhost:3000/admin/b2b/contracts

The document scrollWidth and window.innerWidth are both exactly 1,744px. The full ten-column table is visible from x=393 to x=1,607, including the last "Hành động" column, and the header "Thêm hợp đồng" button is fully visible from x=1,452.65625 to x=1,608 without horizontal page scroll. HD-2026-MKE-001 still shows Mekong Energy Academy, "Đang hiệu lực", 25/05/2026 to 25/05/2027, 8 / 50 seats, 2 licences, 120.000.000 ₫ value, 90.000.000 ₫ collected, and 30.000.000 ₫ outstanding.

B2B contracts list and filters
07-contracts.webp

08 Seeded contract detail, licences, and payments PASS

http://viettel.localhost:3000/admin/b2b/contracts/d1a5c3e7-8b24-4f69-9c07-3e5b8d2a6f11

HD-2026-MKE-001 is active for 25/05/2026–25/05/2027 with 8 / 50 seats, 2 licences, 120.000.000 ₫ contracted, 90.000.000 ₫ collected, and 30.000.000 ₫ outstanding. The Licensed courses tab shows Quản trị dự án cho doanh nghiệp viễn thông at 6 learners, 2 completions, snapshot 25/05/2026 and An toàn thông tin cho người lao động at 5 learners, 1 completion, snapshot 16/06/2026. The Payments tab shows the two seeded payments: 30.000.000 ₫ dated 25/07/2026 with reference UNC-2026-0517 and 60.000.000 ₫ dated 25/06/2026 with reference UNC-2026-0412.

Seeded contract detail, licences, and payments
08-contract-courses.webp
Seeded contract detail, licences, and payments
08-contract-payments.webp

09 Add and delete a contract payment PASS

http://viettel.localhost:3000/admin/b2b/contracts/d1a5c3e7-8b24-4f69-9c07-3e5b8d2a6f11

A 30.000.000 ₫ payment dated 25/08/2026 with reference QA-TEST-001 was recorded successfully; the header updated to 120.000.000 ₫ collected and 0 ₫ outstanding. After confirming deletion of that exact row, the success toast appeared, QA-TEST-001 was absent from the table, and the header returned to 90.000.000 ₫ collected and 30.000.000 ₫ outstanding.

Add and delete a contract payment
09-payment-added.webp
Add and delete a contract payment
09-payment-deleted.webp

10 Edit and persist contract notes PASS

http://viettel.localhost:3000/admin/b2b/contracts/d1a5c3e7-8b24-4f69-9c07-3e5b8d2a6f11

The Edit contract dialog accepted the notes value "QA walkthrough note" and saved it. After a full page reload, reopening the dialog showed the same note, confirming that the change persisted.

Edit and persist contract notes
10-contract-edit-dialog.webp
Edit and persist contract notes
10-contract-note-saved.webp

11 Create a provisioned partner PASS

http://viettel.localhost:3000/admin/b2b/partners

The partners page has document scrollWidth = 1,744px and window.innerWidth = 1,744px, and the exact `main *` overflow check is false. The full nine-column table runs from x=393 to x=1,607, including the final "Hành động" column. Song Da Power Academy remains "Chờ SkillPixel kích hoạt" with hostname "Chưa được cấp", admin@songda.example, 1 contract, 1 licence, 0 learners, and 0 ₫ collected; Mekong Energy Academy remains fully visible in the other row.

Create a provisioned partner
11-new-partner-dialog.webp
Create a provisioned partner
11-partners-after-create.webp

12 Create an active Song Da contract PASS

http://viettel.localhost:3000/admin/b2b/contracts

The contracts list has document scrollWidth = 1,744px and window.innerWidth = 1,744px; the full table and final column end at x=1,607 with no horizontal page scroll. HD-2026-SDA-001 remains "Đang hiệu lực" for Song Da Power Academy with 0 / 20 seats, 1 licence, 50.000.000 ₫ value, 0 ₫ collected, and 50.000.000 ₫ outstanding. The "Hợp đồng mới" dialog spans y=106.375 to y=886.625 within window.innerHeight = 993px, and its "Huỷ" and "Tạo mới" footer buttons both end at y=861.625 and are visible.

Create an active Song Da contract
12-new-contract-dialog.webp
Create an active Song Da contract
12-contract-created.webp

13 Licence a Viettel course to Song Da PASS

http://viettel.localhost:3000/admin/b2b/contracts/f7e8fa28-8555-4f5f-aaf5-1686e2464557

Searching the course picker for "Khoá học" returned the empty-state message even though two published courses were available when the search was cleared. After clearing the search, the first course, "An toàn thông tin cho người lao động", was licensed successfully. The row is active as "Đang cấp phép" with copy slug an-toan-thong-tin-cho-nguoi-lao-dong, 0 learners, 0 completions, and snapshot date 25/08/2026. (Reviewer note: the search term "Khoá học" came from the walkthrough script and matches neither seeded title — the picker filters by title substring, so the empty state was correct. Licensing itself succeeded.)

Licence a Viettel course to Song Da
13-course-licensed.webp

14 Revoke the Song Da course licence PASS

http://viettel.localhost:3000/admin/b2b/contracts/f7e8fa28-8555-4f5f-aaf5-1686e2464557

Revoking "An toàn thông tin cho người lao động" on HD-2026-SDA-001 succeeded. The licence row changed from "Đang cấp phép" to the REVOKED label "Đã thu hồi", the success toast read "Đã thu hồi giấy phép.", and the revoke button disappeared, so no second click was available or required. A hard reload of Mekong still showed its two separate seeded courses, "An toàn thông tin cho người lao động" and "Quản trị dự án cho doanh nghiệp viễn thông", both marked "Miễn phí".

Revoke the Song Da course licence
14-licence-revoke-failed.webp

15 Mekong partner catalogue PASS

http://mekong.localhost:3000/

The Mekong header shows the default blue SkillPixel "S" mark followed by the text "Mekong Energy Academy"; it does not show the Viettel wordmark. The catalogue lists exactly the two seeded licensed courses, "An toàn thông tin cho người lao động" and "Quản trị dự án cho doanh nghiệp viễn thông". Both cards display "Miễn phí".

Mekong partner catalogue
15-mekong-catalogue.webp

16 Mekong admin navigation gating PASS

http://mekong.localhost:3000/admin

After Google sign-in as skillpixeldeveloper@gmail.com, the Mekong admin sidebar includes the localized Licensed content link "Nội dung được cấp phép" under Học tập. The sidebar has no "Cấp phép B2B" group and no Overview, Partners, or Contracts links.

Mekong admin navigation gating
16-mekong-admin-sidebar.webp

17 Mekong licensed-content dashboard PASS

http://mekong.localhost:3000/admin/licensed-content

The page identifies Viettel Academy as the licensor and shows one active contract, HD-2026-MKE-001, for 25/05/2026–25/05/2027 with 8 / 50 seats used and a progress bar. The table contains exactly the two seeded licences: Quản trị dự án cho doanh nghiệp viễn thông with 6 learners, 2 completions, snapshot 25/05/2026, and An toàn thông tin cho người lao động with 5 learners, 1 completion, snapshot 16/06/2026. Both course records and licences are shown as published/active.

Mekong licensed-content dashboard
17-mekong-licensed-content.webp

18 Licensed course read-only instructor views PASS

http://mekong.localhost:3000/instructor/courses/quan-tri-du-an-cho-doanh-nghiep-vien-thong/chapters/ch905_mke_qtda_01/reading/ls909_mke_qtda_r1

The Mekong header shows the default blue SkillPixel "S" mark followed by "Mekong Energy Academy", with no Viettel wordmark. In the expanded "Khởi tạo và lập kế hoạch" chapter, the READING lesson "Điều lệ dự án và các bên liên quan" renders a read-only "Bài đọc" preview with the full article text. The QUIZ lesson "Trắc nghiệm - Khởi tạo và lập kế hoạch" renders a read-only "Quiz" preview with its description, four questions, and answer options; neither preview has a form or save, add, or delete controls. The `/edit` page still shows only the Viettel Academy read-only notice and "Xem nội dung khoá học", with no editor form.

Licensed course read-only instructor views
18-licensed-course-readonly.webp
Licensed course read-only instructor views
18-licensed-course-edit-readonly.webp

19 Licensed courses in the instructor list PASS

http://mekong.localhost:3000/instructor/courses

The instructor list contains exactly two licensed course cards, and both show the badge "Cấp phép bởi Viettel Academy". Each card is published, open for enrollment, free, and exposes only "Xem nội dung khoá học"; the page contains 0 "Cài đặt" or Manage controls.

Licensed courses in the instructor list
19-instructor-courses.webp

20 Default SkillPixel admin feature gating PASS

http://localhost:3000/admin/users

The default tenant header remains unchanged: it shows the blue SkillPixel "S" mark followed by the blue "SkillPixel" name. The admin sidebar contains only the People, Learning, Business, and Platform groups. It has no B2B licensing group and no Licensed content link.

Default SkillPixel admin feature gating
20-default-admin-sidebar.webp

21 Mobile B2B overview and contract layout PASS

http://viettel.localhost:3000/admin/b2b

At a 360 × 800 viewport, both the B2B overview and HD-2026-MKE-001 detail kept document and body scroll widths at exactly 360px, so neither page had horizontal page scroll. The overview contained two internal overflow-x-auto wrappers measuring 262/553px and 262/719px; the contract table used one 310/1128px wrapper. The normal browser viewport was restored after capture.

Mobile B2B overview and contract layout
21-mobile-overview.webp
Mobile B2B overview and contract layout
21-mobile-contract.webp

22 Tenant bootstrap API spot checks PASS

http://localhost:8123/api/v1/tenant/bootstrap

The Mekong bootstrap response contains licensor.name = "Viettel Academy" and identifies tenant slug mekong-energy. The Viettel bootstrap response lists "b2b_licensing" in enabled_features alongside certificates and docx.import.

23 Default-window overflow audit PASS

http://viettel.localhost:3000/admin/b2b/contracts

The default window.innerWidth was recorded once as 1,744px and was not changed. Every audited page reported document scrollWidth = 1,744px, window.innerWidth = 1,744px, and false for the exact `main *` right-edge overflow check: Viettel overview, partners list, Mekong partner detail, contracts list, HD-2026-MKE-001 Licensed courses tab, HD-2026-MKE-001 Payments tab, and Mekong Licensed content. The screenshot is the contracts page because all seven checks passed.

Default-window overflow audit
23-overflow-audit.webp

24 Default-window contract dialog fit PASS

http://viettel.localhost:3000/admin/b2b/contracts/d1a5c3e7-8b24-4f69-9c07-3e5b8d2a6f11

At window.innerHeight = 993px, the New contract dialog spans y=106.375 to y=886.625 and fits; its "Huỷ" and "Tạo mới" footer buttons end at y=861.625 and are visible. The HD-2026-MKE-001 Edit contract dialog spans y=162.75 to y=830.25 and also fits; its "Huỷ" and "Lưu" footer buttons end at y=805.25 and are visible.

Default-window contract dialog fit
24-dialog-fit.webp