Passed
Push — main ( 036728...6510e5 )
by Thierry
06:29
created

ProfitService::getPartValue()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 1
nop 1
dl 0
loc 17
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Siak\Tontine\Service\Meeting\Saving;
4
5
use Illuminate\Support\Collection;
6
use Illuminate\Support\Facades\Log;
7
use Siak\Tontine\Model\Fund;
8
use Siak\Tontine\Model\Saving;
9
use Siak\Tontine\Model\Session;
10
use Siak\Tontine\Service\BalanceCalculator;
11
use Siak\Tontine\Service\TenantService;
12
use Siak\Tontine\Service\Tontine\FundService;
13
14
use function gmp_gcd;
15
16
class ProfitService
17
{
18
    /**
19
     * @param BalanceCalculator $balanceCalculator
20
     * @param TenantService $tenantService
21
     * @param FundService $fundService
22
     */
23
    public function __construct(private BalanceCalculator $balanceCalculator,
24
        private TenantService $tenantService, private FundService $fundService)
25
    {}
26
27
    /**
28
     * @param Collection $sessions
29
     * @param Saving $saving
30
     *
31
     * @return int
32
     */
33
    private function getSavingDuration(Collection $sessions, Saving $saving): int
34
    {
35
        // Count the number of sessions before the current one.
36
        return $sessions->filter(function($session) use($saving) {
37
            return $session->start_at > $saving->session->start_at;
38
        })->count();
39
    }
40
41
    /**
42
     * Get the amount corresponding to one part for a given distribution
43
     *
44
     * @param Collection $savings
45
     *
46
     * @return int
47
     */
48
    private function getPartValue(Collection $savings): int
49
    {
50
        // The value of the unit part is the gcd of the saving amounts.
51
        // The distribution values might be optimized by using the saving distribution
52
        // value instead of the amount, but the resulting part value might be confusing
53
        // since it can be greater than some saving amounts in certain cases.
54
        return (int)$savings->reduce(function($gcd, $saving) {
55
            if($gcd === 0)
56
            {
57
                return $saving->amount;
58
            }
59
            if($saving->duration === 0)
60
            {
61
                return $gcd;
62
            }
63
            return gmp_gcd($gcd, $saving->amount);
64
        }, $savings->first()->amount);
65
    }
66
67
    /**
68
     * Get the profit distribution for savings.
69
     *
70
     * @param Collection $sessions
71
     * @param Collection $savings
72
     * @param int $profitAmount
73
     *
74
     * @return Distribution
75
     */
76
    private function distribution(Collection $sessions, Collection $savings,
77
        int $profitAmount): Distribution
78
    {
79
        if($savings->count() === 0)
80
        {
81
            return new Distribution($savings);
82
        }
83
84
        // Set savings durations and distributions
85
        foreach($savings as $saving)
86
        {
87
            $saving->duration = $this->getSavingDuration($sessions, $saving);
88
            $saving->distribution = $saving->amount * $saving->duration;
89
            $saving->profit = 0;
90
        }
91
92
        // Reduce the distributions by the value of the unit part.
93
        $partValue = $this->getPartValue($savings);
94
        if($partValue > 0)
95
        {
96
            $sum = (int)($savings->sum('distribution') / $partValue);
97
            foreach($savings as $saving)
98
            {
99
                $saving->distribution /= $partValue;
100
                $saving->profit = (int)($profitAmount * $saving->distribution / $sum);
101
            }
102
        }
103
        return new Distribution($savings, $partValue);
104
    }
105
106
    /**
107
     * Get the profit distribution for savings.
108
     *
109
     * @param Session $session
110
     * @param Fund $fund
111
     * @param int $profitAmount
112
     *
113
     * @return Distribution
114
     */
115
    public function getDistribution(Session $session, Fund $fund, int $profitAmount): Distribution
116
    {
117
        $sessions = $this->fundService->getFundSessions($session, $fund);
118
        // Get the savings to be rewarded
119
        $savings = $fund->savings()
120
            ->select('savings.*')
121
            ->join('members', 'members.id', '=', 'savings.member_id')
122
            ->join('sessions', 'sessions.id', '=', 'savings.session_id')
123
            ->whereIn('sessions.id', $sessions->pluck('id'))
124
            ->orderBy('members.name', 'asc')
125
            ->orderBy('sessions.start_at', 'asc')
126
            ->with(['session', 'member'])
127
            ->get();
128
        return $this->distribution($sessions, $savings, $profitAmount);
129
    }
130
131
    /**
132
     * Get the total saving and profit amounts.
133
     *
134
     * @param Session $session
135
     * @param Fund $fund
136
     *
137
     * @return array<int>
138
     */
139
    public function getSavingAmounts(Session $session, Fund $fund): array
140
    {
141
        // Get the ids of all the sessions until the current one.
142
        $sessionIds = $this->fundService->getFundSessionIds($session, $fund);
143
        return [
144
            'saving' => $this->balanceCalculator->getSavingsAmount($sessionIds, $fund),
145
            'refund' => $this->balanceCalculator->getRefundsAmount($sessionIds, $fund) +
146
                $this->balanceCalculator->getPartialRefundsAmount($sessionIds, $fund),
147
        ];
148
    }
149
}
150