cloudSignの契約書作成ツールを作成した

# はじめに
cloudSignにはAPIがあり、APIを使うことで契約書の作成時間を短縮することが可能です。
そこでGASを使い、契約書の作成（送信はせず、下書きまで）を行うツールを作成しました。

- [spreadsheetのリンク](https://docs.google.com/spreadsheets/d/1GFbRuHqxA8G5fy-wBS65JKI4YjXTNVaQ0XGkfKApT9o/edit?usp=sharing)

## 導入方法

### 1. spreadsheetをコピーしてください
### 2. 「テンプレート」シートの以下項目を、自身の環境に合わせて変えてください

- シート名
  - spreadsheetのシートタブの名前と合わせてください。合っていればなんでも大丈夫です。
- テンプレート名
  - テンプレートの名前を記入します
  - 丸々契約書など
  - このツールではなにか使っているわけではないです
- クラス名
  - 後ほど作成するクラス名に使用します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172695/e035b925-70a8-6865-d5ca-d372191fd44f.png)
### 3. 各書類のシートタブの以下項目を、自身の環境に合わせて変えてください

- 入力項目列群(F列から右)
  - ツールの仕様上、相手が入力するところであろうとこちら側が入力するところであろうと、入力項目ごとに列が必要になっています
  - 上から順番に入力項目をつけてください。列は追加しても削減しても構いません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172695/f3e32461-d22d-0739-78b1-58975a06a820.png)

### 4. ソースを修正します。

#### cloudSign.js

自分のcloudSignアカウントのclientIdを設定
clientIdは申し込みをした後に確認できます。申込みは[こちら](https://help.cloudsign.jp/ja/articles/2681259-%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E3%82%B5%E3%82%A4%E3%83%B3-web-api-%E5%88%A9%E7%94%A8%E3%82%AC%E3%82%A4%E3%83%89)

```javaScript:cloudSign.js
var CLIENT_ID = '<クラウドサインで取得したclient_id>';
```

#### Document.js

```javaScript:Document.js
// クラス名を設定した名前に変更
class Doc01 extends Document{
  constructor(){
    super();
    // cloudSignのURLからテンプレートidを取得
    this.template.id = '<TEMPLATEのID>';
    // PDFが1つだけの場合は0。もしPDFが2つあり、2つ目のほうに入力項目がある場合は1を設定。
    this.document.file.targetIndex = 0;
  }

  setDataFromSheet(row, index){
    super.setDataFromSheet(row, index);
    // sheet.jsにSHEET_DOC_01というところがあります。クラス名と揃える場合はsheet.jsも変更してください。
    this.document.widget.textList = SHEET_DOC_01.column.widgetList.map(columnId => {
      return row[columnId - 1];
    });
  }
}
```

#### sheet.js

```javaScript:sheet.js

// Document.jsで変数名を変更していれば変更
var SHEET_DOC_01 = {
  column : {
    // 入力項目の列数をArray(2)の数字部分に設定。例: Array(5)
    widgetList : [...Array(2)].map((_, i) => i + 6),
  },
};
```

## 使用方法

以下を設定して、メニューの操作→作成をクリック
- ステータス
  - 未作成に設定
- 宛先（メールアドレス、氏名）
  - 宛先の情報を設定
- 入力項目群
  - 自分が入力する部分だけ記入する

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172695/892d9535-c5c5-f9e0-4aff-0abf3e7e1277.png)

# 参考
## ソース

```javaScript:main.js
function onOpen(){
  const menu = SpreadsheetApp.getUi().createMenu('操作');
  menu.addItem('作成', 'main');
  menu.addToUi();
}

function main(){
  const templateList = getTemplateList();
  let documentList = getDocumentList(templateList);
  documentList = documentList.filter(document => document.isStatus(STATUS.notCreate));
  documentList.forEach(document => {
    document.setToken();
    document.create();
    document.setDestination();
    document.setForm();
    document.setDataToSheet();
  });
}
```

