File: /var/www/test/Installationlist/repair_submit.php
<?php
// ============================================================
// repair_submit.php ─ 電子派工維修單送出處理
// ============================================================
ob_start();
ini_set('display_errors', 0);
error_reporting(E_ALL);
session_start();
header('Content-Type: application/json; charset=utf-8');
register_shutdown_function(function() {
$err = error_get_last();
if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
while(ob_get_level()) ob_end_clean();
header('HTTP/1.1 200 OK');
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success'=>false,'message'=>'PHP Fatal: '.$err['message'].' (line '.$err['line'].')'], JSON_UNESCAPED_UNICODE);
}
});
set_error_handler(function($errno, $errstr, $errfile, $errline) {
if (in_array($errno, [E_ERROR, E_WARNING, E_PARSE])) {
while(ob_get_level()) ob_end_clean();
http_response_code(200);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success'=>false,'message'=>"PHP[$errno]: $errstr (line $errline)"], JSON_UNESCAPED_UNICODE);
exit;
}
return false;
});
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
ob_clean(); echo json_encode(['success'=>false,'message'=>'不合法的請求']); exit;
}
// ── 輔助函式(務必放在最前面,任何使用之前先宣告完畢)─────
function fmtD($s) {
if (!$s) return '—';
$dt = DateTime::createFromFormat('Y-m-d', $s);
return $dt ? $dt->format('Y年m月d日') : $s;
}
function imgB64tmp($key) {
if (!isset($_FILES[$key]) || $_FILES[$key]['error'] !== 0) return '';
$tmp = $_FILES[$key]['tmp_name'];
$mime = mime_content_type($tmp) ?: 'image/png';
return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($tmp));
}
// $e 是 htmlspecialchars 包裝用的閉包,先宣告好,後面才能安全使用
$e = function($v) { return htmlspecialchars((string)$v); };
// ── POST 資料 ────────────────────────
$userId = $_POST['user_id'] ?? '';
$enname = $_POST['enname'] ?? '';
$apname = $_POST['apname'] ?? '';
$repairContent = $_POST['repair_content'] ?? '';
$repairDate = $_POST['repair_date'] ?? date('Y-m-d');
$feeMode = $_POST['fee_mode'] ?? 'free';
$feeTotal = (int)($_POST['fee_total'] ?? 0);
$feesJson = $_POST['fees_json'] ?? '[]';
$devicesJson = $_POST['devices_json'] ?? '[]';
$formDataJson = $_POST['form_data'] ?? '{}';
$fees = json_decode($feesJson, true) ?: [];
$devices = json_decode($devicesJson, true) ?: [];
$G = json_decode($formDataJson, true) ?: [];
$csigB64 = imgB64tmp('csig');
$isStaticIP = !empty($G['ipaddress']);
// ── 頁首 ─────────────────────────────
$pf = '<div style="font-size:8.5px;text-align:center;color:#666;padding:1mm 0 1.5mm">公司地址:桃園市桃園區大興西路二段61號16樓 電話:03-358-5867 傳真:03-301-2115 網址:www.ysnet.com.tw 營業時間:10:00~20:00</div>';
// 標題絕對置中;日期放在最右側;LOGO 在左側,三者互不影響排版
$ph2 = '<div style="position:relative;border-bottom:1.5px solid #000;padding-bottom:4px;margin-bottom:6px;min-height:26px">'
. '<div style="padding-left:3px;line-height:1.4"><strong style="font-size:14px">YSNET</strong><br><span style="font-size:8.5px;color:#555">亞訊股份有限公司</span></div>'
. '<div style="position:absolute;top:3px;left:0;right:0;text-align:center;font-size:18px;font-weight:bold;pointer-events:none">亞訊寬頻派工維修單</div>'
. '<div style="position:absolute;top:3px;right:3px;font-size:9.5px;color:#555">日期:'.fmtD($repairDate).'</div>'
. '</div>';
// ── 使用設備列(只定義一次)─────────────
// 每筆設備資料結構:{action:'新增'|'回收', loc:'住戶端'|'據點', type:設備名稱, sn:料號, basenum:據點編號}
$hasDevices = !empty($devices);
$deviceRows = '';
if ($hasDevices) {
foreach ($devices as $dv) {
$action = htmlspecialchars($dv['action'] ?? '');
$loc = htmlspecialchars($dv['loc'] ?? '');
$type = htmlspecialchars($dv['type'] ?? '');
$sn = htmlspecialchars($dv['sn'] ?? '—');
$basenum = htmlspecialchars($dv['basenum'] ?? '');
$locDisplay = $loc === '據點' && $basenum ? $loc.'('.$basenum.')' : $loc;
$deviceRows .= '<tr><td>'.$action.'</td><td>'.$locDisplay.'</td><td>'.$type.'</td><td>'.$sn.'</td></tr>';
}
}
// ── 費用列(只定義一次)─────────────
// 無收費:PDF 只顯示「本次無收費」文字,不顯示項目/金額表格
// 有收費:列出各項目;押金獨立列出且不計入總計
$feeRows = '';
$depositAmt = 0;
$showFeeTable = ($feeMode === 'paid' && !empty($fees));
if ($showFeeTable) {
foreach ($fees as $f) {
$fType = $f['type'] ?? '';
$fAmt = (int)($f['amt'] ?? 0);
if ($fType === '押金') {
$depositAmt += $fAmt;
continue; // 押金不列在一般項目列表中,改在下方獨立顯示
}
$feeRows .= '<tr><td>'.htmlspecialchars($fType).'</td><td style="text-align:right">$'.number_format($fAmt).'</td></tr>';
}
$feeRows .= '<tr><td style="text-align:right;font-weight:bold;border-top:1.5px solid #000">本次收費總計</td><td style="text-align:right;font-weight:bold;color:#C00;border-top:1.5px solid #000">$'.number_format($feeTotal).'</td></tr>';
if ($depositAmt > 0) {
$feeRows .= '<tr><td style="text-align:right">押金(不列入總計)</td><td style="text-align:right;color:#8B5000">$'.number_format($depositAmt).'</td></tr>';
}
}
// ── 表單列輔助:值為空時顯示破折號 ──────
function rowVal($val) {
$val = trim((string)$val);
return $val !== '' ? htmlspecialchars($val) : '<span style="color:#999">—</span>';
}
// ── 網路資訊:PPPoE 或 IP 二選一的表單列 ──
// IP 上網時整行只顯示「IP 上網」文字,不展開帳密細節
$netRows = $isStaticIP
? '<tr><td class="lbl">連線方式</td><td colspan="3" style="font-weight:bold;color:#3730A3">IP 上網</td></tr>'
: '<tr><td class="lbl">PPPoE帳號</td><td>'.rowVal($G['pppoeid'] ?? '').'</td><td class="lbl">PPPoE密碼</td><td>'.rowVal($G['pppoepw'] ?? '').'</td></tr>';
// ── 組合完整 PDF HTML(單頁,仿紙本派工單版面)──────────
$pdfHtml = '<!DOCTYPE html><html lang="zh-Hant"><head><meta charset="UTF-8">
<style>
*{box-sizing:border-box}
body{font-family:"Microsoft JhengHei","WenQuanYi Micro Hei",sans-serif;font-size:11px;color:#111;margin:0;padding:0}
.page{width:100%;padding:3mm 0}
table{border-collapse:collapse;width:100%;margin-bottom:7px;table-layout:fixed}
td{border:1px solid #000;padding:4px 6px;vertical-align:top;font-size:10.5px;word-break:break-word}
.sec{background:#f0f0f0;font-weight:bold;font-size:11.5px;text-align:center}
.lbl{font-weight:bold;background:#f7f7f7;width:25%}
.pre{white-space:pre-wrap;line-height:1.7;min-height:20mm}
</style></head><body>
<div class="page">
'.$pf.$ph2.'
<!-- 用戶基本資料:三欄式,對應紙本排列 -->
<table>
<tr><td class="lbl" style="width:13%">用戶編號</td><td style="width:22%">'.rowVal($G['user_id'] ?? '').'</td><td class="lbl" style="width:11%">社區名稱</td><td style="width:18%">'.rowVal($G['member'] ?? '').'</td><td class="lbl" style="width:13%">申請人姓名</td><td style="width:23%">'.rowVal($G['user_name'] ?? '').'</td></tr>
<tr><td class="lbl">住家電話</td><td>'.rowVal($G['homenumber'] ?? '').'</td><td class="lbl">行動電話</td><td colspan="3">'.rowVal($G['phone'] ?? '').'</td></tr>
<tr><td class="lbl">裝機地址</td><td colspan="5">'.rowVal($G['memberaddress'] ?? '').'</td></tr>
<tr><td class="lbl">電子郵件</td><td colspan="5" style="font-size:9.5px">'.rowVal($G['email'] ?? '').'</td></tr>
</table>
<!-- 網路資訊 -->
<table>
<tr><td colspan="4" class="sec">網路資訊</td></tr>
'.$netRows.'
<tr><td class="lbl">據點編號</td><td>'.rowVal($G['basenumber'] ?? '').'</td><td class="lbl">使用方案</td><td>'.rowVal($G['speed'] ?? '').'</td></tr>
</table>
<!-- 設備資訊:獨立表格,避免被上方4欄網格的table-layout:fixed基準覆蓋寬度設定 -->
<table>
<tr><td colspan="3" class="sec">設備資訊</td></tr>
<tr>
<td style="width:40%;padding:0;vertical-align:top">
<div style="font-weight:bold;background:#f7f7f7;border-bottom:1px solid #000;padding:4px 6px;white-space:nowrap">屋內網路設備</div>
<div style="padding:4px 6px">'.rowVal($G['homene'] ?? '').'</div>
</td>
<td style="width:40%;padding:0;vertical-align:top;border-left:1px solid #000">
<div style="font-weight:bold;background:#f7f7f7;border-bottom:1px solid #000;padding:4px 6px;white-space:nowrap">屋內資訊箱設備</div>
<div style="padding:4px 6px">'.rowVal($G['inforne'] ?? '').'</div>
</td>
<td style="width:20%;padding:0;vertical-align:top;border-left:1px solid #000">
<div style="font-weight:bold;background:#f7f7f7;border-bottom:1px solid #000;padding:4px 6px;white-space:nowrap">施工方式</div>
<div style="padding:4px 6px">'.rowVal($G['networkcm'] ?? '').'</div>
</td>
</tr>
</table>
<!-- 問題狀況描述 -->
<table>
<tr><td class="sec">問題狀況描述</td></tr>
<tr><td class="pre">'.rowVal($G['question'] ?? '').'</td></tr>
</table>
<!-- 維修概況紀錄 -->
<table>
<tr><td class="sec">維修概況紀錄</td></tr>
<tr><td class="pre" style="min-height:28mm">'.$e($repairContent).'</td></tr>
</table>
<!-- 使用設備:無資料時整個區塊不顯示 -->
'.($hasDevices ? '
<table>
<tr><td colspan="4" class="sec">使用設備</td></tr>
<tr><td style="font-weight:bold;background:#f7f7f7;width:14%">類別</td><td style="font-weight:bold;background:#f7f7f7;width:24%">地點</td><td style="font-weight:bold;background:#f7f7f7;width:38%">設備名稱</td><td style="font-weight:bold;background:#f7f7f7">料號</td></tr>
'.$deviceRows.'
</table>
' : '').'
<!-- 收費紀錄:無收費時只顯示文字,不顯示項目表格 -->
<table>
<tr><td class="sec">收費紀錄</td></tr>
'.($showFeeTable
? '<tr><td style="padding:0"><table style="margin-bottom:0"><tr><td style="font-weight:bold;background:#f7f7f7;width:50%">項目</td><td style="font-weight:bold;background:#f7f7f7">金額</td></tr>'.$feeRows.'</table></td></tr>'
: '<tr><td style="text-align:center;color:#1D9E75;font-weight:bold;padding:10px 6px">本次無收費</td></tr>'
).'
</table>
<!-- 簽名確認 + 服務須知:併入同一頁,左右並排 -->
<table>
<tr><td colspan="2" class="sec">簽名確認</td></tr>
<tr>
<!-- 左:客戶簽章(整欄專用於簽名,加大顯示) -->
<td style="width:58%;vertical-align:top;padding:6px">
<div style="font-weight:bold;font-size:10.5px;margin-bottom:4px">客戶簽章</div>
'.($csigB64 ? '<img src="'.$csigB64.'" style="width:100%;max-height:65mm;object-fit:contain;border:1px solid #ccc;display:block">' : '<div style="color:red;font-size:10px;height:50mm;display:flex;align-items:center;justify-content:center;border:1px solid #ccc">未提供簽名</div>').'
</td>
<!-- 右:服務須知 + 工程師資訊(移至下方) -->
<td style="width:42%;vertical-align:top;padding:6px">
<div style="font-size:9px;color:#555;line-height:1.6">
1. 本公司銷售之新品設備,非人為因素造成之故障,皆保固壹年,保固期限自購買日起生效。<br>
2. 本公司維修及銷售一律採現金收費(含稅)為付款條件,恕不接受賒欠或期票。<br>
3. 本公司收到款項後,由綠界科技系統開立電子發票,開立完成後會以電子郵件或簡訊方式通知。
</div>
<div style="margin-top:10px;font-size:10.5px;color:#333;border-top:1px solid #ddd;padding-top:6px;line-height:1.9">
維修工程師:<span style="font-size:13px;font-weight:bold;color:#085041">'.$e($enname).'</span><br>
連絡電話:'.$e($G['ENphone'] ?? '').'<br>
維修日期:'.fmtD($repairDate).'
</div>
</td>
</tr>
</table>
</div>
</body></html>';
// ── wkhtmltopdf ─────────────────────
$wkPaths = ['/usr/bin/wkhtmltopdf', '/usr/local/bin/wkhtmltopdf'];
$wkBin = '';
$disabled = array_map('trim', explode(',', ini_get('disable_functions')));
foreach ($wkPaths as $p) {
if (file_exists($p) && is_executable($p)) {
if (!in_array('exec', $disabled)) {
$v = []; exec('HOME=/tmp '.$p.' --version 2>&1', $v, $vr);
if ($vr === 0) { $wkBin = $p; break; }
} else {
$wkBin = $p; break;
}
}
}
$pdfGenerated = false;
$pdfData = '';
$wkError = '';
$safeId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $userId);
$ts = date('YmdHis');
$htmlTmp = '/tmp/repair_'.$safeId.'_'.$ts.'.html';
$pdfTmp = '/tmp/repair_'.$safeId.'_'.$ts.'.pdf';
$htmlWritten = file_put_contents($htmlTmp, $pdfHtml);
if ($htmlWritten === false) {
ob_clean();
echo json_encode(['success'=>false,'message'=>'無法寫入暫存 HTML:'.$htmlTmp]);
exit;
}
if (!$wkBin) {
$wkError = 'wkhtmltopdf 未找到:'.implode(', ', $wkPaths);
} else {
if (!in_array('exec', $disabled)) {
$cmd = 'HOME=/tmp DISPLAY=:0 XAUTHORITY=/dev/null '.$wkBin
. ' --encoding utf-8 --page-size A4'
. ' --margin-top 8mm --margin-bottom 8mm --margin-left 10mm --margin-right 10mm'
. ' --disable-smart-shrinking --load-error-handling ignore --quiet'
. ' '.escapeshellarg($htmlTmp).' '.escapeshellarg($pdfTmp).' 2>&1';
$out = []; exec($cmd, $out, $ret);
if ($ret === 0 && file_exists($pdfTmp) && filesize($pdfTmp) > 500) {
$pdfData = file_get_contents($pdfTmp);
$pdfGenerated = true;
} else {
$wkError = 'exec ret='.$ret.': '.implode('|', $out);
}
} elseif (!in_array('proc_open', $disabled)) {
$cmd = $wkBin.' --encoding utf-8 --page-size A4'
. ' --margin-top 8mm --margin-bottom 8mm --margin-left 10mm --margin-right 10mm'
. ' --disable-smart-shrinking --load-error-handling ignore --quiet'
. ' '.escapeshellarg($htmlTmp).' '.escapeshellarg($pdfTmp);
$proc = proc_open($cmd, [0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']], $pipes, null,
['HOME'=>'/tmp','DISPLAY'=>':0','XAUTHORITY'=>'/dev/null','PATH'=>'/usr/local/bin:/usr/bin:/bin']);
if (is_resource($proc)) {
fclose($pipes[0]);
$se = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]);
$ret = proc_close($proc);
if ($ret === 0 && file_exists($pdfTmp) && filesize($pdfTmp) > 500) {
$pdfData = file_get_contents($pdfTmp);
$pdfGenerated = true;
} else {
$wkError = 'proc ret='.$ret.': '.$se;
}
} else {
$wkError = 'proc_open失敗';
}
} else {
$wkError = 'exec/proc_open 均被禁用';
}
}
if (!$pdfGenerated) error_log('[repair_submit.php] PDF失敗: '.$wkError);
@unlink($htmlTmp);
@unlink($pdfTmp);
// ── Email ────────────────────────────
// 優先使用前端傳來的 final_email(使用者確認/修改過的),其次用原始 email,兩者皆空則不寄
$finalEmail = trim($_POST['final_email'] ?? '');
$origEmail = $G['email'] ?? '';
$email = filter_var($finalEmail, FILTER_VALIDATE_EMAIL) ? $finalEmail
: (filter_var($origEmail, FILTER_VALIDATE_EMAIL) ? $origEmail : '');
$emailSent = false;
if (!empty($email)) {
$from = 'service@ysnet.com.tw';
$subject = '=?UTF-8?B?'.base64_encode('亞訊寬頻派工維修單 - '.($G['user_name'] ?? '').'('.fmtD($repairDate).')').'?=';
$boundary = md5(uniqid('rp_'));
$pdfFilename = $safeId.'_repair.pdf';
$bodyHtml = '<!DOCTYPE html><html><body style="font-family:Microsoft JhengHei,sans-serif;color:#111">'
. '<p>親愛的 <strong>'.htmlspecialchars($G['user_name'] ?? '').'</strong> 您好,</p>'
. '<p>感謝您使用亞訊寬頻維修服務,'.($pdfGenerated ? '本次維修服務單 PDF 已附於此封信件。' : '您的維修紀錄已送出。').'</p>'
. '<table style="border-collapse:collapse;max-width:480px;width:100%">'
. '<tr><td style="padding:6px;background:#f5f5f5;font-weight:bold;border:1px solid #ddd">維修日期</td><td style="padding:6px;border:1px solid #ddd">'.fmtD($repairDate).'</td></tr>'
. '<tr><td style="padding:6px;background:#f5f5f5;font-weight:bold;border:1px solid #ddd">施工工程師</td><td style="padding:6px;border:1px solid #ddd">'.htmlspecialchars($enname).'</td></tr>'
. '</table>'
. '<p style="margin-top:16px">如有任何問題,歡迎來電 <strong>03-358-5867</strong></p>'
. '<p style="color:#666;font-size:12px">亞訊寬頻 客服中心 | service@ysnet.com.tw</p>'
. '</body></html>';
$headers = "From:{$from}\r\nReply-To:{$from}\r\nMIME-Version:1.0\r\n";
if ($pdfGenerated && $pdfData) {
$headers .= "Content-Type: multipart/mixed; boundary=\"{$boundary}\"\r\n";
$body = "--{$boundary}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n\r\n"
. chunk_split(base64_encode($bodyHtml))
. "--{$boundary}\r\nContent-Type: application/pdf; name=\"{$pdfFilename}\"\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"{$pdfFilename}\"\r\n\r\n"
. chunk_split(base64_encode($pdfData))
. "--{$boundary}--";
} else {
$headers .= "Content-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n";
$body = base64_encode($bodyHtml);
}
$emailSent = mail($email, $subject, $body, $headers);
if (!$emailSent) error_log('[repair_submit.php] mail() failed for: '.$email);
}
// ── Session 清理 ─────────────────────
unset($_SESSION['rp_user_id'], $_SESSION['rp_enname'], $_SESSION['rp_email']);
$_SESSION['repair_done_submitted'] = true;
$_SESSION['repair_done_email'] = $emailSent ? $email : '';
// ── 回傳 ─────────────────────────────
while (ob_get_level() > 1) ob_end_clean();
ob_clean();
echo json_encode([
'success' => true,
'pdf_generated' => $pdfGenerated,
'pdf_error' => $wkError,
'email_sent' => $emailSent,
'message' => $emailSent
? '維修單已送出,PDF 已寄至 '.$email
: ($pdfGenerated ? '送出成功,但 Email 發送失敗' : '送出成功,但 PDF 產生失敗:'.$wkError),
], JSON_UNESCAPED_UNICODE);