mirror of
https://github.com/henry4682/finance_app.git
synced 2026-07-16 00:10:00 +00:00
feat: 前端分離+功能api化
1. 新增資產管理CRUD、googleOAuth登入 2. 更改原記帳controller
This commit is contained in:
180
app/Services/GmailParserService.php
Normal file
180
app/Services/GmailParserService.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class GmailParserService
|
||||
{
|
||||
|
||||
protected array $parserMatrix = [
|
||||
[
|
||||
'keywords' => ['國泰世華銀行消費彙整通知'],
|
||||
'method' => 'parseCathaySummary'
|
||||
],
|
||||
[
|
||||
'keywords' => ['永豐銀行信用卡消費通知'],
|
||||
'method' => 'parseSinoPacNotification'
|
||||
],
|
||||
[
|
||||
'keywords' => ['電子發票', '載具', '發票開立通知'],
|
||||
'method' => 'parseElectronicInvoice'
|
||||
],
|
||||
[
|
||||
'keywords' => ['消費通知', '刷卡', '卡片消費'],
|
||||
'method' => 'parseCreditCardNotification'
|
||||
],
|
||||
];
|
||||
|
||||
public function parse(string $subject, string $body): ?array
|
||||
{
|
||||
// 🔄 遍歷矩陣,尋找第一個命中的規則
|
||||
foreach ($this->parserMatrix as $rule) {
|
||||
if (Str::contains($subject, $rule['keywords'])) {
|
||||
$method = $rule['method'];
|
||||
|
||||
// 動態呼叫對應的方法 (例如 $this->parseCathaySummary($body) )
|
||||
return $this->{$method}($body);
|
||||
}
|
||||
}
|
||||
|
||||
Log::info("【Parser】忽略信件,主旨未命中任何對照規則: [{$subject}]");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🟢 國泰世華消費彙整信件專屬解析
|
||||
*/
|
||||
private function parseCathaySummary(string $body): ?array
|
||||
{
|
||||
$cleanBody = preg_replace('/\s+/', ' ', $body);
|
||||
|
||||
if (preg_match('/(\d{4}\/\d{2}\/\d{2})/u', $cleanBody, $dateMatches)) {
|
||||
$date = Carbon::createFromFormat('Y/m/d', $dateMatches[1])->format('Y-m-d');
|
||||
} else {
|
||||
$date = now()->format('Y-m-d');
|
||||
}
|
||||
|
||||
$pattern = '/NT\$\s*([0-9,]+)\s*<\/td>\s*<td[^>]*>\s*([^<\s&]+)\s*<\/td>/u';
|
||||
|
||||
if (!preg_match_all($pattern, $cleanBody, $matches, PREG_SET_ORDER)) {
|
||||
Log::error("【Parser】國泰信件未命中消費明細。");
|
||||
return null;
|
||||
}
|
||||
|
||||
$results = [];
|
||||
foreach ($matches as $match) {
|
||||
$sellerName = $this->convertFullWidthToHalfWidth(trim($match[2]));
|
||||
$results[] = [
|
||||
'amount' => floatval(str_replace(',', '', $match[1])),
|
||||
'item_name' => "信用卡消費: {$sellerName}",
|
||||
'seller_name' => $sellerName,
|
||||
'date' => $date,
|
||||
'category_id' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 永豐銀行信用卡消費通知(單筆制)
|
||||
*/
|
||||
private function parseSinoPacNotification(string $body): ?array
|
||||
{
|
||||
$cleanBody = preg_replace('/\s+/', ' ', $body);
|
||||
$amount = 0;
|
||||
$sellerName = '永豐刷卡消費';
|
||||
|
||||
if (preg_match('/(?:新臺幣|TWD)\s*([0-9,]+)/u', $cleanBody, $amountMatches)) {
|
||||
$amount = floatval(str_replace(',', '', $amountMatches[1]));
|
||||
}
|
||||
|
||||
if (preg_match('/特約商店[::]\s*([^<\s]+)/u', $cleanBody, $merchantMatches)) {
|
||||
$sellerName = trim($merchantMatches[1]);
|
||||
}
|
||||
|
||||
if ($amount === 0) return null;
|
||||
|
||||
if (preg_match('/(\d{4}\/\d{2}\/\d{2})/u', $cleanBody, $dateMatches)) {
|
||||
$date = Carbon::createFromFormat('Y/m/d', $dateMatches[1])->format('Y-m-d');
|
||||
} else {
|
||||
$date = now()->format('Y-m-d');
|
||||
}
|
||||
|
||||
$sellerName = $this->convertFullWidthToHalfWidth($sellerName);
|
||||
|
||||
return [
|
||||
'amount' => $amount,
|
||||
'item_name' => "信用卡消費: {$sellerName}",
|
||||
'seller_name' => $sellerName,
|
||||
'date' => $date,
|
||||
'category_id' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析電子發票信件
|
||||
*/
|
||||
private function parseElectronicInvoice(string $body): ?array
|
||||
{
|
||||
$amount = 0;
|
||||
$sellerName = '未知商家';
|
||||
|
||||
if (preg_match('/(總計|金額|總金額)[::]\s*?\$?([0-9,]+)/u', $body, $matches)) {
|
||||
$amount = floatval(str_replace(',', '', $matches[2]));
|
||||
}
|
||||
if (preg_match('/(賣方|開立店家|商店名稱)[::]\s*?([^\n<\s]+)/u', $body, $matches)) {
|
||||
$sellerName = trim($matches[2]);
|
||||
}
|
||||
|
||||
if ($amount === 0) return null;
|
||||
|
||||
return [
|
||||
'amount' => $amount,
|
||||
'item_name' => '電子發票消費',
|
||||
'seller_name' => $sellerName,
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'category_id' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析信用卡消費通知(泛用型備援)
|
||||
*/
|
||||
private function parseCreditCardNotification(string $body): ?array
|
||||
{
|
||||
$amount = 0;
|
||||
$sellerName = '一般刷卡消費';
|
||||
|
||||
if (preg_match('/(NT\$|元|TWD)\s*?([0-9,]+)/u', $body, $matches)) {
|
||||
$amount = floatval(str_replace(',', '', $matches[2]));
|
||||
} elseif (preg_match('/([0-9,]+)\s*?(元)/u', $body, $matches)) {
|
||||
$amount = floatval(str_replace(',', '', $matches[1]));
|
||||
}
|
||||
|
||||
if (preg_match('/於\s*?([^\n\]((]+?)\s*?消費/u', $body, $matches)) {
|
||||
$sellerName = trim($matches[1]);
|
||||
} elseif (preg_match('/(特約商店|消費商店|商店)[::]\s*?([^\n<\s]+)/u', $body, $matches)) {
|
||||
$sellerName = trim($matches[2]);
|
||||
}
|
||||
|
||||
if ($amount === 0) return null;
|
||||
|
||||
return [
|
||||
'amount' => $amount,
|
||||
'item_name' => "刷卡消費: {$sellerName}",
|
||||
'seller_name' => $sellerName,
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'category_id' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function convertFullWidthToHalfWidth(string $str): string
|
||||
{
|
||||
$str = str_replace(' ', '', $str);
|
||||
return mb_convert_kana($str, 'asKV', 'UTF-8');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user