Files
finance_app/app/Console/Commands/FetchGmailInvoices.php
henry yo 85972f950c feat: 前端分離+功能api化
1. 新增資產管理CRUD、googleOAuth登入
2. 更改原記帳controller
2026-06-25 14:19:07 +08:00

163 lines
6.6 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use App\Models\User;
use App\Models\Expense;
use App\Services\GmailParserService;
use Google\Client as GoogleClient;
use Google\Service\Gmail;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
class FetchAndParseGmailInvoices implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function handle(GmailParserService $parserService)
{
Log::info("====== [DEBUG START] 開始執行 Gmail 抓信任務 ======");
if (!$this->user->google_refresh_token) {
Log::error("❌ 失敗User ID {$this->user->id} 缺乏 google_refresh_token");
return;
}
try {
Log::info("🔄 正在初始化 Google Client & 刷新 Token...");
$client = new GoogleClient();
$client->setClientId(config('services.google.client_id'));
$client->setClientSecret(config('services.google.client_secret'));
$client->refreshToken($this->user->google_refresh_token);
$gmailService = new Gmail($client);
$afterDate = Carbon::now()->subDays(3)->format('Y/m/d');
$searchQuery = "from:cathaybk.com.tw subject:消費彙整通知 after:{$afterDate}";
Log::info("🔍 正在向 Google API 發送搜尋 Query: [{$searchQuery}]");
$response = $gmailService->users_messages->listUsersMessages('me', [
'q' => $searchQuery,
'maxResults' => 10
]);
$messages = $response->getMessages();
if (empty($messages)) {
Log::warning("⚠️ 警告Google API 回傳空陣列!");
return;
}
Log::info("🎯 成功拿到信件清單!總共發現 " . count($messages) . " 封潛在信件,開始逐封處理...");
foreach ($messages as $index => $msgItem) {
$msgId = $msgItem->getId();
Log::info("--- [處理第 " . ($index + 1) . " 封信] ID: {$msgId} ---");
$message = $gmailService->users_messages->get('me', $msgId, ['format' => 'full']);
$payload = $message->getPayload();
$subject = '';
foreach ($payload->getHeaders() as $header) {
if ($header->getName() === 'Subject') {
$subject = $header->getValue();
break;
}
}
Log::info("📬 信件主旨: [{$subject}]");
$body = $this->getDecodedBody($payload);
Log::info("📄 內文解碼完成,準備丟入 Parser...");
// 1. 執行解析(現在國泰會回報陣列,其餘單筆通知我們手動把它包成陣列)
$parsedResult = $parserService->parse($subject, $body);
if (!$parsedResult) {
Log::error("❌ 失敗GmailParserService 無法解析此信件內容");
continue;
}
// 💡 智慧防呆:如果回傳的是單筆消費(一維陣列),我們把它包成二維陣列,這樣下面統一用 foreach 處理
$expensesList = isset($parsedResult['amount']) ? [$parsedResult] : $parsedResult;
Log::info("✨ Parser 解析成功!共發現 " . count($expensesList) . " 筆刷卡消費項目,開始遍歷寫入...");
// 🔄 遍歷這封信裡面的每一筆刷卡紀錄
foreach ($expensesList as $parsedData) {
// 🟢 2. 智慧資產帳戶對照
$matchedAccountRule = \App\Models\AccountRule::where('user_id', $this->user->id)
->get()
->first(function ($rule) use ($subject) {
return Str::contains($subject, $rule->keyword);
});
$accountId = $matchedAccountRule ? $matchedAccountRule->account_id : null;
// 🟢 3. 智慧消費分類對照
$matchedCategoryRule = \App\Models\CategoryRule::where('user_id', $this->user->id)
->get()
->first(function ($rule) use ($parsedData) {
return Str::contains(strtoupper($parsedData['seller_name']), strtoupper($rule->keyword));
});
$categoryId = $matchedCategoryRule ? $matchedCategoryRule->category_id : null;
// 4. 💾 嘗試寫入資料庫
$expense = Expense::firstOrCreate(
[
'user_id' => $this->user->id,
'amount' => $parsedData['amount'],
'seller_name' => $parsedData['seller_name'],
'date' => $parsedData['date'],
],
[
'item_name' => $parsedData['item_name'],
'account_id' => $accountId,
'category_id' => $categoryId,
'note' => 'Gmail自動同步(多筆完全體)',
]
);
if ($expense->wasRecentlyCreated) {
Log::info("✅ 成功!建立全新消費: [{$parsedData['seller_name']}] 額度: [{$parsedData['amount']}], ID: {$expense->id}");
} else {
Log::info(" 提示:[{$parsedData['seller_name']}] 額度: [{$parsedData['amount']}] 已存在,跳過。");
}
}
}
Log::info("====== [DEBUG END] Gmail 抓信任務順利結束 ======");
} catch (\Exception $e) {
Log::error("💥 崩潰handle 內發生致命錯誤: " . $e->getMessage());
}
}
private function getDecodedBody($part)
{
$body = $part->getBody();
if ($body && $body->getData()) {
return base64_decode(str_replace(['-', '_'], ['+', '/'], $body->getData()));
}
$parts = $part->getParts();
if ($parts) {
foreach ($parts as $subPart) {
$result = $this->getDecodedBody($subPart);
if ($result) return $result;
}
}
return '';
}
}