手冊首頁 / 溝通協作 / 公告檢視

公告檢視(Bulletin View)

功能群:協作(collab)|本群無獨立 _overview;管理側見 公告管理;SPEC 索引見 README

事實基準:2026-07-05 從 FE / BE code 掃出;API schema 逐條對照 marshmallow serializer 與 service 組裝碼

1. 功能描述

本頁涵蓋公告的讀者端三條 route:公告清單(bulletin-list)、公告檢視(bulletin-view)、使用手冊(bulletin-user-guide)。任何登入使用者都能看自己部門 / 全域生效中的公告,點列進入詳情頁閱讀完整內容;使用手冊是側邊選單另一個獨立入口,目前是空殼頁面(見下)。

技術細節bulletin-list 實際上是把管理頁元件 BulletinManage.vue:view="true" prop 重新渲染(compliance-manager-fe/src/views/bulletins/BulletinList.vue:3-7)——同一份 DataTable 程式碼在管理者與一般使用者眼中呈現不同欄位/按鈕(見 §1.1 / §5)。bulletin-view 是純讀 detail 頁,全站僅 BulletinManage.vue(含 view 與 manage 兩模式)會導向它。

主要使用者:任何已登入使用者(無角色門檻,見 §3)。頁面本身不提供任何寫入操作。

角色速覽(本群無 _overview.md,角色定義以本頁實際檢查點為準;完整角色矩陣見 system-admin RBAC 若已存在,否則以下即為完整定義):