```javaScript:sheet.js
var SHEET_TEMPLATE = {
  name : 'テンプレート',
  row : {
    data : 2,
  },
  column : {
    id : 1,
    className : 3,
  },
};

var SHEET_DOCUMENT = {
  row : {
    data : 3,
  },
  column : {
    documentId : 1,
    status : 3,
    mail : 4,
    name : 5,
  },
};

var SHEET_DOC_01 = {
  column : {
    widgetList : [...Array(2)].map((_, i) => i + 6),
  },
};

var SHEET_DOC_02 = {
  column : {
    widgetList : [...Array(3)].map((_, i) => i + 6),
  },
};

var SHEET_DOC_03 = {
  column : {
    widgetList : [...Array(3)].map((_, i) => i + 6),
  },
};

function getDocumentList(templateList, sheetName){
  const sheet = (sheetName === undefined) ? SpreadsheetApp.getActiveSheet()
    : SpreadsheetApp.getActive().getSheetByName(sheetName);
  let data = sheet.getDataRange().getValues();
  [...Array(SHEET_DOCUMENT.row.data - 1)].forEach(_ => data.shift());
  
  const template = templateList.find(template => template.isSameId(sheet.getSheetName()));
  return data.map((row, index) => {
    let document = template.constractClass();
    document.setDataFromSheet(row, index);
    return document;
  });
}

function getTemplateList(){
  const data = getSheetData(SHEET_TEMPLATE);
  return data.map(row => new Template(row));
}

function getThisSheetTemplate(){
  const sheetName = SpreadsheetApp.getActiveSheet().getSheetName();
  return getTemplateList().find(template => template.isSameId(sheetName));
}

function getSheetData(sheetConfig){
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetConfig.name);
  let data = sheet.getDataRange().getValues();
  [...Array(sheetConfig.row.data - 1)].forEach(_ => data.shift());
  
  return data;
}

```

```javaScript:cloudSign.js
var CLIENT_ID = '<クラウドサインで取得したclient_id>';

function getToken(){
  const url = 'https://api.cloudsign.jp/token'
    + `?client_id=${CLIENT_ID}`;

  const response = UrlFetchApp.fetch(url);
  const json = JSON.parse(response.getContentText());
  return json.access_token;
}

function createDocument(token, templateId){

  const url = 'https://api.cloudsign.jp/documents';

  const options = {
    headers : {
      'accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Bearer ${token}`
    },
    method : 'post',
    payload : `template_id=${templateId}`
  };
  const response = UrlFetchApp.fetch(url, options);
  const json = JSON.parse(response.getContentText());
  return json;
}

function getDocument(token, documentId){
  const url = `https://api.cloudsign.jp/documents/${documentId}`;
  const options = {
    headers : {
      'accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Bearer ${token}`
    },
    method : 'get',
  };
  const response = UrlFetchApp.fetch(url, options);
  const json = JSON.parse(response.getContentText());
  return json;
}

function setDestination(token, documentId, destinationId, email, name){

  const url = `https://api.cloudsign.jp/documents/${documentId}/participants/${destinationId}`;

  const options = {
    headers : {
      'accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Bearer ${token}`
    },
    method : 'put',
    payload : `email=${email}&name=${name}`
  };
  UrlFetchApp.fetch(url, options);
}

function setForm(token, documentId, fileId, widgetId, text){

  const url = `https://api.cloudsign.jp/documents/${documentId}/files/${fileId}/widgets/${widgetId}`;

  const options = {
    headers : {
      'accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Bearer ${token}`
    },
    method : 'put',
    payload : `text=${text}`
  };
  UrlFetchApp.fetch(url, options);
}
```

```javaScript:class/Document.js
var STATUS = {
  notCreate : '未作成',
  beforeSend : '送信前',
};

class Document{
  constructor(){
    this.rowNum = null;
    this.status = null;

    this.token = null;
    this.template = {
      id : null,
    };
    this.document = {
      id : null,
      file : {
        id : null,
        targetIndex : null,
      },
      widget : {
        idList: [],
        textList : [],
      },
    };
    this.destination = {
      id : null,
      mail : null,
      name : null,
    };
    this.sentAt = dayjs.dayjs('-');

  }
  setToken(){
    this.token = getToken(this.token);
  }

