見出し画像

【ラーメンを待ってる間にアプリ作るシリーズ🍜】請求書自動作成プログラムの解説

こちらの記事は以下の動画の解説記事になります。

@kata_techtok

インスタントラーメンを待ってる間にアプリを作る。果たして麺が伸びる前に作れるか...🍜 #pr #プログラミング #ラーメン #アプリ

♬ オリジナル楽曲 - かたへい@業務効率化の鬼 - かたへい@業務効率化の鬼

◆目標

GoogleAppsScript(以下GAS)を使って、取引先ごとに請求書をpdfで一括発行する

◆準備:スプレッドシートとGASの準備

まずは対象のスプレッドシートからGASのエディタを開きます。
↓シートは取引先ごとに『明細』『請求金額』『納品日』を一覧化したシートです。

記事で扱うスプレッドシートに編集権限リクエストがよく来るのですが、こちらは記事で取り扱う為のサンプルになるので閲覧権限のみに絞っています。
自身でも実際に手元でプログラムを動かしてみたいという方は

【ファイル → コピーを作成】

で自身のドライブにコピーして、そちらを編集等してお使い下さい。

手元にコピーができたら、【拡張機能 > Apps Script】でGASエディタを開きます。

◆一括請求書発行プログラムの実装

GASのエディタに以下コードを貼り付けます。

const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheet = ss.getSheetByName('請求一覧')
const fmt = ss.getSheetByName('請求書フォーマット')
const nums = {
  column: 4,
  startRow: 2,
  partnersColumn: 1,
  lastRow: sheet.getLastRow()
}

const setScriptProperty = () => {
  PropertiesService.getScriptProperties().setProperties({
    'TARGET_FOLDER_ID': 'ここに保存先のフォルダのID',
  })
}

const createInvoice = () => {
  const sm = new SpreadSheetManager()
  const partners = sm.partners
  const invoiceItems = sm.invoiceItems

  for (let i = 0; i < partners.length; i++) {
    // 請求書のスプレッドシートを新規で作成
    const invoice = new Invoice(partners[i], invoiceItems)
    invoice.parseItems()
    invoice.setValuesToInvoiceSheet()

    Utilities.sleep(4000)
    SpreadsheetApp.flush()

    // pdfを生成
    const pdf = new Pdf(invoice.copySheet.getSheetId(), partners[i])
    pdf.create(pdf.url)
  }
}

/**
 * 請求書に必要な情報をスプレッドシートから取得・操作するクラス
 */
class SpreadSheetManager {
  get partners() {
    const values = sheet.getRange(nums.startRow, nums.partnersColumn, nums.lastRow - 1)
                        .getValues()
                        .flat()
    return Util.uniq(values)
  }

  get invoiceItems() {
    return sheet.getRange(nums.startRow, 1, nums.lastRow - 1, nums.column).getValues()
  }
}

/**
 * 取引先ごとの請求書スプレッドシートを作成する為の処理をまとめたクラス
 */
class Invoice {
  constructor(partner, invoiceItems) {
    this.partner = partner
    this.invoiceItems = invoiceItems
    this.details = []
    this.prices = []
    this.deliveryDates = []
    this.copySheet = fmt.copyTo(ss)
  }

  parseItems() {
    const items = this.invoiceItems
    for (let i = 0; i < items.length; i++) {
      if (this.partner !== items[i][0]) continue
      this.details.push(items[i][1]) // 明細
      this.prices.push(items[i][2]) // 金額
      this.deliveryDates.push(items[i][3]) // 納品日
    }
  }

  setValuesToInvoiceSheet() {
    const copy = this.copySheet
    copy.setName(this.partner)

    this._bulkSetValues(copy, 1, this.details) // 明細をセット
    this._bulkSetValues(copy, 4, this.deliveryDates) // 納品日をセット
    this._bulkSetValues(copy, 5, this.prices) // 金額をセット

    const sum = this.prices.reduce((a, b) => a + b)
    copy.getRange('B7').setValue(sum) // 合計金額をセット
    copy.getRange('A3').setValue(`${this.partner} 御中`) // 取引先をセット
  }

