Passed
Pull Request — main (#63)
by Thierry
06:38
created

SubscriptionService::saveBeneficiary()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 22
c 1
b 0
f 0
nc 5
nop 4
dl 0
loc 40
rs 8.9457
1
<?php
2
3
namespace Siak\Tontine\Service\Planning;
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\Pool;
11
use Siak\Tontine\Model\Session;
12
use Siak\Tontine\Model\Subscription;
13
use Siak\Tontine\Service\TenantService;
14
15
use function strtolower;
16
use function trans;
17
18
class SubscriptionService
19
{
20
    /**
21
     * @param TenantService $tenantService
22
     * @param PoolService $poolService
23
     */
24
    public function __construct(protected TenantService $tenantService,
25
        protected PoolService $poolService)
26
    {}
27
28
    /**
29
     * Get pools for the dropdown list.
30
     *
31
     * @param bool $pluck
32
     *
33
     * @return Collection
34
     */
35
    public function getPools(bool $pluck = true): Collection
36
    {
37
        $query = $this->tenantService->round()->pools()
38
            ->with(['round.tontine'])->whereHas('subscriptions');
39
        return $pluck ? $query->get()->pluck('title', 'id') : $query->get();
40
    }
41
42
    /**
43
     * Get a paginated list of members.
44
     *
45
     * @param Pool $pool
46
     * @param string $search
47
     * @param bool $filter|null
48
     *
49
     * @return Builder|Relation
50
     */
51
    public function getQuery(Pool $pool, string $search, ?bool $filter): Builder|Relation
52
    {
53
        return $this->tenantService->tontine()->members()->active()
54
            ->when($filter === true, function(Builder $query) use($pool) {
55
                // Return only members with subscription in this pool
56
                return $query->whereHas('subscriptions', function(Builder $query) use($pool) {
57
                    $query->where('subscriptions.pool_id', $pool->id);
58
                });
59
            })
60
            ->when($filter === false, function(Builder $query) use($pool) {
61
                // Return only members without subscription in this pool
62
                return $query->whereDoesntHave('subscriptions', function(Builder $query) use($pool) {
63
                    $query->where('subscriptions.pool_id', $pool->id);
64
                });
65
            })
66
            ->when($search !== '', function($query) use($search) {
67
                $search = '%' . strtolower($search) . '%';
68
                return $query->where(DB::raw('lower(name)'), 'like', $search);
69
            });
70
    }
71
72
    /**
73
     * Get a paginated list of members.
74
     *
75
     * @param Pool $pool
76
     * @param string $search
77
     * @param bool $filter|null
78
     * @param int $page
79
     *
80
     * @return Collection
81
     */
82
    public function getMembers(Pool $pool, string $search, ?bool $filter, int $page = 0): Collection
83
    {
84
        return $this->getQuery($pool, $search, $filter)
85
            ->page($page, $this->tenantService->getLimit())
86
            ->withCount([
87
                'subscriptions' => function(Builder $query) use($pool) {
88
                    $query->where('pool_id', $pool->id);
89
                },
90
            ])
91
            ->orderBy('name', 'asc')
92
            ->get();
93
    }
94
95
    /**
96
     * Get the number of members.
97
     *
98
     * @param Pool $pool
99
     * @param string $search
100
     * @param bool $filter|null
101
     *
102
     * @return int
103
     */
104
    public function getMemberCount(Pool $pool, string $search, ?bool $filter): int
105
    {
106
        return $this->getQuery($pool, $search, $filter)->count();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getQuery($...arch, $filter)->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...
107
    }
108
109
    /**
110
     * @param Pool $pool
111
     * @param int $memberId
112
     *
113
     * @return void
114
     */
115
    public function createSubscription(Pool $pool, int $memberId)
116
    {
117
        // When the remitments are planned, don't create a subscription
118
        // if receivables already exist on the pool.
119
        // if($pool->remit_planned &&
120
        //     $pool->subscriptions()->whereHas('receivables')->count() > 0)
121
        // {
122
        //     throw new MessageException(trans('tontine.subscription.errors.create'));
123
        // }
124
125
        $member = $this->tenantService->tontine()->members()->find($memberId);
126
        $subscription = new Subscription();
127
        $subscription->title = '';
128
        $subscription->pool()->associate($pool);
129
        $subscription->member()->associate($member);
130
131
        DB::transaction(function() use($subscription) {
132
            // Create the subscription
133
            $subscription->save();
134
            // Create the payable
135
            $subscription->payable()->create([]);
136
        });
137
    }
138
139
    /**
140
     * @param Pool $pool
141
     * @param int $memberId
142
     *
143
     * @return void
144
     */
145
    public function deleteSubscription(Pool $pool, int $memberId)
146
    {
147
        // When the remitments are planned, don't delete a subscription
148
        // if receivables already exist on the pool.
149
        // if($pool->remit_planned &&
150
        //     $pool->subscriptions()->whereHas('receivables')->count() > 0)
151
        // {
152
        //     throw new MessageException(trans('tontine.subscription.errors.delete'));
153
        // }
154
        $subscriptions = $pool->subscriptions()
155
            ->where('member_id', $memberId)
156
            ->withCount('receivables')
157
            ->get()
158
            ->sortBy('receivables_count');
159
        if($subscriptions->count() === 0)
160
        {
161
            throw new MessageException(trans('tontine.subscription.errors.not_found'));
162
        }
163
164
        // Since the subscriptions are sorted by receivables count, those with no receivable
165
        // will be deleted in priority.
166
        $subscription = $subscriptions->first();
167
        DB::transaction(function() use($subscription) {
168
            // Delete the receivables
169
            $subscription->receivables()->delete();
170
            // Delete the payable
171
            $subscription->payable()->delete();
172
            // Delete the subscription
173
            $subscription->delete();
174
        });
175
    }
176
177
    /**
178
     * Get the number of subscriptions.
179
     *
180
     * @param Pool $pool
181
     *
182
     * @return int
183
     */
184
    public function getSubscriptionCount(Pool $pool): int
185
    {
186
        return $pool->subscriptions()->count();
187
    }
188
189
    /**
190
     * @param Subscription $subscription
191
     * @param Session $session
192
     *
193
     * @return void
194
     */
195
    public function setPayableSession(Subscription $subscription, Session $session)
196
    {
197
        $subscription->payable->session()->associate($session);
198
        $subscription->payable->save();
199
    }
200
201
    /**
202
     * @param Subscription $subscription
203
     *
204
     * @return void
205
     */
206
    public function unsetPayableSession(Subscription $subscription)
207
    {
208
        if(($subscription->payable->session_id))
209
        {
210
            $subscription->payable->session()->dissociate();
211
            $subscription->payable->save();
212
        }
213
    }
214
215
    /**
216
     * @param Session $session
217
     * @param Subscription|null $subscription
218
     *
219
     * @return bool
220
     */
221
    private function canChangeBeneficiary(Session $session, ?Subscription $subscription): bool
222
    {
223
        // Can't change the beneficiary if the session is closed,
224
        if($session->closed)
0 ignored issues
show
Bug introduced by
The property closed does not seem to exist on Siak\Tontine\Model\Session. 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...
225
        {
226
            return false;
227
        }
228
        // Or if the collected amount has already been remitted.
229
        return $subscription === null || $subscription->payable === null ||
230
            $subscription->payable->remitment === null;
231
    }
232
233
    /**
234
     * Set or unset the beneficiary of a given pool.
235
     *
236
     * @param Pool $pool
237
     * @param int $sessionId
238
     * @param int $currSubscriptionId
239
     * @param int $nextSubscriptionId
240
     *
241
     * @return bool
242
     */
243
    public function saveBeneficiary(Pool $pool, int $sessionId, int $currSubscriptionId,
244
        int $nextSubscriptionId): bool
245
    {
246
        $session = $this->tenantService->tontine()
247
            ->sessions()
248
            ->ofPool($pool)
249
            ->find($sessionId);
250
        $currSubscription = null;
251
        $nextSubscription = null;
252
        if($currSubscriptionId > 0)
253
        {
254
            $currSubscription = $pool->subscriptions()
255
                ->with('payable')
256
                ->find($currSubscriptionId);
257
            if(!$this->canChangeBeneficiary($session, $currSubscription))
258
            {
259
                return false;
260
            }
261
        }
262
        if($nextSubscriptionId > 0)
263
        {
264
            $nextSubscription = $pool->subscriptions()
265
                ->with('payable')
266
                ->find($nextSubscriptionId);
267
        }
268
269
        DB::transaction(function() use($session, $currSubscription, $nextSubscription) {
270
            // If the beneficiary already has a session assigned, first remove it.
271
            if($currSubscription !== null)
272
            {
273
                $this->unsetPayableSession($currSubscription);
274
            }
275
            // If there is a new session assigned to the beneficiary, then save it.
276
            if($nextSubscription !== null)
277
            {
278
                $this->setPayableSession($nextSubscription, $session);
279
            }
280
        });
281
282
        return true;
283
    }
284
}
285