角色 一句話
一般使用者(任何已登入者) 唯一角色——公告讀取無任何角色 / capability 限制,只受部門與生效時間範圍過濾

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 公告清單(授權範圍) 顯示 enable=1 且在生效期間、且屬使用者部門(或全域)的公告,lazy 分頁 bulletin-list UC-BV-01
2 關鍵字搜尋 title / category / created_user_name / updated_user_name / created_user / updated_user 全域搜尋(前端組 filter) bulletin-list Header §6.1
3 重置查詢 清空搜尋 / 排序、回到第一頁(queryStore.resetCurrent() bulletin-list Header §7
4 分類顯示 類別 code 轉譯為中文(system-menu/BULLETIN_CATEGORY 動態選單 + i18n) bulletin-list 表格 / bulletin-view 標題列 §6.1
5 排序 / 分頁 標題 / 起訖時間 / 建立修改時間欄位可排序,10/25/50 筆分頁 bulletin-list 表格 §7
6 整列可點進入檢視 view 模式下整列 cursor-pointer,點擊導向 bulletin-view bulletin-list 表格 UC-BV-02
7 公告內容檢視 標題(含分類前綴)+ HTML 富文本內容(v-dompurify-html 消毒渲染) bulletin-view UC-BV-02
8 返回按鈕 router.back() 回上一頁(非固定路徑,view/manage 兩入口都適用) bulletin-view §5
9 使用手冊頁 側邊選單獨立入口,目前僅顯示標題「使用手冊」,無內容、無 API 呼叫 bulletin-user-guide §1 / §12 坑 1

UC(§2)展開 1(清單載入與授權範圍)與 6/7(進入檢視)這兩條核心路徑;其餘為常規 CRUD 讀取或純顯示邏輯。使用手冊頁因無任何邏輯,不另立 UC。

2. Use Case

角色速覽(本群無 _overview.md,角色定義以本頁實際檢查點為準;完整角色矩陣見 system-admin RBAC 若已存在,否則以下即為完整定義):

角色 一句話
一般使用者(任何已登入者) 唯一角色——公告讀取無任何角色 / capability 限制,只受部門與生效時間範圍過濾

流程圖視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。

UC-BV-01 讀取公告清單(授權範圍過濾)

項目 內容
角色 任何已登入使用者
前置條件 已登入(JWT 有效)
產出 / 後置條件 無寫入;回傳「enable=1 + 生效期間內 + 使用者部門(或全域)」範圍的公告清單
bulletin_view_uc_01_read enter 使用者進入公告清單頁 (onMounted) list_api POST /bulletins {filters: {auth: true}} enter->list_api scope BE 檢查:user.org_unit_id 是否存在? list_api->scope scopeY 加 org_units__in = [user.org_unit_id] (只看自己部門的公告) scope->scopeY scopeN 不加部門篩選 (org_unit_id 為 null 時視同無限制) scope->scopeN live 套用 enable=1 且 release_time ≤ now 且 expire_time ≥ now (domain service 強制,不受前端傳值影響) scopeY->live scopeN->live render 卡片列表渲染(DataTable lazy 分頁) live->render click 使用者點列 → router.push bulletin-view?uid=... render->click detail GET /bulletin/{uid} (無 auth 篩選,直接以 uid 查) click->detail exists BE 檢查:uid 對應紀錄存在? detail->exists err404 404 Not Found exists->err404 show 標題+v-dompurify-html 渲染 content exists->show note ⚠️ 無已讀標記寫入、無存取權二次檢查 (enable/release/expire 皆不擋,見 §12) show->note
UC-BV-01 流程:BE 部門範圍判斷 → 強制套用生效時間篩選 → 清單渲染;點列進入 GET /bulletin/{uid} 無二次授權檢查

UC-BV-02 進入公告檢視頁

項目 內容
角色 任何已登入使用者
前置條件 route.query.uid 存在(否則導回 /bulletin/bulletin-manage,見 §12 坑 2)
產出 / 後置條件 無寫入;GET 單筆公告詳情渲染

本 UC 流程已併入 UC-BV-01 圖下半段(click → detail → exists → show),因兩者共用同一條「清單→詳情」路徑,拆兩張圖會重複超過一半節點。

3. 權限矩陣

本群無 _overview.md 集中定義,角色基調直接列於本頁:

操作 FE 判定 BE 強制 BE 檢查位置
選單 / 頁面可見性 無 capability 判斷式(router/index.js 對三條 route 均無 meta.requiresAdmin 等守門) @jwt_required(),無角色 / capability 二次檢查 api/bulletin/routes/bulletin_route.py:25,43
讀清單(POST /bulletins props.view=true 時固定帶 auth: trueBulletinManage.vue:78-80 domain service 依 auth 旗標套用 enable=1 + 生效時間 + 部門範圍(見 §4) domain/bulletin/service/bulletin_domain_service.py:16-19
讀單筆(GET /bulletin/{uid} 無額外判定,直接打 API ⚠️ 無 enable / 生效時間 / 部門範圍檢查——只要 uid 存在、已登入即可讀取(含已停用、已過期、非本部門的公告) api/bulletin/routes/bulletin_route.py:45-48bulletin_domain_service.get_bulletin_by_uid(無 filter)
使用手冊頁 無 API 呼叫可言

⚠️ 已知落差:清單頁有嚴格的授權範圍過濾(enable / 生效期間 / 部門),但點進詳情頁的 GET /bulletin/{uid} 完全不重複這層檢查——只要拿到 uid(例如清單載入後從網路請求或分享連結取得),任何登入使用者都能讀到理論上「不該看到」的公告內容(已停用、已過期、或屬其他部門)。因公告內容本身非機密性資料(系統公告皆為告知性質),暫列為已知行為而非資安缺口,但新增類似「先過濾清單、detail 不重複檢查」的模式時應留意,收進 §12 坑 3。

4. 狀態機與前置條件

本頁無跨頁狀態機(公告無審核流程,enable 只是啟用/停用二態旗標,非狀態機)。相關 gate:

條件 效果 出處
enable = 1 清單中僅顯示啟用中的公告(auth=true 時強制套用,不受呼叫端傳值影響) domain/bulletin/service/bulletin_domain_service.py:17
release_time <= now() 只顯示已到發布時間的公告 infra/bulletin/repository/bulletin_repo_impl.py:63-64(欄位名 release_time__gt_now 語意具誤導性,見 §12 坑 4)
expire_time >= now() 只顯示尚未過期的公告(expire_time 為 null 視為永不過期,SQL 條件對 null 天生不成立需留意,見 §12 坑 4) 同檔 :65-66(欄位名 expire_time__le_now
org_units__in = [user.org_unit_id] 使用者有 org_unit_id 時只看自己部門的公告;org_unit_id 為 null 時不篩選部門(視為全域使用者) app/bulletin/service/bulletin_service.py:38-40
route.query.uid 缺失 bulletin-view 頁 onMounted 直接 router.push 導回 /bulletin/bulletin-manage(非管理者也會被導向此頁,見 §12 坑 2) BulletinView.vue:20-26

5. UI 設計

📸 實際畫面截圖待補(一律亮色模式拍攝):以 playwright 對 STG 擷取,預計補以下三張到 assets/img/: ① 公告清單頁(搜尋列 + DataTable,view 模式欄位配置)bulletin-list-overview.png ② 公告檢視頁(標題 + HTML 內容 + 返回按鈕)bulletin-view-detail.png ③ 使用手冊頁現況(僅標題的空殼畫面)bulletin-user-guide-empty.png

版面骨架(三頁合併示意):

Header 列:關鍵字搜尋 +「重置查詢」(無新增/編輯/刪除,純讀端) queryStore.resetCurrent() 開啟:讀取公告清單(授權範圍過濾)UC-BV-01 公告清單 DataTable(僅顯示 enable=1 + 生效期間內 + 本部門或全域) POST /bulletins(filters.auth=true,BE 強制套 enable/生效時間/部門範圍) GET /system-menu/BULLETIN_CATEGORY(分類轉譯) → #uc-bv-01-讀取公告清單授權範圍過濾(點我開啟) 列:標題(tooltip 全文)/分類/起訖時間/建立修改人(不顯示發布/部門欄) 整列 cursor-pointer,點擊 → 進入檢視頁(lazy 分頁) 開啟:進入公告檢視頁 UC-BV-02 公告檢視頁:〔分類〕標題 + HTML 內容 + 返回按鈕 GET /bulletin/{uid}(無 auth 過濾,見 §3 已知落差) v-dompurify-html 消毒渲染內容;route.query.uid 缺失導回管理頁 → #uc-bv-02-進入公告檢視頁(點我開啟) 使用手冊頁 側邊選單獨立入口 目前僅顯示標題,無內容 無任何 API 呼叫(空殼頁) 彩色=可點跳轉本頁 UC,灰=本頁結構(Header/使用手冊空殼)|本頁三 route 皆純讀,無 dialog/表單
公告檢視 wireframe:灰=Header 搜尋列與使用手冊空殼頁;teal=授權範圍過濾清單(可點 → UC-BV-01)與公告檢視頁(可點 → UC-BV-02),view 模式隱藏發布狀態與部門欄

狀態呈現:清單頁載入中 submit 旗標控制 DataTable :loading;檢視頁載入中全頁遮罩 ProgressSpinnerBulletinView.vue:63-69,與清單頁的局部 loading 不同做法);無 socket,資料僅在頁面載入 / 操作觸發時查詢,無輪詢或即時更新。

6. API 規格

Envelope:成功 {"status": true, "data": …}common/util/response_util.py:return_response);失敗依 jedi_common.handler.handler.register_error_handlers 統一處理。

6.1 總清單(本頁呼叫的全部 endpoint)

分類 Method + Path 說明 完整規格
載入 POST /bulletins 公告清單(分頁 + 排序 + auth=true 授權範圍過濾) §6.2
載入 GET /bulletin/{uid} 單筆公告詳情(無 auth 過濾,見 §3 ⚠️) §6.3
載入 GET /system-menu/BULLETIN_CATEGORY 公告分類下拉選單(清單頁與檢視頁各自獨立呼叫一次) GAI-SD-02

使用手冊頁(bulletin-user-guide)目前無任何 API 呼叫——純靜態模板,見 §1 / §12 坑 1。本頁不含新增 / 修改 / 刪除 endpoint(那些屬管理側,見 公告管理)。

6.2 公告清單類

[POST] /bulletins

Request:

欄位 型別 必填 說明
pager object|null {page, page_size}
sort array|null 排序條件陣列
filters.auth bool view 模式固定送 trueBulletinManage.vue:78-80);true 時 BE 強制套用 enable/生效時間/部門範圍(§4),前端無法覆蓋
filters.title / category / created_user / updated_user 等 string 全域搜尋欄位對應到的個別篩選(實務上前端走單一關鍵字框,非逐欄輸入)

出處:BulletinPageQueryRequest / BulletinQueryRequestapi/bulletin/serializers/bulletin_query.py:5-22

Response data[](每筆一則公告):

欄位 型別 說明
uid string 公告識別碼
title string 標題
category string 分類 code(對照 BULLETIN_CATEGORY 選單轉譯顯示)
org_units[] array {uid, name}(view 模式表格不顯示此欄,管理頁才顯示)
content string(HTML) 公告內容(清單不渲染,僅 detail 頁用)
release_time / expire_time date(YYYY-MM-DD 發布 / 過期日期
enable int 1=啟用 0=停用(view 模式表格不顯示此欄)
created_user / created_user_name string login_name / nickname
created_at datetime YYYY-MM-DD HH:MM:SS
updated_user / updated_user_name string login_name / nickname
updated_at datetime 同上格式

出處:BulletinResponse / BulletinPageQueryResponseapi/bulletin/serializers/bulletin.py:15-32);組裝邏輯 app/bulletin/service/bulletin_service.py:32-55get_bulletins_and_pager

6.3 公告詳情類

[GET] /bulletin/{uid}

無 query 參數,無 request body。Response data:與 §6.2 的單筆欄位形狀相同(同一 BulletinResponse schema)。

出處:api/bulletin/routes/bulletin_route.py:41-48BulletinRoute.get)→ app/bulletin/service/bulletin_service.py:72-76get_bulletin,內部呼叫 get_bulletin_by_uid無任何 filter 條件

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/bulletins/BulletinList.vue 清單頁入口(8 行,僅包一層 <BulletinManage :view="true"/>
src/views/bulletins/BulletinManage.vue 實際渲染邏輯(283 行);同時服務管理頁(bulletin-manage)與讀者清單頁(bulletin-listview=true)兩種模式
src/views/bulletins/BulletinView.vue 檢視頁(89 行):讀 route.query.uidGET /bulletin/{uid} → 渲染標題 + v-dompurify-html 內容
src/views/bulletins/UserGuide.vue 使用手冊頁(16 行):僅一個 <h3> 標題,無 script 邏輯、無 API 呼叫
src/config/router/index.js:4-52 四條 route 皆掛在 /bulletin 底下:bulletin-manage / bulletin-form / bulletin-list / bulletin-view / bulletin-user-guide
src/config/api/api.js:83-84 BULLETINS(清單)/ BULLETIN(單筆 CRUD)常數
src/service/BaseService.js axios 封裝(get() / post(),本頁唯一呼叫方式)
src/composables/useTableQuery.js 清單頁分頁 / 排序 / 搜尋狀態管理(依使用者與 route scope 持久化)
src/stores/queryStore.js 提供 resetCurrent() 重置查詢
src/config/locales/i18n/{zh-tw,en}/bulletin.jsonmenu.json 頁面 i18n(含分類文案 lang.bulletin_category.*

8. 後端檔案地圖(本頁核心鏈路)

鏈路 Route App Service 底層
公告清單 api/bulletin/routes/bulletin_route.py::BulletinsRoute.post app/bulletin/service/bulletin_service.py::get_bulletins_and_pager@transaction;依 auth 旗標決定部門篩選邏輯) domain/bulletin/service/bulletin_domain_service.py(套用 enable/生效時間白名單)→ infra/bulletin/repository/bulletin_repo_impl.py::BulletinRepoImpl(繼承 BaseRepositoryImpl
公告詳情 api/bulletin/routes/bulletin_route.py::BulletinRoute.get app/bulletin/service/bulletin_service.py::get_bulletin@transaction無 filter,純 uid 查找 domain/bulletin/service/bulletin_domain_service.py::get_bulletin_by_uidbulletin_repo.get_by_uidBaseRepositoryImpl 內建方法)
部門選單資料 系統選單模組(GET /system-menu/{code} 非本頁專屬,通用 system-menu 服務 BULLETIN_CATEGORY 屬資料字典項目

DDD 提醒:本鏈路完整遵守分層(route 不碰 DB、app service @transaction、domain service 定義 filter 邏輯、infra 層唯一操作 ORM),是規範遵守良好的範例。唯一落差是 §3 提到的「detail API 缺乏 enable/生效時間/部門範圍二次檢查」——這不是分層違規,是業務邏輯層面的授權範圍遺漏,新增類似 detail API 時應比照清單頁的 filter 邏輯補上,不要只複製本頁 get_bulletin 的寫法。

9. DB

9.1 資料表總清單(本頁讀寫的全部表)

讀/寫 說明 欄位詳述
public.bulletins 公告主檔(標題 / 內容 / 分類 / 發布時效 / 啟用狀態) §9.3
public.bulletin_org_units 公告 × 部門多對多關聯(清單頁部門篩選、管理頁部門欄顯示用) §9.3

本頁完全無寫入——是純讀頁面(新增 / 修改 / 刪除屬管理側 bulletin-manage.md)。無獨立的「已讀」/「瀏覽紀錄」表——db_schema.json 全庫掃描確認只有這兩張 bulletin* 表,公告閱讀不做任何已讀狀態追蹤(見 §12 坑 5)。

9.2 ER 圖(本頁讀取範圍)

bulletin_view_er bulletins public.bulletins (黃=主檔,本頁只讀) id (PK) integer uid varchar(50) title / content varchar / text category varchar(50) release_time / expire_time timestamp enable / is_delete integer tenant_id integer(FK tenants) org_unit_id integer(FK org_units,用途未確認,見§12坑6) bulletin_org_units public.bulletin_org_units (多對多關聯,本頁只讀) bulletin_id (PK,FK) integer org_unit_id (PK,FK) integer bulletins->bulletin_org_units 1:N(CASCADE 由部門端) org_units org_units (jedi-auth,外部參照) id (PK) integer name varchar bulletins->org_units org_unit_id(單一 FK,ON DELETE RESTRICT, 用途與上方關聯表不同,見坑6) bulletin_org_units->org_units N:1(ON DELETE CASCADE)
公告讀取範圍:黃 = 主檔、虛線 = 多對多關聯(部門篩選用)

9.3 核心表欄位(取自 DEV DB dump,含 DB comment)

public.bulletins — 公告主檔(本頁只讀)

DB comment:「公告表:儲存系統公告資訊,包含標題、內容、類別及發布時效」

欄位 型別 說明
id integer 內部序號(PK)
uid varchar(50) 對外識別碼(API 走 uid,DB comment:「公告唯一識別碼UUID」)
title varchar(255) 公告標題
content text 公告內容(HTML 富文本,前端 v-dompurify-html 消毒後渲染)
category varchar(50) 公告類別,預設 'others';對照 system-menuBULLETIN_CATEGORY 選單
release_time timestamp(無 tz) 發布時間
expire_time timestamp(無 tz)|null 過期時間;null 視為永不過期
enable integer 是否啟用:1 啟用 0 停用(DB comment 明載)
is_delete integer 軟刪除旗標:0 未刪除 1 已刪除,預設 0
tenant_id integer|null 租戶 FK(bulletins_tenant_fkON DELETE RESTRICT
org_unit_id integer|null 部門 FK(bulletins_org_fkON DELETE RESTRICTbulletin_org_units 關聯表是兩種不同機制,本欄位用途需個案確認,本頁清單 API 邏輯走的是 bulletin_org_units 多對多,見 §12 坑 6)
created_user / updated_user varchar(50)|null login_name(審計欄;本頁 response 額外 enrich _name 版本)
created_at / updated_at timestamp(無 tz) 審計欄

索引 idx_bulletins_liveenable=1 AND is_delete=0 的部分索引,涵蓋 tenant_id, org_unit_id, release_time)明確為本頁「授權範圍清單查詢」而設。

public.bulletin_org_units — 公告 × 部門關聯(本頁只讀)

複合主鍵(無獨立 uid),純 link table。

欄位 型別 說明
bulletin_id integer bulletins.idON DELETE 未特別設定,隨父列刪除行為由 FK 定義決定)
org_unit_id integer org_units.idON DELETE CASCADE:部門刪除時關聯自動清除)