  _bulkSetValues(sheet, row, items) {
    sheet.getRange(12, row, items.length, 1).setValues(Util.splitArray(items))
  }
}

/**
 * pdfを発行する為の処理をまとめたクラス
 */
class Pdf {
  constructor(sheetId, partner) {
    this.sheetId = sheetId
    this.partner = partner
    this.baseUrl = `https://docs.google.com/spreadsheets/d/${ss.getId()}/export?`
    this.targetFolderId = PropertiesService.getScriptProperties().getProperty('TARGET_FOLDER_ID')
  }

  get settings() {
    return {
      exportFormat: 'pdf',
      format:       'pdf',
      size:         'A4',
      portrait:     'true',
      fitw:         'true',
      sheetnames:   'false',
      printtitle:   'false',
      pagenumbers:  'false',
      gridlines:    'false',
      fzr:          'false',
      gid:           this.sheetId
    }
  }

  get url() {
    const pdfUrl = []
    for (const setting in this.settings) {
      pdfUrl.push(setting + '=' + this.settings[setting])
    }
    return this.baseUrl + pdfUrl.join('&')
  }

  async create(url) {
    const blob = await UrlFetchApp.fetch(url, this._option).getBlob().setName(`${this.partner}.pdf`)
    const folder = DriveApp.getFolderById(this.targetFolderId)
    folder.createFile(blob)
  }

  get _option() {
    const token = ScriptApp.getOAuthToken()
    return {
      headers: { 
        'Authorization': 'Bearer ' +  token 
      }
    }
  }
}

/**
 * 汎用的に使用できる処理をまとめたクラス
 * Util.uniq()のようにクラスを初期化せずに使えるようにする
 */
class Util {
  static uniq(array) {
    return array.filter((elem, index, self) => self.indexOf(elem) === index)
  }

  static splitArray(array) {
    const output = []
    for (let i = 0; i < array.length; i++) {
      output.push(array.slice(i, i + 1))
    }
    return output
  }
}

/**
 * initSheet()をスプレッドシートのメニュー上に出す
 */
const onOpen = () => {
  const menu = [
    {name: "初期化", functionName: "initSheet"},
  ]
  ss.addMenu("自作の処理", menu)
}

/**
 * 請求書のスプレッドシートを手作業で消していくのは面倒なので、まとめて削除する関数を追加
 */
const initSheet = () => {
  const partners = [
    "株式会社春日部制作","合同会社与野マーケティング","株式会社越谷産業","戸田システム株式会社","合同会社大宮メディア","浦和建設","所沢医院","株式会社川越マーケティング","株式会社株式会社熊谷システムズ"
  ]

  for (let i = 0; i < partners.length; i++) {
    const delSheet = ss.getSheetByName(partners[i])
    ss.deleteSheet(delSheet)
  }
}

ざっくりコードの流れを解説すると、

①スプレッドシートから請求情報を取得
②①の情報を元に取引先ごとに請求書をスプレッドシートで作成
③②で作成したスプレッドシートをpdf化する

という流れになります!

また、↓の『ここに保存先のフォルダのID』はご自身の環境で変更いただく必要があります。

const setScriptProperty = () => {
  PropertiesService.getScriptProperties().setProperties({
    'TARGET_FOLDER_ID': 'ここに保存先のフォルダのID',
  })
}

ここには保存先のGoogleDriveのIDに書き換えます。
フォルダのIDはURLの↓赤い部分が該当しますので、そこをコピペして書き換えていただければと思います!

次に、この情報をGASに認識させる必要があるので、↓のように関数を実行しましょう。

これで準備は完了したので請求書発行関数を実行してみましょう!

これでしばらく待った後、取引先ごとに請求書が自動発行されました👍

◆おわりに

他にも『サクッと使えそうな』自動化情報などをエンタメっぽく
TikTokで配信してます!!

面白い、使えそうな動画だと思ったら
『シェア』『コメント』『イイね』
していただけると更新の励みになります🍺

TikTokアカウント ➔ https://vt.tiktok.com/ZSJnM2NCX/

この記事が気に入ったらサポートをしてみませんか?