Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.84% covered (success)
91.84%
45 / 49
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreateProposalVersionAction
91.84% covered (success)
91.84%
45 / 49
50.00% covered (danger)
50.00%
1 / 2
8.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
91.67% covered (success)
91.67%
44 / 48
0.00% covered (danger)
0.00%
0 / 1
7.03
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Proposals\Application\Actions;
6
7use App\Models\User;
8use Illuminate\Support\Facades\DB;
9use Illuminate\Support\Str;
10use ImovelZapAi\Proposals\Application\Services\ProposalHistoryWriter;
11use ImovelZapAi\Proposals\Application\Services\ProposalPdfGenerator;
12use ImovelZapAi\Proposals\Application\Services\ProposalTemplateRenderer;
13use ImovelZapAi\Proposals\Domain\Enums\ProposalHistoryEvent;
14use ImovelZapAi\Proposals\Domain\Enums\ProposalStatus;
15use ImovelZapAi\Proposals\Domain\Exceptions\ProposalException;
16use ImovelZapAi\Proposals\Infrastructure\Persistence\Eloquent\ProposalModel;
17use ImovelZapAi\Proposals\Infrastructure\Persistence\Eloquent\ProposalTemplateModel;
18use ImovelZapAi\Proposals\Infrastructure\Persistence\Eloquent\ProposalVersionModel;
19
20final class CreateProposalVersionAction
21{
22    public function __construct(
23        private readonly ProposalTemplateRenderer $renderer,
24        private readonly ProposalPdfGenerator $pdf,
25        private readonly ProposalHistoryWriter $history,
26    ) {}
27
28    /**
29     * @param  array{
30     *     amount?: float|string|null,
31     *     commission_percent?: float|string|null,
32     *     intro_message?: string|null,
33     *     title?: string|null,
34     *     change_summary?: string|null,
35     * }  $data
36     */
37    public function execute(ProposalModel $proposal, User $actor, array $data = []): ProposalVersionModel
38    {
39        if (in_array($proposal->status, [
40            \ImovelZapAi\Proposals\Domain\Enums\ProposalStatus::Accepted,
41            \ImovelZapAi\Proposals\Domain\Enums\ProposalStatus::Rejected,
42            \ImovelZapAi\Proposals\Domain\Enums\ProposalStatus::Expired,
43        ], true)) {
44            throw ProposalException::invalidState('proposta encerrada.');
45        }
46
47        return DB::transaction(function () use ($proposal, $actor, $data): ProposalVersionModel {
48            if (array_key_exists('amount', $data)) {
49                $proposal->amount = $data['amount'];
50            }
51            if (array_key_exists('commission_percent', $data)) {
52                $proposal->commission_percent = $data['commission_percent'];
53            }
54            if (array_key_exists('intro_message', $data)) {
55                $proposal->intro_message = $data['intro_message'];
56            }
57            if (! empty($data['title'])) {
58                $proposal->title = $data['title'];
59            }
60            if ($proposal->status === ProposalStatus::ChangesRequested) {
61                $proposal->status = ProposalStatus::Draft;
62            }
63            $proposal->save();
64
65            $proposal->load(['contact', 'property.neighborhood', 'property.city', 'responsible', 'template']);
66            $next = ((int) $proposal->versions()->max('version')) + 1;
67            $template = $proposal->template
68                ?? ProposalTemplateModel::query()->where('tenant_id', $proposal->tenant_id)->where('is_default', true)->firstOrFail();
69
70            $snapshot = $this->renderer->buildSnapshot($proposal, $next);
71            $html = $this->renderer->render($template, $proposal, $snapshot);
72
73            $version = ProposalVersionModel::query()->create([
74                'tenant_id' => $proposal->tenant_id,
75                'proposal_id' => $proposal->id,
76                'version' => $next,
77                'snapshot' => $snapshot,
78                'html_body' => $html,
79                'change_summary' => $data['change_summary'] ?? 'Nova revisão',
80                'validation_code' => Str::upper(Str::random(10)),
81                'created_by' => $actor->id,
82            ]);
83
84            $pdf = $this->pdf->generate($proposal, $version, '');
85            $version->forceFill(['pdf_disk' => $pdf['disk'], 'pdf_path' => $pdf['path']])->save();
86            $proposal->forceFill(['current_version_id' => $version->id])->save();
87
88            $this->history->write(
89                $proposal->tenant_id,
90                $proposal->id,
91                ProposalHistoryEvent::VersionCreated,
92                actorType: 'broker',
93                actorUserId: (int) $actor->id,
94                payload: ['version' => $next],
95                message: 'Versão v'.$next.' criada.',
96            );
97
98            return $version->fresh();
99        });
100    }
101}