Feature #1442
進行中[功能開發] CourseProfile 後台管理 API 與 FE 交接需求
0%
概述
目標¶
新增 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。 - 權限只提供
administratorrole。
名稱驗證¶
- 必填,儲存前 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_mgmtcourse_profile_mgmt_listcourse_profile_mgmt_createcourse_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 個月 前更新
Backend implementation completed¶
CourseProfile backend management module is implemented. FE code was intentionally not changed.
Implemented APIs¶
POST /api/admin/course/profile/searchGET /api/admin/course/profile/info/{id}POST /api/admin/course/profile/createPOST /api/admin/course/profile/updateGET /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, andenabled; 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 includesfield: "name"for FE field mapping. - Empty description is normalized to
null. - Optional Boolean search conditions are omitted when the property is absent; both
trueandfalsegenerate real conditions. - Status impact counts all Course rows by
pidand all Booking rows by exactbooking_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_nameunique index creation; - API IDs
A3201–A3206; - CourseProfile function/i18n/API mappings;
- role mappings reset so only
administratorreceives 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
keywordcontract with hiddenname/descriptionproperties. - 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 NULLwith no default. - The repository's legacy
db_init.sqldoes 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_namealready 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_nameand requiredaliasare 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