PHPを使った請求書PDF生成システムの実装

ここでは、PHPを使用して請求書PDFを生成するシステムの実装方法について解説します。

本システムではPHP・JavaScript・データベースを連携させることで、請求データを管理画面からPDF出力する構成としています。

請求書の登録・管理機能を含むシステム全体につきましては、請求書管理システムをご確認下さい。

ファイル構成

ここでは、請求書PDF生成システムの基本的なファイル構成について説明します。

PDF出力処理は管理画面から呼び出され、データベースの請求情報を取得してPDFを生成する構成としています。

一覧画面から請求書ボタンでPDFがダウンロードされる

admin/kanri/shorui/seisan/
├─ index.php            ← 一覧・管理画面
└─ pdf.php              ← PDF生成・出力処理

tcpdf/
└─ tcpdf.php            ← PDFライブラリ

db/
└─ db.php               ← データベース接続

PDF生成処理

実際にPHPで作成した請求書のPDFは下記のようなイメージとなります。

実際にPHPから出力したPDFイメージ

請求書PDFは、TCPDFライブラリを使用してHTMLレイアウトから生成する構成としています。

① PDF生成処理(pdf.php)

TCPDFはHTMLをそのままPDF化できる便利なライブラリですが、通常のWebブラウザと異なり、CSSが完全には反映されない場合があります。

実際の開発ではclass指定よりもtableレイアウト+inline styleを中心に構築する方が安定します。

PDF生成処理の流れ
  • TCPDFライブラリの読み込みとPDF初期設定
  • URLパラメータ(hid_id)から対象データIDを取得
  • 請求データ・会社情報をデータベースから取得
  • 明細情報を取得しループでHTML行を生成
  • 税区分(税別・税込)に応じた金額計算処理
  • 明細行数が少ない場合の空行補完によるレイアウト調整
  • HTMLテンプレートを組み立てて帳票レイアウトを構築
  • TCPDFのwriteHTMLでPDF描画
  • ファイル名を自動生成(文字コード変換対応)
  • ダウンロード形式でPDFを出力
<?php

require_once '../../../../tcpdf/tcpdf.php';
$pdf = new TCPDF("P", "mm", "A4", true, "UTF-8" );
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->AddPage();
$pdf->SetFont('kozminproregular', '', 12);
$pdf->SetMargins(20, 10, true);

if(isset($_GET['hid_id'])){
    $st_id = $_GET['hid_id'];
}elseif(isset($_POST['hid_id'])){
    $st_id = $_POST['hid_id'];
}


//css
$css = '<style>
      table {
          text-align: left;
          width: 100%;
        font-size:10px;
      }
    th {
        vertical-align: middle;
        background-color: rgb(153, 153, 153);
        text-align: center;"
        padding: 3px 3px;
        }
    th.num {
        vertical-align: middle;
        background-color: rgb(153, 153, 153);
        text-align: right;"
    }
    td {
        vertical-align: middle;
        text-align: center;"
        padding: 3px 3px;
    }
    td.num {
        vertical-align: middle;
          text-align: right;
    }
    td.migi {
        vertical-align: middle;
          text-align: right;
    }
    table.noline {
      border:none;
    }
    table.noline th {
      border:none;
      width:60%;
      background-color:#fff;
      text-align:left;
    }
    table.noline td {
      border:none;
      width:40%;
      background-color:#fff;
      text-align:left;
      font-size:10px;
    }
        table.noline2 {
      border:none;
    }
    table.noline2 th {
      border:none;
      width:60%;
      background-color:#fff;
      text-align:left;
    }
    table.noline2 td {
      border:none;
      background-color:#fff;
      text-align:left;
      font-size:10px;
    }

    .strleft {
      float:left;
      text-align:left;
    }
    .strright {
      float:right;
      text-align:right;
    }
    .relative {
        position: relative;
    }
    .absolute_test {
        position:  relative;
        text-align:right;
    }
    .absolute_test img{
        width:20px;
        height:20px;
        float:right;
        text-align:right;
    }
   </style>';

require "../../../db/db.php";
$sum = 0;


$sql = "SELECT 
            seisan.goukei, seisan.zeigoukei, seisan.s_kenmei, seisan.s_no,
            seisan.s_hakko, seisan.s_company, seisan.s_yukou, seisan.s_siharai,
            seisan.s_nohin, seisan.zeiritu, seisan.zeikubun 
        FROM seisan 
        WHERE seisan.id = :id";

