## 商家分析 (前端)
[前端操作說明](https://hackmd.io/@shaomin/jba)
## 商家分析 (後端)
### 權限
> user 的 group 若包含
> - 'DS'則可以看到全部使用的資料。 (內部使用)
> - 'CUS_DS' 則可以看到自己及shop_user的資料。 (客戶使用)
### 統計資料表和執行及排程
| model name | Desc. | Update time| file name|
| -------- | -------- | -------- | -------- |
| CommodityStatistic| 商品銷售狀況統計 | 每日清晨4:50 | update_statistic_commodity.py|
| OrderStatistic |訂單成立數量、訂單成立金額、訂單付款金額統計| 每日清晨4:53 |update_statistic_order.py|
| LiveStatistic |直播留言觀看人次統計| 每日清晨4:56 |update_statistic_comment.py|
| CustomerStatistic | 紀錄每日每位消費者是否為新客、距離上次購買天數是幾天,以及當日消費金額、數量。| 每日清晨4:58 |update_statistic_customer.py|
| CustomerStatisticExtra | 因為一開始沒有記錄到棄單狀況,故使用這個資料表補上。| 每日清晨4:58 |update_statistic_customer.py|
| CustomerStatisticSession| 紀錄每場次每位消費者是否為新客、距離上次購買天數是幾天,以及該場次消費金額、數量。| 每日清晨4:40 | update_statistic_customer_session.py|
| StreamStatistic | 使用FB Graph API `read_insights` 於直播結束後讀取影片分析資訊。 model有註解說明各欄位用意。| 每日清晨4:45 |update_statistic_stream.py|
| CommodityPredictCategory | 依照模型儲存預測結果,若有人工修改也會存入。| 每日清晨6:51 | predict_commodity_category.py|
### 開發注意事項
#### 前端
其他地方沒使用過的JS套件`tablesorter`, `echarts`
> [Apache ECharts](https://echarts.apache.org/zh/index.html)
> 圓餅圖、柱狀圖,後端算好資料,依照格式套入這個套件呈現出來。
```javascript=
var CloudChart = echarts.init(document.getElementById('plot_word_cloud')); // init 一個echarts可以使用的畫板
var data = [];
{% if word_cloud %}
data = {{ word_cloud|safe }}; //接收後端資料
{% endif %}
var maskImage = new Image();
var option = {
tooltip: {},
series: [ {
type: 'wordCloud', // 使用文字雲
sizeRange: [12, 150],
// ... 省略
data: data // 將資料放入套件
}]
};
CloudChart.setOption(option); // 將欲呈現的圖形參數呈現在畫板上。
```
>
> [tablesorter](https://mottie.github.io/tablesorter/docs/#Demo)
> 該處為了減少查詢壓力,並不會在表格點選排序時重新查詢,僅會透過前端套件,針對現有資料排序。
>
`filterChange` 這個函示會用來控制顯示哪些東西。原則上是一個功能會用一個 css name 去包住全部。 所以filter改變的時候,會全部隱藏,再調整顯示對應內容。
> EX. `search_user`: 這個是用來顯示搜尋使用者的功能。
```javascript=
function filterChange() {
let option_dates = document.getElementById('search_dates');
let option_keyword = document.getElementById('search_commodity_keyword');
let option_sessions = document.getElementById('search_sessions');
option_dates.setAttribute('hidden', 'hidden');
option_keyword.setAttribute('hidden', 'hidden');
option_sessions.setAttribute('hidden', 'hidden');
$('.commodity-display').hide();
$('#commodity-table').hide();
$('#plot_word_cloud').hide();
$('.live-display').hide();
$('.category-display').hide();
$('.customer-display').hide();
$('.store-recommendation-display').hide();
$('.fb-stream-display').hide();
$('.order-amount-display').hide();
$('.business-report-display').hide();
$('.search_user').hide();
$('#search_commodity_keyword').removeAttr('disabled');
$('#search_sessions').removeAttr('disabled');
on_time_period_changed();
if ($('#filter').val() == 'commodity') {
$('.commodity-display').show();
$('.search_user').show();
option_dates.removeAttribute('hidden');
option_keyword.removeAttribute('hidden');
if ($("select[name='search']").val() == 'sessions'){
document.querySelector('#search-select').value='dates';
}
if($('#commodity_display').val() == 'word_cloud') {
$('#plot_word_cloud').show();
} else {
$('#commodity-table').show();
}
} else if ($('#filter').val() == 'live') {
$('.live-display').show();
document.querySelector('#search-select').value='dates';
option_dates.removeAttribute('hidden');
} else if ($('#filter').val() == 'category') {
$('.category-display').show();
$('.search_user').show();
document.querySelector('#search-select').value='dates';
option_dates.removeAttribute('hidden');
} else if ($('#filter').val() == 'customer') {
$('.customer-display').show();
$('.search_user').show();
if ($("select[name='search']").val() == 'commodity_keyword'){
document.querySelector('#search-select').value='dates';
}
option_dates.removeAttribute('hidden');
option_sessions.removeAttribute('hidden');
} else if ($('#filter').val() == 'live_store_recommendation') {
$('.store-recommendation-display').show();
document.querySelector('#search-select').value='commodity_keyword';
option_keyword.removeAttribute('hidden');
} else if ($('#filter').val() == 'fb_stream') {
$('.fb-stream-display').show();
$('.search_user').show();
document.querySelector('#search-select').value='dates';
option_dates.removeAttribute('hidden');
} else if ($('#filter').val() == 'order_amount') {
$('.order-amount-display').show();
$('.search_user').show();
document.querySelector('#search-select').value='dates';
option_dates.removeAttribute('hidden');
} else if ($('#filter').val() == 'business_report') {
$('.business-report-display').show();
$('.search_user').show();
document.querySelector('#search-select').value='dates';
option_dates.removeAttribute('hidden');
}
change_datepicker();
selectSearch();
}
```
#### 後端
> 針對`search_table`去排查。

## ES 相關設定及使用
* 安裝套件 [[安裝篇] Elasticsearch Logstash Kibana](https://hackmd.io/@shaomin/ELK_install)
* 同步資料及管理設定 [[使用/管理篇] Elasticsearch Logstash Kibana](https://hackmd.io/@shaomin/ELK_usage)
* ES 語法 [Elasticsearch 語法](https://hackmd.io/@shaomin/es_query)
##### STEP
1. 透過安裝套件教學,逐步安裝 ES 及 Logstash 的套件。
2. 確認服務預作正常後,填寫設定檔,並開始透過Logstash將資料倒入ES。
3. 透過 ES 語法查看確認 ES 內的資料及服務狀況。
## 粉絲小幫手
### 粉絲訂單資料統計排程
>每秒執行`update_user_transaction_summary_aggregate.py` 去從 `Api_UserTransactionChangeLog` 撈取訂單變化量加總存入`Api_UserTransactionSummaryAggregate`
>> 執行的時候會透過 `Api_config` 去紀錄執行到Log的哪一筆資料,以及最後更新時間,其中 utsa update status 若取消勾選則停止同步。
>> 
`Api_UserTransactionChangeLog` 會在 api_order 執行 save 的時候做一個變化量的計算。下圖函示寫於`api/utils.py`中。

`Api_UserTransactionSummaryAggregate`會在粉絲小幫手,如下圖方式顯示。


## 東南亞
### 購物車一頁式結單
:::warning
> 設定方式
> -
> - PC 開啟 <font color="#f00">`enable_one_page_checkout`</font>
>
> 設計架構
> -
> - 一個連結(hash_key)代表一次付款流程。結帳之後該連結轉為追蹤訂單狀態(訂單處理中 or 歷史訂單)。
>
> ```python
> class OnePageCheckout(models.Model):
> user = models.ForeignKey(User)
> from_id = models.CharField(max_length=64, db_index=True)
> from_name = models.CharField(max_length=128, null=True, blank=True)
> comment_user = models.ForeignKey(CommentUser, on_delete=models.SET_NULL, null=True, blank=True)
> hash_key = models.CharField(max_length=16, null=True, blank=True, unique=True, db_index=True)
> orders = models.ManyToManyField('Order', related_name='order_one_pages', blank=True)
> bill = models.ForeignKey('pay.Bill', on_delete=models.SET_NULL, null=True, blank=True)
> session = models.ForeignKey(Session, on_delete=models.SET_NULL, null=True, blank=True)
> created_time = models.DateTimeField(auto_now_add=True)
> ```
>
> - 連結中的 hash_key 主要是用來查找 from_id 以及是否有 bill,若沒有bill,這個hash_key可以一直加入新訂單。
> - 訂單建立時,若有找到可用的 hash_key,便不會再新增,直到該hash_key 被賦予 bill。
>
> 得標/結單... 通知連結
> -
> 1. 開啟一頁式結單後,基本上發送的結單連結都會是一頁式。
> 邏輯為 `https://{DOMAIN_NAME}/op/{USER_ID}/{HASH_KEY}`
> 其中 op 代表 `one page`
> ex. https://sea.jambolive.tv/op/2853/86763d49f6/
> 2. 一頁式結單連結的通知內容是加總的,並不是發送單次order資訊,而是所有未結單 order 的資訊。
>
> 3. 取得或建立連結方式
> - 以下函示可透過order或from_id取得連結。若資料查不到既有可用連結,則會建立新的。
> File: <font color="Blue"> **`comment/utils.py`** </font>
> ```python
> def get_one_page_checkout_url(pc, order=None, from_id=None):
> ```
> - bill 可以透過models中如下的函示取得。
> `checked` => 為真時,不會重抓PC。
> `origin_url` => 若設定沒開,或是bill沒有對應的link,則回傳購物車或訂單處理中連結。
> ```python
> def get_opc_url(self, checked=False, origin_url=None):
> ```
>
> 登入邏輯
> -
> - 若為一頁式結單,會將消費者的session中`is_one_page`標示為真,並轉往一頁式結單,不進行後續登入比對。
> File: <font color="Blue"> **`pay/decorators.py`** </font>
>
> ```python
> def facebook_login(view):
> #...省略
> # One Page Checkout
> is_one_page = request.session.get('is_one_page', False)
> one_page_do_pay = request.session.get('one_page_do_pay', False)
> if is_one_page and ('/op/' in path_info):
> return view(request, *args, **kwargs)
> elif is_one_page and one_page_do_pay:
> qd = request.GET.copy()
> if user_id:
> return redirect(reverse('one_page_checkout', kwargs={'user_id': user_id, 'hash_key': request.session.get('one_page_key')}) + '?' + qd.urlencode())
> else:
> return redirect(reverse('opc_redirect', kwargs={'hash_key': > request.session.get('one_page_key')}) + '?' + qd.urlencode())
> elif is_one_page and not from_name and not from_id and '/checkout/' not in path_info and ('/shop/' in path_info or '/store/' in path_info):
> return view(request, *args, **kwargs)
> else:
> clear_OPC_session(request)
> ```
> 需注意事項
> -
> - <font color="green">```一頁式購物車的跳轉```</font>
目前一頁式連結進入後,會在session紀錄,若有跳轉的連結,將session清掉了,便會無法回到需要登入購物車。
Ex. 例如購物車前往商城,商城再點選購物車。
> - <font color="green">```psid的功能使用問題```</font>
因為一頁式結單主要是psid免登入,所以有些asid在用的功能可能會有衝突。
Ex. 專屬折扣,原先只能在asid上設置。
:::
### 購物車合併結單
:::warning
> 設定方式
> -
> - PC 開啟 <font color="#f00">`enable_checkout_merge_bill`</font>
>
> 設計架構
> -
> - 進入購物車後,會抓取上次已成立未確認付款的付款單。並在前後端做金額及折扣的計算。
> - 其中扣除前次已付款金額,並告知當前應付款金額,為該功能主要賣點。
> 
>
> - 前端會同時列出,已成立跟未成立的訂單。上方為未成立,下方為已成立。
> 
>
> 開發邏輯
> -
> - 若開啟該功能,會去抓取符合條件的付款單。
> ```python
> could_merge_bills = models.Bill.objects.filter(payment_method__in=payment_method_supported_for_checkout_bill_merge,user_id=user_id,from_id=from_id).filter(status=models.Bill.Status.ESTABLISHED, is_paid=False)
> ```
> - 若有可以合併的付款單,會用變數```could_merge_bill```, ```could_merge_bill_orders```, ```could_merge_bill_orders_total```作為參數去計算。
> 1. could_merge_bill: 紀錄最新一筆可以合併的付款單。
> 2. could_merge_bill_orders: 紀錄可合併付款單的訂單資料。
> 3. could_merge_bill_orders_total: 訂單金額總和,不包含運費、手續費...等
>
> 需注意事項
> -
> - <font color="green">```僅支援轉帳付款方式使用```</font>
因為泰國僅能透過轉帳方式去驗證實際付款金額,驗證後於前端做計算,告知建議的應付款金額。
> - <font color="green">```紅利功能未開放```</font>
因為合併後計算折扣即實際付款金額尚未穩定,故紅利部分先暫緩。
> - <font color="green">``````</font>
因為合併後計算折扣即實際付款金額尚未穩定,故紅利部分先暫緩。
:::
## LINE
### 購物
:::info
> <font color="Green">**商品倒入LINE購物可被搜尋**</font>[color=#f24f9e]
>
> 操作流程
> -
> 1. 賣家在商品小幫手點選```LINE上架商品```,便會將商品的```line_on_shelf_time``` 更新。
> 2. LINE 定期呼叫 API 取得資料後,便可以出現在LINE購物APP上。
>
> - 下方為我們提供給LINE的API網址。
> File: <font color="Blue"> **`api/lineshopping.py`** </font>
> https://jambolive.tv/api/lineshopping/product_full/{USER_ID}/
https://jambolive.tv/api/lineshopping/product_partial/{USER_ID}/
https://jambolive.tv/api/lineshopping/cate_full/{USER_ID}/
>
> - 文件我找不到了QQ 檔名如下

>
> 需注意事項
> -
> - 若未來使用量大,應當先處理好資料,存成檔案,直接回傳給LINE。
:::
### 直播 - GLC
:::info
> 設定方式
> -
> table: <font color="Blue"> **`api_lineaccount`** </font>
> 
> 1. 基本LINE帳號資料。
> 2. Live api version: 選擇 GLC。
(JP為日本團隊開發,GLC為韓國團隊開發)
> 3. Live product display status: 卡牌顯示方式。
> 4. Post back mode: 訂單及付款單建立後回拋給LINE的邏輯。
> 訂單建立時,會回傳資料給LINE,顯式於LINE購物車。
> 付款單建立時,會回傳資料給LINE,用於回饋點數計算。
>
| 回拋方式 | 付款單 | 訂單 |
| -------- | -------- | -------- |
| All Bill, LINE Order | 全回傳 | 僅LINE帳號建立訂單回傳 |
| All Bill, All Order | 全回傳 | 全回傳 |
| LINE Bill, LINE Order| 僅LINE帳號建立付款單回傳 | 僅LINE帳號建立訂單回傳 |
| LINE Bill, All Order | 僅LINE帳號建立付款單回傳 | 全回傳 |
>
> 操作流程
> -
> 
>
> 0. 請先確保OA帳號有申請 GLC 直播功能。
> 1. 直播小幫手點選LINE直播。其中`直播首圖`、`直播標題`、`開播訊息` 為必填。
> 2. 點選建立後,進入[LINE GLC 後台](https://global-live-commerce.line.biz/)取得串流金鑰、串流網址。
> 
> 3. 透過直播串流工具,傳送直播畫面到GLC後台。
> 4. 確定GLC後台有影像,可以點選開始直播,或點進入JamboLive直播小幫手點選`LINE直播設定`中的開始直播。
> 
>
> 開發事項
> -
> #### 啟用直播的API
> Path: <font color="Blue"> **`pay/linebuy.py`** </font> => `def live_settings(live, line_account, action=False):`
>>
>> 
>>
>> - 基本上沿用先前開發JP版本直播的功能,僅需注意若`line_account.live_api_version == GLC` 會有不同的API server,並且參數也會些微不同。
>
> #### 啟用直播的參數
> Path: <font color="Blue"> **`api/views.py`** </font> => `def line_live(request):`
>>
>> 
>> - BC=order by chat(關鍵字收單)、PL=Product list(商品列表收單)
>> - PL mode 不用送關鍵字。
>
> #### LINE ecid 資料交流
>> LINE ecid 有點類似 psid。是在直播喊單的時候,LINE傳訂單給我們的識別碼。
>> - 在LINE點選進入購物車的時候,連結會帶上ecid,進入購物車LINE帳號登入後,可以用 LINE uid 綁定 ecid,將訂單歸戶。
>> - ecid 有時效且同用戶每場直播都不同,所以我們並沒有儲存。
>> ```
>> https://jambolive.tv/pay/checkout/12305/line/?ecid=800ffeae8895c32f0aa62ab21fe5fc5c8440bc714b34b26ec3e678acac48bc89
>> ```
> #### 手動回拋訂單給LINE
> Path: <font color="Blue"> **`pay/tasks.py`** </font> => `def linePostBackOrder(bill_id, ecid, kind):`
>> ```=python
>> bs = pay_models.Bill.objects.filter(user_id=13665).exclude(Q(status=pay_models.Bill.Status.NOT_ESTABLISHED) | Q(status=pay_models.Bill.Status.TRASHED)).order_by('-id')
>> for b in bs:
>> linePostBackOrder(b.id, 'jambolive0line0order0fb0checkout', 'custom')
>
> 文件
> -
> [[LINE ECAC][LINE TW Shopping] GLC LIVE Commerce Product data feed API Specification (zh-tw) ver. 1.5 .pdf](https://drive.google.com/file/d/1aUKTza9nxsrwajrBb5hxiPT_WSWtW8ea/view?usp=sharing)
:::
### LINE 通知
* 未結單通知包含按鈕(內文160字元)。
* 一般通知(內文5000字元)。
#### Refference
1. [LINE OA 後台](https://manager.line.biz/)
2. [LINE GLC 後台](https://global-live-commerce.line.biz/)
3. [LINE Developers 後台](https://developers.line.biz/console/)
## Fasney 後支付
> 設定方式
> -
1. 於 admin後台api/personalconfig 中搜尋 fasney pay account,並選取或建立帳號 。
2. 於 admin後台api/personalconfig 中搜尋 After pay percentage,用於建立後支付手續費。
3. 若需開啟後支付分期
1. 於admin後台 api/personalconfig 中搜尋Enable after pay installment,並將其勾選。
2. 於 admin後台api/personalconfig 中搜尋 After pay installment list,輸入與合作廠商簽約之分期期數 (EX. 簽約3,6期,則輸入 3,6)。
3. 於 admin後台api/personalconfig 中搜尋 After pay installment percentage 3/6/12/18/24,輸入後支付分期之手續費。
4. 代後台設定完成後,商家便可於商家設定中,將付款設定勾選「後支付」啟用。
> 開發注意事項
> -
- 交易流程跟其他第三方支付一樣,唯一差異是消費者第一次使用Fasney後支付會需要驗證個人資料,驗證個人資料這段期間,付款單會屬於未成立狀態,等待付款完成的API送過來,該筆付款單的訂單也沒有被其他付款方式付款,才會轉成成立。

- 有設定`ip_white_list`去限制可以回傳交易狀況給我們的IP.
- 可提供測試環境,給他們窗口信箱,會寄測試APP的連結。

#### refference
1. [官網](https://www.fasney.com/)
2. [開發文件](https://docs.google.com/document/d/1mkA50JUpwIMbtz2m6fTRTpsVu2LpteDf/edit?usp=sharing&ouid=101567832984958542928&rtpof=true&sd=true)
## FB 權限申請
:::success

1. 上線中的APP必須提供測試帳號,或使用測試環境送審。
2. 登入過程中必須跳出可以讓使用者勾選權限的畫面。(每次要送審時,都要確保測試帳號的token清空權限,讓審核人員登入平台時可以出現取得權限畫面。
3. FB開發人員中雖然有提供的測試帳號,但那個測試環境是封閉的,而且我這次一直沒辦法在那個封閉環境中建立粉絲專頁。雖然我有在送審文案中提出這點,也請審核人員用我們提供的測試用戶檢查登入機制,再用可以獲取粉專分析資料的審核帳號登入。
綜合以上原因,最後是我自己在FB正式環境中新建一個全新的帳號,並且將粉專應該要有的分析資料也都準備好,再將這個帳好設定為開發人員,讓審核人員用這個帳號登入平台,待審核通過後再更改密碼及移除測試人員。
送審範例申請了很多權限,後續可以參考這個範例取得權限。
:::

#### refference
1. [送審範例](https://xeroneit.net/blog/xerochat-how-to-submit-app-for-review)
2. [應用程式審查說明文件](https://developers.facebook.com/docs/app-review/introduction)
## 政大資料匯出
### 連線方式
[政大NAS 連結](https://uoclabnas720.synology.me:5001/#/signin)
- 帳號: uoclab_jambo
- 密碼: Uoclab85024jambo??
### 腳本
```
53 21 * * * /bin/sh /home/web/projects/scpbackend/django/scripts/nccu/cronjob_export.sh
```
- cronjob_export.sh => 因為留言cache存兩天,所以兩天匯出一次前兩天得留言即可,用判斷`/tmp/altday` 這個存不存在去判斷要不要匯出留言。
- nccu_export_comments.py => 匯出留言資料,存入/home/web/nccu_comment
- nccu_export_comments_email.py => 匯出留言資料,寄送mail
- nccu_export_commodity.py => 匯出商品資料
- nccu_export_order_info.py => 匯出訂單資料
## Kbank
### 驗證流程
#### i. 從圖片中解析QRCode.
:::warning
Here is our read slip qrcode solution now.
We use this one for frontend scan.
1. https://github.com/nimiq/qr-scanner
And if not found, we will do it by opencv package on backend.
2. https://note.nkmk.me/en/python-opencv-qrcode/
If above function can't get any result, then we call following link.
3. https://zxing.org/w/decode.jspx
:::
#### ii. Kbank 驗證
Path: <font color="Blue"> **`pay/kbank.py`** </font>
::: warning
1. function ```do_verify``` =>
- 取得欲查詢的付款單及其對應的所有匯款紀錄。
- 驗證當前帶入的匯款編碼(QR Code 解碼內容)。
- 取得匯款單資料後,檢驗`時間`, `銀行代碼`, `重複上傳`, `金額`
- 若有啟用發送通知,執行發送。
2. function ```readQR``` =>
- 執行上述解析QRCode的第二、第三步驟。
3. function ``re_verify``` =>
- 購物車前端抓不到匯款單編碼時執行。
- 出貨小幫手,賣家手動執行。
:::