フロントエンドだけで見積書・納品書・請求書を一括生成するWebアプリ

![ 2021-06-04 at 14.24.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301047/2d5f8a02-c014-477f-8fa8-d45dbbe02760.png)

法人であれ個人事業主であれ、他社との取引においては帳票生成が欠かせません。その度にわざわざExcelを開いて方眼紙を作って、、、なんてことはエンジニアたるものやりたくないですよね。そこで、単一のjsonファイルから見積書・納品書・請求書を一括生成できるツールを作ってみることにしました。

# デモサイト

[https://studio-mizutama.github.io/Quotation/](https://studio-mizutama.github.io/Quotation/)

# リポジトリ
[https://github.com/studio-mizutama/Quotation](https://github.com/studio-mizutama/Quotation)

# 心掛けたこと

## ビルド不要で動く

サクッと作ってサクッと動かすことを目標にしたため、フレームワークやAltJSは使わずに素のJavaScriptだけで開発しました。

## 再利用可能

自社の情報を`config/config.js`にまとめることで、他の人も再利用しやすいアプリになるように作りました。

```config/config.js
const company = 
  {
    "name": "<Your company name>",
    "zipCode": "",
    "address": "",
    "tel": "",
    "logoPath": "./images/logo.svg",
    "sealPath": "./images/seal.svg",
    "bank": "＊＊銀行",
    "branch": "＊＊支店",
    "typeOfAccount": "普通",
    "accountNumber": "111-1111111",
    "accountHolder": ""
  };

export default company;
```

## テキストベースでデータを管理

テキストベース（今回はjsonファイル）でデータを管理することで、git等を使ってバージョン管理がしやすくなります。脱Excelの一番大きなメリットです。

### jsonファイルフォーマット

```json
{
  "details": [
    {
      "description": "項目1",
      "quantity": 1,
      "unit": "式",
      "price": 30000
    },
    {
      "description": "項目2",
      "quantity": 5,
      "unit": "式",
      "price": 10000
    }
  ],
  "client": {
    "name": "株式会社クライアント",
    "title": "Webサイト構築"
  },
  "dateAndNumbers": {
    "no": "2021-05-30-01",
    "quotationDate": "2021-05-30",
    "validUntil": "2021-06-10",
    "deliveryDue": "2021-06-30",
    "paymentMethod": "月末締め翌月末払い",
    "deliveryDate": "2021-06-30",
    "invoiceDate": "2021-06-30",
    "paymentDue": "2021-07-31"
  }
}
```

# 技術的知見

## ドラッグ＆ドロップでファイル読み込み

ダイアログから選択だけでなく、ドラッグ＆ドロップでもファイルを読み込めるように実装しました。

![ 2021-06-04 at 14.31.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301047/b75325af-94b4-9afa-8118-abd30ad0c3ab.png)


```index.html
<h1 id="h1">jsonファイルを選択してください。</h1>
<input type="file" id="input-file" accept="application/json">
```


```main.js

/**
 * @param {number} 1: 見積書 2:納品書 3:請求書
 */
let type = 1;

/**
 * @param {boolean}
 */
let display = false;

const input = document.getElementById("input-file");

input.addEventListener("change", function() {
  result = input.files;
  main(result,type);
});

document.addEventListener("DOMContentLoaded",() => {

  const dropArea = document.getElementById("drop-area");
  dropArea.addEventListener("dragover",(e) => {
    e.preventDefault();
    dropArea.classList.add("drag");
  });
  dropArea.addEventListener("dragleave",() => {
    dropArea.classList.remove("drag");
  });
  dropArea.addEventListener("drop",(e) => {
    e.preventDefault();
    dropArea.classList.remove("drag");
    result = e.dataTransfer.files;
    main(result,type);
  });
});

/**
 * メイン処理を実行する関数
 * @param {FileList} result 
 * @param {number} type
 */
const main = function(result,type){
  display = true;
  const reader = new FileReader();
  reader.readAsText(result[0]);
  reader.addEventListener("load", function() {
    const json = JSON.parse(reader.result);
    //略
    input.style.display = "none";
  });
}
```

## 印刷と同じ表示でプレビュー

印刷プレビューとほぼ同じ見た目で画面表示できるようにCSSを書きました。

```index.html
<div class="sheet" id="drop-area">
  <div class="margin-container"></div>
</div>
```

```style.css

@page {
  size: A4;
  margin: 0;
}
@media print {
  body {
    width: 210mm;
  }
  html, body {
    height: 100vh;
    margin: 0 !important;
    padding: 0 !important;
    overflow: hidden;
  }
}

.sheet {
  width: 210mm;
  height: 296mm;
  page-break-after: auto;
  position: relative;
}

.margin-container {
  margin: 5mm;
  width: 200mm;
  position: absolute;
  top: 0;
  left: 0;
}


@media screen {
  body {
    background: #eee;
  }
  .sheet {
    background: white;
    box-shadow: 0 .5mm 2mm rgba(0,0,0,.3);
    margin: auto;
  }
}

```