feat(vmis): 租戶自動開通完整流程 + Admin Portal SSO + NC 行事曆訂閱

Backend:
- schedule_tenant: NC 新容器自動 pgsql 安裝 (_nc_db_check 全新容器處理)
- schedule_tenant: NC 初始化加入 Redis + APCu memcache 設定 (修正 OIDC invalid_state)
- schedule_tenant: 新租戶 KC realm 自動設定 accessCodeLifespan=600s (修正 authentication_expired)
- schedule_account: NC Mail 帳號自動設定 (nc_mail_result/nc_mail_done_at)
- schedule_account: NC 台灣國定假日行事曆自動訂閱 (CalDAV MKCALENDAR)
- nextcloud_client: 新增 subscribe_calendar() CalDAV 訂閱方法
- settings: 新增系統設定 API (site_title/version/timezone/SSO/Keycloak)
- models/result: 新增 nc_mail_result, nc_mail_done_at 欄位
- alembic: 遷移 002(system_settings) 003(keycloak_admin) 004(nc_mail_result)

Frontend (Admin Portal):
- 新增完整管理後台 (index/tenants/accounts/servers/schedules/logs/settings/system-status)
- api.js: Keycloak JS Adapter SSO 整合 (PKCE/S256, fallback KC JS 來源, 自動 token 更新)
- index.html: Promise.allSettled 取代 Promise.all,防止單一 API 失敗影響整頁
- 所有頁面加入 try/catch + toast 錯誤處理
- 新增品牌 LOGO 與 favicon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
VMIS Developer
2026-03-15 15:31:37 +08:00
parent 42d1420f9c
commit 62baadb06f
53 changed files with 5638 additions and 195 deletions

373
docs/開發規範.md Normal file
View File

