Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.11% covered (success)
98.11%
52 / 53
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
ChangeListingStageAction
98.11% covered (success)
98.11%
52 / 53
50.00% covered (danger)
50.00%
1 / 2
14
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
98.08% covered (success)
98.08%
51 / 52
0.00% covered (danger)
0.00%
0 / 1
13
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Owners\Application\Actions;
6
7use App\Models\User;
8use Illuminate\Support\Facades\DB;
9use ImovelZapAi\Owners\Application\Services\DefaultListingPipelineSeeder;
10use ImovelZapAi\Owners\Application\Services\ListingHistoryWriter;
11use ImovelZapAi\Owners\Domain\Enums\ListingHistoryEvent;
12use ImovelZapAi\Owners\Domain\Enums\ListingStageKey;
13use ImovelZapAi\Owners\Domain\Events\ListingLost;
14use ImovelZapAi\Owners\Domain\Events\ListingPublished;
15use ImovelZapAi\Owners\Domain\Exceptions\OwnerException;
16use ImovelZapAi\Owners\Infrastructure\Persistence\Eloquent\ListingOpportunityModel;
17use ImovelZapAi\Owners\Infrastructure\Persistence\Eloquent\ListingStageModel;
18
19final class ChangeListingStageAction
20{
21    public function __construct(
22        private readonly DefaultListingPipelineSeeder $pipelineSeeder,
23        private readonly ListingHistoryWriter $history,
24    ) {}
25
26    public function execute(
27        ListingOpportunityModel $opportunity,
28        User $actor,
29        string $stageKey,
30        ?string $lostReason = null,
31    ): ListingOpportunityModel {
32        if ($opportunity->isClosed() && ! in_array($stageKey, [
33            ListingStageKey::Published->value,
34            ListingStageKey::Lost->value,
35        ], true)) {
36            throw OwnerException::invalidState('oportunidade já encerrada.');
37        }
38
39        $from = $opportunity->stage ?? ListingStageModel::query()->find($opportunity->stage_id);
40        $to = $this->pipelineSeeder->stageByKey($opportunity->tenant_id, $stageKey, $opportunity->pipeline_id);
41
42        $fromKey = $from?->key instanceof ListingStageKey ? $from->key->value : (string) ($from?->key ?? '');
43        $toKey = $to->key instanceof ListingStageKey ? $to->key->value : (string) $to->key;
44
45        if ($from !== null && $from->id === $to->id) {
46            return $opportunity;
47        }
48
49        return DB::transaction(function () use ($opportunity, $actor, $from, $to, $fromKey, $toKey, $lostReason): ListingOpportunityModel {
50            $opportunity->forceFill([
51                'stage_id' => $to->id,
52                'stage_entered_at' => now(),
53                'last_activity_at' => now(),
54                'closed_at' => $to->isTerminal() ? now() : null,
55                'lost_at' => $to->is_lost ? now() : null,
56                'lost_reason' => $to->is_lost ? $lostReason : null,
57            ])->save();
58
59            $this->history->write(
60                $opportunity->tenant_id,
61                ListingHistoryEvent::StageChanged,
62                opportunityId: $opportunity->id,
63                ownerId: $opportunity->owner_id,
64                actorUserId: (int) $actor->id,
65                before: ['stage' => $fromKey],
66                after: ['stage' => $toKey],
67                message: 'Estágio: '.($from?->name ?? '?').' → '.$to->name,
68            );
69
70            if ($to->is_won || $toKey === ListingStageKey::Published->value) {
71                $this->history->write(
72                    $opportunity->tenant_id,
73                    ListingHistoryEvent::Published,
74                    opportunityId: $opportunity->id,
75                    ownerId: $opportunity->owner_id,
76                    actorUserId: (int) $actor->id,
77                    message: 'Captação publicada.',
78                );
79                ListingPublished::dispatch($opportunity->tenant_id, $opportunity->id);
80            }
81
82            if ($to->is_lost) {
83                $this->history->write(
84                    $opportunity->tenant_id,
85                    ListingHistoryEvent::Lost,
86                    opportunityId: $opportunity->id,
87                    ownerId: $opportunity->owner_id,
88                    actorUserId: (int) $actor->id,
89                    message: $lostReason ?? 'Captação perdida.',
90                );
91                ListingLost::dispatch($opportunity->tenant_id, $opportunity->id, $lostReason);
92            }
93
94            return $opportunity->fresh(['stage', 'owner', 'property']);
95        });
96    }
97}