---
# System prepended metadata

title: Kanban
tags: [project]

---

# Kanban

![](https://i.imgur.com/7swXyI7.png)

這是一個工作流程管理系統

## Functional Requirments
1. 工作流程可以分成需求等待區（RequestQueue）、Top 5 需求（Top5Request）、需求（Request）、分析（Analysis）、開發（Development）、測試（Test）、上線（Release），七種階段（Stage）。
2. 使用「卡片（Card）」作為從需求規劃階段到上線階段中，需被執行的工作事項。
3. 每個階段都有各自同時能夠負荷的任務上限（Limit）數量，如上圖所示。
4. 每個階段中分別有進行中（Processing）與完成（Completed）兩種子階段（Substage），每通過該子階段時都需要紀錄時間。
5. 每個卡片都有一個優先處理的順序（CardOrder），並且在每個子階段中的所有卡片要依照此順序執行。
6. 圖片中的兩條綠色線分別為卡片的起點與終點，此外需要知道每個卡片從起點流動到終點所需的時間（Lead time）。
7. 需要知道每張卡片在各階段中從進行中移動到完成階段所需的時間（Cycle time）。
8. 在各階段中可能會遇到阻礙（Block）的情況，需要等待某些事情完成後才能繼續往下進行。
9. 如遇到緊急情況，有需要立即修復的 Bug 時，建立一個緊急通道且設定 WIP=1（讓所有流程的 Limit=1），優先執行此卡片。

## Non-Functional Requirments
1. Controller 傳入參數檢查
2. 使用 filter 印出 Request & Response 資訊
3. 自行生成憑證並掛上 HTTPS，再更改本機 host file 模擬提供 domain 服務

## ER Model

![](https://i.imgur.com/3aK1o2l.png)

## ER Schema

![](https://i.imgur.com/IuHiQLi.png)

[連結](https://editor.ponyorm.com/user/zacharyzhuo/kanban/designer)

## Class Diagram

![](https://i.imgur.com/iNsaMiV.png)

## Flow Chart

![](https://i.imgur.com/F6X2a7C.png)

## Use Case Specification

**Card Order**

| 順序 |   備註   |
|:----:|:--------:|
|  -1  |   預設   |
|  0   | 緊急任務 |
|  1   |  順位 1  |
| ...  |   ...    |
|  n   |  順位 n  |

## Localhost on https://\<my domain>

[傳送門](https://hackmd.io/7g8zKNZdQTyov4kaFBaoEQ?both#Localhost-on-httpsltmy-domaingt)

## API

### Member

[GET] `/members`
Response
```json 
[
    {
        "id": 1,
        "name": "Zachary",
        "email": "zachary@webcomm.com.tw"
    },
    {
        "id": 2,
        "name": "Derek",
        "email": "derek@webcomm.com.tw"
    }
]
```
[GET] `/member/{id}`
Response
```json
{
    "id": 1,
    "name": "Zachary",
    "email": "zachary@webcomm.com.tw"
}
```
[POST] `/member`
Request
```json
{
    "name": "somebody",
    "email": "somebody@webcomm.com.tw"
}
```
[PUT] `/member/{id}`
Request
```json
{
    "name": "somebody",
    "email": "somebody@webcomm.com.tw"
}
```
[DELETE] `/member/{id}`

### Card

[GET] `/cards`
Response
```json 
[
    {
        "id": 1,
        "member": {
            "id": 1,
            "name": "Zachary",
            "email": "zachary@webcomm.com.tw"
        },
        "title": "test",
        "content": "test",
        "cardOrder": 1,
        "emergent": false,
        "createdTime": "2021-08-12T03:51:42.000+00:00",
        "completedTime": null
    },
    {
        "id": 2,
        "member": {
            "id": 1,
            "name": "Zachary",
            "email": "zachary@webcomm.com.tw"
        },
        "title": "todo",
        "content": "66666",
        "cardOrder": 0,
        "emergent": true,
        "createdTime": "2021-08-12T03:55:09.000+00:00",
        "completedTime": "2021-08-12T06:04:31.000+00:00"
    }
]
```
[GET] `/card/{id}`
Response
```json
{
    "id": 1,
    "member": {
        "id": 1,
        "name": "Zachary",
        "email": "zachary@webcomm.com.tw"
    },
    "title": "test",
    "content": "test",
    "cardOrder": 1,
    "emergent": false,
    "createdTime": "2021-08-12T03:51:42.000+00:00",
    "completedTime": null
}
```
[POST] `/card`
Request
```json
{
    "title": "todo",
    "content": "very emergent",
    "emergent": true
}
```
[PUT] `/card/{id}`
Request
```json
{
    "title": "todo",
    "content": "very emergent",
    "emergent": true,
    "cardOrder": 0
}
```
[DELETE] `/card/{id}`

### Task

[GET] `/tasks`
Response
```json 
[
    {
        "id": 1,
        "card": {
            "id": 1,
            "member": {
                "id": 1,
                "name": "Zachary",
                "email": "zachary@webcomm.com.tw"
            },
            "title": "test",
            "content": "test",
            "cardOrder": 1,
            "emergent": false,
            "createdTime": "2021-08-12T03:51:42.000+00:00",
            "completedTime": null
        },
        "stage": {
            "id": 1,
            "name": "RequestQueue",
            "stageLimit": 0
        },
        "substage": {
            "id": 1,
            "stage": {
                "id": 1,
                "name": "RequestQueue",
                "stageLimit": 0
            },
            "status": null
        },
        "member": {
            "id": 1,
            "name": "Zachary",
            "email": "zachary@webcomm.com.tw"
        },
        "block": false,
        "startTime": "2021-08-12T03:51:42.000+00:00",
        "endTime": "2021-08-12T03:59:46.000+00:00"
    },
    {
        "id": 2,
        "card": {
            "id": 2,
            "member": {
                "id": 1,
                "name": "Zachary",
                "email": "zachary@webcomm.com.tw"
            },
            "title": "todo",
            "content": "66666",
            "cardOrder": 0,
            "emergent": true,
            "createdTime": "2021-08-12T03:55:09.000+00:00",
            "completedTime": "2021-08-12T06:04:31.000+00:00"
        },
        "stage": {
            "id": 1,
            "name": "RequestQueue",
            "stageLimit": 0
        },
        "substage": {
            "id": 1,
            "stage": {
                "id": 1,
                "name": "RequestQueue",
                "stageLimit": 0
            },
            "status": null
        },
        "member": {
            "id": 1,
            "name": "Zachary",
            "email": "zachary@webcomm.com.tw"
        },
        "block": false,
        "startTime": "2021-08-12T03:56:42.000+00:00",
        "endTime": "2021-08-12T03:59:25.000+00:00"
    }
]
```
[GET] `/task/{id}`
Response 略

[GET] `/task/cardid/{id}`：查詢該卡片底下所有的任務
Response 略

[GET] `/task/substageid/{id}`：查詢該子階段底下所有的任務
Response 略

[GET] `/task/stageid/{id}`：查詢該階段底下所有的任務
Response 略

[PUT] `/card/{id}/next`：將卡片並移動至下個子階段

[PUT] `/card/{id}/finish`：將卡片目前子階段標示完成

[PUT] `/card/{id}/block`：將卡片變更為阻礙 (block) 狀態

### Substage

[GET] `/substages`
Response
```json
[
    {
        "id": 1,
        "stage": {
            "id": 1,
            "name": "RequestQueue",
            "stageLimit": 0
        },
        "status": null
    },
    {
        "id": 2,
        "stage": {
            "id": 2,
            "name": "Top5Request",
            "stageLimit": 5
        },
        "status": null
    },
    {
        "id": 3,
        "stage": {
            "id": 3,
            "name": "Request",
            "stageLimit": 3
        },
        "status": "Processing"
    },
    {
        "id": 4,
        "stage": {
            "id": 3,
            "name": "Request",
            "stageLimit": 3
        },
        "status": "Completed"
    },
    {
        "id": 5,
        "stage": {
            "id": 4,
            "name": "Analysis",
            "stageLimit": 4
        },
        "status": "Processing"
    },
    {
        "id": 6,
        "stage": {
            "id": 4,
            "name": "Analysis",
            "stageLimit": 4
        },
        "status": "Completed"
    },
    {
        "id": 7,
        "stage": {
            "id": 5,
            "name": "Development",
            "stageLimit": 5
        },
        "status": "Processing"
    },
    {
        "id": 8,
        "stage": {
            "id": 5,
            "name": "Development",
            "stageLimit": 5
        },
        "status": "Completed"
    },
    {
        "id": 9,
        "stage": {
            "id": 6,
            "name": "Test",
            "stageLimit": 3
        },
        "status": "Processing"
    },
    {
        "id": 10,
        "stage": {
            "id": 6,
            "name": "Test",
            "stageLimit": 3
        },
        "status": "Completed"
    },
    {
        "id": 11,
        "stage": {
            "id": 7,
            "name": "Release",
            "stageLimit": 0
        },
        "status": null
    }
]
```
[GET] `/substage/{id}`
Response
```json 
{
    "id": 1,
    "stage": {
        "id": 1,
        "name": "RequestQueue",
        "stageLimit": 0
    },
    "status": null
}
```

### Stage

[GET] `/stages`
Response
```json 
[
    {
        "id": 1,
        "name": "RequestQueue",
        "stageLimit": 0
    },
    {
        "id": 2,
        "name": "Top5Request",
        "stageLimit": 5
    },
    {
        "id": 3,
        "name": "Request",
        "stageLimit": 3
    },
    {
        "id": 4,
        "name": "Analysis",
        "stageLimit": 4
    },
    {
        "id": 5,
        "name": "Development",
        "stageLimit": 5
    },
    {
        "id": 6,
        "name": "Test",
        "stageLimit": 3
    },
    {
        "id": 7,
        "name": "Release",
        "stageLimit": 0
    }
]
```
[GET] `/stage/{id}`
Response
```json 
{
    "id": 1,
    "name": "RequestQueue",
    "stageLimit": 0
}
```

## SQL
``` SQL
CREATE TABLE `MEMBER` (
  `ID` INTEGER PRIMARY KEY AUTO_INCREMENT,
  `NAME` VARCHAR(255) UNIQUE NOT NULL,
  `EMAIL` VARCHAR(255) NOT NULL
);

CREATE TABLE `CARD` (
  `ID` INTEGER PRIMARY KEY AUTO_INCREMENT,
  `MEMBER_ID` INTEGER NOT NULL REFERENCES MEMBER(ID) ON DELETE CASCADE,
  `TITLE` VARCHAR(255) NOT NULL,
  `CONTENT` VARCHAR(255) NOT NULL,
  `CARD_ORDER` INTEGER NOT NULL,
  `EMERGENT` BOOLEAN NOT NULL,
  `CREATED_TIME` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `COMPLETED_TIME` DATETIME 
);

CREATE TABLE `STAGE` (
  `ID` INTEGER PRIMARY KEY AUTO_INCREMENT,
  `NAME` VARCHAR(255) NOT NULL,
  `STAGE_LIMIT` INTEGER NOT NULL
);

CREATE TABLE `SUBSTAGE` (
  `ID` INTEGER PRIMARY KEY AUTO_INCREMENT,
  `STAGE_ID` INTEGER NOT NULL REFERENCES STAGE(ID) ON DELETE CASCADE,
  `STATUS` VARCHAR(255)
);

CREATE TABLE `TASK` (
  `ID` INTEGER PRIMARY KEY AUTO_INCREMENT,
  `CARD_ID` INTEGER NOT NULL REFERENCES CARD(ID) ON DELETE CASCADE,
  `STAGE_ID` INTEGER NOT NULL REFERENCES STAGE(ID) ON DELETE CASCADE,
  `SUBSTAGE_ID` INTEGER NOT NULL REFERENCES SUBSTAGE(ID) ON DELETE CASCADE,
  `MEMBER_ID` INTEGER NOT NULL REFERENCES MEMBER(ID) ON DELETE CASCADE,
  `BLOCK` BOOLEAN NOT NULL,
  `START_TIME` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `END_TIME` DATETIME
);
	
INSERT INTO MEMBER (ID, NAME, EMAIL)
values (0, 'Zachary', 'zachary@webcomm.com.tw');

INSERT INTO STAGE (ID, NAME, STAGE_LIMIT)
values (0, 'RequestQueue', 0), (0, 'Top5Request', 5), (0, 'Request', 3), 
(0, 'Analysis', 4), (0, 'Development', 5), (0, 'Test', 3), (0, 'Release', 0);

INSERT INTO SUBSTAGE (ID, STAGE_ID, STATUS)
values (0, (SELECT ID from STAGE WHERE ID=1), null),
(0, (SELECT ID from STAGE WHERE ID=2), null),
(0, (SELECT ID from STAGE WHERE ID=3), 'Processing'),
(0, (SELECT ID from STAGE WHERE ID=3), 'Completed'),
(0, (SELECT ID from STAGE WHERE ID=4), 'Processing'),
(0, (SELECT ID from STAGE WHERE ID=4), 'Completed'),
(0, (SELECT ID from STAGE WHERE ID=5), 'Processing'),
(0, (SELECT ID from STAGE WHERE ID=5), 'Completed'),
(0, (SELECT ID from STAGE WHERE ID=6), 'Processing'),
(0, (SELECT ID from STAGE WHERE ID=6), 'Completed'),
(0, (SELECT ID from STAGE WHERE ID=7), null);

INSERT INTO CARD (ID, MEMBER_ID, TITLE, CONTENT, CARD_ORDER, EMERGENT)
values (0, (SELECT ID from MEMBER WHERE ID=1), 'test', 'test', 1, FALSE);

INSERT INTO TASK (ID, CARD_ID, STAGE_ID, SUBSTAGE_ID, MEMBER_ID, BLOCK)
values (0, (SELECT ID from CARD WHERE ID=1), (SELECT ID from STAGE WHERE ID=1), (SELECT ID from SUBSTAGE WHERE ID=1), (SELECT ID from MEMBER WHERE ID=1), FALSE);
```

## Note
1. ```@NotNull``` 任何物件的 value 不能為 null
2. ```@NotEmpty``` 集合物件的元素不為 0，即集合不為空，也可以用於字串不為 null
3. ```@NotBlank``` 只能用於字串不為 null，並且字串 trim() 以後 length 要大於 0
4. SQL 建表時欄位有設定初值且非空，但建立實體時沒有給值會報錯，可使用 ```@Column(name="CREATED_TIME", insertable=false)``` 讓此欄位不會插入新的值。
5. Repository 中定義取資料表中第一筆資料```findFirstByOrderByIdAsc();```


```java 
#log SQL
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.format_sql=true

logging.level.org.hibernate.type=trace
```
## Reference

[[1] 了解看板(Kanban)方法的核心方法，活化你的管理思維](https://progressbar.tw/posts/66)

###### tags: `project`
