跳轉到

案例研究:金融報表自動化

摘要:某台灣上市金融機構的財務部門每季耗費 3 個工作日手工製作 PDF 季報,無法附加法律效力的數位簽章,且報告品質因人工操作而參差不齊。導入 NextPDF Pro 後,季報生成時間縮短至 18 分鐘,所有輸出均附 PAdES B-LTA 簽章,並通過主管機關電子申報系統的格式驗證。


挑戰

現況痛點

一家擁有 200 名員工的台灣上市保險公司(化名「台灣安心保險」),其財務部門每季面臨以下挑戰:

  • 手工製作耗時:財務分析師使用 Microsoft Office 與 Adobe Acrobat 手工製作季報,整合來自 5 個不同系統的數據,每季耗費 3 個工作日
  • 缺乏法律效力簽章:向主管機關(金管會)提交的 PDF 僅有掃描簽名圖片,不符合電子申報規範中對數位簽章的要求
  • 版本混亂:多人協作時,無法追蹤哪一版本是最終定稿,曾發生提交舊版本的錯誤
  • 格式不一致:不同人製作的章節排版、字體選擇、圖表樣式不統一,不符合上市公司資訊揭露品質要求
  • 無自動化圖表:每季需手動更新 12 個 Excel 圖表並截圖貼入 PDF

技術約束

  • 既有系統以 PHP 8.2 + Laravel 9 構建(需升級至 PHP 8.5)
  • 財務數據存於 Oracle 資料庫,需即時查詢
  • 簽章金鑰必須使用公司現有的 AWS KMS(已通過 ISO 27001 稽核)
  • 最終 PDF 必須符合金管會電子申報格式(PDF/A-3b)

解決方案

graph TD
    Oracle[(Oracle DB\n財務數據)] --> Query["Laravel\nQuery Service"]
    Query --> DataDTO["FinancialReportDTO\n強型別資料物件"]
    DataDTO --> Engine["StreamingLayoutEngine\n串流排版"]
    Engine --> Charts["ChartEngine\n12 個向量圖表"]
    Engine --> TOC["自動目錄\n(兩遍渲染)"]
    Engine --> PDF["原始 PDF"]
    PDF --> Optimizer["PdfOptimizer\nMedium 等級"]
    Optimizer --> PdfA["PDF/A-3b 轉換器"]
    PdfA --> Signer["PadesSignatureAppender\nB-LTA via AWS KMS"]
    Signer --> Final["最終簽署 PDF"]
    Final --> Archive["文件歸檔系統"]
    Final --> Regulator["金管會\n電子申報"]

核心實作

use NextPDF\Pro\Document\ProDocument;
use NextPDF\Pro\FlowLayout\StreamingLayoutEngine;
use NextPDF\Pro\FlowLayout\LayoutOptions;
use NextPDF\Pro\FlowLayout\Content\{Heading, Paragraph, FlowTable, ChartContent, PageBreak};
use NextPDF\Pro\Charts\{BarChart, LineChart, PieChart, ChartDataset, ChartOptions};
use NextPDF\Pro\Optimizer\{PdfOptimizer, OptimizationLevel};
use NextPDF\Pro\Compliance\PdfA\PdfAConverter;
use NextPDF\Pro\Compliance\PdfA\PdfALevel;
use NextPDF\Pro\Signatures\PAdES\{PadesSignatureAppender, PadesSignatureLevel, PadesSignatureOptions};
use NextPDF\Pro\Signatures\Hsm\Driver\AwsKmsSigningDriver;
use NextPDF\Pro\Signatures\Timestamp\TimestampAuthorityClient;

final class FinancialReportGenerator
{
    public function __construct(
        private readonly FinancialDataService $dataService,
        private readonly AwsKmsSigningDriver $kmsDriver,
        private readonly TimestampAuthorityClient $tsa,
    ) {}

