RemitmentService::getPayables()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 20
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 29
rs 9.6
1
<?php
2
3
namespace Siak\Tontine\Service\Meeting\Pool;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Relations\Relation;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Facades\DB;
9
use Siak\Tontine\Exception\MessageException;
10
use Siak\Tontine\Model\Auction;
11
use Siak\Tontine\Model\Pool;
12
use Siak\Tontine\Model\Payable;
13
use Siak\Tontine\Model\Session;
14
use Siak\Tontine\Service\LocaleService;
15
use Siak\Tontine\Service\Meeting\Session\SummaryService;
16
use Siak\Tontine\Service\Payment\BalanceCalculator;
17
use Siak\Tontine\Service\TenantService;
18
use Siak\Tontine\Service\Planning\PoolService;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Siak\Tontine\Service\Meeting\Pool\PoolService. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
19
20
use function trans;
21
22
class RemitmentService
23
{
24
    /**
25
     * @param BalanceCalculator $balanceCalculator
26
     * @param LocaleService $localeService
27
     * @param TenantService $tenantService
28
     * @param PoolService $poolService
29
     * @param SummaryService $summaryService
30
     */
31
    public function __construct(private BalanceCalculator $balanceCalculator,
32
        private LocaleService $localeService, private TenantService $tenantService,
33
        private PoolService $poolService, private SummaryService $summaryService)
34
    {}
35
36
    /**
37
     * @param Pool $pool
38
     * @param Session $session
39
     *
40
     * @return Builder|Relation
41
     */
42
    private function getQuery(Pool $pool, Session $session): Builder|Relation
43
    {
44
        return $session->payables()
45
            ->join('subscriptions', 'subscriptions.id', '=', 'payables.subscription_id')
46
            ->where('subscriptions.pool_id', $pool->id);
47
    }
48
49
    /**
50
     * Get the number of payables in the given pool.
51
     *
52
     * @param Pool $pool
53
     * @param Session $session
54
     *
55
     * @return int
56
     */
57
    public function getPayableCount(Pool $pool, Session $session): int
58
    {
59
        return $this->getQuery($pool, $session)->count();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getQuery($pool, $session)->count() could return the type Illuminate\Database\Eloq...uent\Relations\Relation which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
60
    }
61
62
    /**
63
     * Get the number of remitments in the given pool.
64
     *
65
     * @param Pool $pool
66
     * @param Session $session
67
     *
68
     * @return int
69
     */
70
    public function getRemitmentCount(Pool $pool, Session $session): int
71
    {
72
        return $this->getQuery($pool, $session)->whereHas('remitment')->count();
73
    }
74
75
    /**
76
     * @param Pool $pool
77
     * @param Session $session
78
     *
79
     * @return int
80
     */
81
    public function getRemitmentAmount(Pool $pool, Session $session): int
82
    {
83
        $count = $this->getRemitmentCount($pool, $session);
84
        if(!$pool->deposit_fixed)
0 ignored issues
show
Bug introduced by
The property deposit_fixed does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
85
        {
86
            // Sum the amounts for all deposits
87
            return $count === 0 ? 0 :
88
                $this->balanceCalculator->getPoolDepositAmount($pool, $session);
89
        }
90
91
        return $count * $this->balanceCalculator->getPayableAmount($pool, $session);
92
    }
93
94
    /**
95
     * @param Pool $pool
96
     * @param Session $session
97
     * @param int $page
98
     *
99
     * @return Collection
100
     */
101
    public function getPayables(Pool $pool, Session $session, int $page = 0): Collection
102
    {
103
        $amount = $this->balanceCalculator->getPayableAmount($pool, $session);
104
        $payables = $this->getQuery($pool, $session)
105
            ->select('payables.*')
106
            ->addSelect(DB::raw('member_defs.name as member'))
107
            ->join('members', 'members.id', '=', 'subscriptions.member_id')
108
            ->join('member_defs', 'members.def_id', '=', 'member_defs.id')
109
            ->with(['remitment', 'remitment.auction'])
110
            ->page($page, $this->tenantService->getLimit())
111
            ->get()
112
            ->each(function($payable) use($amount) {
113
                $payable->amount = $amount;
114
            });
115
        if(!$pool->remit_planned)
0 ignored issues
show
Bug introduced by
The property remit_planned does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
116
        {
117
            return $payables;
118
        }
119
120
        // When the number of remitments is planned, the list is padded to the expected number.
121
        $remitmentCount = $this->summaryService->getSessionRemitmentCount($pool, $session);
122
        $emptyPayable = (object)[
123
            'id' => 0,
124
            'member' => trans('tontine.remitment.labels.not-assigned'),
125
            'amount' => $amount,
126
            'remitment' => null,
127
        ];
128
129
        return $payables->pad($remitmentCount, $emptyPayable);
130
    }
131
132
    /**
133
     * Find the unique payable for a pool and a session.
134
     *
135
     * @param Pool $pool The pool
136
     * @param Session $session The session
137
     * @param int $payableId
138
     *
139
     * @return Payable|null
140
     */
141
    public function getPayable(Pool $pool, Session $session, int $payableId): ?Payable
142
    {
143
        return $this->getQuery($pool, $session)
144
            ->with(['remitment', 'remitment.auction'])
145
            ->select('payables.*')
146
            ->find($payableId);
147
    }
148
149
    /**
150
     * Create a remitment.
151
     *
152
     * @param Pool $pool The pool
153
     * @param Session $session The session
154
     * @param int $payableId
155
     *
156
     * @return void
157
     */
158
    public function savePlannedRemitment(Pool $pool, Session $session, int $payableId): void
159
    {
160
        if(!$pool->remit_planned || $pool->remit_auction)
0 ignored issues
show
Bug introduced by
The property remit_planned does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
Bug introduced by
The property remit_auction does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
161
        {
162
            // Only when remitments are planned and without auctions.
163
            return;
164
        }
165
        // The payable is supposed to already have been associated to the session.
166
        $payable = $this->getPayable($pool, $session, $payableId);
167
        if(!$payable)
168
        {
169
            throw new MessageException(trans('tontine.subscription.errors.not_found'));
170
        }
171
        if($payable->remitment)
172
        {
173
            return;
174
        }
175
        $payable->remitment()->create([]);
176
    }
177
178
    /**
179
     * Create a remitment.
180
     *
181
     * @param Pool $pool The pool
182
     * @param Session $session The session
183
     * @param int $payableId
184
     * @param int $auction
185
     *
186
     * @return void
187
     */
188
    public function saveRemitment(Pool $pool, Session $session, int $payableId, int $auction): void
189
    {
190
        // Cannot use the getPayable() method here,
191
        // because there's no session attached to the payable.
192
        $payable = Payable::with(['subscription.member'])
193
            ->whereDoesntHave('remitment')
194
            ->whereIn('subscription_id', $pool->subscriptions()->pluck('id'))
195
            ->find($payableId);
196
        if(!$payable)
197
        {
198
            throw new MessageException(trans('tontine.subscription.errors.not_found'));
199
        }
200
        if($payable->session_id !== null && $payable->session_id !== $session->id)
201
        {
202
            // The selected member is already planned on another session.
203
            throw new MessageException(trans('tontine.remitment.errors.planning'));
204
        }
205
        // If the pool remitments are planned, then check the payable count for the session.
206
        if($pool->remit_planned)
0 ignored issues
show
Bug introduced by
The property remit_planned does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
207
        {
208
            $plannedCount = $this->summaryService->getSessionRemitmentCount($pool, $session);
209
            $remittedCount = $this->getRemitmentCount($pool, $session);
210
            if($remittedCount >= $plannedCount)
211
            {
212
                throw new MessageException(trans('tontine.remitment.errors.max-count'));
213
            }
214
        }
215
216
        DB::transaction(function() use($pool, $session, $payable, $auction) {
217
            // Associate the payable with the session.
218
            $payable->session()->associate($session);
219
            $payable->save();
220
            // Create the remitment.
221
            $remitment = $payable->remitment()->create([]);
222
223
            if($pool->remit_auction && $auction > 0)
0 ignored issues
show
Bug introduced by
The property remit_auction does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
224
            {
225
                // Create the corresponding auction.
226
                Auction::create([
227
                    'amount' => $auction,
228
                    'paid' => true, // The auction is supposed to have been immediatly paid.
229
                    'session_id' => $session->id,
230
                    'remitment_id' => $remitment->id,
231
                ]);
232
            }
233
        });
234
    }
235
236
    /**
237
     * Delete a remitment.
238
     *
239
     * @param Pool $pool The pool
240
     * @param Session $session The session
241
     * @param int $payableId
242
     *
243
     * @return void
244
     */
245
    public function deleteRemitment(Pool $pool, Session $session, int $payableId): void
246
    {
247
        $payable = $this->getQuery($pool, $session)
248
            ->with(['remitment', 'remitment.auction'])
249
            ->select('payables.*')
250
            ->find($payableId);
251
        if(!$payable || !($remitment = $payable->remitment))
252
        {
253
            return;
254
        }
255
256
        DB::transaction(function() use($pool, $payable, $remitment) {
257
            // Delete the corresponding auction, in case there was one on the remitment.
258
            $remitment->auction()->delete();
259
            $remitment->delete();
260
            // Detach from the session, but only if the remitment was not planned.
261
            if(!$pool->remit_planned || $pool->remit_auction)
0 ignored issues
show
Bug introduced by
The property remit_planned does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
Bug introduced by
The property remit_auction does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
262
            {
263
                $payable->session()->dissociate();
264
                $payable->save();
265
            }
266
        });
267
    }
268
269
    /**
270
     * Get the unpaid subscriptions of a given pool.
271
     *
272
     * @param Pool $pool
273
     * @param Session $session The session
274
     *
275
     * @return Collection
276
     */
277
    public function getSubscriptions(Pool $pool, Session $session): Collection
278
    {
279
        $subscriptions = $pool->subscriptions()
280
            ->join('members', 'subscriptions.member_id', '=', 'members.id')
281
            ->join('member_defs', 'members.def_id', '=', 'member_defs.id')
282
            ->orderBy('member_defs.name', 'asc')
283
            ->orderBy('subscriptions.id', 'asc')
284
            ->with(['payable', 'member'])
285
            ->whereHas('payable', fn($query) => $query->whereDoesntHave('remitment'))
286
            ->select('subscriptions.*')
287
            ->get();
288
        if($pool->remit_planned && !$pool->remit_auction)
0 ignored issues
show
Bug introduced by
The property remit_auction does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
Bug introduced by
The property remit_planned does not seem to exist on Siak\Tontine\Model\Pool. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
289
        {
290
            // Only the beneficiaries that are not yet planned.
291
            return $subscriptions
292
                ->filter(fn($subscription) => !$subscription->payable->session_id)
293
                ->pluck('member.name', 'payable.id');
294
        }
295
        // Also return the beneficiaries that have not yet been remitted.
296
        return $subscriptions
297
            ->filter(fn($subscription) => !$subscription->payable->session_id ||
298
                $subscription->payable->session_id === $session->id)
299
            ->pluck('member.name', 'payable.id');
300
    }
301
}
302