Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 13s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 13s
Gemini PR Review / Gemini PR Review (pull_request) Failing after 19s
Laravel Pint / pint (pull_request) Successful in 4m4s
Laravel Larastan / larastan (pull_request) Failing after 5m9s
248 lines
7.0 KiB
PHP
248 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Mail\DailyTaskReportMail;
|
|
use App\Models\AlertMailRule;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Storage;
|
|
use Smalot\PdfParser\Parser;
|
|
|
|
class SendDailyTaskReport extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'send:daily-task-report {schedule_type}';
|
|
|
|
|
|
protected array $knownStatuses = [
|
|
'in progress', 'new', 'closed', 'on hold', 'rejected',
|
|
'done', 'resolved', 'confirmed', 'in specification', 'scheduled',
|
|
];
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Command description';
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*/
|
|
public function handle()
|
|
{
|
|
$directory = 'task_updates';
|
|
$scheduleType = $this->argument('schedule_type');
|
|
|
|
$mailRules = AlertMailRule::where('module', 'DailyTaskReport')
|
|
->where('rule_name', 'DailyTaskReportMail')
|
|
->where('schedule_type', $scheduleType)
|
|
->get();
|
|
|
|
if (!$mailRules) {
|
|
$this->error('Daily Task Report mail rule not found.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
// Get all PDF files
|
|
$pdfFiles = collect(Storage::disk('local')->files($directory))
|
|
->filter(function ($file) {
|
|
return strtolower(pathinfo($file, PATHINFO_EXTENSION)) == 'pdf';
|
|
})
|
|
->values();
|
|
|
|
if ($pdfFiles->isEmpty()) {
|
|
$this->info('No PDF files found.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$parser = new Parser();
|
|
$attachments = [];
|
|
$rows = [];
|
|
$sno = 1;
|
|
|
|
$attachments = [];
|
|
|
|
foreach ($pdfFiles as $file) {
|
|
$fullPath = Storage::disk('local')->path($file);
|
|
$filename = basename($file, '.pdf');
|
|
|
|
$attachments[] = [
|
|
'path' => Storage::disk('local')->path($file),
|
|
'name' => basename($file),
|
|
];
|
|
|
|
// Read PDF content for name/task/status
|
|
try {
|
|
$pdf = $parser->parseFile($fullPath);
|
|
$text = $pdf->getText();
|
|
|
|
\Log::info('PDF RAW TEXT: ' . $text);
|
|
} catch (\Throwable $e) {
|
|
$text = '';
|
|
}
|
|
|
|
$rows[] = [
|
|
'sno' => $sno++,
|
|
'name' => $this->extractNameFromFilename($filename),
|
|
// 'project-name' => $this->extractProjectName($text),
|
|
'project-name' => $pdf ? $this->extractProjectName($pdf) : 'Unknown project',
|
|
'task' => $this->extractTaskTitle($text),
|
|
'status' => $this->extractStatus($text),
|
|
'completed' => $this->extractPercentComplete($text),
|
|
];
|
|
|
|
$this->info('Attached: ' . basename($file));
|
|
}
|
|
|
|
$toEmails = [];
|
|
$ccEmails = [];
|
|
|
|
foreach ($mailRules as $mailRule) {
|
|
|
|
// TO email
|
|
if (!empty($mailRule->email)) {
|
|
$toEmails[] = trim($mailRule->email);
|
|
}
|
|
|
|
// CC emails
|
|
if (!empty($mailRule->cc_emails)) {
|
|
|
|
$ccEmails = array_merge(
|
|
$ccEmails,
|
|
array_map(
|
|
'trim',
|
|
explode(',', $mailRule->cc_emails)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
// Remove duplicate emails
|
|
$toEmails = array_values(array_unique(array_filter($toEmails)));
|
|
$ccEmails = array_values(array_unique(array_filter($ccEmails)));
|
|
|
|
|
|
try {
|
|
|
|
$mail = Mail::to($toEmails);
|
|
|
|
if (!empty($ccEmails)) {
|
|
$mail->cc($ccEmails);
|
|
}
|
|
|
|
$mail->send(
|
|
new DailyTaskReportMail($attachments, $rows)
|
|
);
|
|
|
|
foreach ($pdfFiles as $file) {
|
|
|
|
if (Storage::disk('local')->exists($file)) {
|
|
Storage::disk('local')->delete($file);
|
|
|
|
$this->info('Deleted: ' . basename($file));
|
|
}
|
|
}
|
|
|
|
$this->info(
|
|
$pdfFiles->count() . ' PDF(s) mailed and deleted successfully.'
|
|
);
|
|
|
|
return self::SUCCESS;
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
// Mail failed -> DO NOT delete PDFs
|
|
$this->error('Mail sending failed.');
|
|
$this->error($e->getMessage());
|
|
|
|
return self::FAILURE;
|
|
}
|
|
}
|
|
|
|
protected function extractNameFromFilename(string $filename): string
|
|
{
|
|
$name = preg_replace('/[-_]?task.*$/i', '', $filename);
|
|
$name = preg_replace('/[-_]+/', ' ', $name);
|
|
$name = preg_replace('/(?<!^)([A-Z])/', ' $1', $name);
|
|
return trim(ucwords(strtolower($name)));
|
|
}
|
|
|
|
protected function extractTaskTitle(string $text): string
|
|
{
|
|
if (preg_match('/Task\s*#\d+\s*-\s*(.+)/i', $text, $matches)) {
|
|
$title = trim($matches[1]);
|
|
foreach ($this->knownStatuses as $status) {
|
|
$pos = stripos($title, $status);
|
|
if ($pos != false) {
|
|
$title = trim(substr($title, 0, $pos));
|
|
break;
|
|
}
|
|
}
|
|
return $title;
|
|
}
|
|
return 'Unknown task';
|
|
}
|
|
|
|
protected function extractStatus(string $text): string
|
|
{
|
|
// Replace non-breaking spaces (common in OpenProject PDF exports) with regular spaces
|
|
$normalized = str_replace("\xC2\xA0", ' ', $text);
|
|
$normalized = preg_replace('/\s+/', ' ', $normalized);
|
|
|
|
foreach ($this->knownStatuses as $status) {
|
|
if (stripos($normalized, $status) !== false) {
|
|
return ucwords($status);
|
|
}
|
|
}
|
|
return 'Unknown';
|
|
}
|
|
|
|
protected function extractPercentComplete(string $text): string
|
|
{
|
|
$normalized = str_replace("\xC2\xA0", ' ', $text);
|
|
$normalized = preg_replace('/[ \t]+/', ' ', $normalized);
|
|
|
|
if (preg_match('/%\s*Complete\s*([0-9]{1,3}\s*%)/i', $normalized, $matches)) {
|
|
return trim(str_replace(' ', '', $matches[1]));
|
|
}
|
|
|
|
return '0%';
|
|
}
|
|
|
|
// protected function extractProjectName(string $text): string
|
|
// {
|
|
// $normalized = str_replace("\xC2\xA0", ' ', $text);
|
|
|
|
// // Footer pattern: <page#><date>\t<Project Name> at the very end of the PDF text
|
|
// if (preg_match('/\d{1,2}-\d{2}-\d{4}\s*\t\s*(.+?)\s*$/s', $normalized, $matches)) {
|
|
// return trim($matches[1]);
|
|
// }
|
|
|
|
// return 'Unknown project';
|
|
// }
|
|
|
|
protected function extractProjectName($pdf): string
|
|
{
|
|
$pages = $pdf->getPages();
|
|
|
|
if (empty($pages)) {
|
|
return 'Unknown project';
|
|
}
|
|
|
|
$firstPageText = str_replace("\xC2\xA0", ' ', $pages[0]->getText());
|
|
|
|
if (preg_match('/\d{1,2}-\d{2}-\d{4}\s*\t\s*(.+?)\s*$/s', $firstPageText, $matches)) {
|
|
return trim($matches[1]);
|
|
}
|
|
|
|
return 'Unknown project';
|
|
}
|
|
|
|
}
|