Passed
Push — main ( 5eb91d...7b831f )
by Thierry
06:50
created

SubscriptionService::canChangeBeneficiary()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 4
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 10
rs 9.6111
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 or pending,
224
        if($session->closed || $session->pending)
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 = $pool->sessions()->find($sessionId);
247
        $currSubscription = null;
248
        $nextSubscription = null;
249
        if($currSubscriptionId > 0)
250
        {
251
            $currSubscription = $pool->subscriptions()
252
                ->with('payable')
253
                ->find($currSubscriptionId);
254
            if(!$this->canChangeBeneficiary($session, $currSubscription))
255
            {
256
                return false;
257
            }
258
        }
259
        if($nextSubscriptionId > 0)
260
        {
261
            $nextSubscription = $pool->subscriptions()
262
                ->with('payable')
263
                ->find($nextSubscriptionId);
264
        }
265
266
        DB::transaction(function() use($session, $currSubscription, $nextSubscription) {
267
            // If the beneficiary already has a session assigned, first remove it.
268
            if($currSubscription !== null)
269
            {
270
                $this->unsetPayableSession($currSubscription);
271
            }
272
            // If there is a new session assigned to the beneficiary, then save it.
273
            if($nextSubscription !== null)
274
            {
275
                $this->setPayableSession($nextSubscription, $session);
276
            }
277
        });
278
279
        return true;
280
    }
281
}
282