Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.59% covered (success)
94.59%
35 / 37
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
RecordVisitOutcomeAction
94.59% covered (success)
94.59%
35 / 37
66.67% covered (warning)
66.67%
2 / 3
13.03
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
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
4
 feedFunnel
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
8.30
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Visits\Application\Actions;
6
7use App\Models\User;
8use Illuminate\Support\Facades\DB;
9use ImovelZapAi\Conversations\Application\Actions\UpdatePersonInterestStageAction;
10use ImovelZapAi\Properties\Domain\Enums\PropertyInterestStatus;
11use ImovelZapAi\Properties\Infrastructure\Persistence\Eloquent\PropertyInterestModel;
12use ImovelZapAi\Visits\Application\Services\VisitLogWriter;
13use ImovelZapAi\Visits\Application\Services\VisitSnapshotter;
14use ImovelZapAi\Visits\Domain\Enums\VisitLogEvent;
15use ImovelZapAi\Visits\Domain\Enums\VisitOutcome;
16use ImovelZapAi\Visits\Domain\Enums\VisitStatus;
17use ImovelZapAi\Visits\Domain\Events\VisitCompleted;
18use ImovelZapAi\Visits\Domain\Exceptions\VisitException;
19use ImovelZapAi\Visits\Infrastructure\Persistence\Eloquent\VisitModel;
20
21final class RecordVisitOutcomeAction
22{
23    public function __construct(
24        private readonly VisitLogWriter $logWriter,
25        private readonly VisitSnapshotter $snapshotter,
26        private readonly UpdatePersonInterestStageAction $updateInterestStage,
27    ) {}
28
29    public function execute(
30        VisitModel $visit,
31        User $actor,
32        VisitOutcome $outcome,
33        ?string $notes = null,
34    ): VisitModel {
35        if (! $visit->status->canRecordOutcome() && $visit->status !== VisitStatus::Confirmed && $visit->status !== VisitStatus::Scheduled) {
36            throw VisitException::invalidState($visit->status->value, 'record_outcome');
37        }
38
39        return DB::transaction(function () use ($visit, $actor, $outcome, $notes): VisitModel {
40            $before = $this->snapshotter->snapshot($visit);
41
42            $visit->forceFill([
43                'status' => VisitStatus::Completed,
44                'outcome' => $outcome,
45                'outcome_notes' => $notes,
46                'completed_at' => now(),
47                'check_out_at' => $visit->check_out_at ?? now(),
48            ])->save();
49
50            $this->feedFunnel($visit, $outcome);
51
52            $this->logWriter->write(
53                $visit->tenant_id,
54                $visit->id,
55                VisitLogEvent::OutcomeRecorded,
56                actorUserId: (int) $actor->id,
57                before: $before,
58                after: $this->snapshotter->snapshot($visit->fresh()),
59                message: 'Resultado da visita registrado.',
60            );
61
62            VisitCompleted::dispatch($visit->tenant_id, $visit->id, $outcome->value);
63
64            return $visit->fresh(['propertyInterest']);
65        });
66    }
67
68    private function feedFunnel(VisitModel $visit, VisitOutcome $outcome): void
69    {
70        if ($visit->property_interest_id === null) {
71            return;
72        }
73
74        $interest = PropertyInterestModel::query()->find($visit->property_interest_id);
75        if ($interest === null) {
76            return;
77        }
78
79        $next = match ($outcome) {
80            VisitOutcome::NegotiationStarted, VisitOutcome::WantsProposal => PropertyInterestStatus::Negotiating,
81            VisitOutcome::NotInterested => PropertyInterestStatus::Lost,
82            VisitOutcome::Interested, VisitOutcome::WantsAnotherProperty => PropertyInterestStatus::VisitScheduled,
83        };
84
85        if ($interest->status === $next || ($interest->status?->canTransitionTo($next) ?? false)) {
86            $this->updateInterestStage->execute($visit->tenant_id, $interest, $next);
87        }
88    }
89}