Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.11% covered (success)
99.11%
111 / 112
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
SettlementService
99.11% covered (success)
99.11%
111 / 112
66.67% covered (warning)
66.67%
2 / 3
7
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
 generate
98.63% covered (success)
98.63%
72 / 73
0.00% covered (danger)
0.00%
0 / 1
4
 markItemPaid
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace App\Services;
4
5use App\Enums\CommissionStatus;
6use App\Enums\PaymentStatus;
7use App\Enums\SettlementStatus;
8use App\Models\Commission;
9use App\Models\CommissionAdjustment;
10use App\Models\Influencer;
11use App\Models\MonthlySettlement;
12use App\Models\PaymentRecord;
13use App\Models\SettlementItem;
14use App\Models\Store;
15use App\Models\User;
16use Carbon\CarbonImmutable;
17use Illuminate\Support\Facades\DB;
18
19class SettlementService
20{
21    public function __construct(
22        private readonly CommissionCalculatorService $commissionCalculator,
23        private readonly GamificationService $gamificationService,
24    ) {
25    }
26
27    public function generate(Store $store, int $year, int $month, ?User $admin = null): MonthlySettlement
28    {
29        $start = CarbonImmutable::create($year, $month, 1)->startOfMonth();
30        $end = $start->endOfMonth();
31
32        $this->commissionCalculator->approveEligibleForecasted();
33
34        return DB::transaction(function () use ($store, $year, $month, $start, $end, $admin) {
35            $settlement = MonthlySettlement::updateOrCreate(
36                ['store_id' => $store->id, 'period_year' => $year, 'period_month' => $month],
37                [
38                    'tenant_id' => $store->tenant_id,
39                    'period_start' => $start->toDateString(),
40                    'period_end' => $end->toDateString(),
41                    'status' => SettlementStatus::Draft->value,
42                    'generated_by' => $admin?->id,
43                ]
44            );
45
46            $settlement->items()->delete();
47
48            $influencers = Influencer::query()->where('store_id', $store->id)->get();
49            $grossTotal = 0.0;
50            $adjustmentTotal = 0.0;
51
52            foreach ($influencers as $influencer) {
53                $this->gamificationService->evaluatePeriod($influencer, $year, $month);
54
55                $commissions = Commission::query()
56                    ->where('store_id', $store->id)
57                    ->where('influencer_id', $influencer->id)
58                    ->where('status', CommissionStatus::Approved->value)
59                    ->whereHas('order', function ($query) use ($start, $end) {
60                        $query->whereBetween('paid_at', [$start->startOfDay(), $end->endOfDay()]);
61                    })
62                    ->get();
63
64                $gross = (float) $commissions->sum('commission_amount');
65
66                $adjustments = CommissionAdjustment::query()
67                    ->where('store_id', $store->id)
68                    ->where('influencer_id', $influencer->id)
69                    ->whereNull('settlement_id')
70                    ->whereNotNull('approved_at')
71                    ->get();
72
73                $adjustmentAmount = (float) $adjustments->sum('amount');
74                $net = round($gross + $adjustmentAmount, 2);
75
76                if ($gross == 0.0 && $adjustmentAmount == 0.0) {
77                    continue;
78                }
79
80                SettlementItem::create([
81                    'settlement_id' => $settlement->id,
82                    'tenant_id' => $store->tenant_id,
83                    'store_id' => $store->id,
84                    'influencer_id' => $influencer->id,
85                    'gross_amount' => $gross,
86                    'adjustments_amount' => $adjustmentAmount,
87                    'net_amount' => $net,
88                    'payment_status' => PaymentStatus::Pending->value,
89                    'metadata' => [
90                        'commission_ids' => $commissions->pluck('id')->all(),
91                        'adjustment_ids' => $adjustments->pluck('id')->all(),
92                    ],
93                ]);
94
95                Commission::query()
96                    ->whereIn('id', $commissions->pluck('id'))
97                    ->update([
98                        'status' => CommissionStatus::Released->value,
99                        'released_at' => now(),
100                        'settlement_id' => $settlement->id,
101                        'updated_at' => now(),
102                    ]);
103
104                CommissionAdjustment::query()
105                    ->whereIn('id', $adjustments->pluck('id'))
106                    ->update(['settlement_id' => $settlement->id, 'updated_at' => now()]);
107
108                $grossTotal += $gross;
109                $adjustmentTotal += $adjustmentAmount;
110            }
111
112            $settlement->update([
113                'gross_amount' => $grossTotal,
114                'adjustments_amount' => $adjustmentTotal,
115                'net_amount' => round($grossTotal + $adjustmentTotal, 2),
116            ]);
117
118            return $settlement->fresh(['items.influencer']);
119        });
120    }
121
122    public function markItemPaid(SettlementItem $item, User $admin, ?string $reference = null): PaymentRecord
123    {
124        return DB::transaction(function () use ($item, $admin, $reference) {
125            $item->update([
126                'payment_status' => PaymentStatus::Paid->value,
127                'paid_at' => now(),
128                'paid_by' => $admin->id,
129                'payment_reference' => $reference,
130            ]);
131
132            Commission::query()
133                ->where('settlement_id', $item->settlement_id)
134                ->where('influencer_id', $item->influencer_id)
135                ->where('status', CommissionStatus::Released->value)
136                ->update([
137                    'status' => CommissionStatus::Paid->value,
138                    'paid_at' => now(),
139                    'updated_at' => now(),
140                ]);
141
142            $record = PaymentRecord::create([
143                'tenant_id' => $item->tenant_id,
144                'store_id' => $item->store_id,
145                'influencer_id' => $item->influencer_id,
146                'settlement_id' => $item->settlement_id,
147                'settlement_item_id' => $item->id,
148                'amount' => $item->net_amount,
149                'status' => PaymentStatus::Paid->value,
150                'pix_key' => $item->influencer?->pix_key,
151                'paid_at' => now(),
152                'paid_by' => $admin->id,
153                'reference' => $reference,
154                'notes' => 'Pagamento manual registrado pelo painel administrativo.',
155            ]);
156
157            $pendingItems = SettlementItem::query()
158                ->where('settlement_id', $item->settlement_id)
159                ->where('payment_status', PaymentStatus::Pending->value)
160                ->count();
161
162            if ($pendingItems === 0) {
163                $item->settlement->update(['status' => SettlementStatus::Paid->value]);
164            }
165
166            return $record;
167        });
168    }
169}