專案

一般

配置概況

動作

Feature #1442

進行中

[功能開發] CourseProfile 後台管理 API 與 FE 交接需求

是由 陳國瑋 於 約 1 個月 前加入. 於 約 1 個月 前更新.

狀態:
Resolved
優先權:
Normal
被分派者:
開始日期:
2026-08-17
完成日期:
完成百分比:

0%

預估工時:
(總計: 0.00 小時)

概述

目標

新增 CourseProfile 後台管理 API,讓 administrator 維護「療程項目」主檔;後端完成後交由 FE 實作 UI。

CourseProfile 是療程類型/主檔,Course 是學員實際建立的療程,關係為:

  • hm_course.pid → hm_course_profile.pid(邏輯 1:N,目前無實體 FK)
  • Booking 目前以 hm_booking.booking_item 儲存 CourseProfile.name,無 FK

本工作以 staging 驗證與開發,不操作 production。

已確認的 domain rule

  • CourseProfile.name 建立後永久不可修改。
  • name 全系統永久唯一,包含停用資料;停用後也不可重複使用名稱。
  • has_diary、has_satisfaction、has_post_event_card 建立後不可修改。
  • 建立後可修改欄位只有 description、enabled。
  • 不提供實體刪除;只能啟用/停用。
  • 新建 CourseProfile 一律為 enabled=false,後續由管理員手動啟用。
  • enabled=false 只阻止未來建立 Course/Booking 時被選取;既有 Course、Booking、歷史資料、日誌、提醒及排程行為均不受影響。
  • has_diary=false 仍可建立 Course;它是功能能力,不是可建立條件。
  • has_satisfaction、has_post_event_card 目前沒有程式行為,但必須可於建立時設定,供後續功能使用。
  • description 非必填、建立後可修改;空白內容正規化為 null。
  • 權限只提供 administrator role。

名稱驗證

  • 必填,儲存前 trim 前後空白。
  • 最長 255 字元。
  • 不限制中文、英文、數字或符號;保留內部空白。
  • API 驗證唯一性,並新增 DB unique index 雙重保護。
  • 唯一性包含 enabled=false 資料,依現有 DB collation 判斷。
  • 重複時回傳可讓 FE 對應到 name 欄位的 validation error。
  • 更新 API 不接受 name 與三個功能旗標,不能只依 FE disabled 防護。

API contract

1. 搜尋

POST /api/admin/course/profile/search

可選搜尋條件:

{
  "page": 1,
  "size": 10,
  "sidx": [],
  "sord": [],
  "keyword": "選填",
  "enabled": true,
  "hasDiary": true,
  "hasSatisfaction": false,
  "hasPostEventCard": true
}

規則:

  • keyword 對 name、description 做 OR 模糊搜尋。
  • enabled 與三個旗標皆為 optional Boolean。
  • 未選擇條件時,request 不得帶該 property;不是傳「全部」。
  • true 與 false 都是有效搜尋值,FE 不可用 truthy 判斷導致 false 被省略。
  • SS3A NativeSearch 欄位為 null 時不產生 SQL condition。
  • 預設 create_time DESC;支援既有 Datatable 分頁/排序。
  • Page content 至少回傳 id、name、hasDiary、hasSatisfaction、hasPostEventCard、enabled。

2. 取得單筆

GET /api/admin/course/profile/info/{id}

回傳完整 CourseProfile。FE 編輯頁將 name 與三個旗標顯示為唯讀,只可編輯 description、enabled。

3. 建立

POST /api/admin/course/profile/create

{
  "name": "必填",
  "description": "選填",
  "hasDiary": true,
  "hasSatisfaction": false,
  "hasPostEventCard": false
}

規則:

  • 三個旗標皆為 required Boolean,建立時必須明確傳 true 或 false。
  • enabled 不由 FE 傳入;後端固定建立為 false。
  • 建立前 FE 會二次確認 name 與三個不可變旗標。
  • description 空白轉 null。

4. 更新

POST /api/admin/course/profile/update

{
  "id": "Pxxxxxxx",
  "description": "選填",
  "enabled": true
}

