本文為臺大資管團隊與長沂國際實業股份有限公司(COMEBUY)產學合作專案所開發之系統技術文件,由專案團隊撰寫,可提供公司人員在未來實際使用系統時參考。 本文共分為四節,依序說明系統環境、資料庫 schema、後端及前端撰寫。 --- ## 1. 系統環境 ### 1.1 系統安裝說明 1. 安裝 Node.js、Yarn、Python、miniconda、Postgresql 等必要軟體。 2. 在 `COMEBUY_SALES\frontend` 底下執行 `yarn` 指令,安裝所需套件。 ``` // under COMEBUY_SALES\frontend yarn ``` 3. 建置前端 build 檔:在 `COMEBUY_SALES\frontend` 下執行 `yarn build`。 ``` // under COMEBUY_SALES\frontend yarn build ``` 4. 建立一個 Postgresql database。 5. 編輯環境變數檔 `.env`,內容參照 `.env.example` 設定資料庫連接資訊以及希望伺服器運行之連接阜(在 `COMEBUY_SALES\frontend`、`COMEBUY_SALES\backend`、`COMEBUY_SALES\database` 下各有一個 `env` 檔,兩者皆需進行設定。) 6. 在 `COMEBUY_SALES\database` 下執行 `createTable.py` 檔建立資料庫的表格。 ``` // under COMEBUY_SALES\database python createTable.py ``` 7. 安裝完 miniconda 後,在 window 左下角搜尋欄輸入 anaconda prompt,開啟後輸入 `conda init powershell`,關閉 anaconda prompt。 ![](https://hackmd.io/_uploads/SJUcA04o2.png =350x350) 8. 接著回到 `COMEBUY_SALES\backend` 下,輸入以下指令。 ``` // under COMEBUY_SALES\backend conda create --name CB_NTU_2 python=3.9.16 conda activate CB_NTU_2 ``` 9. 安裝後端所需套件:在 `COMEBUY_SALES\backend` 下,輸入以下指令。 ``` // under COMEBUY_SALES\backend pip install -r requirements.txt ``` 10. 啟動:在 `COMEBUY_SALES\backend` 底下執行以下指令,若無錯誤系統即安裝完成開始運行。 ``` // under COMEBUY_SALES\backend uvicorn main:app --host 0.0.0.0 --port 8000 ``` ## 2. 資料庫 ### 2.1 資料庫說明 本專案使用 PostgreSQL。使用前須先安裝相關軟體。 ### 2.2 資料表說明 資料表共 21 張,各表說明如下。 1. :::spoiler status <table border="0" style="text-align:center;"> <tr> <td colspan="6"><b style="font-size:20px">status</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>狀態名稱</td> <td>Varchar</td> <td>販賣中</td> </tr> </table> 2. :::spoiler categories <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">categories</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>種類名稱</td> <td>Varchar</td> <td>原葉鮮萃茶</td> </tr> </table> 3. :::spoiler drinks <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">drinks</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>飲品名稱</td> <td>Varchar</td> <td>海神</td> </tr> <tr> <td>category</td> <td>F</td> <td>Y</td> <td>種類</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>status</td> <td>F</td> <td>Y</td> <td>狀態</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>comebuy_id</td> <td></td> <td>Y</td> <td>公司飲品代號</td> <td>Varchar</td> <td>A020006</td> </tr> </table> 4. :::spoiler sweets <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">sweets</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>甜度名稱</td> <td>Varchar</td> <td>無糖</td> </tr> </table> 5. :::spoiler ices <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">ices</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>冰塊名稱</td> <td>Varchar</td> <td>去冰</td> </tr> </table> 6. :::spoiler tastes <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">tastes</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>口味名稱</td> <td>Varchar</td> <td>去籽</td> </tr> </table> 7. :::spoiler toppins <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">toppins</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>加料名稱</td> <td>Varchar</td> <td>布丁</td> </tr> </table> 8. :::spoiler regions <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">regions</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>區域名稱</td> <td>Varchar</td> <td>東區</td> </tr> </table> 9. :::spoiler counties <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">counties</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>縣市名稱</td> <td>Varchar</td> <td>台北市</td> </tr> </table> 10. :::spoiler districts <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">districts</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>行政區名稱</td> <td>Varchar</td> <td>士林區</td> </tr> </table> 11. :::spoiler stores <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">stores</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>name</td> <td></td> <td>Y</td> <td>門市名稱</td> <td>Varchar</td> <td>新店光明</td> </tr> <tr> <td>region</td> <td>F</td> <td>Y</td> <td>區域</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>county</td> <td>F</td> <td>Y</td> <td>縣市</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>district</td> <td>F</td> <td>Y</td> <td>行政區</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>comebuy_id</td> <td></td> <td>Y</td> <td>公司儲存的代號</td> <td>Varchar</td> <td>0045</td> </tr> </table> 12. :::spoiler tempSales <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">tempSales</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>store</td> <td></td> <td>Y</td> <td>門市代號</td> <td>Varchar</td> <td>0045</td> </tr> <tr> <td>time</td> <td></td> <td>Y</td> <td>訂單時間</td> <td>Timestamp</td> <td>2023/07/04 11:25:00</td> </tr> <tr> <td>drink</td> <td></td> <td>Y</td> <td>飲料代號</td> <td>Varchar</td> <td>A020006</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar</td> <td>少奶/少冰/無糖</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar</td> <td>布丁/椰果</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> 13. :::spoiler sales <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">Sales</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>store</td> <td>F</td> <td>Y</td> <td>門市</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>time</td> <td></td> <td>Y</td> <td>訂單時間</td> <td>Timestamp</td> <td>2023/07/04 11:25:00</td> </tr> <tr> <td>drink</td> <td>F</td> <td>Y</td> <td>飲料</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>ice</td> <td>F</td> <td>N</td> <td>冰塊</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>sweet</td> <td>F</td> <td>N</td> <td>甜度</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar[]</td> <td>[少奶, 去籽]</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar[]</td> <td>[布丁, 椰果]</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> 14. :::spoiler aggregateSalesDay <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">aggregateSalesDay</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>store</td> <td>F</td> <td>Y</td> <td>門市</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>date</td> <td></td> <td>Y</td> <td>日期</td> <td>Date</td> <td>2023-04-30</td> </tr> <tr> <td>drink</td> <td>F</td> <td>Y</td> <td>飲料</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>ice</td> <td>F</td> <td>N</td> <td>冰塊</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>sweet</td> <td>F</td> <td>N</td> <td>甜度</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar[]</td> <td>[少奶, 去籽]</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar[]</td> <td>[布丁, 椰果]</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> 15. :::spoiler aggregateSalesDayDistrict <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">aggregateSalesDayDistrict</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>district</td> <td>F</td> <td>Y</td> <td>行政區</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>date</td> <td></td> <td>Y</td> <td>日期</td> <td>Date</td> <td>2023-04-30</td> </tr> <tr> <td>drink</td> <td>F</td> <td>Y</td> <td>飲料</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>ice</td> <td>F</td> <td>N</td> <td>冰塊</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>sweet</td> <td>F</td> <td>N</td> <td>甜度</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar[]</td> <td>[少奶, 去籽]</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar[]</td> <td>[布丁, 椰果]</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> 16. :::spoiler aggregateSalesDayCounty <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">aggregateSalesDayCounty</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>county</td> <td>F</td> <td>Y</td> <td>縣市</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>date</td> <td></td> <td>Y</td> <td>日期</td> <td>Date</td> <td>2023-04-30</td> </tr> <tr> <td>drink</td> <td>F</td> <td>Y</td> <td>飲料</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>ice</td> <td>F</td> <td>N</td> <td>冰塊</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>sweet</td> <td>F</td> <td>N</td> <td>甜度</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar[]</td> <td>[少奶, 去籽]</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar[]</td> <td>[布丁, 椰果]</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> 17. :::spoiler aggregateSalesDayRegion <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">aggregateSalesDayRegion</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>region</td> <td>F</td> <td>Y</td> <td>區域</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>date</td> <td></td> <td>Y</td> <td>日期</td> <td>Date</td> <td>2023-04-30</td> </tr> <tr> <td>drink</td> <td>F</td> <td>Y</td> <td>飲料</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>ice</td> <td>F</td> <td>N</td> <td>冰塊</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>sweet</td> <td>F</td> <td>N</td> <td>甜度</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar[]</td> <td>[少奶, 去籽]</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar[]</td> <td>[布丁, 椰果]</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> 18. :::spoiler aggregateSalesHour <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">aggregateSalesHour</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>store</td> <td>F</td> <td>Y</td> <td>門市</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>date</td> <td></td> <td>Y</td> <td>日期</td> <td>Date</td> <td>2023-04-30</td> </tr> <tr> <td>hour</td> <td></td> <td>Y</td> <td>時間 <div>(12 表示12:00-12:59)</div> </td> <td>Integer</td> <td>12</td> </tr> <tr> <td>drink</td> <td>F</td> <td>N</td> <td>飲料</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>ice</td> <td>F</td> <td>N</td> <td>冰塊</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>sweet</td> <td>F</td> <td>Y</td> <td>甜度</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar[]</td> <td>[少奶, 去籽]</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar[]</td> <td>[布丁, 椰果]</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> 19. :::spoiler aggregateSalesHourDistrict <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">aggregateSalesHourDistrict</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>district</td> <td>F</td> <td>Y</td> <td>行政區</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>date</td> <td></td> <td>Y</td> <td>日期</td> <td>Date</td> <td>2023-04-30</td> </tr> <tr> <td>hour</td> <td></td> <td>Y</td> <td>時間 <div>(12 表示12:00-12:59)</div> </td> <td>Integer</td> <td>12</td> </tr> <tr> <td>drink</td> <td>F</td> <td>Y</td> <td>飲料</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>ice</td> <td>F</td> <td>N</td> <td>冰塊</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>sweet</td> <td>F</td> <td>N</td> <td>甜度</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar[]</td> <td>[少奶, 去籽]</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar[]</td> <td>[布丁, 椰果]</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> 20. :::spoiler aggregateSalesHourCounty <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">aggregateSalesHourCounty</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>county</td> <td>F</td> <td>Y</td> <td>縣市</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>date</td> <td></td> <td>Y</td> <td>日期</td> <td>Date</td> <td>2023-04-30</td> </tr> <tr> <td>hour</td> <td></td> <td>Y</td> <td>時間 <div>(12 表示12:00-12:59)</div> </td> <td>Integer</td> <td>12</td> </tr> <tr> <td>drink</td> <td>F</td> <td>Y</td> <td>飲料</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>ice</td> <td>F</td> <td>N</td> <td>冰塊</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>sweet</td> <td>F</td> <td>N</td> <td>甜度</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar[]</td> <td>[少奶, 去籽]</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar[]</td> <td>[布丁, 椰果]</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> 21. :::spoiler aggregateSalesHourRegion <table border="0" style="text-align:center"> <tr> <td colspan="6"><b style="font-size:20px">aggregateSalesHourRegion</b></td> </tr> <tr> <th>Variable</th> <th>Primary/Foreign</th> <th>Not null</th> <th>Description</th> <th>Type</th> <th>Example</th> </tr> <tr> <td>id</td> <td>P</td> <td>Y</td> <td>Serial number</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>region</td> <td>F</td> <td>Y</td> <td>區域</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>date</td> <td></td> <td>Y</td> <td>日期</td> <td>Date</td> <td>2023-04-30</td> </tr> <tr> <td>hour</td> <td></td> <td>Y</td> <td>時間 <div>(12 表示12:00-12:59)</div> </td> <td>Integer</td> <td>12</td> </tr> <tr> <td>drink</td> <td>F</td> <td>Y</td> <td>飲料</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>ice</td> <td>F</td> <td>N</td> <td>冰塊</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>sweet</td> <td>F</td> <td>N</td> <td>甜度</td> <td>Integer</td> <td>1</td> </tr> <tr> <td>taste</td> <td></td> <td>N</td> <td>口味</td> <td>Varchar[]</td> <td>[少奶, 去籽]</td> </tr> <tr> <td>topping</td> <td></td> <td>N</td> <td>加料</td> <td>Varchar[]</td> <td>[布丁, 椰果]</td> </tr> <tr> <td>price</td> <td></td> <td>Y</td> <td>訂單總價</td> <td>Integer</td> <td>55</td> </tr> <tr> <td>amount</td> <td></td> <td>Y</td> <td>杯數</td> <td>Integer</td> <td>2</td> </tr> </table> ### 2.3 注意事項: 與外接資料有關的資料表只有 tempSales,只要注意匯入的資料符合該表欄位即可,資料中需注意門市代號、飲品代號、冰塊、甜度需事先儲存在 stores、 drinks、 sweets、ices 表中,若不存在該筆交易會被移除,請確保需要的資料都有事先透過 sql 加入或是從網站頁面操作加入。詳細的匯入流程請參閱另一份[文件資料庫建立及匯入詳細說明](https://drive.google.com/file/d/1RbFoQBiQmXDRMl0d0i9iccFx1aN1a3G_/view?usp=drive_link)。</div> ## 3. 後端 本專案後端由以 python 建立的 fastapi 框架所撰寫,故下列說明皆為 python 語法及用語。後端共提供 6 支 API,以下分別敘述每一支的功能。 1. /data - 請求方式:`get` - 輸入說明:`None` - 輸出說明:`dictionary`,按照各維度建立出的 `dictionary`, 詳見 [Schema](#Schema1) - 功能說明:用於獲取各維度的數據。包含地區、飲品、甜度、冰塊、口味及加料。而地區與飲品會依照層級回傳以便前端呈現,如新店光明店會下屬於北區、新北市、新店區,而海神會下屬於原葉鮮萃茶類。 2. /search_item - 請求方式:`post` - 輸入說明: ```python class SearchInput(BaseModel): start_date: date=None # 搜尋日期的開始時間(e.g. 2023-01-23) end_date: date=None # 搜尋日期的結束時間(e.g. 2023-01-26) start_hour: int=None # 搜尋的開始小時(e.g. 10,自 10:00 開始的銷售數據) end_hour: int=None # 搜尋的結束小時(e.g. 14,至 13:59 的銷售數據) counties: List[str]=None # 欲比較之縣市層級(e.g. [新北市])則輸入,無需則不用 districts: List[str]=None # 欲比較之地方行政區層級(e.g. [新店區])則輸入,無需則不用 regions: List[str]=None # 欲比較之區域層級(e.g. [北區])則輸入,無需則不用 stores: List[str]=None # 欲比較之門市層級(e.g. [新店光明店])則輸入,無需則不用 drink: List[str]=None # 欲查詢之飲品名稱(e.g. [海神, 鮮萃大麥紅茶]) toppings: List[str]=None # 配料(e.g. [布丁, 椰果]) tastes: List[str]=None # 口味(e.g. [少奶, 去籽]) sweets: List[str]=None # 甜度(e.g. [正常糖, 少糖]) ices: List[str]=None #冰塊(e.g. [少冰, 去冰]) part: bool=None #是否需計算部分佔比 ``` - 輸出說明:`Array[dictionary]`,`dictionary`為 ```json { date:交易日期, location:地區名稱, drink:飲品名稱, constraints:欲查詢之額外條件, all:所有條件, price:飲品總金額, amount:飲品總數量, total_price:地區總金額, total_amount:地區總數量, price_proportion:飲品金額佔比, amount_proportion:飲品數量佔比, } ``` - 功能說明:根據使用者所選擇的維度篩選對應的總表資料。將彙整後擁有不同額外條件的飲品數據除以全品項數據會得到佔比,用於得知每日特定條件飲品的銷售狀況。 3. /bar_item - 請求方式:`post` - 輸入說明:`int period`,欲查詢之期數 - 輸出說明:`Array[dictionary]`,`dictionary` 為 ```json { location:地區名稱, start_date:開始日期, end_date:結束日期, drink:飲品名稱, constraint:額外條件, price:飲品總金額, amount:飲品總數量, total_price:地區總金額, total_amount:地區總數量, price_proportion:飲品金額佔比, amount_proportion:飲品數量佔比, } ``` - 功能說明:根據輸入的期數以呈現若干時段的銷售趨勢。期數所劃分出的時間區段意義如下。假設查詢區段為 2023/01/01 至 2023/01/05 共 5 天,並選擇 3 期。上一期的時間區間則為 2022/12/27 至 2022/12/31,更上一期則為 2022/12/22 至 2022/ 12/26。 在不勾選分別飲品統整的條件下,數據計算方式為「特定條件的飲品數據 / 全品項數據」;而在勾選的狀況下,分母會變成「特定飲品數據」。舉例來說,假設春假期間海神在新店光明店共賣出 560 杯,其中去冰有 80 杯。而新店光明店在春假期間的總銷售杯數為 56000 杯。那麼前者則在計算「春假期間去冰海神在新店光明店的杯數占比」,即 $\dfrac{80}{56000}$;而後者代表「去冰海神在海神銷售中的杯數占比」,即 $\dfrac{80}{560}$。若並無查詢特定條件,那麼計算方式則為「特定飲品數據 / 全品項數據」,即「海神在所有銷售杯數中的佔比」,算式為 $\dfrac{560}{56000}$。 4. /line_item - 請求方式:`post` - 輸入說明:`int year`,欲查詢之年份總數 - 輸出說明:`Array[dictionary]`,`dictionary` 為 ```json { location:地區名稱, year:年份, drink:飲品名稱, constraint:額外條件, price:飲品總金額, amount:飲品總數量, total_price:地區總金額, total_amount:地區總數量, price_proportion:飲品金額佔比, amount_proportion:飲品數量佔比, } ``` - 功能說明:根據輸入的年分以呈現若干年的銷售趨勢。假設查詢區段為 2023/01/01 至 2023/01/05 ,並選擇 3 年,會呈現 2021 至 2023 年在 01/01 到 01/05 的銷售數據,數據計算方式與期數銷售趨勢的計算方式相同。若期間有跨年資訊,則會將該筆數據歸類在後一年。舉例,2022/12/25 至 2023/01/02 的數據在圖上會被歸類在 2023 年,而 2021/12/25 至 2022/01/02 的數據則會被歸類在 2022 年。 5. /update - 請求方式:`post` - 輸入說明: ```python class UpdateInput(BaseModel): # 以下 list 若有輸入則須包含兩個元素 # 第一個是原始數據,第二個是欲更新之名稱,e.g. [北區, 北北區] counties: List[str]=None districts: List[str]=None regions: List[str]=None stores: List[str]=None drinks: List[str]=None categories: List[str]=None toppings: List[str]=None tastes: List[str]=None sweets: List[str]=None ices: List[str]=None ``` - 輸出說明:`{status: "success"}` - 功能說明:用於更新資料庫中的各維度品項名稱。有鑑於公司會不定期更改門市或飲品名稱,因此更新的需求隨之誕生,更新方式即透過選擇既有名稱再輸入新名稱即可,唯注意需在匯入修改名稱後的新數據之前便在本系統進行更新,才不會造成往後的數據計算疏漏。 6. /new - 請求方式:`post` - 輸入說明: ```python class CreateInput(BaseModel): # 每一個 dictionary 會包含兩個 key, 分別是 value, new # value 是欲新增的名稱, 是 Array 或單一物件, new 代表是否是新增的名稱, 是 bool # e.g. toppings: {value: 新配料, new: true}, stores: {value: [新門市, comebuy_id], new: true}, regions: {value: 北區, new: false} counties: dict=None districts: dict=None regions: dict=None stores: dict=None categories: dict=None drinks: dict=None toppings: dict=None tastes: dict=None sweets: dict=None ices: dict=None ``` - 輸出說明:`{status: "success"}` - 功能說明:用於新增各維度的品項。在新增門市與飲品前,須按照層級一步步輸入,如「北區、新北市、新店區」最後再輸入門市名稱「新店光光店」,日後才得以比較不同地區層級的銷售數據。而飲品則需要先輸入 飲品類別,如「原葉先萃茶」再輸入「海霸王」。如同更新所言,需在匯入 新資料前便在本平台進行新增,以確保後續數據計算的正確性。 ## 4. 前端 前端由建於 javascript 上的 React 框架撰寫,主要程式碼位於 `frontend/src` 之下,以下將以 `src` 下一層之資料夾逐一說明功能 ### 1. `components`:提供在專案內重複使用之 UI layout #### `Drawer.js`:在視覺化分析頁面之滑抽及選項 :::spoiler **props** #### <div style="font-weight:700">open</div> 如果為 `true`,顯示元件。 #### Type: `boolean` --- #### <div style="font-weight:700">setOpen</div> 控制 [`open`](#open) prop 值。 #### Type: `React.Dispatch<React.SetStateAction<boolean>>` --- #### <div style="font-weight:700">showType</div> 圖上顯示之數值類型。 #### Type: `'杯數' | '杯數佔比' | '金額' | '金額佔比'` --- #### <div style="font-weight:700">setShowType</div> 控制 [`showType`](#showType) prop 值。 #### Type: `React.Dispatch<React.SetStateAction<string>>` --- #### <div style="font-weight:700">graphType</div> 圖類。 #### Type: `'長條圖' | '折線圖'` --- #### <div style="font-weight:700">setGraphType</div> 控制 [`graphType`](#graphType) prop 值。 #### Type: `React.Dispatch<React.SetStateAction<string>>` --- #### <div style="font-weight:700">calType</div> 圖上顯示的最終結果之數值計算方法。 #### Type: `'分開計算' | '合併計算'` --- #### <div style="font-weight:700">setCalType</div> 控制 [`calType`](#calType) prop 值。 #### Type: `React.Dispatch<React.SetStateAction<string>>` --- #### <div style="font-weight:700">constraint</div> 圖上顯示的最終結果之限制的額外條件。 #### Type: `string` (the union of constraints in database) --- #### <div style="font-weight:700">setConstraint</div> 控制 [`constraint`](#constraint) prop 值。 #### Type: `React.Dispatch<React.SetStateAction<string>>` --- #### <div style="font-weight:700">constraints</div> 所有提供的額外條件選項。 #### Type: `Array[string (the union of constraints in database)]` ::: --- #### `Graph.js`:視覺化分析之圖形,包含長條圖及折線圖 :::spoiler **props** #### [graphType](#graphType) --- #### <div style="font-weight:700">THEME</div> 圖上每個元素的代表顏色。 #### Type: `Array[string (hex color)]` --- #### <div style="font-weight:700">handleLegendClick</div> 當各圖例被點擊時觸發的 Callback。 #### Type: `func` --- #### <div style="font-weight:700">graphKey</div> The render key. 改變時會觸發圖的動畫顯示。 #### Type: `number` --- :::success :art: **Graph Props** #### 見更多 [recharts.js guideline](https://recharts.org/en-US/api). * cartesianGridProps * xAxisProps * yAxisProps * tooltipProps * legendProps ::: --- #### `GridToolBar.js`:表格上方之客製化工具列 :::spoiler **props** #### <div style="font-weight:700">fileName</div> 匯出 excel 之檔案名稱。 #### Type: `string` ::: --- ### 2. `hooks`:提供 customed React hooks #### `useCondition.js`:useContext 全局提供傳送到後端的資料群 :::spoiler **returns** #### <div style="font-weight:700">condition</div> 傳送到後端的全局資料,包含時間、地點、飲品等等。 #### Schema: ```json { time: { time: { start: dayjs(dayjs().format().slice(0, 11) + "T00:00"), end: dayjs(dayjs().format().slice(0, 11) + "T23:59"), }, date: { start: dayjs("2023-01-07"), end: dayjs("2023-01-07"), }, }, location: [ { name: "新店光明店", level: "store", route: ["北區", "新北市", "新店區"], }, { name: "花蓮中山店", level: "store", route: ["東區", "花蓮縣", "花蓮市"], }, ], beverage: [ { name: "海神", category: "原葉鮮萃茶" }, { name: "鮮萃大麥紅茶", category: "原葉鮮萃茶" }, ], ice: [], sweet: [], flavor: [], topping: [], part: false, } ``` --- #### <div style="font-weight:700">DATA</div> 全局資料,用於顯示主頁的篩選選項。 #### Schema: ```json { "全台": { "北區": { "新北市": { "新店區": [ "新店光明店" ] } }, "東區": { "花蓮縣": { "花蓮市": [ "花蓮中山店" ] } } }, "飲品": { "原葉鮮萃茶": [ "海神", "鮮萃大麥紅茶" ] }, "甜度": ["半蜜", "多糖", "微糖", "少糖", "正常糖", "標準糖", "微蜜", "多蜜", "半糖"...], "冰塊": ["熱品", "正常冰", "微冰", "少冰", "溫飲", "去冰", "標準冰", "熱飲", "溫品"...], "加料": ["紫米", "蘆薈", "搖果樂", "愛玉", "小紫蘇", "荔枝凍", "新雙Q", "雙Q條"...], "口味": ["少奶", "去籽", "多籽", "紅茶", "換奶綠", "多奶", "改小珍", "改鬥陣", "芋"...] } ``` --- #### <div style="font-weight:700">systemState</div> 目前系統狀況, 包含「地區級別」和「是否為個別佔比計算」。每一次送出 request 都會更新。 #### Schema: ```json { "locationLevel": "region", "part": false } ``` --- #### <div style="font-weight:700">setSystemState</div> 控制 `systemState` 值。 #### Type: `React.Dispatch<React.SetStateAction<{}>>` --- #### <div style="font-weight:700">setCondition</div> 控制 `condition` 值。 #### Type: `React.Dispatch<React.SetStateAction<{}>>` --- #### <div style="font-weight:700">setDATA</div> 控制 `DATA` 值。 #### Type: `React.Dispatch<React.SetStateAction<{}>>` --- #### <div style="font-weight:700">loading</div> 如果為 `true`,會顯示加載元件。在送出 request 至接收 response 以前為 `true` #### Type: `boolean` --- #### <div style="font-weight:700">setLoading</div> 控制 `loading` 值。 #### Type: `React.Dispatch<React.SetStateAction<boolean>>` --- #### <div style="font-weight:700">addLocationCondtion</div> 當未被選取之地區被選取時觸發的 Callback。 #### Type: `func` --- #### <div style="font-weight:700">deleteLocationCondition</div> 當被選取之地區被選取時觸發的 Callback。 #### Type: `func` --- #### <div style="font-weight:700">addBeverageCondition</div> 當未被選取之飲品被選取時觸發的 Callback。 #### Type: `func` --- #### <div style="font-weight:700">deleteBeverageCondition</div> 當被選取之飲品被選取時觸發的 Callback。 ::: --- #### `useGraph.js`:提供視覺化所需 states 及 effects :::spoiler **returns** #### <div style="font-weight:700">period</div> 圖上顯示的時間區間數。 #### Type: `number` --- #### [showType](#showType) --- #### [setShowType](#setShowType) --- #### [graphType](#graphType) --- #### [setGraphType](#setGraphType) --- #### [calType](#calType) --- #### [setCalType](#setCalType) --- #### [constraint](#constraint) --- #### [setConstraint](#setConstraint) --- #### <div style="font-weight:700">points</div> 圖的資料點元素。 #### Schema: ```json { data: [ { year: "2023-01-05 2023-01-05", "新店光明 海神": 48.04, "新店光明 鮮萃大麥紅茶": 51.96, "花蓮中山 海神": 51.85, "花蓮中山 鮮萃大麥紅茶": 48.15 }, { year: "2023-01-06 2023-01-06", "新店光明 海神": 54.2, "新店光明 鮮萃大麥紅茶": 45.8, "花蓮中山 海神": 51.49, "花蓮中山 鮮萃大麥紅茶": 48.51 }, { year: "2023-01-07 2023-01-07", "新店光明 海神": 50, "新店光明 鮮萃大麥紅茶": 50, "花蓮中山 海神": 50.44, "花蓮中山 鮮萃大麥紅茶": 49.56 } ], point: [ { name: "新店光明 海神", hide: false }, { name: "新店光明 鮮萃大麥紅茶", hide: false}, { name: "花蓮中山 海神", hide: false }, { name: "花蓮中山 鮮萃大麥紅茶", hide: false } ] } ``` --- #### [key](#graphKey) --- #### <div style="font-weight:700">handlePeriodChange</div> 當 [period](#period) 改變時觸發的 Callback #### Type: `func` --- #### [handleLegendClick](#handleLegendClick) --- :::success :star: **Effects** #### 1. 當任何圖類相關變數改變時重建構 [`points`](#points) 值。 ```javascript=111 useEffect(() => { setPoints(dataFormatProcessing(rawData, period, showType, constraint)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [rawData, showType, constraint, dataFormatProcessing]); ``` #### 2. 當任何圖類相關變數改變時改變 [`key`](#graphKey) 值<text style="color:#c62828">以觸發內建動畫</text>。 ```javascript=140 useEffect(() => { setKey((prev) => (prev % 10) + 1); }, [showType, constraint, rawData, graphType, calType]); ``` ::: --- ### 3. `Manipulate`:`/manipluate` 頁面 #### `Add.js`:服務「新增」頁面 #### `constant.js`:各式 mapping object #### `index.js`:`/manipluate` 彙整頁面,包含 Tab Pages #### `Rename.js`:服務「修改」頁面 ### 4. `middleware`:提供 Data Fetching function #### `axios.js`: * create general instance for single endpoint base. * provide interceptor during data fetching. #### `const.js`:provide function to fetch `GET /data`. #### `index.js`:provide collection of all data-fetching function. #### `simplePost.js`:provide functions to fetch `post`-related endpoints. * `POST /search_item` * `POST /line_item` * `POST /bar_item` * `POST /new` * `POST /update` ### 5. `Options`:提供主頁(搜尋頁)的各式選項 #### `assets`:Every images and GIFs used in Options. #### `Beverage`:飲品選項 #### `Ice`:冰塊選項 #### `Location`:地區選項 * **`Modal.js`:地區的彈出視窗** * **`Recursive_Component.js`:遞迴的 components shown in Modal.js** #### `Sweet`:甜度選項 #### `Time`:時間選項 #### `Flavor.js`:口味選項 #### `SieveType`:彙整所有選項 #### `Topping`:加料選項 ### 6. `utils`:專案內使用的工具 #### `day.js`:提供全局 dayjs 的額外配置(e.g. 設置 sameElse,不使用 dayjs 內建顯示時間) ### 7. `Visualization`:視覺化分析 #### `Aggregate.js`:提供「數據統計」表 :::spoiler **props** #### <div style="font-weight:700">data</div> 表格的資料。 #### Schema: ```json [ { location: "新店光明", start_date: "2023-01-07", end_date: "2023-01-07", drink: "海神", price: 3275, amount: 57, total_price: 6465, total_amount: 114, price_proportion: 50.66, amount_proportion: 50 }... ] ``` ::: --- #### `index.js`:彙整所有視覺化之圖表 #### `PeriodAnalysis.js`:提供「過去若干時段趨勢」分析圖 :::spoiler **props** #### <div style="font-weight:700">data</div> 圖上元素依賴的資料。 #### [Schema](#Schema4) --- #### [THEME](#THEME) --- #### <div style="font-weight:700">setTheme</div> 控制 `THEME` prop 值。 #### Type: `React.Dispatch<React.SetStateAction<[]>>` ::: --- #### `StoreBeverage.js`:提供「總表」,是後臺資料庫的原始數據 :::spoiler **props** #### <div style="font-weight:700">data</div> 表格的資料。 #### Schema: ```json [ { date: "2023-01-07", location: "新店光明", drink: "海神", price: 435, amount: 9, total_price: 6465, total_amount: 114, price_proportion: 0.0673, amount_proportion: 0.0789, constraints: [], all: ["正常冰", "半糖", "椰果"] }... ] ``` ::: --- #### `YearAnalysis.js`:提供「過去若干年趨勢」分析圖 :::spoiler **props** #### <div style="font-weight:700">data</div> 圖上元素依賴的資料。 #### Schema: ```json [ { location: "新店光明", drink: "海神", year: 2021, price: 3535, amount: 62, total_price: 6655, total_amount: 117, price_proportion: 53.12, amount_proportion: 52.99 }... ] ``` --- #### [THEME](#THEME) --- #### [setTheme](#setTheme) :::