PleasanterとPowerPointで作成したテンプレートを組み合わせた帳票印刷

## はじめに
Pleasanterはさまざまなデータの管理用途などで使用されていますが、業務内容によってはその内容を印刷出力したいという要件も多くあるかと思います。  

最も簡単な手法としては、以下のマニュアルにあるような手順で、ヘッダー、フッターやボタンの表示を切り替え、Pleasanterの編集画面をそのまま印刷する方法です。

[FAQ：印刷時に画面下部のコマンドボタンを非表示にしたい | Pleasanter](https://pleasanter.org/manual/faq-hide-footer-when-printing)

また、これ以上の高度な印刷要件に対応するには、各パートナー企業から以下のようなソリューションが提供されています。

- [Report Create for プリザンター - イメージ情報システム株式会社](http://iis.image-inf.co.jp/solution/reportcreate)
- [プリザンターPDF帳票出力関連サービス - 株式会社シーイーシーカスタマサービス](https://www.ceccs.co.jp/it_introduction/c-webdb#PDF)
- [ローコードツールPleasanterの帳票出力システム導入事例 | OSSのデージーネット](https://www.designet.co.jp/system/oss.php?category=improvement&id=3&oss=Pleasanter)

Pleasanterで本格的に帳票印刷を行う場合はこれらの利用を推奨しますが、今回はこれらのソリューションを使わず、Microsoft PowerPointで作成した帳票レイアウトとPleasanterを組合せて簡易的な帳票印刷を実現する方法をご紹介します。

#### 動作サンプル
以下が、実際の動作例の画面です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492700/787c9ce6-ecfc-00d4-4d54-8eeed4bacf05.png)


左側が通常のPleasanterの編集画面のデータで、添付ファイルとして帳票のテンプレートをアップロードしています。  
右側の領域にはアップロードした帳票テンプレートにが画面項目データが埋め込まれています。編集内容はリアルタイムで帳票のプレビューに反映されます。
## 動作概要
この方式では、以下のようにして帳票印刷を実現しています。
1. PowerPointでテンプレートとなる帳票レイアウトを作成、SVG形式でファイル出力
2. SVGファイルをPleasanterの添付ファイルとしてアップロード
3. PleasanterのスクリプトでSVGファイルを読み込み、画面内のデータをSVG内の変数に置換
4. Javascriptでレイアウトを調整して印刷ダイアログ表示、印刷

この方法を使えば、多くの方が使い慣れているPowerPointで帳票レイアウトを作成し、Pleasanterと組み合わせて印刷を実現することが可能です。

## 動作環境
- Pleasanter Ver.1.4.6.3
- Microsoft PowerPoint Microsoft 365 Apps（バージョン2406）

Webブラウザは以下のバージョンで検証
- Google Chrome バージョン: 126.0.6478.183
- Microsoft Edge バージョン 126.0.2592.113
## 設定の手順

### PowerPointでの帳票レイアウト作成

はじめに PowerPoint でテンプレートとなる帳票のレイアウトを作成します。例では、出張の目的等の概要と移動経路・宿泊の明細表で構成された簡略化した出張旅費申請書とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492700/a76c3561-7165-fc85-4d41-87bd849b98eb.png)

レイアウトは、設定項目のスライドのサイズでA4の縦サイズを選択します。

設定するうえでの注意点として、このようなテンプレートに変数を埋め込む場合、一般的には `{変数名}` や  `%変数名%` 等のプレースホルダーを使った記述が一般的ですが、PowerPointの仕様により記号と文字列を連続して記述しても、SVG形式で出力すると記号と文字列が分割されて出力されます。

例:  
`{{NumA}}` と記述したものが SVG形式で出力すると、  
`<text>{{</text><text>NumA</text><text>}}</text>` といった結果なる

この例では処理を簡略化するため、Pleasanterのデータ項目名（`ClassA`、`DataA` 等）をそのまま使用します。
また、表示するするフォントやサイズ等の装飾もここで行います。使用フォントについては、ユーザのWebブラウザで利用可能なものを使用します。

### PowerPointで作成したレイアウトをSVG形式で出力
続いて、作成したレイアウトをSVG形式でエクスポートします。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492700/e8e0c2e5-2d60-5db1-7acc-0859b0767b80.png)
エクスポートの操作方法は、メニューのエクスポート項目から、ファイルの種類の変更 > 別のファイル形式として保存 と進み、ファイルの種類を `スケーラブルベクターグラフィックス形形式（*.svg）` を選択して任意の名称で保存します。
### Pleasanterのサイト設定
前項で作成した帳票レイアウトに対応したPleasanterのサイトを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492700/4ff29f59-841f-43f8-cd42-0a8010a187cc.png)
項目名はどのような名称でも構いませんが、説明で帳票との対比で分りやすいようにほぼ標準のまま使用しています。  
その他、特殊な設定はしていないので各項目の説明は割愛します。

