前置き
Pleasanterのダッシュボード・カスタムHtmlに実装するものを、AI(ChatGPT / 有料版)に任せてみる、という試みです。
最近よく耳にする Vibe Coding(AI コーディング)の実践例としてまとめました。
やりたいこと
SI案件の予定工数・実績工数から、粗利率・工数消化率を計算し、ダッシュボード上で可視化するという仕組みを Pleasanter で作成します。
事前準備
Pleasanterテーブルの準備
以下のようなテーブルを事前に用意しました。
最近リリースされたPleasanter Site Visualizer を使うと ER 図が簡単に確認できるのでおすすめです。
各テーブルの要件は以下のとおり
- プロジェクトテーブル
SIプロジェクト単位のデータ。開始終了日、受注金額、総工数などを扱う - 作業計画テーブル
プロジェクト × 作業月の単位で、月ごとの稼働予定工数を記録 - 工数割り当てテーブル
プロジェクト × 作業者 × 作業月 の単位で、作業者ごとの月次予定工数を定義
同一プロジェクトの割当工数合計が、プロジェクトテーブルの工数以下になるよう設計 - 作業実績テーブル
プロジェクト × 作業者 × 作業日 の単位で、日々の実績工数を入力
プロンプト準備
実装したい内容をそのままプロンプト化し、ChatGPT に投げます。
※Mermaidの部分、うまいことコードブロックに書けなかったので、コードブロック指定(```)を外しています。
# 概要
SI プロジェクトに対する以下の要素をPleasanterのダッシュボード・カスタムHTMLに表示する。
- 粗利率
- 工数消化率
# データ定義/業務運用
以下 4 種類のデータを入力として扱う。業務運用のイメージは記載のとおり。
- プロジェクトテーブル
Si プロジェクトごとにデータを作成。プロジェクトの内容、開始終了、受注金額、工数などの情報をあつかう。
- 作業計画テーブル
プロジェクトテーブルから連動し、プロジェクト × 作業月 単位でデータを作成する。プロジェクト全体の工数から、月ごとの稼働予定工数を登録する。
- 工数割り当てテーブル
プロジェクトテーブルから連動し、プロジェクト × 作業者 × 作業月 単位でデータを作成する。プロジェクト担当者が、その月にどのくらいの工数を使うかを計画し、月ごとの予定工数を割り当てる。プロジェクトテーブルとの工数の関係は以下のとおり。
同プロジェクトの工数合計 =< プロジェクトテーブルの工数
- 作業実績テーブル
プロジェクト × 作業者 × 作業日 単位でデータを作成する。作業担当者が、日々、作業したプロジェクトにかけた実績時間を入力する。
# データ構造
各データの項目、関連は以下のとおり
mermaid
erDiagram
TBL_5661["プロジェクトテーブル(5661)"] {
Title タイトル "プロジェクト名"
Body 内容 "プロジェクト概要"
DateA 日付A "開始"
DateB 日付B "終了"
NumA 数値A "単価"
NumB 数値B "工数(人月)"
Owner 担当者 "担当者"
}
TBL_5662["工数割り当てテーブル(5662)"] {
Title タイトル "タイトル"
ClassA 分類A FK "プロジェクト(プロジェクトテーブル.プロジェクト名)"
DateA 日付A "作業月"
Status 状況 "状況"
NumA 数値A "割当工数(人月)"
NumB 数値B "割当工数(時間)"
Owner 担当者 "担当者"
Comments コメント "コメント"
}
TBL_5665["作業実績テーブル(5665)"] {
ClassA 分類A FK "工数割り当て(工数割り当てテーブル.タイトル)"
Body 内容 "作業内容"
DateA 日付A "作業日"
NumA 数値A "作業工数(時間)"
Status 状況 "状況"
Owner 担当者 "担当者"
}
TBL_6113["作業計画(6113)"] {
ResultId ID PK "ID"
Ver バージョン "バージョン"
ClassA 分類A FK "プロジェクト(プロジェクトテーブル.プロジェクト名)"
DateA 日付A "作業月"
NumA 数値A "工数(人月)"
Title タイトル "タイトル"
Status 状況 "状況"
Manager 管理者 "管理者"
Owner 担当者 "担当者"
Comments コメント "コメント"
}
TBL_5661 |o--o{ TBL_5662 : "プロジェクト"
TBL_5662 |o--o{ TBL_5665 : "工数割り当て"
TBL_5661 |o--o{ TBL_6113 : "プロジェクト"
# 集計要件
- 前提 : すべて、プロジェクト × 作業月 単位でサマリ・集計する。
## 粗利率
- 売上 = 作業計画.工数 × プロジェクト.単価
- 原価 = 時給(※) × 作業実績.作業工数(時間)
- 粗利 = 売上 - 原価
- 粗利率 = 粗利 ÷ 売上(%で表示)
※時給はプログラム内で定数定義しておき、後から変更できるようにする。
## 工数消化率
- 予定工数 = 作業計画.工数
- 実績工数 = 作業実績.作業工数(時間)
- 工数消化率 = 実績工数 ÷ 予定工数
# 画面仕様
## 粗利率
- マトリクスとグラフで表示する
- プルダウンで表示する案件を選べる
- プルダウンの初期値・未選択は全案件を表示する
- プルダウンは複数選択できる
### マトリクス
以下イメージ
|案件名|YYYY/MM|YYYY/MM|YYYY/MM|YYYY/MM|YYYY/MM|
|:--|:--|:--|:--|:--|:--|
|案件A|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|
|案件B|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|
|案件C|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|粗利率:99.99%<br>売上:¥9,999,999<br>原価:¥9,999,999<br>粗利:¥9,999,999|
- 見出しの「YYYY/MM」は、作業月を表示(データ上存在する月)
- プルダウンで案件を選択したらその案件情報のみ表示する
### グラフ
- 作業月ごと2軸の棒グラフで表示する
- 1軸→売上、2軸→原価
- グラフに重ねるイメージでラベルで粗利率を表示する
- プルダウンで選択された案件の合算値にて表示する
## 工数消化率
- マトリクスとグラフで表示する
- プルダウンで表示する案件を選べる
- プルダウンの初期値・未選択は全案件を表示する
- プルダウンは複数選択できる
### マトリクス
以下イメージ
|案件名|YYYY/MM|YYYY/MM|YYYY/MM|YYYY/MM|YYYY/MM|
|:--|:--|:--|:--|:--|:--|
|案件A|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|
|案件B|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|
|案件C|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|予定工数:9.99<br>実績工数:9.99<br>消化率:99.99%|
- 工数は”人月”単位で表示する
- 見出しの「YYYY/MM」は、作業月を表示(データ上存在する月)
- プルダウンで案件を選択したらその案件情報のみ表示する
### グラフ
- 作業月ごと2軸の棒グラフで表示する
- 1軸→予定工数、2軸→実績工数
- グラフに重ねるイメージでラベルで工数消化率を表示する
- プルダウンで選択された案件の合算値にて表示する
# 実装の前提
- htmlとjavascriptで実装する(非Node.js環境)
- マトリクス・グラフはWebコンポーネント化する
- 初期実装時はダミーデータなどを定義し、プレビューでデザインを確認。本稼働前に実データ読み込みに置き換えられるようにしておく。
# サンプルデータ
サンプルデータを添付するので実装の参考としてください。
- projects.json → プロジェクトのデータ
- plans.json → 作業計画のデータ
- assignments.json → 工数割り当てのデータ
- worklogs.json → 作業実績のでデータ
サンプルデータ準備
プロンプトでは、実際に Pleasanter から取得する想定の「サンプルデータ」も読み込ませます。
理由:AI は Pleasanter のAPI仕様やメソッドを完全に理解していないため、データ取得部分は自分で実装した方が安定するからです。
そこで、
- AIには 取得済みのデータ を前提にしたロジックだけ作ってもらう
- 後から 実際のPleasanter API のデータ に置き換える
という方針を取ります
いざVibeCoding...の前に
まずはプロンプトとサンプルデータを読み込ませ 要件チェック を依頼します。
チェックの結果がこちら


いくつか指摘をもらいました。実装案も提示してくれます。

結果、特に問題なさそうだったので、この仕様で実装を依頼します。
実装
1点補足で仕様の指示をし、ChatGPT に実装の開始を指示すると、
以下のようにコードを書いてくれます。
ChatGPT上でプレビューしてみると、すでにいい感じです。
細かく修正したい点はありますが、まずは十分な出来です。
Pleasanterへ移植
生成されたコードは script タグ直書きのため、JSファイルとHTMLを分ける形に整理してもらう よう追加で指示します。
ということで、こんな感じで提案してくれました。
※jsファイルを読み込んでいますが、Pleasanter実装においては不要なので移植する際に消しておきます。
javascriptも以下のような感じ
で、Pleasanterへ

※プルダウンの選択がダッシュボードパーツのドラッグドロップ操作に負けてしまったので、no-dragを指定することで、ドラッグドロップ操作を無効にしています。
カスタムHTMLパーツに反映し、問題なく表示できました。
実データの読み込みへ修正
最後に、AI が作った処理へ実際のPleasanter APIのデータを組み込みます。
前述の通りデータ取得部分だけは自分で実装します。
const project = {
siteName: 'プロジェクトテーブル',
gridColumns: ['Title', 'Body', 'DateA', 'DateB', 'NumA', 'NumB', 'Owner'],
};
const assignment = {
siteName: '工数割り当て',
gridColumns: [
'Title',
'ClassA',
'DateA',
'Status',
'NumA',
'NumB',
'Owner',
],
};
const worklog = {
siteName: '作業実績',
gridColumns: ['ClassA', 'DateA', 'Status', 'NumA', 'Owner'],
};
const plan = {
siteName: '作業計画',
gridColumns: ['Title', 'ClassA', 'DateA', 'Status', 'NumA', 'Owner'],
};
const getSiteId = async (siteName) => {
let res;
await $p.apiGetClosestSiteId({
id: $p.siteId(),
data: {
FindSiteNames: [`${siteName}`],
},
done: function (data) {
res = data;
},
});
return res.Data[0].SiteId;
};
function getItems(siteId, gridColumns) {
return new Promise((resolve, reject) => {
$p.apiGet({
id: siteId,
data: {
View: {
ApiDataType: 'KeyValues',
ApiColumnKeyDisplayType: 'ColumnName',
GridColumns: gridColumns,
},
},
done: function (data) {
resolve(data.Response.Data);
},
fail: function (err) {
reject(err);
},
});
});
}
async function getData() {
const projectData = await getItems(
await getSiteId(project.siteName),
project.gridColumns
);
const assignmentData = await getItems(
await getSiteId(assignment.siteName),
assignment.gridColumns
);
const worklogData = await getItems(
await getSiteId(worklog.siteName),
worklog.gridColumns
);
const planData = await getItems(
await getSiteId(plan.siteName),
plan.gridColumns
);
return {
projectData,
assignmentData,
worklogData,
planData,
};
}
"getData()"で取得したデータを、AIが実装してくれた処理へ組み込みます。
で、以下のように実データによるダッシュボード表示ができました。
いい感じです。
実運用に向けて
AIでの初期構築はスムーズでしたが、実運用では調整すべき点もあります。
1. PageSize(デフォルト200件)の問題
Pleasanter のApi.jsonはデフォルトで 200 件。実運用の工数データは件数が多いため、ページング処理が必要になります。
2. 取得件数が多い場合のパフォーマンス問題
ページロード時に毎回 $p.apiGet すると、かなり時間がかかります。
→ 対策
- 取得したデータを中間テーブルの説明項目にJSON文字列として保存
- ダッシュボードでは中間テーブルの説明項目から$p.apiGetで読み込み
- JSON.parse() して表示
この構成で、実運用でも快適なパフォーマンスを確保できます。
さらに複数のダッシュボードパーツを想定すると、Webコンポーネント化してスタイル衝突を防ぐのもポイントと思います。
(エラー処理ももう少し考慮したいところ…)
実際にかかった時間
厳密な記録ではありませんが、感覚値では以下の通りです。
| 作業内容 | 時間 |
|---|---|
| プロンプト整理 | 1h |
| モック作成(ChatGPTによるプレビュー) | 0.5h |
| PleasanterApi実装 | 1h |
| サンプルデータ→実データ置き換え | 0.5h |
合計:約3時間
今回は過去の実装経験があったためスムーズでしたが、AIがうまく解釈してくれない場合は試行錯誤が必要になります。それでも “自力でゼロから作るより大幅に早い” のは間違いありません。
最後に
Pleasanter × AI の相性は非常に良く、実務のダッシュボード開発でもどんどん使える段階になってきたと実感しています。
引き続き、さまざまな業務で活用してみようと思います。











