SubscriptionService::setPayableSession()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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