fixed build-report refactored enum status and HasStatusTrait

This commit is contained in:
Martin Folkerts 2024-02-14 21:01:15 +01:00
parent a7a333fb0f
commit 645929ae4e
26 changed files with 356 additions and 86 deletions

View file

@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Project;
use Illuminate\Console\Command;
class BuildReports extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'smartcane:build-reports';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
Project::find(1)->schedule();
}
}

View file

@ -12,8 +12,8 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule): void
{
// $schedule->command('invoices:create')->weekdays()->twiceDaily(9,17)->timezone('Europe/Amsterdam');
$schedule->command('queue:work --sansdaemon')->everyMinute()->withoutOverlapping();
$schedule->command('smartcane:build-reports')->dailyAt('2:00');
}
/**

View file

@ -0,0 +1,10 @@
<?php
namespace App\Enums;
enum Status: string
{
case Failed = 'failed';
case Pending = 'pending';
case Success = 'success';
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Jobs;
use App\Models\Project;
use App\Models\ProjectDownload;
use App\Models\ProjectMosaic;
use App\Models\ProjectReport;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Symfony\Component\Process\Process;
class NullJob implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct()
{
logger('NullJob construct called');
}
/**
* Execute the job.
*/
public function handle(): void
{
logger('NullJob handle called');
}
}

View file

