SettlementService::deleteSettlement()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
nc 3
nop 3
dl 0
loc 13
rs 10
c 2
b 0
f 0
1
<?php
2
3
namespace Siak\Tontine\Service\Meeting\Charge;
4
5
use Illuminate\Support\Collection;
6
use Illuminate\Support\Facades\DB;
7
use Siak\Tontine\Exception\MessageException;
8
use Siak\Tontine\Model\Charge;
9
use Siak\Tontine\Model\Fund;
10
use Siak\Tontine\Model\ProfitTransfer;
11
use Siak\Tontine\Model\Round;
12
use Siak\Tontine\Model\Session;
13
use Siak\Tontine\Model\Settlement;
14
use Siak\Tontine\Service\Payment\PaymentServiceInterface;
15
use Siak\Tontine\Service\TenantService;
16
use Siak\Tontine\Validation\SearchSanitizer;
17
18
use function collect;
19
use function trans;
20
21
class SettlementService
22
{
23
    /**
24
     * @param TenantService $tenantService
25
     * @param SearchSanitizer $searchSanitizer
26
     * @param BillService $billService
27
     * @param PaymentServiceInterface $paymentService;
28
     */
29
    public function __construct(private TenantService $tenantService,
30
        private SearchSanitizer $searchSanitizer, private BillService $billService,
31
        private PaymentServiceInterface $paymentService)
32
    {}
33
34
    /**
35
     * @param Round $round
36
     *
37
     * @return Collection
38
     */
39
    public function getFunds(Round $round): Collection
40
    {
41
        return $round->funds()->real()
42
            ->join('fund_defs', 'fund_defs.id', '=', 'funds.def_id')
43
            ->orderBy('fund_defs.type') // The default fund is first in the list.
44
            ->orderBy('funds.id')
45
            ->get()
46
            ->pluck('title', 'id');
47
    }
48
49
    /**
50
     * @param Round $round
51
     * @param int $fundId
52
     *
53
     * @return Fund|null
54
     */
55
    public function getFund(Round $round, int $fundId): Fund|null
56
    {
57
        return $round->funds()->real()->find($fundId);
58
    }
59
60
    /**
61
     * @param Collection $bills
62
     * @param Session $session
63
     * @param Fund|null $fund
64
     *
65
     * @return int
66
     */
67
    private function _createSettlements(Collection $bills, Session $session, Fund|null $fund): int
68
    {
69
        // Todo: use one insert query
70
        return DB::transaction(function() use($bills, $session, $fund) {
71
            $balances = $fund === null ? null:
72
                ProfitTransfer::query()
73
                    ->whereFund($fund)
74
                    ->whereIn('member_id', $bills->pluck('member_id'))
75
                    ->select('member_id', DB::raw('sum(amount*coef) as value'))
76
                    ->groupBy('member_id')
77
                    ->get()
78
                    ->pluck('value', 'member_id');
79
            $cancelled = 0;
80
81
            foreach($bills as $bill)
82
            {
83
                $settlement = new Settlement();
84
                $settlement->bill()->associate($bill);
85
                $settlement->session()->associate($session);
86
                if($fund !== null)
87
                {
88
                    // When the settlement amount is transfered from a fund,
89
                    // skip the bill if the fund doesn't have enough balance.
90
                    if($bill->amount > ($balances[$bill->member_id] ?? 0))
91
                    {
92
                        $cancelled++;
93
                        continue;
94
                    }
95
96
                    $settlement->fund()->associate($fund);
97
                }
98
                $settlement->save();
99
            }
100
101
            return $cancelled;
102
        });
103
    }
104
105
    /**
106
     * Create a settlement
107
     *
108
     * @param Charge $charge
109
     * @param Session $session
110
     * @param int $billId
111
     * @param Fund|null $fund
112
     *
113
     * @return int
114
     */
115
    public function createSettlement(Charge $charge, Session $session,
116
        int $billId, Fund|null $fund = null): int
117
    {
118
        $bill = $this->billService->getBill($charge, $session, $billId);
119
        // Return if the bill is not found or the bill is already settled.
120
        if(!$bill || ($bill->settlement))
121
        {
122
            throw new MessageException(trans('tontine.bill.errors.not_found'));
123
        }
124
125
        return $this->_createSettlements(collect([$bill]), $session, $fund);
0 ignored issues
show
Bug introduced by
array($bill) of type array<integer,Siak\Tontine\Model\Bill> is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

125
        return $this->_createSettlements(collect(/** @scrutinizer ignore-type */ [$bill]), $session, $fund);
Loading history...
126
    }
127
128
    /**
129
     * Delete a settlement
130
     *
131
     * @param Charge $charge
132
     * @param Session $session
133
     * @param int $Id
134
     *
135
     * @return void
136
     */
137
    public function deleteSettlement(Charge $charge, Session $session, int $billId): void
138
    {
139
        $bill = $this->billService->getBill($charge, $session, $billId);
140
        // Return if the bill is not found or the bill is not settled.
141
        if(!$bill || !($bill->settlement))
142
        {
143
            throw new MessageException(trans('tontine.bill.errors.not_found'));
144
        }
145
        if((!$this->paymentService->isEditable($bill->settlement)))
146
        {
147
            throw new MessageException(trans('tontine.errors.editable'));
148
        }
149
        $bill->settlement()->where('session_id', $session->id)->delete();
150
    }
151
152
    /**
153
     * Create a settlement for all unpaid bills
154
     *
155
     * @param Charge $charge
156
     * @param Session $session
157
     * @param string $search
158
     * @param Fund|null $fund
159
     *
160
     * @return int
161
     */
162
    public function createAllSettlements(Charge $charge, Session $session,
163
        string $search, Fund|null $fund = null): int
164
    {
165
        $bills = $this->billService->getBills($charge, $session, $search, false);
166
        return $bills->count() === 0 ? 0 :
167
            $this->_createSettlements($bills, $session, $fund);
168
    }
169
170
    /**
171
     * Delete all settlements
172
     *
173
     * @param Charge $charge
174
     * @param Session $session
175
     * @param string $search
176
     *
177
     * @return void
178
     */
179
    public function deleteAllSettlements(Charge $charge, Session $session, string $search): void
180
    {
181
        $bills = $this->billService->getBills($charge, $session, $search, true)
182
            ->filter(fn($bill) => $this->paymentService->isEditable($bill->settlement));
183
        if($bills->count() > 0)
184
        {
185
            Settlement::whereIn('bill_id', $bills->pluck('id'))
186
                ->where('session_id', $session->id)
187
                ->delete();
188
        }
189
    }
190
191
    /**
192
     * @param Charge $charge
193
     * @param Session $session
194
     *
195
     * @return array<int>
196
     */
197
    public function getSettlementTotal(Charge $charge, Session $session): array
198
    {
199
        $total = DB::table('v_settlements')
200
            ->where('session_id', $session->id)
201
            ->where('charge_id', $charge->id)
202
            ->select(DB::raw('count(*) as count'), DB::raw('sum(amount) as amount'))
203
            ->first();
204
        return [$total->count ?? 0, $total->amount ?? 0];
205
    }
206
}
207