- 一眼看到
- 減錯
手上有統編、發票號或半個名字,都查得到同一個客戶
從客戶名稱、統編或發票等六種線索,查到同一個客戶總覽。工具只有查詢,沒有寫入路徑。
背景與使用技術(4 項)
同事手上的線索通常是一組統編、一張發票號,或一個只記得一半的客戶名稱,而這幾種都無法直接在計費系統裡篩選,查一家客戶得在後台來回切換好幾個畫面。做了一個唯讀的 Apps Script 網頁工具:提供六種查詢角度,全部收斂到同一個客戶總覽,訂閱、發票、交易與事件時間線一頁看完,且沒有任何能改動帳務資料的寫入路徑。
誰在用
公司的財務與營收營運同事
原本怎麼做
同事常問的是:這家客戶的訂閱還在嗎?這期的續約帳單開出去了沒?上個月那筆款項最後結算成什麼?答案都在計費系統裡,難的是線索對不上:系統只認它自己發的客戶編號,同事手上通常是統一編號、發票號碼,或一個只記得一半的客戶名稱。查一家客戶,就得先用名字猜,猜不到再換一個畫面試發票號,來回切換,最後常常還是要轉手去問人。
前後對比
量測:實測=有計時或有紀錄;估計=喜特先生推估;客戶自述=客戶回報;規格=設計上的固定條件;待補=尚未取得數字。
流程一眼看懂
手上有統編或半個客戶名稱
用名字猜客戶
換畫面試發票號
還是要問人
選一種線索
對照內部對照表
同一個客戶總覽
訂閱、發票、交易與事件
流程示意:依本案既有做法整理,非客戶實際畫面。
做了什麼
- 唯讀:後端沒有任何寫入路徑,查錯人也不會改到資料。
- 六個查詢角度各自一個分頁,讓同事自己選,不讓程式猜類型後再除錯。
- 系統查不到的線索(客戶名稱、登記名稱、統編),由一張自己維護的對照表補上。
- 不論從哪個角度進來,最後都停在同一個客戶總覽。
程式碼與實作細節收在頁面下方,需要時再展開。
你的情況像這樣嗎?
- 同事手上的線索(統編、發票號、半個客戶名稱)和系統認的編號對不上。
- 查資料的工具同時有改資料的權限,讓人不太敢隨便點。
- 你想要的是「查得到」,不是再多一個要訓練的後台。
更多成果與運作條件
6 種查法,1 個客戶總覽
統編、發票號、分支 ID 或只記得一半的客戶名稱,都停在同一頁的訂閱、發票、交易與事件時間線
快取有效期 300 秒與 1800 秒
搜尋與客戶總覽依變動速度用 300 秒,發票、明細與對照表索引用 1800 秒
沒有寫入路徑
取代的是「查錯客戶就可能改到帳務資料」的風險;查詢省下的時間尚未實測
以上為本案既有成果與運作條件;估計值依原始資料標示,其他專案需另行評估,並非通用成效保證。
六種線索,同一份總覽
從手邊已有的線索查詢,最後查看同一個客戶的訂閱、發票與交易。
畫面示意・範例資料,非客戶實際截圖。手機可左右滑動查看。
挑戰:原本遇到的困難
財務與營收營運的同事常問的是三種問題:這家客戶的訂閱還在嗎?這期的續約帳單開出去了沒?上個月那筆款項最後結算成什麼?
答案都在計費系統裡,難的是線索對不上。計費系統只認它自己發的客戶編號,而同事手上通常是統一編號、發票號碼,或一個只記得一半的客戶名稱。統編不能篩選,公司名稱只能從開頭比對,登記名稱在資料模型裡沒有對應的欄位。查一家客戶,就得先用名字猜,猜不到再換一個畫面試發票號,來回切換,最後常常還是要轉手去問人。
完整解決方案與設計取捨
做一個部署在公司網域下的 Google Apps Script 網頁工具,唯讀。前端是網頁(用 Apps Script 的 HtmlService 做成),後端是一層內建重試的計費系統用戶端,沒有任何寫入路徑。
查詢分六個角度,各自對應一種手上的線索。客戶名稱、營業登記名稱、統一編號,系統本身查不到,就從一張自己維護的對照表補上;分支 ID 與客戶 ID 直接查,因為它們本來就是系統的識別碼;發票號碼先反查客戶 ID;統一發票號先查對照表換成計費系統的發票號,再走發票號的路徑。
不論從哪個角度進來,最後都停在同一個客戶總覽:訂閱與狀態、發票(每筆可下載 PDF)、交易與事件時間線。搜尋頁用分頁籤切換查詢角度,不用單一輸入框自動判斷類型,因為統編與發票號的格式有時相近,讓同事自己選,比程式猜錯後再除錯省事。
技術與實作細節
展開完整實作說明
週二上午,一句「訂閱還在嗎」查了十幾分鐘
情境改寫:依這類查詢常見的狀況整理,細節為示意。
週二上午,群組裡有人問:「這家客戶的訂閱還在嗎?」問的人手上只有一個記得一半的客戶名稱。先打開計費系統後台,用名稱搜尋,沒有結果,因為後台的公司名稱欄只能從開頭比對,而他記錯了前半。接著改用發票號碼試,對不上。最後只能換到別的畫面一個一個比,前後大約十幾分鐘,大半時間花在切換畫面。
這一次還查得到。下一次換一個人、換一種線索,還是得從頭來。
人工查,為什麼總卡在同一個地方?
計費系統認人的方式只有它自己給的客戶 ID。同事手上通常是四種線索:招牌上的名字、合約上的營業登記名稱、統一編號、發票號碼。系統對這四種的支援程度不一樣。vat_number(統一編號欄位)完全不能用來篩選;公司名稱只支援 starts_with(只能比對開頭),只記得一半的名稱查出來是空的;登記名稱在資料模型裡沒有對應的欄位。
不能要求同事先學會系統的 ID 規則,所以缺的那幾個維度只能從外面補。人工查慢還是其次,更麻煩的是每種線索都要換一個入口,每換一次就要重新判斷這條線索屬於哪一種。
六個入口,各自接到同一個客戶 ID
六種線索各自接到計費系統的客戶 ID 上。有的能直接問系統,有的得先經過本地對照表。
| 線索 | 計費系統能不能直接問 | 這個工具怎麼接 |
|---|---|---|
| 客戶名稱 | 只能前綴比對,記不全就查不到 | 掃對照表的客戶名稱欄(子字串比對),每個分支 ID 各查一次客戶 |
| 營業登記名稱 | 沒有這個概念 | 掃同一張對照表的登記名稱欄,獨立成一個查詢角度 |
| 統一編號 | 不能用 vat_number 篩選 | 查對照表解出分支 ID,一個統編可能對到多個分支,全部回傳 |
| 分支 ID/客戶 ID | 能,這是系統自己的識別碼 | 正規化後用 id[starts_with] 直接查 |
| 發票號碼 | 能,發票端點可以反查 | 查 /invoices/{id} 取得 customer_id,再載入客戶總覽 |
| 統一發票號 | 不認得,它是另一套號碼 | 查統一發票對照表換成計費系統的發票號,再走發票號的路徑 |
圖:六種線索經過的入口不同,最後都停在同一個客戶總覽。
對照表要怎麼讀,表變長後才不會壞?
對照表的欄位是 branch ID | 統編 | 客戶名稱 | 營業登記名稱,前四個查詢角度都靠它把線索解析回分支 ID。
讀表有兩種做法:整張載入記憶體一次比對,或逐行搜尋。這裡選逐行搜尋。Apps Script 的 CacheService 單一值上限是 100KB,表一長,整張載入很容易撐爆快取。逐行搜尋慢一些,但表變長後不會突然失效。
分支 ID 寫法不一,容錯要放在哪一層?
同一個分支,同事貼進來的寫法可能是 -AbC123、AbC123,或標準的 B-AbC123。計費系統慣用的是 B-AbC123。程式認得這些寫法,收斂回標準形,同事就不必為了格式去改寫。下面是簡化過的示意寫法:
function normalizeBranchId(input) {
const id = input.trim().replace(/^-+/, ''); // 去掉多打的減號
return id.startsWith('B-') ? id : 'B-' + id; // 補上 B- 前綴
}
// normalizeBranchId('-AbC123') -> 'B-AbC123'
// normalizeBranchId('AbC123') -> 'B-AbC123'
// normalizeBranchId('B-AbC123') -> 'B-AbC123'轉換完成後,BillingClient.fetchAll() 會同時發出客戶基本資料、訂閱、發票三個 GET 請求,三個一起等,不必等客戶資料回來才發下一個。事件時間線(/events)沒有放進這一批,因為它的 content_snapshot 欄位很肥,常常失敗。如果把它放進同一批,一個 events 失敗就會連累整個總覽,所以它獨立取得,失敗時只影響時間線那一區。
快取多久,重試幾次?
TTL(快取的有效秒數,過了就重新向系統拿)依資料變動速度分兩級。所有快取 key 都帶固定前綴,不同查詢條件換不同 key,結果不會互相污染。搜尋與客戶總覽變動快,設 300 秒;發票對應、明細與 VAT/統一發票索引變動慢,設 1800 秒。
重試的分界線是錯誤碼。網路失敗、429、5xx 做三層重試,延遲依序是 5、10、15 秒。4xx(429 除外)立刻拋錯。示意如下:
const RETRY_DELAYS = [5, 10, 15]; // 秒,最多三層
function shouldRetry(status) {
if (status === 429 || status >= 500) return true; // 對方忙,過幾秒再送可能成功
return false; // 4xx:我方送錯,重送一樣錯
}客戶總覽加入每張發票的明細之後,如果沿用舊的 cache key,舊快取值就會在 300 秒的有效期內,被當成新結構讀出來,前端解析會壞掉,而且要等快取過期才會恢復。所以把 key 加上版號 v2,不去賭舊值相不相容。
誰查得到,出錯時誰先知道?
前端用一個 DOM 工廠函式建立節點,所有文字都用 textContent 寫入,所以計費系統回傳的資料不會被當成 HTML 插進頁面,也就沒有 XSS(惡意程式碼注入)的入口。
網頁入口有網域閘,只放行公司網域的帳號,其餘回傳 403。每個 API 入口再經過 assertAuthorized_() 檢查,並包進共用 Apps Script 函式庫的 tryRun()。出錯時,錯誤會自動送進 Slack,所以頁面出狀況時,維護者會先收到通知,不必等同事來反映。
這個工具只查詢計費系統,不會更動客戶設定或帳務資料,設計上沒有任何寫入路徑。
查不到的客戶,要從哪裡找?
代價也要說清楚:客戶名稱、營業登記名稱、統一編號與統一發票號四個角度,完整度就等於對照表的完整度。沒有被加進對照表的客戶,從這四個角度查不到,只能改用發票號或分支 ID。發票號與分支 ID 不依賴對照表,所以這兩條路永遠可用。
查詢省下多少時間沒有量測,目前是尚未實測。能確定的是同一個問題不再需要轉手:以前同事問不到就要去問維護者,現在多數情況自己就查完了。