  setDataFromSheet(row, index){
    this.rowNum = index + SHEET_DOCUMENT.row.data;
    this.document.id = row[SHEET_DOCUMENT.column.documentId - 1];
    this.status = row[SHEET_DOCUMENT.column.status - 1];
    this.destination.mail = row[SHEET_DOCUMENT.column.mail - 1];
    this.destination.name = row[SHEET_DOCUMENT.column.name - 1];
  }
  
  isStatus(status){
    return this.status === status;
  }

  create(){
    const destinationIndex = 1;

    const json = createDocument(this.token, this.template.id);
    this.document.id = json.id;
    this.document.file.id = json.files[this.document.file.targetIndex].id;
    this.document.widget.idList = json.files[this.document.file.targetIndex].widgets.map(widget => widget.id);
    this.destination.id = json.participants[destinationIndex].id;
  }

  setDestination(){
    setDestination(
      this.token,
      this.document.id,
      this.destination.id,
      this.destination.mail,
      this.destination.name
    );
  }

  setForm(){
    this.document.widget.idList.forEach((widgetId, index) => {
      
      let text = this.document.widget.textList[index];
      if(text === '') return;

      if(Object.prototype.toString.call(text) === '[object Date]'){
        text = dayjs.dayjs(text).format('YYYY年MM月DD日');
      
      }else if(isFinite(text)){
        text = text.toLocaleString();
      }

      setForm(
        this.token,
        this.document.id,
        this.document.file.id,
        widgetId,
        text
      );
    });
  }

  setDataToSheet(){
    const sheet = SpreadsheetApp.getActive().getActiveSheet();
    sheet.getRange(
      this.rowNum,
      SHEET_DOCUMENT.column.documentId
    ).setValue(this.document.id);
    
    sheet.getRange(
      this.rowNum,
      SHEET_DOCUMENT.column.status
    ).setValue(STATUS.beforeSend);
  }

  setStatusToSheet(sheet){
    sheet.getRange(
      this.rowNum,
      SHEET_DOCUMENT.column.status
    ).setValue(this.status);
  }
}

class Doc01 extends Document{
  constructor(){
    super();
    this.template.id = '<TEMPLATEのID>';
    this.document.file.targetIndex = 0;
  }

  setDataFromSheet(row, index){
    super.setDataFromSheet(row, index);
    this.document.widget.textList = SHEET_DOC_01.column.widgetList.map(columnId => {
      return row[columnId - 1];
    });
  }
}

class Doc02 extends Document{
  constructor(){
    super();
    this.template.id = '<TEMPLATEのID>';
    this.document.file.targetIndex = 0;
  }

  setDataFromSheet(row, index){
    super.setDataFromSheet(row, index);
    this.document.widget.textList = SHEET_DOC_02.column.widgetList.map(columnId => {
      return row[columnId - 1];
    });
  }
}

class Doc03 extends Document{
  constructor(){
    super();
    this.template.id = '<TEMPLATEのID>';
    this.document.file.targetIndex = 0;
  }

  setDataFromSheet(row, index){
    super.setDataFromSheet(row, index);
    this.document.widget.textList = SHEET_DOC_03.column.widgetList.map(columnId => {
      return row[columnId - 1];
    });
  }
}
```

```javaScript:class/Template.js
class Template{
  constructor(row){
    this.id = row[SHEET_TEMPLATE.column.id - 1];
    this.className = row[SHEET_TEMPLATE.column.className - 1];
  }
  
  isSameId(id){
    return this.id === id;
  }

  getId(){
    return this.id;
  }

  getSheet(){
    return SpreadsheetApp.getActive().getSheetByName(this.id);
  }

  constractClass(){
    const getClass = classname => Function(`return (${classname})`)();
    const c = getClass(this.className);
    return new c();
  }
}

```