News
この連載は、2026年7月のMicrosoft 365値上げをきっかけに、中小企業が「Excelでやっている仕事」を仕分けして、人数課金に振り回されない環境を作るまでを書いた記録です(全3回)。vol.2は実装編です。Officeの編集機能が組み込まれた顧客管理システム(CRM)——顧客カルテから見積書をそのままブラウザで編集・保存できる仕組み——を自作して、小さなVPS1台で動かすまでを書きます。前回(vol.1 値上げと「Excel仕分け」)で書いた「③台帳を業務システムへ移す」の、実物をつくる回です。
こんな方向けの記事
- 顧客管理システムと見積書づくりがバラバラで、「顧客情報をコピーしてExcelの雛形に貼る」作業が残っている会社の方
- 自社サーバーにブラウザ版のオフィス環境を建ててみたいエンジニア・兼任情シスの方
- 業務システムにOnlyOfficeを組み込む方法(APIの使い方)を知りたい開発者
- 「自分たちでやれるのか、外注すべきか」を判断するために、作業の全体像を知りたい経営者
なぜ「顧客管理の中で見積書を開く」のか
この連載で繰り返している「Excelの仕事の3つの仕分け」で、③台帳(顧客・案件・対応履歴)を業務システムに移したとします。それでも現場にはこういう作業が残ります。
- 顧客管理システムで顧客の住所・担当者名を確認する
- Excelの見積書テンプレートを開いて、それをコピペする
- ファイル名を付けて、共有フォルダのどこかに保存する
- あとで「あの見積書どこだっけ」「最新版はどれだっけ」になる
③を移しても①文書づくりがシステムの外に残っている限り、この手作業と「最新版迷子」は消えません。そこで、顧客カルテの画面に「見積書を作る」ボタンを置き、押すと顧客情報が入った見積書がブラウザの中でそのまま開いて編集でき、保存するとその顧客に紐づいて履歴に残るようにします。文書はExcel形式(xlsx)のまま保存されるので、取引先にはそのまま送れます。


全体構成
登場するのは3つです。
- 顧客管理システム(CRM) — 素のPHP+SQLiteで書いた小さなシステム(フレームワークなし・全部で450行ほど)。顧客・対応履歴・見積書の台帳を持ち、ファイルの実体もここが持つ
- OnlyOffice Docs(Document Server) — ブラウザで編集するためのエンジン。ファイルは持たず、CRMから渡されたファイルを開いて、保存時にCRMへ返す
- VPS — 上の2つを1台に同居させる。小さめのプランで足りる