@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Enums\Status;
use App\Models\Project;
use App\Models\ProjectDownload;
use Illuminate\Bus\Batchable;
@ -50,14 +51,14 @@ public function handle(): void
logger('error', $process->getErrorOutput());
}
$this->download->update(['status' => 'completed']);
$this->download->setStatusSuccess();
}
public static function handleForDate(Project $project, Carbon $date)
{
$filename = $date->format('Y-m-d') . '.tif';
if ($project->downloads()->where(['status' => 'completed', 'name' => $filename])->count() > 0) {
return;
if ($project->downloads()->statusSuccess()->where(['name' => $filename])->exists()) {
return new NullJob();
}
$path = $project->download_path . '/merged_final_tif/' . $filename;
@ -65,7 +66,6 @@ public static function handleForDate(Project $project, Carbon $date)
$project->downloads()->create([
'name' => $filename,
'path' => $path,
'status' => 'completed',
]),
$date
);

View file

@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Enums\Status;
use App\Models\Project;
use App\Models\ProjectDownload;
use App\Models\ProjectMosaic;
@ -53,6 +54,7 @@ public function handle(): void
$process->setEnv(['PATH' => $currentPath.':/usr/local/Cellar/pandoc/3.1.8/bin/pandoc']);
// $process->setTimeout(36000); // stel een geschikte timeout in
$process->start();
try {
$myOutput = [];
$process->wait(function ($type, $buffer) use (&$myOutput) {
@ -61,25 +63,30 @@ public function handle(): void
logger($buffer);
});
$this->processOutput = collect($myOutput)->join('\n');
$this->mosaic->setStatusSuccess();
} catch (ProcessFailedException $exception) {
echo $exception->getMessage();
$this->mosaic->setStatusFailed();
}
$this->mosaic->update([
'status' => 'complete',
]);
}
public static function handleFor(Project $project, $year, $startWeekNumber) {
if ($project->mosaics()->where(['status' => 'complete', 'year' => $year, 'week' => $startWeekNumber])->count() > 0) {
return;
public static function handleFor(Project $project, $year, $startWeekNumber)
{
if ($project->hasInvalidMosaicFor($year, $startWeekNumber)) {
return new NullJob();
}
$mosaic = $project->mosaics()->create([
'name' => sprintf('Week %d, %d', $startWeekNumber, $year),
'path' => sprintf('%s/%s/%s', $project->download_path, 'mosaics', sprintf('week_%d_%d.tif', $startWeekNumber, $year)),
$mosaic = $project->mosaics()->updateOrCreate(
[
'year' => $year,
'week' => $startWeekNumber,
'status' => 'pending',
],
[
'name' => sprintf('Week %d, %d', $startWeekNumber, $year),
'path' => sprintf('%s/%s/%s', $project->download_path, 'mosaics',
sprintf('week_%d_%d.tif', $startWeekNumber, $year)),
]);
return new self($mosaic);
}

View file

@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Livewire\Forms\MailingForm;
use App\Models\ProjectReport;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
@ -18,22 +19,22 @@ class ProjectReportGeneratorJob implements ShouldQueue
public $timeout = 220;
private ProjectReport $projectReport;
private bool $sendMail;
/**
* Create a new job instance.
*/
public function __construct(ProjectReport $projectReport)
public function __construct(ProjectReport $projectReport, $sendMail = false)
{
$this->projectReport = $projectReport;
//
$this->sendMail = $sendMail;
}
/**
* Execute the job.
*/
public function handle(): void
public function handle()
{
logger('in handle');
$this->projectReport->weeksAgo();
$projectFolder = base_path('../');
@ -68,6 +69,19 @@ public function handle(): void
$this->projectReport->setStatusFailed();
}
//
$this->sendMail();
}
private function sendMail(): void
{
if ($this->sendMail && $this->projectReport->statusIsSuccessful()) {
logger('sendMail');
MailingForm::saveAndSendMailing(
$this->projectReport,
$this->projectReport->project->mail_subject,
$this->projectReport->project->mail_template,
$this->projectReport->project->emailRecipients()->get(['email', 'name'])->toArray()
);
}
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Listeners;
use App\Models\ProjectMailing;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Events\MessageSent;
use Illuminate\Queue\InteractsWithQueue;
class UpdateMailingStatus
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(MessageSent $event): void
{
ProjectMailing::where('id', $event->data['mailing']['id'])
->update(['status' => 'success']);
}
}

View file

@ -28,26 +28,30 @@ public function setReport(ProjectReport $report)
$this->subject = $report->project->mail_subject;
$this->message = $report->project->mail_template;
$this->recipients = $this->report->project->emailRecipients()->get(['email', 'name'])->toArray();
}
public function save()
{
$this->validate();
self::saveAndSendMailing($this->report, $this->subject, $this->message, $this->recipients);
$mailing = $this->report->project->mailings()->create([
'subject' => $this->subject,
'message' => $this->message,
$this->setReport($this->report);
}
public static function saveAndSendMailing($report, $subject, $message, $recipients) {
$mailing = $report->project->mailings()->create([
'subject' => $subject,
'message' => $message,
]);
$mailing->attachments()->create([
'name' => $this->report->name,
'path' => $this->report->path,
'name' => $report->name,
'path' => $report->path,
]);
$mailing->recipients()->createMany($this->recipients);
$this->setReport($this->report);
$mailing->recipients()->createMany($recipients);
Mail::to($mailing->recipients()->pluck('email')->toArray())
->send(new \App\Mail\ReportMailer($mailing));

View file

@ -38,7 +38,6 @@ public function saveMosaic(){
'year' => $this->formData['year'],
'week' => $this->formData['week'],
],[
'status' => 'pending',
'path' => $this->project->getMosaicPath(),
]);

View file

@ -42,7 +42,6 @@ private function resetFormData()
public function saveProjectReport()
{
sleep(1);
$this->validate([
'formData.week' => ['required', 'integer', 'min:1', 'max:53' ],
'formData.year' => 'required|integer|min:2020|max:'.now()->addYear()->year,

View file

@ -26,6 +26,9 @@ class ReportMailer extends Mailable
public function __construct(ProjectMailing $mailing)
{
$this->mailing = $mailing;
$this->withSwiftMessage(function ($message) use ($mailing) {
$message->getHeaders()->addTextHeader('X-Mailing-ID', $mailing->id);
});
}
/**
@ -56,11 +59,18 @@ public function content(): Content
public function attachments(): array
{
return $this->mailing->attachments()->get()->map(function (ProjectMailingAttachment $attachment) {
$mime = 'application/pdf'; // default MIME type
$extension = pathinfo($attachment->path, PATHINFO_EXTENSION);
if ($extension === 'docx') {
$mime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}
return Attachment::fromStorage(
path: $attachment->mailing->project->download_path.$attachment->path
path: $attachment->mailing->project->download_path."/".$attachment->path
)
->as($attachment->name)
->withMime('application/pdf');
->as($attachment->path)
->withMime($mime);
})->toArray();
}
}

View file

@ -5,6 +5,7 @@
use App\Jobs\ProjectDownloadTiffJob;
use App\Jobs\ProjectMosiacGeneratorJob;
use App\Jobs\ProjectReportGeneratorJob;
use App\Livewire\Forms\MailingForm;
use Carbon\CarbonPeriod;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -243,12 +244,12 @@ public function getMergedTiffList()
public function hasPendingDownload(): bool
{
return $this->downloads()->where('status', 'pending')->count() > 0;
return $this->downloads()->statusPending()->count() > 0;
}
public function hasPendingMosaic(): bool
{
return $this->mosaics()->where('status', 'pending')->count() > 0;
return $this->mosaics()->statusPending()->count() > 0;
}
public function startDownload(Carbon $date)
@ -260,29 +261,69 @@ public function startDownload(Carbon $date)
],
[
'path' => sprintf('%s/%s/%s.tif', $this->download_path, 'merged_final_tif', $date->format('Y-m-d')),
'status' => 'pending',
]
);
ProjectDownloadTiffJob::dispatch($downloadRequest, $date);
}
public function scheduleReport($year, $week)
public function schedule()
{
// if ($this->reports()->where(['year' => $year, 'week' => $week])->count() > 0) {
// return;
// }
if ($this->shouldSchedule()) {
$this->scheduleReport();
}
$this->mail_frequency = 'weekly';
$this->mail_day = 'friday';
$this->scheduleReport();
}
public function shouldSchedule(): bool
{
if (strtolower($this->mail_day) === strtolower(now()->englishDayOfWeek)) {
return strtolower($this->mail_frequency) === 'weekly' || now()->day <= 7;
}
return false;
}
public function hasInvalidMosaicFor($year, $week): bool
{
$dayOfWeekIso = Carbon::parse($this->mail_day)->dayOfWeekIso;
$min_updated_at_date = now()
->setISODate($year, $week)
->startOfWeek()
->addDays($dayOfWeekIso - 1)
->format('Y-m-d');
return $this->mosaics()
->where('updated_at', '>=', $min_updated_at_date)
->statusSuccess()
->where(['year' => $year, 'week' => $week])
->exists();
}
public function scheduleReport($year = null, $week = null)
{
// if year, week is in the future set year and week to null;
if (now()->year < $year || (now()->year === $year && now()->weekOfYear < $week)) {
$year = null;
$week = null;
logger('year and week is in the future default to now');
}
$year = $year ?? now()->year;
$week = $week ?? now()->weekOfYear;
Bus::chain([
Bus::batch(self::getFileDownloadsFor($year, $week)),
Bus::batch(self::getMosaicsFor($year, $week)),
Bus::batch(self::getReportFor($year, $week)),
])->dispatch();
Bus::batch($this->getFileDownloadsFor($year, $week)),
Bus::batch($this->getMosaicsFor($year, $week)),
Bus::batch($this->getReportFor($year, $week, true)),
])
->dispatch();
return "done";
}
public function getReportFor($year, $week)
public function getReportFor($year, $week, $sendMail = false)
{
$report = $this->reports()->create([
'name' => 'Report for week '.$week.' of '.$year,
'week' => $week,
@ -290,27 +331,30 @@ public function getReportFor($year, $week)
'path' => 'reports/week_'.$week.'_'.$year.'.docx',
]);
return new ProjectReportGeneratorJob($report);
return [new ProjectReportGeneratorJob($report, $sendMail)];
}
public function getFileDownloadsFor($year, $startWeekNumber)
{
$endOfRange = \Illuminate\Support\Carbon::now()->setISODate($year, $startWeekNumber)->endOfWeek();
$endOfRange = now()->setISODate($year, $startWeekNumber)->endOfWeek();
$startOfRange = (clone $endOfRange)->subWeeks(3)->startOfWeek();
$dateRange = CarbonPeriod::create($startOfRange, $endOfRange);
return collect($dateRange)
$return = collect($dateRange)
->map(fn($date) => ProjectDownloadTiffJob::handleForDate($this, $date))
->filter()
->toArray();
return $return;
}
public function getMosaicsFor($year, $startWeekNumber)
{
return collect(range(0, 3))
$return = collect(range(0, 3))
->map(fn($weekDiff) => ProjectMosiacGeneratorJob::handleFor($this, $year, $startWeekNumber - $weekDiff))
->filter()
->toArray();
return $return;
}
}

View file

@ -2,17 +2,19 @@
namespace App\Models;
use App\Traits\HasStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ProjectDownload extends Model
{
use HasFactory;
use HasStatus;
protected $fillable = [
'name',
'path',
'status',
];
public function project(): \Illuminate\Database\Eloquent\Relations\BelongsTo

View file

@ -2,6 +2,8 @@
namespace App\Models;
use App\Traits\HasStatus;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
@ -12,6 +14,7 @@
class ProjectMailing extends Model
{
use HasFactory;
use HasStatus;
protected $fillable = [
'subject',
@ -51,4 +54,13 @@ public function attachments()
{
return $this->hasMany(ProjectMailingAttachment::class);
}
protected function subject(): Attribute
{
return Attribute::make(
set: function ($value) {
return str_replace(['{date}'], [now()->toDateString()], $value);
}
);
}
}

View file

@ -8,12 +8,11 @@
class ProjectMosaic extends Model
{
use HasFactory;
use \App\Traits\HasStatus;
protected $fillable = [
'name',
'path',
'status',
'year',
'week',
];

View file

@ -4,6 +4,7 @@
use App\Jobs\ProjectDownloadTiffJob;
use App\Jobs\ProjectMosiacGeneratorJob;
use App\Traits\HasStatus;
use Carbon\CarbonPeriod;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -12,19 +13,10 @@
class ProjectReport extends Model
{
protected $fillable = ['name', 'path', 'week', 'year', 'status'];
use HasStatus;
protected $fillable = ['name', 'path', 'week', 'year'];
public function setStatusSuccess()
{
$this->status = 'success';
$this->save();
}
public function setStatusFailed()
{
$this->status = 'failed';
$this->save();
}
public function project()
{

View file

@ -2,9 +2,11 @@
namespace App\Providers;
use App\Listeners\UpdateMailingStatus;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Mail\Events\MessageSent;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
@ -18,6 +20,9 @@ class EventServiceProvider extends ServiceProvider
Registered::class => [
SendEmailVerificationNotification::class,
],
MessageSent::class => [
UpdateMailingStatus::class,
],
];
/**

View file

@ -0,0 +1,67 @@
<?php
namespace App\Traits;
use App\Enums\Status;
use Illuminate\Database\Eloquent\Builder;
trait HasStatus
{
public function setStatus(string $status)
{
if (!Status::isValid($status)) {
throw new \InvalidArgumentException("Invalid status");
}
$this->status = $status;
}
public function setStatusSuccess()
{
$this->status = Status::Success;
$this->save();
}
public function setStatusFailed()
{
$this->status = Status::Failed;
$this->save();
}
public function setStatusPending()
{
$this->status = Status::Pending;
$this->save();
}
public function scopeStatusSuccess(Builder $query): Builder
{
return $query->where('status', Status::Success);
}
public function scopeStatusFailed(Builder $query): Builder
{
return $query->where('status', Status::Failed);
}
public function scopeStatusPending(Builder $query): Builder
{
return $query->where('status', Status::Pending);
}
public function statusIsSuccessful(): bool
{
return $this->status === Status::Success;
}
public function statusHasFailed(): bool
{
return $this->status === Status::Failed;
}
public function statusIsPending(): bool
{
return $this->status === Status::Pending;
}
}

View file

@ -1,5 +1,6 @@
<?php
use App\Enums\Status;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
@ -16,7 +17,7 @@ public function up(): void
$table->foreignId('project_id');
$table->string('subject');
$table->text('message');
$table->string('status')->default('pending');
$table->string('status')->default(Status::Pending);
// $table->text('recipients');
$table->timestamps();
});

View file

@ -62,13 +62,13 @@ private function createChembaProject()
]);
foreach ($chembaProject->getMergedTiffList() as $mergedTiff) {
$chembaProject->downloads()->create([
$download = $chembaProject->downloads()->create([
'name' => basename($mergedTiff),
'path' => $mergedTiff,
'status' => 'completed',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00'
]);
$download->setStatusSuccess();
}
$chembaProject->emailRecipients()->createMany([
@ -89,19 +89,20 @@ private function createChembaProject()
[
'subject' => 'Chemba 2021-01-01',
'message' => 'Chemba 2021-01-01',
'status' => 'completed', // 'pending
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
], [
'subject' => 'Chemba 2021-01-08',
'message' => 'Chemba 2021-01-08',
'status' => 'completed',
'created_at' => '2021-01-08 00:00:00',
'updated_at' => '2021-01-08 00:00:00',
],
]);
foreach ($chembaProject->mailings as $mailing) {
$mailing->setStatusSuccess();
$mailing->recipients()->createMany([
[
'name' => 'Martin Folkerts',
@ -184,19 +185,18 @@ private function createXinavaneProject()
[
'subject' => 'Xinavane 2021-01-01',
'message' => 'Xinavane 2021-01-01',
'status' => 'completed',
'created_at' => '2021-01-01 00:00:00',
'updated_at' => '2021-01-01 00:00:00',
], [
'subject' => 'Xinavane 2021-01-08',
'message' => 'Xinavane 2021-01-08',
'status' => 'completed',
'created_at' => '2021-01-08 00:00:00',
'updated_at' => '2021-01-08 00:00:00',
],
]);
foreach ($project->mailings as $mailing) {
$mailing->setStatusSuccess();
$mailing->recipients()->createMany([
[
'name' => 'Martin Folkerts',

View file

@ -1,5 +1,5 @@
@props([
'status' => 'success',
'status' => \App\Enums\Status::Success,
])
<?php
@ -12,14 +12,18 @@
'pending' => ['bg' => 'bg-gray-100', 'text' => 'text-gray-600'],
];
// If status is an object, try to convert it to a string
if (is_object($status)) {
$status = (string) $status;
}
// Default to 'success' if the provided status is not in the defined array
if (!array_key_exists($status, $statusToColors)) {
$status = 'success';
$status = \App\Enums\Status::Success;
}
// Get the color class for the given status
$colorClasses = $statusToColors[$status];
?>
<span {{ $attributes }} class="inline-flex items-center rounded-md {{ $colorClasses['bg'] }} px-1.5 py-0.5 text-xs font-medium {{ $colorClasses['text'] }}">

View file

@ -1,7 +1,7 @@
<tr>
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 lg:pl-8">{{ $report->name }}</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
@if($report->status == 'pending')
@if($report->status == \App\Enums\Status::Pending)
<x-badge status="pending" wire:poll.1s=""></x-badge>
@else
<x-badge :status="$report->status"></x-badge>

View file

@ -9,7 +9,7 @@
<h1 class="text-base font-semibold leading-6 text-gray-900">Downloads</h1>
<p class="mt-2 text-sm text-gray-700">
@if ($project->hasPendingDownload())
Pending downloads for this project: {{ $project->downloads()->where('status', 'pending')->count() }}
Pending downloads for this project: {{ $project->downloads()->statusPending()->count() }}
@endif
</p>
</div>

View file

@ -58,8 +58,8 @@ public function it_can_get_the_full_path_name()
'year' => 2021,
'week' => 41,
'path' => 'path/doc.pdf',
'status' => 'success',
]);
$projectReport->setStatusSuccess();
$this->assertEquals(storage_path('app/project_download_path/path/doc.pdf'), $projectReport->getFullPathName());
}
@ -79,8 +79,8 @@ public function it_can_return_the_reportDate($expected, $mail_day, $week, $year)
'year' => $year,
'week' => $week,
'path' => 'path/doc.pdf',
'status' => 'success',
]);
$projectReport->setStatusSuccess();
$this->assertEquals($expected, $projectReport->getReportDate());
}

Binary file not shown.