Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
62 / 62 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| CalculateCommissionAction | |
100.00% |
62 / 62 |
|
100.00% |
2 / 2 |
8 | |
100.00% |
1 / 1 |
| execute | |
100.00% |
30 / 30 |
|
100.00% |
1 / 1 |
3 | |||
| resolveSplits | |
100.00% |
32 / 32 |
|
100.00% |
1 / 1 |
5 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace ImovelZapAi\Financial\Application\Actions; |
| 6 | |
| 7 | use Illuminate\Support\Facades\DB; |
| 8 | use ImovelZapAi\Financial\Domain\Enums\BeneficiaryType; |
| 9 | use ImovelZapAi\Financial\Domain\Enums\CommissionBasis; |
| 10 | use ImovelZapAi\Financial\Domain\Enums\CommissionStatus; |
| 11 | use ImovelZapAi\Financial\Domain\Enums\SplitStatus; |
| 12 | use ImovelZapAi\Financial\Domain\Events\CommissionCalculated; |
| 13 | use ImovelZapAi\Financial\Infrastructure\Persistence\Eloquent\FinancialCommissionModel; |
| 14 | use ImovelZapAi\Financial\Infrastructure\Persistence\Eloquent\FinancialCommissionRuleModel; |
| 15 | use ImovelZapAi\Financial\Infrastructure\Persistence\Eloquent\FinancialCommissionSplitModel; |
| 16 | use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\DealModel; |
| 17 | |
| 18 | final class CalculateCommissionAction |
| 19 | { |
| 20 | public function execute( |
| 21 | string $tenantId, |
| 22 | DealModel $deal, |
| 23 | FinancialCommissionRuleModel $rule, |
| 24 | ): FinancialCommissionModel { |
| 25 | return DB::transaction(function () use ($tenantId, $deal, $rule): FinancialCommissionModel { |
| 26 | $baseAmount = (float) ($deal->amount ?? 0); |
| 27 | $totalAmount = $rule->basis === CommissionBasis::Percent |
| 28 | ? round($baseAmount * ((float) $rule->value / 100), 2) |
| 29 | : round((float) $rule->value, 2); |
| 30 | |
| 31 | $commission = FinancialCommissionModel::query()->create([ |
| 32 | 'tenant_id' => $tenantId, |
| 33 | 'deal_id' => $deal->id, |
| 34 | 'rule_id' => $rule->id, |
| 35 | 'base_amount' => $baseAmount, |
| 36 | 'total_amount' => $totalAmount, |
| 37 | 'status' => CommissionStatus::Calculated, |
| 38 | 'calculated_at' => now(), |
| 39 | ]); |
| 40 | |
| 41 | $splitDefinitions = $this->resolveSplits($deal, $rule); |
| 42 | |
| 43 | foreach ($splitDefinitions as $split) { |
| 44 | $amount = round($totalAmount * ($split['percent'] / 100), 2); |
| 45 | FinancialCommissionSplitModel::query()->create([ |
| 46 | 'tenant_id' => $tenantId, |
| 47 | 'commission_id' => $commission->id, |
| 48 | 'beneficiary_type' => $split['type'], |
| 49 | 'beneficiary_user_id' => $split['user_id'] ?? null, |
| 50 | 'beneficiary_label' => $split['label'], |
| 51 | 'percent' => $split['percent'], |
| 52 | 'amount' => $amount, |
| 53 | 'status' => SplitStatus::Pending, |
| 54 | ]); |
| 55 | } |
| 56 | |
| 57 | CommissionCalculated::dispatch($tenantId, (string) $commission->id); |
| 58 | |
| 59 | return $commission->load('splits'); |
| 60 | }); |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * @return list<array{type: BeneficiaryType, user_id?: int|null, label: string, percent: float}> |
| 65 | */ |
| 66 | private function resolveSplits(DealModel $deal, FinancialCommissionRuleModel $rule): array |
| 67 | { |
| 68 | if (is_array($rule->split_json) && $rule->split_json !== []) { |
| 69 | $splits = []; |
| 70 | foreach ($rule->split_json as $item) { |
| 71 | $type = BeneficiaryType::tryFrom((string) ($item['type'] ?? 'agency')) ?? BeneficiaryType::Agency; |
| 72 | $splits[] = [ |
| 73 | 'type' => $type, |
| 74 | 'user_id' => $item['user_id'] ?? null, |
| 75 | 'label' => (string) ($item['label'] ?? $type->label()), |
| 76 | 'percent' => (float) ($item['percent'] ?? 0), |
| 77 | ]; |
| 78 | } |
| 79 | |
| 80 | return $splits; |
| 81 | } |
| 82 | |
| 83 | if ($deal->responsible_user_id !== null) { |
| 84 | return [ |
| 85 | [ |
| 86 | 'type' => BeneficiaryType::Agency, |
| 87 | 'label' => 'Imobiliária', |
| 88 | 'percent' => 50.0, |
| 89 | ], |
| 90 | [ |
| 91 | 'type' => BeneficiaryType::User, |
| 92 | 'user_id' => $deal->responsible_user_id, |
| 93 | 'label' => 'Corretor responsável', |
| 94 | 'percent' => 50.0, |
| 95 | ], |
| 96 | ]; |
| 97 | } |
| 98 | |
| 99 | return [ |
| 100 | [ |
| 101 | 'type' => BeneficiaryType::Agency, |
| 102 | 'label' => 'Imobiliária', |
| 103 | 'percent' => 100.0, |
| 104 | ], |
| 105 | ]; |
| 106 | } |
| 107 | } |