2026-03-26 16:40:49 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* CSV 엑셀 내보내기 헬퍼
|
|
|
|
|
*
|
|
|
|
|
* UTF-8 BOM 포함으로 한글 엑셀 호환성 보장
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (! function_exists('export_csv')) {
|
|
|
|
|
/**
|
|
|
|
|
* CSV 파일을 브라우저로 다운로드 전송
|
|
|
|
|
*
|
|
|
|
|
* @param string $filename 파일명 (확장자 포함, 예: 'export.csv')
|
|
|
|
|
* @param string[] $headers 컬럼 헤더 배열
|
|
|
|
|
* @param array $rows 데이터 행 배열 (각 행은 배열)
|
|
|
|
|
*/
|
|
|
|
|
function export_csv(string $filename, array $headers, array $rows): void
|
|
|
|
|
{
|
|
|
|
|
// 파일명에 .csv 확장자 보장
|
|
|
|
|
if (! str_ends_with($filename, '.csv')) {
|
|
|
|
|
$filename .= '.csv';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$response = service('response');
|
|
|
|
|
$response->setHeader('Content-Type', 'text/csv; charset=UTF-8');
|
|
|
|
|
$response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
|
|
|
|
|
$response->setHeader('Pragma', 'no-cache');
|
|
|
|
|
$response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
|
|
|
|
|
|
|
|
// UTF-8 BOM (한글 엑셀 호환)
|
|
|
|
|
$output = "\xEF\xBB\xBF";
|
|
|
|
|
|
|
|
|
|
// 헤더 행
|
|
|
|
|
$output .= csv_encode_row($headers);
|
|
|
|
|
|
|
|
|
|
// 데이터 행
|
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
|
$output .= csv_encode_row(array_values((array) $row));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$response->setBody($output);
|
|
|
|
|
$response->send();
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! function_exists('csv_encode_row')) {
|
|
|
|
|
/**
|
|
|
|
|
* 배열 한 행을 CSV 문자열로 변환
|
|
|
|
|
*
|
|
|
|
|
* @param array $fields
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
function csv_encode_row(array $fields): string
|
|
|
|
|
{
|
|
|
|
|
$escaped = [];
|
|
|
|
|
foreach ($fields as $field) {
|
|
|
|
|
$val = (string) ($field ?? '');
|
|
|
|
|
// 쌍따옴표 이스케이프 및 감싸기
|
|
|
|
|
if (str_contains($val, '"') || str_contains($val, ',') || str_contains($val, "\n") || str_contains($val, "\r")) {
|
|
|
|
|
$val = '"' . str_replace('"', '""', $val) . '"';
|
|
|
|
|
}
|
|
|
|
|
$escaped[] = $val;
|
|
|
|
|
}
|
|
|
|
|
return implode(',', $escaped) . "\r\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-01 16:15:15 +09:00
|
|
|
|
|
|
|
|
if (! function_exists('export_excel_2003_xml')) {
|
|
|
|
|
/**
|
|
|
|
|
* Excel 2003 XML(SpreadsheetML)로 브라우저 다운로드 (.xls 확장자, 별도 라이브러리 불필요)
|
|
|
|
|
*
|
|
|
|
|
* @param string $filename 저장 파일명(확장자는 .xls로 정규화)
|
|
|
|
|
* @param string $sheetName 시트 이름(Excel 제한: 길이·일부 문자)
|
|
|
|
|
* @param string[] $headers 컬럼 헤더
|
|
|
|
|
* @param array $rows 데이터 행(각 행은 배열, 값은 문자열로 출력)
|
|
|
|
|
*/
|
|
|
|
|
function export_excel_2003_xml(string $filename, string $sheetName, array $headers, array $rows): void
|
|
|
|
|
{
|
|
|
|
|
$filename = preg_replace('/\.[^.]+$/u', '', $filename) . '.xls';
|
|
|
|
|
|
|
|
|
|
$safeSheet = str_replace(['/', '\\', '?', '*', '[', ']'], '', $sheetName);
|
|
|
|
|
$safeSheet = function_exists('mb_substr')
|
|
|
|
|
? mb_substr($safeSheet, 0, 31, 'UTF-8')
|
|
|
|
|
: substr($safeSheet, 0, 31);
|
|
|
|
|
|
|
|
|
|
$esc = static function (mixed $v): string {
|
|
|
|
|
return htmlspecialchars((string) ($v ?? ''), ENT_XML1 | ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$parts = [];
|
|
|
|
|
$parts[] = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
|
|
|
$parts[] = '<?mso-application progid="Excel.Sheet"?>';
|
|
|
|
|
$parts[] = '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">';
|
|
|
|
|
$parts[] = '<Worksheet ss:Name="' . $esc($safeSheet) . '">';
|
|
|
|
|
$parts[] = '<Table>';
|
|
|
|
|
|
|
|
|
|
$parts[] = '<Row>';
|
|
|
|
|
foreach ($headers as $h) {
|
|
|
|
|
$parts[] = '<Cell><Data ss:Type="String">' . $esc($h) . '</Data></Cell>';
|
|
|
|
|
}
|
|
|
|
|
$parts[] = '</Row>';
|
|
|
|
|
|
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
|
$parts[] = '<Row>';
|
|
|
|
|
foreach (array_values((array) $row) as $cell) {
|
|
|
|
|
$parts[] = '<Cell><Data ss:Type="String">' . $esc($cell) . '</Data></Cell>';
|
|
|
|
|
}
|
|
|
|
|
$parts[] = '</Row>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$parts[] = '</Table>';
|
|
|
|
|
$parts[] = '</Worksheet>';
|
|
|
|
|
$parts[] = '</Workbook>';
|
|
|
|
|
|
|
|
|
|
$output = implode('', $parts);
|
|
|
|
|
|
|
|
|
|
$response = service('response');
|
|
|
|
|
$response->setHeader('Content-Type', 'application/vnd.ms-excel; charset=UTF-8');
|
|
|
|
|
$response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
|
|
|
|
|
$response->setHeader('Pragma', 'no-cache');
|
|
|
|
|
$response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
|
|
|
$response->setBody($output);
|
|
|
|
|
$response->send();
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! function_exists('export_excel_2003_xml_workbook')) {
|
|
|
|
|
/**
|
|
|
|
|
* Excel 2003 XML — 다중 시트, 인쇄 미리보기와 유사한 헤더·줄바꿈·열 너비
|
|
|
|
|
*
|
|
|
|
|
* @param string $filename 저장 파일명
|
|
|
|
|
* @param list<array{name: string, headers: list<string>, rows: list<list<string>>, col_widths?: list<int>}> $sheets
|
|
|
|
|
*/
|
|
|
|
|
function export_excel_2003_xml_workbook(string $filename, array $sheets): void
|
|
|
|
|
{
|
|
|
|
|
$filename = preg_replace('/\.[^.]+$/u', '', $filename) . '.xls';
|
|
|
|
|
|
|
|
|
|
$esc = static function (mixed $v): string {
|
|
|
|
|
return htmlspecialchars((string) ($v ?? ''), ENT_XML1 | ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$safeSheetName = static function (string $name) use ($esc): string {
|
|
|
|
|
$safe = str_replace(['/', '\\', '?', '*', '[', ']', ':'], '', $name);
|
|
|
|
|
$safe = function_exists('mb_substr') ? mb_substr($safe, 0, 31, 'UTF-8') : substr($safe, 0, 31);
|
|
|
|
|
|
|
|
|
|
return $esc($safe !== '' ? $safe : 'Sheet');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$parts = [];
|
|
|
|
|
$parts[] = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
|
|
|
$parts[] = '<?mso-application progid="Excel.Sheet"?>';
|
|
|
|
|
$parts[] = '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">';
|
|
|
|
|
$parts[] = '<Styles>';
|
|
|
|
|
$parts[] = '<Style ss:ID="Default"><Alignment ss:Vertical="Top" ss:WrapText="1" ss:Horizontal="Left"/><Font ss:FontName="맑은 고딕" x:CharSet="129" ss:Size="9"/></Style>';
|
|
|
|
|
$parts[] = '<Style ss:ID="Header"><Font ss:Bold="1" ss:Size="9" ss:FontName="맑은 고딕" x:CharSet="129"/><Interior ss:Color="#F3F4F6" ss:Pattern="Solid"/><Alignment ss:Horizontal="Left" ss:Vertical="Center" ss:WrapText="1"/><Borders><Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/></Borders></Style>';
|
|
|
|
|
$parts[] = '</Styles>';
|
|
|
|
|
|
|
|
|
|
foreach ($sheets as $sheet) {
|
|
|
|
|
$sheetName = $safeSheetName((string) ($sheet['name'] ?? 'Sheet'));
|
|
|
|
|
$headers = array_values((array) ($sheet['headers'] ?? []));
|
|
|
|
|
$rows = (array) ($sheet['rows'] ?? []);
|
|
|
|
|
$colWidths = array_values((array) ($sheet['col_widths'] ?? []));
|
|
|
|
|
|
|
|
|
|
$parts[] = '<Worksheet ss:Name="' . $sheetName . '">';
|
|
|
|
|
$parts[] = '<WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"><PageSetup><Layout x:Orientation="Landscape"/></PageSetup></WorksheetOptions>';
|
|
|
|
|
$parts[] = '<Table>';
|
|
|
|
|
|
|
|
|
|
$colCount = max(count($headers), 1);
|
|
|
|
|
for ($i = 0; $i < $colCount; $i++) {
|
|
|
|
|
$px = (int) ($colWidths[$i] ?? 72);
|
|
|
|
|
$width = max(48, min(280, $px));
|
|
|
|
|
$excelW = round($width / 6.5, 1);
|
|
|
|
|
$parts[] = '<Column ss:Index="' . ($i + 1) . '" ss:AutoFitWidth="0" ss:Width="' . $excelW . '"/>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$parts[] = '<Row ss:StyleID="Header">';
|
|
|
|
|
foreach ($headers as $h) {
|
|
|
|
|
$parts[] = '<Cell ss:StyleID="Header"><Data ss:Type="String">' . $esc($h) . '</Data></Cell>';
|
|
|
|
|
}
|
|
|
|
|
$parts[] = '</Row>';
|
|
|
|
|
|
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
|
$parts[] = '<Row>';
|
|
|
|
|
foreach (array_values((array) $row) as $cell) {
|
|
|
|
|
$parts[] = '<Cell ss:StyleID="Default"><Data ss:Type="String">' . $esc($cell) . '</Data></Cell>';
|
|
|
|
|
}
|
|
|
|
|
$parts[] = '</Row>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$parts[] = '</Table>';
|
|
|
|
|
$parts[] = '</Worksheet>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$parts[] = '</Workbook>';
|
|
|
|
|
|
|
|
|
|
$output = implode('', $parts);
|
|
|
|
|
|
|
|
|
|
$response = service('response');
|
|
|
|
|
$response->setHeader('Content-Type', 'application/vnd.ms-excel; charset=UTF-8');
|
|
|
|
|
$response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
|
|
|
|
|
$response->setHeader('Pragma', 'no-cache');
|
|
|
|
|
$response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
|
|
|
$response->setBody($output);
|
|
|
|
|
$response->send();
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! function_exists('bag_flow_report_build_spreadsheet')) {
|
|
|
|
|
/**
|
|
|
|
|
* 기간별 봉투 수불 엑셀 통합문서 생성 (PhpSpreadsheet — 열 너비·병합 안정)
|
|
|
|
|
*
|
|
|
|
|
* @param list<array<string, mixed>> $reportRows
|
|
|
|
|
* @param list<string> $metaLines
|
|
|
|
|
*/
|
|
|
|
|
function bag_flow_report_build_spreadsheet(
|
|
|
|
|
string $lgName,
|
|
|
|
|
string $title,
|
|
|
|
|
array $metaLines,
|
|
|
|
|
array $reportRows
|
|
|
|
|
): \PhpOffice\PhpSpreadsheet\Spreadsheet {
|
|
|
|
|
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
|
|
|
|
|
$spreadsheet->getDefaultStyle()->getFont()->setName('맑은 고딕')->setSize(10);
|
|
|
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
|
|
|
$sheet->setTitle('수불현황');
|
|
|
|
|
$bodyFontSize = 10;
|
|
|
|
|
|
|
|
|
|
$lastCol = 'N';
|
|
|
|
|
$colWidths = [
|
|
|
|
|
'A' => 22.0,
|
|
|
|
|
'B' => 26.0,
|
|
|
|
|
'C' => 12.0,
|
|
|
|
|
'D' => 11.0,
|
|
|
|
|
'E' => 11.0,
|
|
|
|
|
'F' => 11.0,
|
|
|
|
|
'G' => 12.0,
|
|
|
|
|
'H' => 11.0,
|
|
|
|
|
'I' => 12.0,
|
|
|
|
|
'J' => 12.0,
|
|
|
|
|
'K' => 12.0,
|
|
|
|
|
'L' => 11.0,
|
|
|
|
|
'M' => 12.0,
|
|
|
|
|
'N' => 12.0,
|
|
|
|
|
];
|
|
|
|
|
foreach ($colWidths as $col => $width) {
|
|
|
|
|
$sheet->getColumnDimension($col)->setWidth($width);
|
|
|
|
|
$sheet->getColumnDimension($col)->setAutoSize(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$r = 1;
|
|
|
|
|
if ($lgName !== '') {
|
|
|
|
|
$sheet->setCellValue("A{$r}", $lgName);
|
|
|
|
|
$sheet->getStyle("A{$r}")->getFont()->setSize(10)->getColor()->setRGB('666666');
|
|
|
|
|
$r++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$sheet->setCellValue("A{$r}", $title);
|
|
|
|
|
$sheet->mergeCells("A{$r}:{$lastCol}{$r}");
|
|
|
|
|
$sheet->getStyle("A{$r}")->getFont()->setBold(true)->setSize($bodyFontSize);
|
|
|
|
|
$r++;
|
|
|
|
|
|
|
|
|
|
foreach ($metaLines as $line) {
|
|
|
|
|
$sheet->setCellValue("A{$r}", $line);
|
|
|
|
|
$sheet->mergeCells("A{$r}:{$lastCol}{$r}");
|
|
|
|
|
$sheet->getStyle("A{$r}")->getFont()->setSize(10)->getColor()->setRGB('555555');
|
|
|
|
|
$r++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$r++;
|
|
|
|
|
|
|
|
|
|
$h1 = $r;
|
|
|
|
|
$h2 = $r + 1;
|
|
|
|
|
|
|
|
|
|
$sheet->setCellValue("A{$h1}", '일자');
|
|
|
|
|
$sheet->mergeCells("A{$h1}:A{$h2}");
|
|
|
|
|
$sheet->setCellValue("B{$h1}", '품목');
|
|
|
|
|
$sheet->mergeCells("B{$h1}:B{$h2}");
|
|
|
|
|
$sheet->setCellValue("C{$h1}", '전일');
|
|
|
|
|
$sheet->mergeCells("C{$h1}:C{$h2}");
|
|
|
|
|
$sheet->setCellValue("D{$h1}", '입고');
|
|
|
|
|
$sheet->mergeCells("D{$h1}:G{$h1}");
|
|
|
|
|
$sheet->setCellValue("H{$h1}", '출고');
|
|
|
|
|
$sheet->mergeCells("H{$h1}:M{$h1}");
|
|
|
|
|
$sheet->setCellValue("N{$h1}", '잔량');
|
|
|
|
|
$sheet->mergeCells("N{$h1}:N{$h2}");
|
|
|
|
|
|
|
|
|
|
$subHeaders = ['입고', '반품', '기타', '입계', '판매', '일반', '무료', '반품', '기타', '출계'];
|
|
|
|
|
foreach ($subHeaders as $i => $label) {
|
|
|
|
|
$col = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex(4 + $i);
|
|
|
|
|
$sheet->setCellValue("{$col}{$h2}", $label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$headerStyle = [
|
|
|
|
|
'font' => ['bold' => true, 'size' => $bodyFontSize],
|
|
|
|
|
'alignment' => [
|
|
|
|
|
'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
|
|
|
|
|
'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER,
|
|
|
|
|
'wrapText' => false,
|
|
|
|
|
],
|
|
|
|
|
'fill' => [
|
|
|
|
|
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
|
|
|
|
|
'startColor' => ['rgb' => 'E9ECEF'],
|
|
|
|
|
],
|
|
|
|
|
'borders' => [
|
|
|
|
|
'bottom' => ['borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN],
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
$sheet->getStyle("A{$h1}:{$lastCol}{$h2}")->applyFromArray($headerStyle);
|
|
|
|
|
|
|
|
|
|
$dataRow = $h2 + 1;
|
|
|
|
|
foreach ($reportRows as $row) {
|
|
|
|
|
$rowType = (string) ($row['row_type'] ?? 'data');
|
|
|
|
|
if (! in_array($rowType, ['data', 'subtotal', 'grand'], true)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$sheet->fromArray([
|
|
|
|
|
(string) ($row['date'] ?? ''),
|
|
|
|
|
(string) ($row['item_name'] ?? ''),
|
|
|
|
|
(int) ($row['prev_stock'] ?? 0),
|
|
|
|
|
(int) ($row['recv_in'] ?? 0),
|
|
|
|
|
(int) ($row['recv_return'] ?? 0),
|
|
|
|
|
(int) ($row['recv_misc'] ?? 0),
|
|
|
|
|
(int) ($row['recv_total'] ?? 0),
|
|
|
|
|
(int) ($row['out_sale'] ?? 0),
|
|
|
|
|
(int) ($row['out_issue_gen'] ?? 0),
|
|
|
|
|
(int) ($row['out_issue_free'] ?? 0),
|
|
|
|
|
(int) ($row['out_return'] ?? 0),
|
|
|
|
|
(int) ($row['out_misc'] ?? 0),
|
|
|
|
|
(int) ($row['out_total'] ?? 0),
|
|
|
|
|
(int) ($row['balance'] ?? 0),
|
|
|
|
|
], null, "A{$dataRow}", true);
|
|
|
|
|
|
|
|
|
|
$sheet->getStyle("C{$dataRow}:{$lastCol}{$dataRow}")
|
|
|
|
|
->getNumberFormat()
|
|
|
|
|
->setFormatCode('#,##0');
|
|
|
|
|
$sheet->getStyle("C{$dataRow}:{$lastCol}{$dataRow}")
|
|
|
|
|
->getAlignment()
|
|
|
|
|
->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_RIGHT);
|
|
|
|
|
$sheet->getStyle("A{$dataRow}:B{$dataRow}")
|
|
|
|
|
->getAlignment()
|
|
|
|
|
->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT)
|
|
|
|
|
->setWrapText(false);
|
|
|
|
|
$sheet->getStyle("A{$dataRow}:{$lastCol}{$dataRow}")
|
|
|
|
|
->getFont()
|
|
|
|
|
->setSize($bodyFontSize);
|
|
|
|
|
|
|
|
|
|
if (in_array($rowType, ['subtotal', 'grand'], true)) {
|
|
|
|
|
$sheet->getStyle("A{$dataRow}:{$lastCol}{$dataRow}")->applyFromArray([
|
|
|
|
|
'font' => ['bold' => true, 'size' => $bodyFontSize],
|
|
|
|
|
'fill' => [
|
|
|
|
|
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
|
|
|
|
|
'startColor' => ['rgb' => 'FFF8E1'],
|
|
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$dataRow++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($dataRow > $h2 + 1) {
|
|
|
|
|
$sheet->getStyle('A' . ($h2 + 1) . ':' . $lastCol . ($dataRow - 1))
|
|
|
|
|
->getBorders()
|
|
|
|
|
->getAllBorders()
|
|
|
|
|
->setBorderStyle(\PhpOffice\PhpSpreadsheet\Style\Border::BORDER_HAIR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$sheet->getPageSetup()->setOrientation(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_PORTRAIT);
|
|
|
|
|
$sheet->getPageSetup()->setPaperSize(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::PAPERSIZE_A4);
|
|
|
|
|
$sheet->getPageSetup()->setFitToWidth(1);
|
|
|
|
|
$sheet->getPageSetup()->setFitToHeight(0);
|
|
|
|
|
|
|
|
|
|
return $spreadsheet;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! function_exists('export_bag_flow_report_excel')) {
|
|
|
|
|
/**
|
|
|
|
|
* 기간별 봉투 수불 (/bag/flow) — 인쇄와 동일한 헤더·2단 표 (xlsx, PhpSpreadsheet)
|
|
|
|
|
*
|
|
|
|
|
* @param list<array<string, mixed>> $reportRows
|
|
|
|
|
* @param list<string> $metaLines
|
|
|
|
|
*/
|
|
|
|
|
function export_bag_flow_report_excel(
|
|
|
|
|
string $filename,
|
|
|
|
|
string $lgName,
|
|
|
|
|
string $title,
|
|
|
|
|
array $metaLines,
|
|
|
|
|
array $reportRows
|
|
|
|
|
): void {
|
|
|
|
|
$baseName = preg_replace('/\.[^.]+$/u', '', $filename);
|
|
|
|
|
$baseName = preg_replace('/[^\p{L}\p{N}_\-]+/u', '_', $baseName) ?? 'bag_flow';
|
|
|
|
|
$baseName = trim($baseName, '_') !== '' ? trim($baseName, '_') : 'bag_flow';
|
|
|
|
|
$filename = $baseName . '.xlsx';
|
|
|
|
|
|
|
|
|
|
$spreadsheet = bag_flow_report_build_spreadsheet($lgName, $title, $metaLines, $reportRows);
|
|
|
|
|
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
|
|
|
|
|
|
|
|
|
|
ob_start();
|
|
|
|
|
try {
|
|
|
|
|
$writer->save('php://output');
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
ob_end_clean();
|
|
|
|
|
$spreadsheet->disconnectWorksheets();
|
|
|
|
|
unset($spreadsheet);
|
|
|
|
|
throw $e;
|
|
|
|
|
}
|
|
|
|
|
$spreadsheet->disconnectWorksheets();
|
|
|
|
|
unset($spreadsheet);
|
|
|
|
|
$output = ob_get_clean();
|
|
|
|
|
if ($output === false) {
|
|
|
|
|
$output = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$response = service('response');
|
|
|
|
|
$response->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
|
|
|
$asciiName = preg_replace('/[^\x20-\x7E]+/', '_', $filename) ?? 'bag_flow.xlsx';
|
|
|
|
|
$response->setHeader(
|
|
|
|
|
'Content-Disposition',
|
|
|
|
|
'attachment; filename="' . $asciiName . '"; filename*=UTF-8\'\'' . rawurlencode($filename)
|
|
|
|
|
);
|
|
|
|
|
$response->setHeader('Pragma', 'no-cache');
|
|
|
|
|
$response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
|
|
|
$response->setBody($output);
|
|
|
|
|
$response->send();
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (! function_exists('export_xlsx')) {
|
|
|
|
|
/**
|
|
|
|
|
* Office Open XML(.xlsx) 브라우저 다운로드 (PhpSpreadsheet)
|
|
|
|
|
*
|
|
|
|
|
* @param string $filename 저장 파일명(확장자는 .xlsx로 정규화)
|
|
|
|
|
* @param string $sheetName 시트 이름(Excel 제한: 길이·일부 문자)
|
|
|
|
|
* @param string[] $headers 컬럼 헤더
|
|
|
|
|
* @param array $rows 데이터 행(각 행은 배열)
|
|
|
|
|
*/
|
|
|
|
|
function export_xlsx(string $filename, string $sheetName, array $headers, array $rows): void
|
|
|
|
|
{
|
|
|
|
|
$filename = preg_replace('/\.[^.]+$/u', '', $filename) . '.xlsx';
|
|
|
|
|
|
|
|
|
|
$safeSheet = str_replace(['/', '\\', '?', '*', '[', ']'], '', $sheetName);
|
|
|
|
|
$safeSheet = function_exists('mb_substr')
|
|
|
|
|
? mb_substr($safeSheet, 0, 31, 'UTF-8')
|
|
|
|
|
: substr($safeSheet, 0, 31);
|
|
|
|
|
if ($safeSheet === '') {
|
|
|
|
|
$safeSheet = 'Sheet1';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
|
|
|
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
|
|
|
$sheet->setTitle($safeSheet);
|
|
|
|
|
|
|
|
|
|
$data = [array_map(static fn ($v): string => (string) ($v ?? ''), array_values($headers))];
|
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
|
$data[] = array_map(static fn ($v): string => (string) ($v ?? ''), array_values((array) $row));
|
|
|
|
|
}
|
|
|
|
|
$sheet->fromArray($data, null, 'A1', true);
|
|
|
|
|
|
|
|
|
|
$headerCount = max(1, count($headers));
|
|
|
|
|
$rowCount = max(1, count($data));
|
|
|
|
|
$lastCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($headerCount);
|
|
|
|
|
$fullRange = 'A1:' . $lastCol . $rowCount;
|
|
|
|
|
|
|
|
|
|
// 헤더/데이터 모두 좌측 정렬(요구사항)
|
|
|
|
|
$sheet->getStyle($fullRange)->getAlignment()->setHorizontal(
|
|
|
|
|
\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_LEFT
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 가독성을 위해 기본 열 너비를 넓게 지정
|
|
|
|
|
for ($i = 1; $i <= $headerCount; $i++) {
|
|
|
|
|
$col = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($i);
|
|
|
|
|
$sheet->getColumnDimension($col)->setWidth(22);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
|
|
|
|
|
ob_start();
|
|
|
|
|
try {
|
|
|
|
|
$writer->save('php://output');
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
ob_end_clean();
|
|
|
|
|
$spreadsheet->disconnectWorksheets();
|
|
|
|
|
unset($spreadsheet);
|
|
|
|
|
throw $e;
|
|
|
|
|
}
|
|
|
|
|
$spreadsheet->disconnectWorksheets();
|
|
|
|
|
unset($spreadsheet);
|
|
|
|
|
$output = ob_get_clean();
|
|
|
|
|
if ($output === false) {
|
|
|
|
|
$output = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$response = service('response');
|
|
|
|
|
$response->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
|
|
|
$response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
|
|
|
|
|
$response->setHeader('Pragma', 'no-cache');
|
|
|
|
|
$response->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
|
|
|
$response->setBody($output);
|
|
|
|
|
$response->send();
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|