### スクリプトの設定
#### 1. SVG読み込みスクリプト
対象サイトのテーブルの管理からスクリプトを選択し、以下のコードを貼り付けます。
  出力先は `編集`を指定します。
```Javascript
// 変更を監視する画面項目のリスト
const itemsList = [
  "Title", "Body","Owner", 
  "DateA", "ClassA", "DescriptionA", "NumA",
  "DateB", "ClassB", "DescriptionB", "NumB",
  "DateC", "ClassC", "DescriptionC", "NumC",
  "DateD", "ClassD", "DescriptionD", "NumD",
  "DateE", "ClassE", "DescriptionE", "NumE",
  "DateF", "ClassF", "DescriptionF", "NumF",
  "DateG", "ClassG", "DescriptionG", "NumG",
  "DateH", "ClassH", "DescriptionH", "NumH",
  "DateZ",           "DescriptionZ", "NumZ"
];

// テンプレートのSVG内容を保持
let TemplateSvgContent = ''; 

// テンプレートSVGを読み込む
function loadSVG() {

  console.log("SVG読み込み開始");
  
  try {
    const attachmentData = $p.getControl("AttachmentsA").val();
    const attachments = JSON.parse(attachmentData);

    if (attachments.length > 0) {
      const svgGuid = attachments[0].Guid;
      const svgUrl = `/binaries/${svgGuid}/show`;

      $.ajax({
        url: svgUrl,
        dataType: "text",
        success: (svgContent) => {
          TemplateSvgContent = svgContent; // テンプレートのSVG内容を保存
          updateSVG(); // SVGを更新して表示
        },
        error: (xhr, status, error) => {
          console.error("SVGの読み込みに失敗しました:", error);
        },
      });
    } else {
      console.log("添付ファイルが見つかりません。");
    }
  } catch (e) {
    console.error("添付ファイルデータの解析に失敗しました:", e);
  }
}

// SVGの内容を更新する関数
function updateSVG() {
  console.log("SVGの内容を更新");

  if (TemplateSvgContent){

    let updatedSvgContent = TemplateSvgContent;

    // SVG内のプレースホルダーをPleasanterの画面項目の値で置換
    itemsList.forEach(item => {
      let value = $p.getControl(item).val() || ''; // 値が取得できない場合は空文字

      if (item.startsWith('Num')) {
        // Numで始まる項目は右詰めにし、位置を調整する
        updatedSvgContent = updatedSvgContent.replace(
          new RegExp(`<text([^>]*)>${item}</text>`, 'g'),
          `<text$1 text-anchor="end" dx="30">${value}</text>`
        );
      } else {
        // 数値型以外の値の置換
        updatedSvgContent = updatedSvgContent.replace(new RegExp(item, 'g'), value);
      }
    });

    // 既存の描画済みSVGを削除し、新しいSVGを挿入
    $("#svg-preview").remove();
    $("#Comments").before(`
      <div id="svg-preview" class="field-section">
        プレビュー
        <div id="print-svg" class="print-svg">${updatedSvgContent}</div>
      </div>
    `);

  }
}

// 画面初期表示時にSVGのロード実行
$p.events.on_editor_load = function () {
  loadSVG();
}

// 画面更新後にSVGのロード実行
$p.events.after_set = function (args) {
  loadSVG();
}

// AttachmentsAの変更があった場合にSVGのロード実行
$p.on("change", "AttachmentsA", function () {
  loadSVG();
});

// 監視対象の要素に共通のクラスを付与
itemsList.forEach(item => {
  $p.getControl(item).addClass('watch-item');
});

// 一括でイベントリスナーを設定
$(document).on('change', '.watch-item', updateSVG);

```

##### 1.SVG読み込みスクリプトの解説

上記のスクリプトは、はじめに添付された帳票テンプレートを画面にロードし、各入力項目の内容をテンプレートに反映させます。また、編集中も画面項目の変更を監視し、随時テンプレートへ反映させます。  

冒頭で宣言している配列 `itemsList` で監視項目対象の名称を列挙します。スクリプト末尾でこれを指定して一括でイベントリスナーを設定しています。また、`itemsList`は後述のプレースホルダーの置換でも利用しています。

`loadSVG()`は、添付A（AttachmentsA）に添付されたSVG形式の帳票テンプレートを読み込み、変数へ格納しています。このとき、ファイルごとに発行されたGUIDをもとに `/binaries/${GUID}/show` とURLを指定することで、添付されたSVGファイルをajaxで直接取得しています。