10. 頁面邏輯與資料對應

載入時序

bulletin_view_load_sequence mountedList bulletin-list onMounted cat GET /system-menu/BULLETIN_CATEGORY (分類下拉選單) mountedList->cat fetchList POST /bulletins {filters: {auth: true}, pager, sort} mountedList->fetchList renderList bulletins[] → DataTable 渲染 fetchList->renderList rowClick row-click(view 模式限定) renderList->rowClick nav router.push bulletin-view {uid, from: route.name} rowClick->nav mountedView bulletin-view onMounted nav->mountedView checkQuery route.query.uid 存在? mountedView->checkQuery redirect router.push /bulletin/bulletin-manage (無 uid 直接導回管理頁) checkQuery->redirect getBulletin GET /bulletin/{uid} checkQuery->getBulletin getCat2 GET /system-menu/BULLETIN_CATEGORY (各自獨立呼叫,非共用 store) checkQuery->getCat2 renderView currData → 標題+content 渲染 getBulletin->renderView getCat2->renderView
清單頁 → 檢視頁的完整載入時序:兩頁分類選單各自獨立呼叫、無共用 store;uid 缺失時直接導回管理頁

關鍵欄位對應(API ↔︎ 畫面):

畫面元素 FE state API 欄位
清單搜尋框 filterTable.global.valueuseTableQuery 管理) request filters(組合為關鍵字比對)
清單分類欄 trensCategoryDesc(data.category)(比對 categoryMenut('lang.bulletin_category.' + value) response category
清單標題欄 直接綁定 + tooltip 顯示完整標題 response title
清單起訖時間欄 直接綁定 response release_time / expire_time
清單建立 / 修改人欄 直接綁定 created_user_name / updated_user_name 同名欄位
檢視頁標題列 currData.category + currData.title 組合顯示 [分類] 標題 response category / title
檢視頁內容區 v-dompurify-html="currData.content" response content

儲存流程:無——本頁三條 route 皆為純讀取展示頁,沒有任何欄位可編輯或儲存。

錯誤對應BulletinView.vueBulletinList.vue(經 BulletinManage.vue)均未見自訂 catch handler,錯誤走 axios 全域攔截器的統一 toast(BaseService 慣例)。GET /bulletin/{uid} 若 uid 不存在,底層 get_by_uid 回傳行為需視 BaseRepositoryImpl 實作而定(可能回傳 None 導致後續 dump 出錯,而非明確拋 404 — 未在本頁程式碼見到針對此情況的顯式 NotFound 拋出,見 §12 坑 7)。

11. 背景行為與外部依賴

類型 內容
通知 無——本頁不觸發、也不消費任何通知(公告的建立會不會觸發通知,屬管理側行為,見 公告管理
Socket 無 socket / 輪詢;每次進頁 / 搜尋皆重新 fetch
jedi-* 套件 jedi-bulletinBulletinEntity 基礎 DTO/BulletinDTO.from_entity 繼承自 jedi_bulletin.app.dto.bulletin.BulletinDTO)、jedi-authOrgUnitDomainService / UserDomainService 做部門與使用者查詢、JWT)
系統參數 無 feature flag;公告分類為 system-menu 資料字典項目 BULLETIN_CATEGORY,非寫死 enum