$params = [':id' => $st_id];
$result = execPrep($sql, $params);
foreach( $result as $row ) {
    $s_no=$row["s_no"];
    $s_hakko=$row["s_hakko"];
    $s_company=$row["s_company"];
    $s_yukou=$row["s_yukou"];
    $s_siharai=$row["s_siharai"];
    $s_nohin=$row["s_nohin"];
    $s_kenmei=$row["s_kenmei"];
    $zeiritu=$row["zeiritu"];
    $zeikubun=$row["zeikubun"];
    $total=$row["goukei"];
    $totalzei=$row["zeigoukei"];
}

$sql = "SELECT * FROM companyinfo;";
$result = execPrep($sql);
foreach( $result as $row ) {
    $h_company=$row["h_company"];
    $h_simei=$row["h_simei"];
    $h_busho=$row["h_busho"];
    $h_yubin=$row["h_yubin"];
    $h_jyusho=$row["h_jyusho"];
    $h_tel=$row["h_tel"];
    $h_fax=$row["h_fax"];
    $h_mail=$row["h_mail"];
    $h_hurikomi=$row["h_hurikomi"];
    $h_biko=$row["h_biko"];
}

$hinmokucnt=0;
$goukei=0;
$html2='';

$sql = "SELECT name, cnt, tanka, kingaku FROM hinmoku WHERE s_no = :s_no";
$params = [':s_no' => $s_no];
$result = execPrep($sql, $params);

foreach( $result as $row ) {
    $hinmokucnt=$hinmokucnt+1;
    $goukei=$goukei+$row["kingaku"];
    $html2.='<tr>';
    $html2.='<td class="num">'.$hinmokucnt.'</td>';
    $html2.='<td class="strleft">'.$row["name"].'</td>';
    $html2.='<td>'.$row["cnt"].'</td>';
    $html2.='<td class="num">'.number_format($row["tanka"]).'</td>';
    $html2.='<td class="num">'.number_format($row["kingaku"]).'</td>';
    $html2.='</tr>';
}

if($zeikubun == '税込み'){
    $zeigaku = 0;
    $kei=$total;
    $kei=number_format($kei);
}else{
    $zeigaku = $totalzei-$total;
    $kei=$totalzei;
    $kei=number_format($kei);
}

$html = '';

$html.='<div style="text-align: center;font-size: 22pt;font-weight: bold;">&nbsp;&nbsp;&nbsp;&nbsp;御請求書&nbsp;&nbsp;&nbsp;&nbsp;</div>';
$html.= '<br><br>';

$html.= '<table class="noline">';
$html.= '<tr>';
$html.= '<th style="font-size:13px;font-weight:bold;text-decoration: underline;">'.$s_company.' 御中</th>';
$html.= '<td>請求書番号. '.$s_no.'</td>';
$html.= '</tr>';
$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td>登録番号. Txxxxxxxxxxxxx</td>';
$html.= '</tr>';
$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td>発行日. '.date('Y年m月d日',  strtotime($s_hakko)).'</td>';
$html.= '</tr>';

$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td><img src="/images/samplelogo.png" width="300" height="90"></td>';
$html.= '</tr>';

$html.= '</table>';


$html.= '<table class="noline">';
$html.= '<tr>';
$html.= '<th style="font-size:13px;font-weight:bold;text-decoration: underline;">件名:'.$s_kenmei.'</th>';
$html.= '<td>'.$h_company.'</td>';
$html.= '</tr>';

$html.= '<tr>';
$html.= '<th>下記の通り御請求申し上げます。</th>';
$html.= '<td>〒'.$h_yubin.'</td>';
$html.= '</tr>';

$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td>'.$h_jyusho.'</td>';
$html.= '</tr>';

$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td></td>';
$html.= '</tr>';

$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td>TEL.'.$h_tel.'</td>';
$html.= '</tr>';

$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td class="strright">FAX.'.$h_fax.'</td>';
$html.= '</tr>';

$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td>MAIL.'.$h_mail.'</td>';
$html.= '</tr>';

$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td>担当者.'.$h_simei.'</td>';
$html.= '</tr>';

if($h_busho <> ''){
    $html.= '<tr>';
    $html.= '<th></th>';
    $html.= '<td>部署.'.$h_busho.'</td>';
    $html.= '</tr>';
}

$html.= '<tr>';
$html.= '<th></th>';
$html.= '<td></td>';
$html.= '</tr>';

$html.= '<tr>';
$html.= '<th style="font-size:13px;font-weight:bold;text-decoration: underline;">合計金額 '.$kei.' (税込)</th>';
$html.= '<td style="font-size:11px;font-weight:bold;text-decoration: underline;">支払期限.'.date('Y年m月d日',  strtotime($s_yukou)).'</td>';
$html.= '</tr>';