`updateSVG()`は、SVGテンプレートの内容を更新し、画面に反映させます。`itemsList`リストの各項目の値を取得し、SVGテンプレート内のプレースホルダーを置換しています。この際、項目の種類によって分岐し、置換とあわせて書式の設定などを行います。例では、数値型とOwner（担当者）項目で個別の置換を行っています。

#### 2. SVG印刷スクリプト
対象サイトのテーブルの管理からスクリプトを選択し、以下のコードを貼り付けます。
  出力先は`編集`を指定します。  
```Javascript
function printSVG() {
  const printDiv = document.getElementById('print-svg');
  const windowTitle = $p.getControl('Title').val();

  // 印刷用のウィンドウを作成
  const printWindow = window.open('', '_blank');
  printWindow.document.write(`
    <html>
      <head>
        <title>${windowTitle} 印刷プレビュー</title>
        <style>
          @page {size: A4; margin: 0; }
          body{margin: 0; padding:0;}
          div, svg {
            width: 720px;height: 1040px;
            margin-right: auto;margin-left: auto;
          }
        </style>
      </head>
      <body>
        ${printDiv.outerHTML}
      </body>
    </html>
  `);
  printWindow.document.close();

  // コンテンツが読み込まれた後にSVG属性を調整
  printWindow.onload = function() {
    const svgElement = printWindow.document.querySelector('#print-svg svg');
    if (svgElement) {
      svgElement.setAttribute('width', '720');
      svgElement.setAttribute('height', '1040');
      svgElement.removeAttribute('viewBox');
    }

    // コンテンツを印刷して印刷ウィンドウを閉じる
    printWindow.focus();
    printWindow.print();
  };
}
```
##### 2. SVG印刷スクリプトの解説
このスクリプトでは、編集画面で画面項目のデータが埋め込まれたSVGデータを別ウィンドウ（別のタブ）へ複製し、印刷ダイアログを表示します。


### プロセスの設定
管理 > テーブルの管理 からプロセスタブを選択し、プロセスの新規作成を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492700/ada4dafb-c9cd-8d70-013b-c36d658e53a9.png)

必須の設定は以下の点です。
- 現在の状態 : * 
- OnClick : `printSVG() `
- 実行種別 : 追加したボタン
- アクション種別 : なし  
その他、名称、表示名などは任意の値で構いません。

この設定を行うことにより、画面下部に印刷用ボタンが表示されます。
## 操作の実行例
当該テーブルに新規レコードを作成し、各データを入力後、`添付A` に先ほど作成した SVGファイルをアップロードして保存することにより以下のような画面となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492700/bcc68a95-51e7-46e4-ca08-bca4f3938051.png)

この状態で画面に配置された印刷ボタンを押すと、以下のようなプレビューウインドウが表示されて印刷が可能となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492700/2624e160-1e27-8f7c-9fd7-da7082162b79.png)

## おわりに 

本記事でのSVGをテンプレートとする方法の着想は以下の記事から得ました。

[ついに、Webアプリでの帳票印刷のベストプラクティスを編み出しました](https://zenn.dev/ttskch/articles/1f1572cfd2e375)

参考としたこちらの記事では一般的なHTMLファイルを直接印刷する方法や、その代替案のExcelで帳票印刷する場合のレイアウト調整の難しさを、SVGファイルのテンプレートを使うことで解消されています。  
こちらの記事の方法では Adobe XD や Figma で帳票のレイアウトを作成していますが、これらのツールに馴染みがないと導入のハードルが高いと思い、PowerPointでの方法を試行してみました。

なお、PowerPointで出力した本手法では、後述に記述するような制限事項があります。本格的な帳票を作成したいは、前述のパートナー企業の帳票ソリューションをおすすめしますが、制限事項の制約を気にしないような簡易の帳票印刷の用途として、お試しいただければ幸いです。

## 制限事項

- 列の増減など動的なレイアウト変更には対応できません
- 複数ページにわたる印刷や改ページ処理などには対応できません
- 文字の配置については、文字数が多い場合の自動改行などには対応していません
- PowerPointの仕様上textタグに固有のidを付与できないので、id指定した装飾ができません
- 印刷の縦横幅はできるだけPowerPointの表示に近づけるよう調整しましたが、完全には調整しきれておらず、周囲に若干の余白が含まれます
- 印刷用ボタンを使わず、ブラウザの印刷メニューやCTRL+Pのショートカットから直接印刷操作をすると、Pleasanter標準の画面のまま印刷されてしまいます