@@ -0,0 +1,373 @@
# Virtual MIS - 開發規範
**版本**: v1.0
**日期**: 2026-02-27
---
## 專案結構
```
virtual-mis/
├── backend/ # 後端服務
│ ├── app/
│ │ ├── api/ # API 路由
│ │ │ ├── v1/
│ │ │ │ ├── tenant_onboarding.py
│ │ │ │ ├── service_integration.py
│ │ │ │ └── billing.py
│ │ │ └── router.py
│ │ ├── core/ # 核心配置
│ │ │ ├── config.py
│ │ │ ├── security.py
│ │ │ └── dependencies.py
│ │ ├── db/ # 資料庫
│ │ │ ├── base.py
│ │ │ └── session.py
│ │ ├── models/ # ORM 模型
│ │ ├── schemas/ # Pydantic Schema
│ │ ├── services/ # 業務邏輯
│ │ └── utils/ # 工具函數
│ ├── alembic/ # 資料庫遷移
│ ├── tests/ # 測試
│ ├── .env # 環境變數
│ ├── requirements.txt # 依賴套件
│ └── main.py # 應用入口
├── frontend/ # 前端服務
│ ├── admin-portal/ # 管理後台
│ │ ├── app/ # Next.js App Router
│ │ ├── components/ # React 元件
│ │ ├── lib/ # 工具函數
│ │ ├── public/ # 靜態資源
│ │ └── package.json
│ │
│ └── landing-page/ # 行銷頁面
│ ├── app/
│ ├── components/
│ └── package.json
└── docs/ # 文件
├── architecture/ # 架構設計
├── api-specs/ # API 規格
├── business/ # 商業計畫
└── deployment/ # 部署文件
```
## 開發環境
### 後端環境
**Python 版本**: 3.11+
**Port 配置**:
- 開發環境: `10281`
- 測試環境: `10282`
**資料庫**:
- Host: `10.1.0.20`
- Port: `5433`
- Database: `virtual_mis`
- User: `admin`
**啟動方式**:
```bash
cd D:\_Develop\porscheworld_develop\virtual-mis\backend
START_BACKEND.bat
```
### 前端環境
**Node.js 版本**: 20+
**Port 配置**:
- Admin Portal: `10280`
- Landing Page: `10290`
**啟動方式**:
```bash
cd D:\_Develop\porscheworld_develop\virtual-mis\frontend\admin-portal
START_FRONTEND.bat
```
## 編碼規範
### Python (後端)
1. **命名規範**:
- 檔案名稱: `snake_case.py`
- 類別名稱: `PascalCase`
- 函數名稱: `snake_case`
- 常數: `UPPER_CASE`
2. **程式碼風格**:
- 使用 Black 格式化
- 遵循 PEP 8
- 最大行長: 100 字元
3. **型別標註**:
```python
def get_tenant(tenant_id: int) -> Optional[Tenant]:
"""取得租戶資料"""
pass
```
4. **文件字串**:
```python
def create_tenant(data: TenantCreate) -> Tenant:
"""
建立新租戶
Args:
data: 租戶建立資料
Returns:
Tenant: 建立的租戶物件
Raises:
ValueError: 租戶代碼已存在
"""
pass
```
### TypeScript (前端)
1. **命名規範**:
- 檔案名稱: `kebab-case.tsx`
- 元件名稱: `PascalCase`
- 函數名稱: `camelCase`
- 介面: `PascalCase` (前綴 I 可選)
2. **元件結構**:
```typescript
'use client'
import { useState } from 'react'
interface Props {
title: string
onSubmit: (data: FormData) => void
}
export default function MyComponent({ title, onSubmit }: Props) {
const [loading, setLoading] = useState(false)
return (
<div>
<h1>{title}</h1>
</div>
)
}
```
3. **型別定義**:
```typescript
// types/tenant.ts
export interface Tenant {
id: number
code: string
name: string
status: 'trial' | 'active' | 'suspended'
}
```
## API 設計規範
### RESTful 規範
**URL 命名**:
- 使用名詞複數: `/api/v1/tenants`
- 使用 kebab-case: `/api/v1/tenant-onboarding`
- 版本控制: `/api/v1/`, `/api/v2/`
**HTTP 方法**:
- `GET`: 查詢資料
- `POST`: 建立資料
- `PUT`: 完整更新
- `PATCH`: 部分更新
- `DELETE`: 刪除資料
**回應格式**:
```json
{
"success": true,
"data": {
"id": 1,
"name": "測試公司"
},
"message": "操作成功"
}
```
**錯誤回應**:
```json
{
"success": false,
"error": {
"code": "TENANT_NOT_FOUND",
"message": "找不到指定的租戶",
"details": {}
}
}
```
### Schema 設計
**命名規範**:
- Base Schema: `TenantBase`
- Create Schema: `TenantCreate`
- Update Schema: `TenantUpdate`
- Response Schema: `TenantResponse`
**範例**:
```python
from pydantic import BaseModel, Field
class TenantBase(BaseModel):
"""租戶基礎 Schema"""
code: str = Field(..., max_length=50, description="租戶代碼")
name: str = Field(..., max_length=200, description="公司名稱")
class TenantCreate(TenantBase):
"""建立租戶 Schema"""
admin_email: str = Field(..., description="管理員郵箱")
class TenantResponse(TenantBase):
"""租戶回應 Schema"""
id: int
status: str
created_at: datetime
model_config = ConfigDict(from_attributes=True)
```
## 資料庫規範
### Migration 管理
**命名規則**:
```
{timestamp}_{description}.py
例如: 20260227_create_subscriptions_table.py
```
**Migration 內容**:
```python
def upgrade() -> None:
"""升級操作"""
op.create_table(
'subscriptions',
sa.Column('id', sa.Integer(), primary_key=True),
sa.Column('tenant_id', sa.Integer(), nullable=False),
# ...
)
def downgrade() -> None:
"""降級操作"""
op.drop_table('subscriptions')
```
### 表格命名
- 使用複數形式: `subscriptions`, `invoices`
- 使用 snake_case
- 加入前綴表示模組: `billing_invoices`
## Git 工作流程
### 分支策略
- `master`: 生產環境
- `develop`: 開發環境
- `feature/*`: 功能開發
- `hotfix/*`: 緊急修復
### Commit 訊息
**格式**:
```
<類型>: <簡短描述>
<詳細說明>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
```
**類型**:
- `feat`: 新功能
- `fix`: 錯誤修復
- `docs`: 文件更新
- `refactor`: 重構
- `test`: 測試
- `chore`: 雜項
**範例**:
```
feat: 新增租戶開通 API
實作租戶自動開通功能,包含:
- 建立租戶資料
- 配置網域
- 建立 Keycloak Realm
- 初始化服務
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
```
## 測試規範
### 單元測試
**覆蓋率目標**: 80%+
**測試檔案命名**:
```
tests/
├── unit/
│ ├── test_tenant_service.py
│ └── test_billing_service.py
└── integration/
├── test_api_tenants.py
└── test_api_billing.py
```
**測試範例**:
```python
import pytest
from app.services.tenant_service import create_tenant
def test_create_tenant_success():
"""測試建立租戶成功"""
data = TenantCreate(
code="testcompany",
name="測試公司",
admin_email="admin@test.com"
)
tenant = create_tenant(data)
assert tenant.code == "testcompany"
assert tenant.status == "trial"
```
## 安全規範
1. **環境變數**: 所有敏感資訊放在 `.env`
2. **密碼處理**: 使用 bcrypt 雜湊
3. **API 驗證**: 所有 API 需要 JWT token
4. **輸入驗證**: 使用 Pydantic 驗證所有輸入
5. **SQL 注入**: 使用 ORM避免原生 SQL
## 文件規範
1. **README.md**: 每個專案必須包含
2. **API 文件**: 使用 FastAPI 自動生成 (Swagger)
3. **程式碼註解**: 複雜邏輯必須註解
4. **設計文件**: 重要功能需要設計文件
---
**參考資源**:
- [FastAPI 官方文件](https://fastapi.tiangolo.com/)
- [Next.js 官方文件](https://nextjs.org/docs)
- [Python PEP 8](https://peps.python.org/pep-0008/)