    public function generate(int $year, int $quarter): string
    {
        // 1. 取得強型別財務數據
        $data = $this->dataService->getQuarterlyReport($year, $quarter);

        // 2. 建立串流排版引擎
        $engine = new StreamingLayoutEngine(
            LayoutOptions::create(
                pageWidth: 595.28,
                pageHeight: 841.89,
                marginTop: 80.0,
                marginBottom: 80.0,
                marginLeft: 72.0,
                marginRight: 72.0,
            )
        );

        // 設定文件語言與詮釋資料(PDF/A 必要)
        $engine->setLanguage('zh-TW');
        $engine->setTitle(sprintf('%d 年第 %d 季季報', $year, $quarter));
        $engine->setAuthor('台灣安心保險股份有限公司');

        // 3. 加入自動目錄佔位符
        $toc = $engine->addTableOfContents(title: '目錄', maxLevel: 2);

        // 4. 執行摘要
        $engine->add(Heading::create('執行摘要', level: 1));
        $engine->add(Paragraph::create($data->getExecutiveSummary()));

        // 5. 財務摘要表
        $engine->add(Heading::create('財務摘要', level: 2));
        $engine->add(
            FlowTable::create(headers: ['項目', '本季', '上季', '同比增減'])
                ->addRow(['總保費收入', $data->formatCurrency($data->premiumRevenue), '...', $data->formatGrowth($data->premiumGrowth)])
                ->addRow(['理賠支出', $data->formatCurrency($data->claimsExpense), '...', $data->formatGrowth($data->claimsGrowth)])
                ->addRow(['淨利潤', $data->formatCurrency($data->netProfit), '...', $data->formatGrowth($data->profitGrowth)])
                ->withRepeatHeaderOnNewPage(true)
        );

        // 6. 向量圖表
        $engine->add(Heading::create('財務趨勢分析', level: 2));

        $revenueChart = BarChart::create(
            ChartOptions::create(title: sprintf('Q1–Q4 %d 年保費收入', $year), width: 450.0, height: 220.0)
        )
        ->setLabels($data->getQuarterLabels())
        ->addDataset(
            ChartDataset::create('保費收入(百萬元)')
                ->data($data->getQuarterlyRevenue())
                ->color('#1E3A8A')
        );

        $engine->add(ChartContent::create($revenueChart)->caption('圖 1:各季保費收入'));

        $engine->add(PageBreak::create());

        // 7. 投資組合分析(圓餅圖)
        $portfolioChart = PieChart::create(
            ChartOptions::create(title: '投資組合分布', width: 280.0, height: 280.0)
        )
        ->setMode('donut');

        foreach ($data->getPortfolioAllocation() as $category => $percentage) {
            $portfolioChart->addSlice(/* ... */);
        }

        $engine->add(ChartContent::create($portfolioChart)->caption('圖 2:投資組合分布'));

        // 8. 渲染 PDF
        $rawPdf = $engine->render();

        // 9. 最佳化(簽章前執行)
        $optimizer = new PdfOptimizer(level: OptimizationLevel::Medium);
        $optimizedPdf = $optimizer->optimize($rawPdf);

        // 10. PDF/A-3b 轉換(金管會申報要求)
        $converter = new PdfAConverter(level: PdfALevel::A3b);
        $pdfABytes = $converter->convert($optimizedPdf);

        // 11. PAdES B-LTA 簽章(使用 AWS KMS)
        $hsmContext = HsmSigningContext::fromDriver(
            driver: $this->kmsDriver,
            publicCertificatePem: file_get_contents('/certs/financial-signing.pem'),
        );

        $options = PadesSignatureOptions::fromHsmContext(
            hsmContext: $hsmContext,
            level: PadesSignatureLevel::BLta,
            timestampAuthority: $this->tsa,
            reason: sprintf('CFO authorized — %dQ%d Financial Report', $year, $quarter),
            location: 'Taipei, Taiwan',
        );

        $appender = new PadesSignatureAppender($options);
        return $appender->sign($pdfABytes);
    }
}

成果

指標 導入前 導入後 改善幅度
季報生成時間 3 個工作日(約 24 人時) 18 分鐘(全自動) 98.7%
人工作業時數 24 人時/季 0 人時(監控 5 分鐘) 99.7%
數位簽章合規 無(掃描簽名) PAdES B-LTA 達標
PDF/A 合規 PDF/A-3b 達標
格式一致性錯誤 平均 8 處/季報 0 100%
主管機關退件次數 2.3 次/年 0 次 100%
PDF 檔案大小 ~18 MB(含截圖) ~4.2 MB(向量圖表) 縮減 77%

量化效益

  • 人力節省:每季節省 24 人時,年化節省約 96 人時,按財務分析師時薪計算,年省約 NT$192,000
  • 合規風險消除:消除因不符合金管會數位簽章規定可能引發的罰款風險
  • 資料準確性:直接從 Oracle 資料庫取數,消除手動抄錄錯誤

技術亮點

強型別資料物件

final readonly class FinancialReportDTO
{
    public function __construct(
        public readonly int $year,
        public readonly int $quarter,
        /** @var positive-int */
        public readonly int $premiumRevenue,
        /** @var float */
        public readonly float $premiumGrowth,
        /** @var list<float> */
        public readonly array $quarterlyRevenue,
        /** @var array<string, float> */
        public readonly array $portfolioAllocation,
        public readonly string $executiveSummary,
    ) {}

    public function formatCurrency(int $amount): string
    {
        return 'NT$' . number_format($amount / 1_000_000, 2) . 'M';
    }
}

自動化排程(Laravel Scheduler)

// App\Console\Kernel.php
$schedule->call(fn() => app(FinancialReportGenerator::class)
    ->generate(now()->year, now()->quarter))
    ->quarterly()
    ->at('02:00')
    ->emailOutputOnFailure('cfo@insurance.example.com');

相關資源

Commercial License

This feature requires a commercial license. Contact our team for pricing and deployment support.

Contact Sales