Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 132
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
ProjectDomainEventToAnalytics
0.00% covered (danger)
0.00%
0 / 132
0.00% covered (danger)
0.00%
0 / 14
1892
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 fromDomainEvent
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 extractContext
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 1
90
 genericContext
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 dealUserId
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 dealMeta
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 dealAmount
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 visitUserId
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 visitMeta
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 paymentUserId
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 paymentAmount
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 paymentMeta
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 commissionUserId
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 commissionAmount
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Analytics\Infrastructure\Projectors;
6
7use ImovelZapAi\Analytics\Application\Actions\RecordAnalyticsFactAction;
8use ImovelZapAi\Analytics\Domain\Enums\AnalyticsEventKey;
9use ImovelZapAi\Financial\Domain\Events\CommissionCalculated;
10use ImovelZapAi\Financial\Domain\Events\PaymentReceived;
11use ImovelZapAi\Financial\Infrastructure\Persistence\Eloquent\FinancialCommissionModel;
12use ImovelZapAi\Financial\Infrastructure\Persistence\Eloquent\FinancialPaymentModel;
13use ImovelZapAi\Sales\Domain\Events\DealCreated;
14use ImovelZapAi\Sales\Domain\Events\DealStageChanged;
15use ImovelZapAi\Sales\Domain\Events\DealWon;
16use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\DealModel;
17use ImovelZapAi\Signatures\Domain\Events\SignatureCompleted;
18use ImovelZapAi\Signatures\Domain\Events\SignatureRequested;
19use ImovelZapAi\Visits\Domain\Events\VisitCompleted;
20use ImovelZapAi\Visits\Domain\Events\VisitConfirmed;
21use ImovelZapAi\Visits\Domain\Events\VisitCreated;
22use ImovelZapAi\Visits\Infrastructure\Persistence\Eloquent\VisitModel;
23use Illuminate\Support\Str;
24
25final class ProjectDomainEventToAnalytics
26{
27    public function __construct(
28        private readonly RecordAnalyticsFactAction $recordFact,
29    ) {}
30
31    public function fromDomainEvent(object $event): void
32    {
33        $eventKey = AnalyticsEventKey::tryFromEvent($event);
34        if ($eventKey === null) {
35            return;
36        }
37
38        [$tenantId, $entityType, $entityId, $userId, $amount, $meta] = $this->extractContext($event, $eventKey);
39
40        if ($tenantId === '') {
41            return;
42        }
43
44        $this->recordFact->execute(
45            $tenantId,
46            $eventKey,
47            null,
48            $entityType,
49            $entityId,
50            $userId,
51            $amount,
52            $meta,
53        );
54    }
55
56    /** @return array{0: string, 1: ?string, 2: ?string, 3: ?int, 4: ?string, 5: array<string, mixed>} */
57    private function extractContext(object $event, AnalyticsEventKey $eventKey): array
58    {
59        $meta = [];
60
61        return match (true) {
62            $event instanceof DealCreated => [
63                $event->tenantId,
64                'deal',
65                $event->dealId,
66                $this->dealUserId($event->dealId),
67                null,
68                $this->dealMeta($event->dealId),
69            ],
70            $event instanceof DealStageChanged => [
71                $event->tenantId,
72                'deal',
73                $event->dealId,
74                $this->dealUserId($event->dealId),
75                null,
76                [
77                    'from_stage' => $event->fromStageKey,
78                    'to_stage' => $event->toStageKey,
79                ],
80            ],
81            $event instanceof DealWon => [
82                $event->tenantId,
83                'deal',
84                $event->dealId,
85                $this->dealUserId($event->dealId),
86                $this->dealAmount($event->dealId),
87                $this->dealMeta($event->dealId),
88            ],
89            $event instanceof VisitCreated,
90            $event instanceof VisitConfirmed,
91            $event instanceof VisitCompleted => [
92                $event->tenantId,
93                'visit',
94                $event->visitId,
95                $this->visitUserId($event->visitId),
96                null,
97                $this->visitMeta($event->visitId),
98            ],
99            $event instanceof SignatureRequested,
100            $event instanceof SignatureCompleted => [
101                $event->tenantId,
102                'signature_request',
103                $event->signatureRequestId,
104                null,
105                null,
106                [],
107            ],
108            $event instanceof PaymentReceived => [
109                $event->tenantId,
110                'payment',
111                $event->id,
112                $this->paymentUserId($event->id),
113                $this->paymentAmount($event->id),
114                $this->paymentMeta($event->id),
115            ],
116            $event instanceof CommissionCalculated => [
117                $event->tenantId,
118                'commission',
119                $event->id,
120                $this->commissionUserId($event->id),
121                $this->commissionAmount($event->id),
122                [],
123            ],
124            default => $this->genericContext($event, $eventKey),
125        };
126    }
127
128    /** @return array{0: string, 1: ?string, 2: ?string, 3: ?int, 4: ?string, 5: array<string, mixed>} */
129    private function genericContext(object $event, AnalyticsEventKey $eventKey): array
130    {
131        $tenantId = property_exists($event, 'tenantId') ? (string) $event->tenantId : '';
132        $entityId = null;
133
134        foreach (['documentId', 'workflowId', 'executionId', 'requestedDocumentId', 'signatureRequestId', 'visitId', 'dealId'] as $prop) {
135            if (property_exists($event, $prop)) {
136                $entityId = (string) $event->{$prop};
137                break;
138            }
139        }
140
141        return [$tenantId, $eventKey->value, $entityId, null, null, []];
142    }
143
144    private function dealUserId(string $dealId): ?int
145    {
146        if (! Str::isUuid($dealId)) {
147            return null;
148        }
149
150        return DealModel::query()->find($dealId)?->responsible_user_id;
151    }
152
153    /** @return array<string, mixed> */
154    private function dealMeta(string $dealId): array
155    {
156        if (! Str::isUuid($dealId)) {
157            return [];
158        }
159
160        $deal = DealModel::query()->find($dealId);
161        if ($deal === null) {
162            return [];
163        }
164
165        return array_filter([
166            'amount' => $deal->amount,
167        ], static fn ($v) => $v !== null);
168    }
169
170    private function dealAmount(string $dealId): ?string
171    {
172        if (! Str::isUuid($dealId)) {
173            return null;
174        }
175
176        $amount = DealModel::query()->find($dealId)?->amount;
177
178        return $amount !== null ? (string) $amount : null;
179    }
180
181    private function visitUserId(string $visitId): ?int
182    {
183        if (! Str::isUuid($visitId)) {
184            return null;
185        }
186
187        return VisitModel::query()->find($visitId)?->responsible_user_id;
188    }
189
190    /** @return array<string, mixed> */
191    private function visitMeta(string $visitId): array
192    {
193        if (! Str::isUuid($visitId)) {
194            return [];
195        }
196
197        return VisitModel::query()->find($visitId) !== null
198            ? ['source' => 'whatsapp']
199            : [];
200    }
201
202    private function paymentUserId(string $paymentId): ?int
203    {
204        if (! Str::isUuid($paymentId)) {
205            return null;
206        }
207
208        return FinancialPaymentModel::query()->find($paymentId)?->created_by;
209    }
210
211    private function paymentAmount(string $paymentId): ?string
212    {
213        if (! Str::isUuid($paymentId)) {
214            return null;
215        }
216
217        $amount = FinancialPaymentModel::query()->find($paymentId)?->amount;
218
219        return $amount !== null ? (string) $amount : null;
220    }
221
222    /** @return array<string, mixed> */
223    private function paymentMeta(string $paymentId): array
224    {
225        if (! Str::isUuid($paymentId)) {
226            return [];
227        }
228
229        $payment = FinancialPaymentModel::query()->find($paymentId);
230
231        return $payment ? ['amount' => $payment->amount] : [];
232    }
233
234    private function commissionUserId(string $commissionId): ?int
235    {
236        if (! Str::isUuid($commissionId)) {
237            return null;
238        }
239
240        $commission = FinancialCommissionModel::query()->with('deal')->find($commissionId);
241
242        return $commission?->deal?->responsible_user_id;
243    }
244
245    private function commissionAmount(string $commissionId): ?string
246    {
247        if (! Str::isUuid($commissionId)) {
248            return null;
249        }
250
251        $amount = FinancialCommissionModel::query()->find($commissionId)?->total_amount;
252
253        return $amount !== null ? (string) $amount : null;
254    }
255}