規則:

  • 只允許更新 description、enabled。
  • name、hasDiary、hasSatisfaction、hasPostEventCard 不可出現在 update DTO。
  • description 與 enabled 在同一 transaction 更新。
  • 若 enabled 有變更,FE 必須先取得 impact 並確認;若只改 description 可直接更新。

5. 狀態影響資訊

GET /api/admin/course/profile/status/impact/{id}

回傳:

{
  "id": "Pxxxxxxx",
  "name": "療程名稱",
  "enabled": true,
  "courseCount": 123,
  "bookingCount": 45
}

計數規則:

  • courseCount:hm_course.pid = CourseProfile.pid 的歷史總數,包含所有 Course status。
  • bookingCount:hm_booking.booking_item = CourseProfile.name 的歷史總數,包含所有 Booking status。
  • 此 API 於列表 Switch 或編輯頁變更 enabled 前呼叫。
  • 統計獨立查詢,不放入列表 search,避免每頁資料都執行聚合。

6. 列表狀態切換

POST /api/admin/course/profile/status/switch

沿用既有 ReqSwitchStatus:

{
  "id": "Pxxxxxxx",
  "newStatus": false
}

FE 先呼叫 impact,顯示名稱、Course/Booking 歷史總數與影響說明;管理員確認後才呼叫 switch。取消時不得改變畫面狀態。

不提供 delete API。

DB/SS3A

  • hm_course_profile.name 新增 unique index;migration 前先檢查重複與空值資料。
  • 不新增 bookable、course_creatable 等欄位,維持現有 enabled 單一邏輯。
  • 新增 SS3A function、i18n、API mapping 與 role mapping。
  • 選單位於 Common Lists,名稱「療程項目管理」。
  • 建議 function IDs:
    • course_profile_mgmt
    • course_profile_mgmt_list
    • course_profile_mgmt_create
    • course_profile_mgmt_edit
  • 不建立 delete function。
  • 上述 function 只 mapping 給 administrator。
  • impact 與 status/switch 歸 course_profile_mgmt_edit 權限。

FE handoff requirement(本 ticket 不實作 FE)

  • 列表沿用既有 SectionTitle、WrapperTable、Datatable、ActionButton、Switch 與搜尋元件。
  • 列表欄位:ItemId、名稱、日誌、滿意度、關懷卡、狀態、編輯操作。
  • 不在列表顯示 Course/Booking 數量;只在狀態確認視窗顯示。
  • 不顯示刪除操作。
  • 搜尋:keyword、enabled、三個旗標。Boolean 選項只有「有/無」;未選擇就不帶 request property。
  • 新增頁:name、description、三個旗標;建立前二次確認;建立結果固定停用。
  • 編輯頁:name 與三個旗標唯讀;description、enabled 可修改。
  • 列表 Switch 與編輯頁修改 enabled 都必須顯示 impact 確認視窗。
  • 啟用提示:啟用後會出現在新建 Course 與 Booking 的選單。
  • 停用提示:停用後不再出現在新建 Course 與 Booking 的選單;既有資料與流程不受影響。

驗收條件

  • 僅 administrator 能存取管理 API 與選單 function。
  • 可搜尋、分頁、排序 CourseProfile;optional Boolean 未帶時不產生條件,false 可正確搜尋。
  • 新增時 enabled 固定為 false;name 與三個旗標建立後無法由 API 修改。
  • 相同名稱(含停用資料)無法再次建立;併發建立亦由 DB unique index 阻擋。
  • 更新只影響 description、enabled。
  • 狀態 impact 回傳正確歷史總數。
  • 停用後不再出現在既有 Course/Booking 建立選單;既有 Course、Booking 及後續歷史行為不受影響。
  • 無 delete API/delete function。
  • 補齊 service、repository、controller 的單元/整合測試,涵蓋驗證、搜尋條件、不可變欄位、唯一性與狀態切換。

子任務 1 (1 進行中 — 0 已結束)

Feature #1443: [介面修改] CourseProfile 後台管理 UIResolved鍾正剛2026-08-17

動作

是由 陳國瑋 於 約 1 個月 前更新

是由 陳國瑋 於 約 1 個月 前更新

