ProfitService::setDistributions()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 18
nc 4
nop 1
dl 0
loc 34
rs 9.6666
c 2
b 0
f 0
1
<?php
2
3
namespace Siak\Tontine\Service\Meeting\Saving;
4
5
use Illuminate\Support\Collection;
6
use Siak\Tontine\Model\Fund;
7
use Siak\Tontine\Model\ProfitTransfer as Transfer;
8
use Siak\Tontine\Model\Session;
9
use Siak\Tontine\Service\Meeting\Saving\FundService;
10
use Siak\Tontine\Service\Payment\BalanceCalculator;
11
use Siak\Tontine\Service\TenantService;
12
13
use function gmp_gcd;
14
15
class ProfitService
16
{
17
    /**
18
     * @param BalanceCalculator $balanceCalculator
19
     * @param TenantService $tenantService
20
     * @param FundService $fundService
21
     */
22
    public function __construct(private BalanceCalculator $balanceCalculator,
23
        private TenantService $tenantService, private FundService $fundService)
24
    {}
25
26
    /**
27
     * @param Collection $sessions
28
     * @param Transfer $transfer
29
     *
30
     * @return int
31
     */
32
    private function getTransferDuration(Collection $sessions, Transfer $transfer): int
33
    {
34
        // Count the number of sessions before the current one.
35
        return $sessions
36
            ->filter(fn($session) => $session->day_date > $transfer->session->day_date)
37
            ->count();
38
    }
39
40
    /**
41
     * @param Collection $values
42
     *
43
     * @return int
44
     */
45
    private function gcd(Collection $values): int
46
    {
47
        return (int)$values->reduce(
48
            fn($gcd, $value) => $gcd === 0 || $value === 0 ? 0 : gmp_gcd($gcd, $value),
49
            $values->first()
50
        );
51
    }
52
53
    /**
54
     * Get the amount corresponding to one part for a given distribution
55
     *
56
     * @param Distribution $distribution
57
     *
58
     * @return Distribution
59
     */
60
    private function setDistributions(Distribution $distribution): Distribution
61
    {
62
        $sessions = $distribution->sessions;
63
        $transfers = $distribution->transfers;
64
        // Set transfers durations and distributions
65
        foreach($transfers as $transfer)
66
        {
67
            $transfer->duration = $this->getTransferDuration($sessions, $transfer);
68
            // The number of parts is determined by the transfer amount and duration.
69
            $transfer->parts = $transfer->amount * $transfer->duration;
70
            $transfer->profit = 0;
71
            $transfer->percent = 0;
72
        }
73
74
        $distribution->rewarded = $transfers->filter(fn($transfer) => $transfer->duration > 0);
75
        // The value of the unit part is the gcd of the transfer amounts.
76
        // The distribution values is optimized by using the transfer parts value instead
77
        // of the amount, but the resulting part amount might be confusing when displayed
78
        // to the users since it can be greater than some transfer amounts in certain cases.
79
        $partsGcd = $this->gcd($distribution->rewarded->pluck('parts'));
80
        if($partsGcd === 0)
81
        {
82
            return $distribution;
83
        }
84
85
        $amountGcd = $this->gcd($distribution->rewarded->pluck('amount'));
86
        $distribution->partAmount = $amountGcd;
87
88
        $transfers->each(function($transfer) use($partsGcd, $amountGcd) {
89
            $transfer->profit = $transfer->parts * $transfer->coef / $partsGcd;
90
            $transfer->parts /= $amountGcd * $transfer->coef;
91
            $transfer->amount *= $transfer->coef;
92
        });
93
        return $distribution;
94
    }
95
96
    /**
97
     * Get the profit distribution for transfers.
98
     *
99
     * @param Session $session
100
     * @param Fund $fund
101
     * @param int $profitAmount
102
     *
103
     * @return Distribution
104
     */
105
    public function getDistribution(Session $session, Fund $fund, int $profitAmount): Distribution
106
    {
107
        $sessions = $this->fundService->getFundSessions($fund, $session);
108
        // Get the transfers until the given session.
109
        $transfers = Transfer::query()
110
            ->select('v_profit_transfers.*')
111
            ->join('members', 'members.id', '=', 'v_profit_transfers.member_id')
112
            ->join('member_defs', 'members.def_id', '=', 'member_defs.id')
113
            ->join('sessions', 'sessions.id', '=', 'v_profit_transfers.session_id')
114
            ->whereFund($fund)
115
            ->whereIn('sessions.id', $sessions->pluck('id'))
116
            ->orderBy('member_defs.name', 'asc')
0 ignored issues
show
Bug introduced by
'member_defs.name' of type string is incompatible with the type Closure|Illuminate\Datab...\Database\Query\Builder expected by parameter $column of Illuminate\Database\Query\Builder::orderBy(). ( Ignorable by Annotation )

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

116
            ->orderBy(/** @scrutinizer ignore-type */ 'member_defs.name', 'asc')
Loading history...
117
            ->orderBy('sessions.day_date', 'asc')
118
            ->with(['session', 'member'])
119
            ->get();
120
121
        $distribution = new Distribution($sessions, $transfers, $profitAmount);
122
        return $transfers->count() === 0 ?  $distribution:
123
            $this->setDistributions($distribution);
124
    }
125
126
    /**
127
     * Get the refunds amount.
128
     *
129
     * @param Session $session
130
     * @param Fund $fund
131
     *
132
     * @return int
133
     */
134
    public function getRefundsAmount(Session $session, Fund $fund): int
135
    {
136
        // Get the ids of all the sessions until the current one.
137
        $sessionIds = $this->fundService->getFundSessionIds($fund, $session);
138
        return $this->balanceCalculator->getRefundsAmount($sessionIds, $fund) +
139
            $this->balanceCalculator->getPartialRefundsAmount($sessionIds, $fund);
140
    }
141
}
142