ポイントは、Document Serverが「ステートレス」であることです。文書の実体と履歴はすべてCRM側のデータベースとストレージにあるので、バックアップの対象はCRMだけで済み、Document Serverは壊れても再構築すれば戻ります。この設計は運用の楽さに直結します。
連携の仕組み — 実装の要点
Document Serverとの連携は、大きく4ステップです。公式のAPIドキュメントに沿っていますが、実際にやってみて詰まった点を中心に書きます。
1. CRMが見積書を生成する
顧客カルテの「見積書を作る」ボタンで、xlsxのテンプレートに顧客名・住所・担当者・案件名を流し込んだファイルを作ります。xlsxの実体はzipなので、テンプレートに{{customer_name}}のような目印を置いておき、zip内のXMLを文字列置換するだけで、書式を完全に温存したまま差し込めます(ライブラリ不要です。PhpSpreadsheetを使う手もあります)。生成したファイルはCRMのストレージに保存し、Document Serverからアクセスできる一時URL(5分で失効)を発行します。
2. ブラウザでエディタを開く
CRMの画面に、Document Serverが配布するJavaScript(api.js)を読み込み、設定オブジェクトを渡してエディタを起動します。最低限の設定はこの形です。
// CRM側(PHP): エディタ設定を組み立ててJWTで署名する(実装からの抜粋)
$config = [
'documentType' => 'cell', // xlsx なら cell
'document' => [
'fileType' => 'xlsx',
'key' => "q{$quoteId}-v{$version}", // 文書の版ごとにユニークなキー
'title' => "見積書_{$quoteNo}.xlsx",
'url' => $appUrl . '/quote/file?token=' . $fileToken, // DSが取りに来るURL(5分で失効)
],
'editorConfig' => [
'callbackUrl' => $appUrl . '/quote/callback?id=' . $quoteId,
'lang' => 'ja',
'user' => ['id' => 'demo', 'name' => 'デモユーザー'],
'customization' => ['autosave' => true, 'forcesave' => true],
],
];
$config['token'] = jwt_encode($config, $jwtSecret); // HS256の署名。自前実装でも20行ほど
ブラウザ側は、Document Serverが配布するapi.jsを読み込んで、この設定を渡すだけです。
<script src="https://docs.example.jp/web-apps/apps/api/documents/api.js"></script>
<script>new DocsAPI.DocEditor('onlyoffice-editor', config);</script>
詰まりやすいのはkeyです。Document Serverは同じキーの文書を「同じ編集セッション」とみなしてキャッシュするので、保存して版が変わったら必ずキーも変えます(例:文書ID+版番号)。逆に、複数人で同時編集させたいときは同じキーを渡します。
3. 保存のコールバックを受け取る
利用者がエディタを閉じると、Document ServerがcallbackUrlにJSONをPOSTしてきます。statusが2(編集終了・保存準備完了)のとき、urlに完成したファイルが置かれているので、CRMがそれをダウンロードして保存し、顧客に紐づけて版を1つ増やします。
// CRM側(PHP): 保存コールバック(実装からの抜粋)
$body = json_decode(file_get_contents('php://input'), true) ?: [];
// DSは Authorization: Bearer <jwt> ヘッダで署名を送ってくる。検証NGは403で拒否
if (!preg_match('/Bearer\s+(\S+)/', $_SERVER['HTTP_AUTHORIZATION'] ?? '', $m)
|| jwt_decode($m[1], $jwtSecret) === null) {
http_response_code(403);
exit(json_encode(['error' => 1]));
}
$status = (int)($body['status'] ?? 0);
if (in_array($status, [2, 6], true) && !empty($body['url'])) { // 2=閉じて保存, 6=強制保存
$bin = file_get_contents((string)$body['url']); // 完成ファイルをDSから取得
file_put_contents(quote_file($quoteId, $version + 1), $bin); // 新しい版として保存
// quotes.version を +1 → カルテの見積書一覧に v2, v3… と並ぶ
}
echo json_encode(['error' => 0]); // これを返さないとDSが保存失敗扱いにする
コールバックの応答は必ず{"error":0}を返します。返さないと、Document Server側で「保存失敗」と記録され、次に開いたときに古い内容が出ることがあります。
4. JWTで認証する
Document Serverには、設定オブジェクトとコールバックの両方にJWT(共有秘密鍵で署名したトークン)を付ける仕組みがあります。これは必ず有効にしてください。無効のままだと、URLを知っている第三者が任意のファイルをDocument Serverに開かせたり、偽のコールバックで文書を上書きしたりできてしまいます。秘密鍵はDocument Serverの環境変数(JWT_SECRET)とCRMの設定の両方に同じ値を置き、CRM側で設定オブジェクトを署名し、コールバック受信時はヘッダーのトークンを検証します。
実装で詰まった点
- 名前解決 — Document ServerはCRMのURLを「自分からアクセス」してファイルを取りに来ます。同じVPS内でも
localhostではなく、外から引ける正式なドメインで渡す必要がありました - 日本語フォント — サーバーに日本語フォントが無いと、ブラウザで見たときに文字の幅が変わり、列幅の計算がズレます。フォントを追加して再生成するコマンドを実行(後述)
- 一時URLの有効期限 — Document Serverがファイルを取りに来るのは起動直後だけなので、一時URLは数分で失効させて問題ありません
- 保存はエディタを閉じた「直後」ではない — Document Serverからコールバックで書き戻されるまで30秒〜1分のラグがあります。閉じた直後にファイルを読むと、まだ旧版でした(実測)
- 条件付き書式はx14拡張形式に書き換わる — Excelでは従来どおり機能しますが、openpyxlなどの解析ライブラリからは見えなくなります。保存後のファイルをプログラムで扱う場合だけ注意
- 縦書きの文書は横書き表示になる — 見積書・請求書のような横書き帳票なら問題ありませんが、縦書きの書類は対象外と考えてください(実測)
VPSに建てる — 小さな1台で足りる
私はXserver VPSの小さめのプランを使っています。OnlyOffice Docsの動作の目安はCPU2コア・メモリ2GB以上(快適に使うなら4GB)ですが、2026年5月の9.4でRabbitMQやデータベースへの依存が無くなり単一プロセス化されたため、以前よりかなり軽くなっています。
構築手順の概要
- Document Serverの起動 — Dockerが最も手軽です。JWTの秘密鍵を環境変数で渡して起動します。
docker run -d --name onlyoffice --restart=always \ -p 127.0.0.1:8080:80 \ -e JWT_SECRET="(長いランダム文字列)" \ -v /srv/onlyoffice/data:/var/www/onlyoffice/Data \ -v /srv/onlyoffice/logs:/var/log/onlyoffice \ onlyoffice/documentserver(イメージ名・タグ・ボリューム構成は公式ドキュメントで最新を確認してください)
- Nginxでリバースプロキシ+HTTPS — サブドメイン(例:docs.example.jp)をDocument Serverに向け、Let’s Encryptで証明書を取ります。共同編集はWebSocketを使うので、
Upgradeヘッダーを通す設定を忘れずに - 日本語フォントの追加 — IPAexフォントやNoto Sans CJKをコンテナ内の
/usr/share/fonts/truetype/配下に置き、documentserver-generate-allfonts.shを実行してフォントキャッシュを再生成します - CRM側の設定 — Document ServerのURL・JWT秘密鍵・コールバックURLを環境変数に設定
- アクセス制限 — Document Serverの管理系のパスは外部から閉じる。可能ならCRMのドメインからのアクセスだけを許可
運用設計
- バックアップ — 文書の実体はCRM側にあるので、CRMのDBとストレージを日次でバックアップ。Document Serverはほぼステートレスなので対象外でよい
- アップデート — Dockerイメージを更新して再起動するだけ。ただし本番はバージョンを固定し、リリースノートを読んでから上げる。2026年は開発元をめぐる動きが活発なので、特に注意
- 監視 —
/healthcheckエンドポイントを外形監視に登録。落ちていてもCRM側は動くので、業務が止まるわけではない - 同時接続 — メモリは同時編集の数に応じて消費します。5〜30名規模の社内利用が対象なら過剰な心配は不要で、実際の同時人数で一度試すのが確実です
費用の考え方
インフラはこのVPS1台とドメインだけで、文書エディタに人数課金のライセンス費用はかかりません。何人で使っても、サーバー側の費用は変わらない構造です。ただし、本当のコストは構築と保守をする人の時間です。自社でやるならその人件費、外注するならその費用が実体で、私はここを月額定額に含める形で提供しています(vol.3で書きます)。
なお、組み込んでいる文書エディタ(OnlyOffice Docs)を業務で使う場合は、画面内のブランド表記を残すなどのライセンス条件があります。導入時に公式のライセンス情報を一読しておくと安心です。
実際の動きを見たいときは
この記事で作った仕組みは、そのままKIZUKI(定額の業務システム開発)の標準構成のひとつです。「うちの見積書でも動く?」「画面を実際に見たい」という方は、記事末尾の窓口からお気軽にどうぞ。オンラインで実際の画面をお見せします。
③台帳と①文書づくりが、つながった
vol.1で書いた仕分けに戻ると、今回作ったものは「③台帳を業務システムに移し、①文書づくりをその中から直接できるようにした」状態です。Excelが必要な人は②計算・分析をしている数名だけになり、文書はExcel形式のまま残るので取引先とのやり取りも変わりません。そして、この仕組みは人数が増えても料金が増えません。
最終回のvol.3では、「エクセルで顧客管理」がどこから限界になるのかと、この仕組みを定額で持つ方法(費用モデル・向かない会社・導入の流れ)をまとめます。
実装と構築についてよくある質問
顧客管理システムとOnlyOfficeの連携は自作できますか?
できます。Document Serverには公式のAPIがあり、この記事の4ステップ(ファイル生成→エディタ起動→保存コールバック→JWT認証)を実装すれば動きます。Webアプリを作った経験があるエンジニアなら、最初の動作確認までは1〜2日が目安です。運用設計(バックアップ・更新・監視)まで含めると、もう少しかかります。
費用はどのくらいかかりますか?
インフラはVPS1台とドメイン代だけで、人数課金のライセンス費用はありません。ただし構築と保守をする人の時間が本当のコストで、自社でやるならその人件費、外注するならその費用を見込んでください。
スマホで使えますか?
顧客管理システム自体はスマホのブラウザでそのまま使えます。見積書はスマホでは閲覧向きで、編集はPCのブラウザが基本です。「カルテは外出先のスマホで確認、書類仕事は事務所のPCで」という使い分けになります。
セキュリティは大丈夫ですか?
JWT認証の有効化、HTTPS化、Document Serverへのアクセス制限、CRM側のバックアップの4点を押さえれば、文書データは自社サーバーの外に出ません。逆にJWTを無効のまま公開するのは危険なので、必ず設定してください。
Nextcloudと組み合わせるのと何が違いますか?
Nextcloud+OnlyOfficeは「社内版のファイル共有+共同編集」で、ファイルを起点にした使い方です。この記事の方法は、顧客や案件という「台帳」を起点に文書を開く使い方で、文書が顧客に紐づいて履歴に残るのが違いです。なお、2026年にOnlyOfficeはNextcloudとの提携を解消しているため、新規にNextcloud連携を組む場合は最新の対応状況を確認してください。
次回予告
最終回のvol.3では、「エクセルで顧客管理」の限界サインを整理し、脱Excelの選択肢(SaaSのCRM・自作・定額開発)を費用で比べたうえで、顧客管理システムと社内オフィス環境を定額でまとめて持つ方法を書きます。
→ vol.3「エクセルで顧客管理の限界サインと、定額でまとめて持つ方法」は2026年9月22日(火)公開予定です。
「③台帳」の仕事が、まだExcelに残っている会社へ
顧客・案件・対応履歴のような台帳は、表計算ソフトを替えても楽になりません。anTechでは、初期費用0円・月額定額で業務システム(顧客管理など)を作り、必要に応じて社内オフィス環境もセットでご用意する「KIZUKI」を提供しています。
まずは相談だけでも
「うちのExcel、どこまでシステムにすべき?」「値上げ分をどう吸収する?」といった相談からで大丈夫です。Excelからのデータ移行もご協力いたします。