Backend implementation completed

CourseProfile backend management module is implemented. FE code was intentionally not changed.

Implemented APIs

  • POST /api/admin/course/profile/search
  • GET /api/admin/course/profile/info/{id}
  • POST /api/admin/course/profile/create
  • POST /api/admin/course/profile/update
  • GET /api/admin/course/profile/status/impact/{id}
  • POST /api/admin/course/profile/status/switch

Business rules enforced by backend

  • Create always stores enabled=false.
  • Update DTO only accepts id, description, and enabled; name and three feature flags cannot be changed through the update API.
  • Name is trimmed, limited to 255 characters, and checked against enabled and disabled records.
  • Concurrent duplicate inserts are protected by the DB unique index migration and returned as HTTP 409-compatible ObjectDuplicateException; response additionally includes field: "name" for FE field mapping.
  • Empty description is normalized to null.
  • Optional Boolean search conditions are omitted when the property is absent; both true and false generate real conditions.
  • Status impact counts all Course rows by pid and all Booking rows by exact booking_item = CourseProfile.name, without status filtering.
  • No delete API was added.

DB / SS3A migration

Added java/course_profile_admin.sql, including:

  • duplicate/blank-name preflight queries;
  • idempotent uk_hm_course_profile_name unique index creation;
  • API IDs A3201–A3206;
  • CourseProfile function/i18n/API mappings;
  • role mappings reset so only administrator receives this module;
  • no delete function.

The migration has not been applied to staging or production in this implementation step.

Verification

mvn test succeeded: 14 tests, 0 failures, 0 errors.

Coverage includes required validation, 255-character name limit, omitted/false Boolean search behavior, keyword OR search, immutable fields, forced-disabled creation, duplicate-name handling including the concurrent DB-constraint path, Course/Booking impact counts, controller identity forwarding, and status switching.

Ticket remains open for migration execution and FE handoff.

是由 陳國瑋 於 約 1 個月 前更新

SOP correction and implementation branch

  • Feature branch: feature/redmine-1442-course-profile-admin
  • Implementation commit: 3cb9c49
  • Branch pushed to GitLab for staging deployment.
  • mvn test: 15 tests, 0 failures, 0 errors.
  • mvn -Ddeploy.target=staging -DskipTests package: BUILD SUCCESS.

Additional hardening completed:

  • NativeSearch internal fields are ignored during JSON deserialization, preventing callers from bypassing the public keyword contract with hidden name / description properties.
  • CourseProfile logs now follow the [COURSE-PROFILE] SOP prefix convention.
  • SS3A DML changes are wrapped in one transaction; function/API and role mappings remain rerunnable and restricted to administrator.

Staging migration and HTTP E2E have not been executed. Per SOP, the next step is Ken's manual staging deployment; after deployment confirmation, E2E and the test report will be completed.

是由 陳國瑋 於 約 1 個月 前更新

Migration fix: required ss3a_function_i18n.alias

The staging migration failed with MySQL error [HY000][1364] Field 'alias' doesn't have a default value.

Root cause:

  • The current staging schema has ss3a_function_i18n.alias varchar(100) NOT NULL with no default.
  • The repository's legacy db_init.sql does not contain this newer column, so the first migration version was generated against stale schema information.

Read-only staging audit after the failure confirmed:

  • uk_hm_course_profile_name already exists.
  • A3201–A3206 rows: 0.
  • CourseProfile function rows: 0.
  • CourseProfile i18n/API/role mappings: 0.

Therefore no partial SS3A DML remains; the corrected migration can be rerun safely. The unique-index block is idempotent and will report that the index already exists.

Fix commit: d9d2a0b

The corrected i18n insert and upsert now explicitly include alias.

是由 陳國瑋 於 約 1 個月 前更新

Migration file path correction

Per project convention, the migration has been moved from java/course_profile_admin.sql to:

patch/20260817_redmine_1442_course_profile_admin.sql

Naming components:

  • Date: 20260817
  • Redmine issue ID: 1442
  • Purpose: course_profile_admin

Please use the file under patch/ for staging execution.