12. 邊界情況與已知坑

  1. 使用手冊頁(bulletin-user-guide)目前是空殼:僅 <h3>{{t('lang.menu.user-guide')}}</h3> 一行(UserGuide.vue:1-16),無任何內容區塊、無 API 呼叫、無資料模型——是靜態占位頁而非資料驅動頁面。若日後要填充內容,需另外設計內容來源(寫死 markdown / 走公告分類專用一則 bulletin / 獨立 CMS 欄位皆可,目前程式碼未給任何線索)
  2. bulletin-view 缺 uid 時導向 bulletin-manage 而非 bulletin-listBulletinView.vue:25router.push('/bulletin/bulletin-manage'))——一般讀者(非管理者)若直接訪問 /bulletin/bulletin-view 網址但沒帶 uid query,會被導去管理頁而非清單頁;管理頁本身無角色守門(§3),畫面上仍會顯示,但語意上更接近管理入口而非讀者入口
  3. detail API 無二次授權範圍檢查:見 §3 ⚠️——清單頁嚴格套用 enable/生效時間/部門範圍,但 GET /bulletin/{uid} 完全不重複這層過濾。目前判斷非資安缺口(公告非機密內容),但屬「清單過濾、detail 不過濾」的不對稱模式,之後若擴充成含機密內容的公告類型,需在此補檢查
  4. release_time__gt_now / expire_time__le_now 欄位命名與實際語意相反:命名讀起來像「release_time 大於 now」「expire_time 小於等於 now」,但實際 SQL 條件是 release_time <= now()(已到發布時間)與 expire_time >= now()(尚未過期)——命名沿用 BulletinQueryEntity 建構參數原樣,維護時勿望文生義(infra/bulletin/repository/bulletin_repo_impl.py:63-66)。另外 expire_time 為 null 時的邊界行為(SQL 對 null 比較恆為 unknown,等同該公告會被排除在「尚未過期」條件外)在程式碼中沒有特別處理,需個案驗證是否為預期行為
  5. 無已讀 / 瀏覽紀錄追蹤:全庫僅 bulletins + bulletin_org_units 兩表(db_schema.json 確認),系統不記錄「誰看過哪則公告」;若需求提到「未讀公告數」等功能,需先新增資料模型,非現有欄位可支援
  6. bulletins.org_unit_id(單一 FK)與 bulletin_org_units(多對多關聯表)並存:本頁清單過濾邏輯只用後者(org_units__inbulletin_org_units join),前者 bulletins.org_unit_id 單一外鍵欄位的實際用途在本頁程式碼路徑中未見使用,可能是舊欄位或另有他用,修改部門篩選邏輯前務必先確認兩者關係,不要誤用單一 FK 欄位
  7. GET /bulletin/{uid} 對不存在 uid 的行為未見明確 404 處理bulletin_service.get_bulletin 直接呼叫 get_bulletin_by_uid 並無 if not bulletin: raise NotFound(...) 判斷,行為取決於 BaseRepositoryImpl.get_by_uid 找不到時是否回傳 None——若回傳 None,後續 BulletinDTO.from_entity(None) 極可能拋出 AttributeError 而非乾淨的 404 錯誤碼,修 bug 或擴充本 API 前應先驗證此路徑的實際行為

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload)
  • 測試帳號:任一 dev 環境已登入帳號即可(無角色限制,密碼見 .env / 部署文件)
  • 導航路徑:登入 → 側邊選單「公告」(bulletin-list)→ 點列進入「檢視公告」(bulletin-view);使用手冊另由選單「使用手冊」(bulletin-user-guide)獨立進入
  • 前置資料:DB 需有至少一筆 enable=1release_time <= now <= expire_time(或 expire_time 為 null)的公告,且該公告的 bulletin_org_units 涵蓋測試帳號所屬部門(或帳號 org_unit_id 為 null)
  • E2Ecompliance-manager-test/ repo(Cucumber + Playwright);本頁相關 feature 檔以 公告 / bulletin 搜尋
  • 相關文件:管理側完整 CRUD 行為見 公告管理;DB / API 全量見 GAI-SD-02/03;docs/api/bulletin/(舊版 API 文件,僅供背景參考,不當清單來源)