$html.= '</table>';
$html.= '<br>';


$html.='<div style="text-align: center;">';
$html.='<table border="1" cellspacing="0">';
$html.='<tbody>';
$html.='<tr>';
$html.='<th style="width:30px;">No</th>';
$html.='<th style="width:200px;">摘要</th>';
$html.='<th style="width:60px;">数量</th>';
$html.='<th>単価</th>';
$html.='<th>金額</th>';
$html.='</tr>';

$html.=$html2;

if($hinmokucnt < 6){
    for($i = 0; $i < 5; $i++){
        $html.='<tr>';
        $html.='<td class="num">&nbsp;</td>';
        $html.='<td>&nbsp;</td>';
        $html.='<td>&nbsp;</td>';
        $html.='<td class="num">&nbsp;</td>';
        $html.='<td class="num">&nbsp;</td>';
        $html.='</tr>';
    }
}else{
    for($i = 0; $i < 1; $i++){
        $html.='<tr>';
        $html.='<td class="num">&nbsp;</td>';
        $html.='<td>&nbsp;</td>';
        $html.='<td>&nbsp;</td>';
        $html.='<td class="num">&nbsp;</td>';
        $html.='<td class="num">&nbsp;</td>';
        $html.='</tr>';
    }
}

$html.='<tr>';
$html.='<td class="migi" colspan="4">消費税</td>';
$html.='<td class="num">'.number_format($zeigaku).'</td>';
$html.='</tr>';

$html.='<tr>';
$html.='<td class="migi" colspan="4">合&nbsp;&nbsp;計</td>';
$html.='<td class="num">'.$kei.'</td>';
$html.='</tr>';

$html.='</tbody></table>';
$html.='</div>';


$html.='<table border="1">';
$html.='<tbody>';
$html.= '<tr>';
$html.= '<th style="text-align: left;">振込先</th>';
$html.= '</tr>';
$html.= '<tr>';
$html.= '<td style="text-align: left;">'.nl2br($h_hurikomi).'</td>';
$html.= '</tr>';
$html.='</tbody>';
$html.= '</table>';
$html.= '<br><br>';

$html.='<table border="1">';
$html.='<tbody>';
$html.= '<tr>';
$html.= '<th style="text-align: left;">備考</th>';
$html.= '</tr>';
$html.= '<tr>';
$html.= '<td style="text-align: left;">'.nl2br($h_biko).'</td>';
$html.= '</tr>';
$html.='</tbody>';
$html.= '</table>';


//output
$pdf->writeHTML($css . $html, true, 0, true, 0);


// PDFファイル名を生成
$filename = $s_no."請求書.pdf";
$filename = mb_convert_encoding( $filename, 'SJIS-WIN', 'UTF-8' );

// PDF出力
$pdf_output = $pdf->Output( $filename, "S" );

header( "Pragma: public" );
header( "Expires: 0 ");
header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
header( "Content-Transfer-Encoding: binary" );
header( "Content-Type: application/octet-streams" );
header( "Content-Disposition: attachment; filename=\"{$filename}\"" );
print $pdf_output;
exit;

?>

まとめ

請求書や帳票の作成業務では、Excelや手作業でPDFを作成しているケースも多く、作業時間や人的ミスが発生しやすい課題があります。

今回解説したように、PHPとTCPDFを組み合わせることで、データベースの情報をもとに帳票を自動生成し、管理画面からワンクリックでPDF出力できる仕組みを構築することが可能になります。

HTMLベースでレイアウトを設計できるため、請求書・見積書・納品書など様々な帳票フォーマットに応用できる点も大きなメリットです。

今回解説した実装ポイント
  • TCPDFライブラリを利用したPDF生成処理
  • 管理画面から対象IDを取得して帳票出力
  • データベース連携による帳票自動生成
  • HTMLレイアウトによる柔軟なデザイン制御
  • 明細行数に応じたレイアウト調整処理
  • ブラウザから直接ダウンロード可能なPDF出力

LOGZAWEBでは、業務内容に合わせた帳票出力機能やPDF生成システムの開発にも対応しております。

既存システムでは対応できない帳票フォーマットや業務フローをご希望の場合も、お気軽にご相談ください。

関連ページのご案内

ホームページ制作のご依頼・ご相談

お電話でのお問い合わせ
平日 09:00-18:00
メールでのお問い合わせ