是由 陳國瑋 於 約 1 個月 前更新

Staging migration verification passed

Read-only verification after executing patch/20260817_redmine_1442_course_profile_admin.sql:

  • uk_hm_course_profile_name: 1 unique index, NON_UNIQUE=0.
  • A3201–A3206: 6 enabled API rows with the expected methods and URLs.
  • CourseProfile functions: 4 enabled rows with the expected hierarchy.
  • zh_TW i18n: 4 rows; function_name and required alias are populated.
  • Function/API mappings: 6 rows, each with exactly one copy.
  • Role/function mappings: 4 rows for administrator.
  • Unexpected non-administrator role mappings: 0.

The database migration is complete.

HTTP E2E has not started: the configured API host currently returns HTTP 503 for Swagger/API discovery. Waiting for manual deployment confirmation per SOP.

是由 陳國瑋 於 約 1 個月 前更新

測試結果(2026-08-17)

Unit Test

  • 命令:cd java && mvn test
  • 結果:15 tests,0 failures,0 errors,0 skipped

HTTP E2E(staging schema)

  • 命令:cd java && mvn -Ddeploy.target=staging -Dtest=CourseProfileAdminE2E test
  • 結果:2 tests,0 failures,0 errors,0 skipped
  • 完整 Spring Boot context,經 Controller → Service → Repository → staging schema
  • 三個 scheduler 全部關閉
  • 測試 transaction 結束後 rollback,並以 @AfterTransaction 確認沒有殘留 E2E CourseProfile
  • 覆蓋 create、名稱 trim、三旗標、description keyword 搜尋、未帶 Boolean 不限制、明確 false 篩選、info、Course/Booking 數量、update 不改名/旗標、status switch、重複名稱 409、必填驗證 400

Authentication smoke

  • 本機 staging build 的受保護 API 未帶 token 回 401,符合預期
  • DB migration audit 已確認六支 API 只 mapping 給 administrator,非 administrator mapping 為 0

尚待環境恢復

目前 https://hm-api.line2me.tw/actuator/health 與 CourseProfile search API 都回 503,因此尚不能完成「已部署 staging artifact + administrator token」smoke test。待 staging service 恢復並部署本分支後再執行。

詳細報告:test_report/20260817_redmine_1442_course_profile_admin.md

是由 陳國瑋 於 約 1 個月 前更新

測試程式與報告已推送

  • Commit:6520c0c
  • Branch:feature/redmine-1442-course-profile-admin
  • Test report:test_report/20260817_redmine_1442_course_profile_admin.md

請確認測試報告格式與 TC 覆蓋度。已部署 staging authenticated smoke test 仍待 hm-api.line2me.tw 從 HTTP 503 恢復並部署本分支後補做。

是由 陳國瑋 於 約 1 個月 前更新

  • 完成百分比 從 0 變更為 80

是由 陳國瑋 於 約 1 個月 前更新

  • 狀態 從 New 變更為 In Progress

階段 5 確認完成

Ken 已確認測試報告格式與 TC 覆蓋度。

  • Unit Test:15/15 通過
  • staging-schema HTTP E2E:2/2 通過
  • Migration:已執行並驗證
  • Commit:6520c0c

Ticket 進度更新為 80%。後續工作為部署本分支、補做已部署 staging administrator authenticated smoke test,並交接 FE 完成 UI。

是由 陳國瑋 於 約 1 個月 前更新

  • 子任務 #1443 已新增

是由 陳國瑋 於 約 1 個月 前更新

FE 交接票已建立

  • 子票:#1443 [介面修改] CourseProfile 後台管理 UI
  • Parent:#1442
  • 範圍:列表、搜尋、新增、編輯、狀態 impact 確認視窗、administrator 權限與六支 API 串接
  • FE 驗收條件與 Backend branch/commit/測試報告已完整列入子票
  • 目前未指定工程師,待安排 FE owner

是由 陳國瑋 於 約 1 個月 前更新

  • 被分派者 設定為 陳國瑋

是由 陳國瑋 於 約 1 個月 前更新

  • 狀態 從 In Progress 變更為 Resolved
動作

匯出至 Atom PDF