3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rubyでboard APIを叩く

Posted at

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を有効にすると表示されます。
image.png

APIトークン

<APIトークン>
任意の数生成することができ、APIトークンごとに可能な操作(例:顧客の新規登録)を定義することができます。
セキュリティのため、可能な操作は最低限に範囲は限定することをお勧めします。そのため、用途に合わせてAPIトークンを登録し、必要な操作のみ有効にしてください。

発行方法

まず「新規トークン作成」
image.png

今回必要な
[1] 案件→リストの取得
[2] 請求→リストの取得
2つのみ チェックを入れ、発行。

APIトークン登録後、生成したトークンが表示されますが、APIトークンは登録直後の一度しか表示されませんので、忘れずに安全な場所に保管してください。
image.png

とのことなので注意して保存しましょう。

コードと解説

httpヘルパ

http_request_helper
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_status1,2,3のものを取得します。
注意して欲しいのは配列的クエリの送信方法です。僕がphpサーバ等でよく見かける形式とは少し違いました。
image.png

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)別に差し引く必要があります。
image.png

  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の「請求済」は入金前のみに付くステータスです。
「回収不能」というステータスもあります。面白いですね。
image.png

  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
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?