boardとは
受託ベンチャーを支えるバックオフィス業務システム
ソース:boardが目指しているもの〜バックオフィス業務のために起業したのではない
その公式提供APIをRubyで叩きます。
算出したいデータ
[1] 見積中の案件について、合計額を算出する
→ 見込み売上
見積には「確度低/中/高」という区別がつけられるので、それで仕分けもする
[2] 受注後〜請求前の案件について、合計額を算出する
→ 確定売上
[3] 請求済みの請求について、合計額を算出する
→ 売掛金
出力例
見積中(高): ¥ 1,000,000
見積中(中): ¥ 2,000,000
見積中(低): ¥ 13,000,000
確定売上: ¥ 4,000,000
売掛金: ¥ 5,000,000
APIキー、APIトークンの取得
公式解説:https://the-board.jp/helps/help_common_api
APIキー
<APIキー>
アカウントで1つ発行され、リクエスト制限のためのリクエスト数は、このAPIキー単位にカウントされます。アカウントの識別キーのため、変更・削除はありません。
発行方法
APIトークン
<APIトークン>
任意の数生成することができ、APIトークンごとに可能な操作(例:顧客の新規登録)を定義することができます。
セキュリティのため、可能な操作は最低限に範囲は限定することをお勧めします。そのため、用途に合わせてAPIトークンを登録し、必要な操作のみ有効にしてください。
発行方法
今回必要な
[1] 案件→リストの取得
[2] 請求→リストの取得
の 2つのみ チェックを入れ、発行。
APIトークン登録後、生成したトークンが表示されますが、APIトークンは登録直後の一度しか表示されませんので、忘れずに安全な場所に保管してください。
とのことなので注意して保存しましょう。
コードと解説
httpヘルパ
require 'jwt'
require 'net/http'
require 'openssl'
module HttpRequestHelper
def get_request(url, headers, params = nil)
uri = URI.parse(url)
uri.query = URI.encode_www_form(params) if params
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === 'https'
req = Net::HTTP::Get.new(uri)
req.initialize_http_header(headers)
response = http.request(req)
response
end
def post_request(url, headers, params)
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === 'https'
req = Net::HTTP::Post.new(uri.path)
req.body = params.to_json
req.initialize_http_header(headers)
response = http.request(req)
response
end
def success?(response_code)
response_code && (200 <= response_code.to_i && response_code.to_i < 300)
end
end
見込み売上
進行中の案件のうち、「見積中(低/中/高)」ステータスのもの。
案件リストをorder_status
が1,2,3
のものを取得します。
注意して欲しいのは配列的クエリの送信方法です。僕がphpサーバ等でよく見かける形式とは少し違いました。
require 'http_request_helper'
class BoardReporter
include HttpRequestHelper
BOARD_API_BASE = 'https://api.the-board.jp'
BOARD_API_HEADER = {
'Authorization' => 'Bearer ' + ENV['BOARD_API_TOKEN'],
'x-api-key' => ENV['BOARD_API_KEY']
}
PER_PAGE = 100
def run()
estimated_sales_group = []
page = 1
has_next_page = true
while has_next_page
result, has_next_page = fetch_estimating_projects(page)
page += 1
estimated_sales_group.push(*result)
end
high_estimated_total = estimated_sales_group.select { |project| project[:order_status] == 1 }.inject(0) { |total, project| total + project[:total].to_i }.to_s(:delimited)
midium_estimated_total = estimated_sales_group.select { |project| project[:order_status] == 2 }.inject(0) { |total, project| total + project[:total].to_i }.to_s(:delimited)
low_estimated_total = estimated_sales_group.select { |project| project[:order_status] == 3 }.inject(0) { |total, project| total + project[:total].to_i }.to_s(:delimited)
puts "見積中(高): ¥ #{high_estimated_total}"
puts "見積中(中): ¥ #{midium_estimated_total}"
puts "見積中(低): ¥ #{low_estimated_total}"
end
def fetch_estimating_projects(page) # 見積中 高/中/低, 見積もり確度別の見込み売上
params = {
'order_status_in[]' => '1,2,3',
'per_page' => PER_PAGE.to_s,
'page' => page
}
response = get_request(BOARD_API_BASE + '/v1/projects', BOARD_API_HEADER, params)
if success?(response.code)
[
JSON.parse(response.body).map do |project|
{
order_status: project['order_status'],
project_no: project['project_no'],
total: project['total']
}
end,
next_page?(page, response)
]
else
puts '見積中の案件が取得できませんでした'
[[], false]
end
end
def next_page?(current_page, response)
response['X-Total-Count'].to_i >= current_page * 100
end
end
確定売上
確定売上は少し複雑です。
進行中の案件のうち「受注確定/受注済」ステータスで、かつ請求前のものです。
つまり請求済のものは計上したくないわけですが、boardでは案件と請求は 別々に管理されている ため、別途請求リストAPIから請求一覧を取得して案件No(project_no
)別に差し引く必要があります。
def run()
invoiced_sales = []
page = 1
has_next_page = true
while has_next_page
result, has_next_page = fetch_issued_invoices(page)
page += 1
invoiced_sales.push(*result)
end
receivable_sales = []
page = 1
has_next_page = true
while has_next_page
result, has_next_page = fetch_receivable_invoices(page)
page += 1
receivable_sales.push(*result)
end
billable_total = confirmed_sales.map do |project|
invoiced_total = invoiced_sales.select { |invoice| invoice[:project_no] == project[:project_no] }.inject(0) { |total, invoice| total + invoice[:total].to_i }
# 1つの保守案件を毎月請求している場合などはマイナスになるので、ここで止める
[(project[:total].to_i - invoiced_total), 0].max
end.inject(0) {|total, billable| total + (billable ? billable : 0) }.to_s(:delimited)
puts "確定売上: ¥ #{billable_total}"
end
def fetch_ordered_projects(page) # 受注確定/受注済, 確定売上
params = {
'order_status_in[]' => '4,5',
'per_page' => PER_PAGE.to_s,
'page' => page
}
response = get_request(BOARD_API_BASE + '/v1/projects', BOARD_API_HEADER, params)
if success?(response.code)
[
JSON.parse(response.body).map do |project|
{
project_no: project['project_no'],
total: project['total']
}
end,
next_page?(page, response)
]
else
puts '受注確定/受注済みの案件が取得できませんでした'
[[], false]
end
end
def fetch_issued_invoices(page) # 請求済/入金済/回収不能, 請求済み。確定売上から案件別に引く
params = {
'invoice_status_in[]' => '2,3,9',
'per_page' => PER_PAGE.to_s,
'page' => page
}
response = get_request(BOARD_API_BASE + '/v1/invoices', BOARD_API_HEADER, params)
if success?(response.code)
[
JSON.parse(response.body).map do |invoice|
{
project_no: invoice['project_no'],
total: invoice['total']
}
end,
next_page?(page, response)
]
else
puts '請求済みの請求書が取得できませんでした'
[[], false]
end
end
売掛金
売掛金は簡単です。
請求リストから「請求済」ステータスの請求を取得するだけです。
なお、これは「入金済」を含みません。boardの「請求済」は入金前のみに付くステータスです。
「回収不能」というステータスもあります。面白いですね。
def run()
receivable_sales = []
page = 1
has_next_page = true
while has_next_page
result, has_next_page = fetch_receivable_invoices(page)
page += 1
receivable_sales.push(*result)
end
receivable_total = receivable_sales.inject(0) { |total, invoice| total + invoice[:total].to_i }.to_s(:delimited)
puts "売掛金: ¥ #{receivable_total}"
end
def fetch_receivable_invoices(page) # 請求済(入金前)。いわゆる売掛金
params = {
'invoice_status_in[]' => '2',
'per_page' =>PER_PAGE.to_s,
'page' => page
}
response = get_request(BOARD_API_BASE + '/v1/invoices', BOARD_API_HEADER, params)
if success?(response.code)
[
JSON.parse(response.body).map do |invoice|
{
project_no: invoice['project_no'],
total: invoice['total']
}
end,
next_page?(page, response)
]
else
puts '売掛金情報が取得できませんでした'
[[], false]
end
end