ProfitService::getDistribution()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 14
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 18
rs 9.7998
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\Saving;
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 Saving $saving
29
     *
30
     * @return int
31
     */
32
    private function getSavingDuration(Collection $sessions, Saving $saving): int
33
    {
34
        // Count the number of sessions before the current one.
35
        return $sessions
36
            ->filter(fn($session) => $session->day_date > $saving->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
        $savings = $distribution->savings;
64
        // Set savings durations and distributions
65
        foreach($savings as $saving)
66
        {
67
            $saving->duration = $this->getSavingDuration($sessions, $saving);
68
            // The number of parts is determined by the saving amount and duration.
69
            $saving->parts = $saving->amount * $saving->duration;
70
            $saving->profit = 0;
71
            $saving->percent = 0;
72
        }
73
74
        $distribution->rewarded = $savings->filter(fn($saving) => $saving->duration > 0);
75
        // The value of the unit part is the gcd of the saving amounts.
76
        // The distribution values is optimized by using the saving 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 saving amounts in certain cases.
79
        $partsGcd = $this->gcd($distribution->rewarded->pluck('parts'));
80
        if($partsGcd > 0)
81
        {
82
            $amountGcd = $this->gcd($distribution->rewarded->pluck('amount'));
83
            $distribution->partAmount = $amountGcd;
84
85
            $savings->each(function($saving) use($partsGcd, $amountGcd) {
86
                $saving->profit = $saving->parts / $partsGcd;
87
                $saving->parts /= $amountGcd;
88
            });
89
90
            $profitSum = $savings->sum('profit');
91
            $profitAmount = $distribution->profitAmount;
92
93
            $savings->each(function($saving) use($profitSum, $profitAmount) {
94
                $percent = $saving->profit / $profitSum;
95
                $saving->percent = $percent * 100;
96
                $saving->profit = (int)($profitAmount * $percent);
97
            });
98
        }
99
        return $distribution;
100
    }
101
102
    /**
103
     * Get the profit distribution for savings.
104
     *
105
     * @param Session $session
106
     * @param Fund $fund
107
     * @param int $profitAmount
108
     *
109
     * @return Distribution
110
     */
111
    public function getDistribution(Session $session, Fund $fund, int $profitAmount): Distribution
112
    {
113
        $sessions = $this->fundService->getFundSessions($fund, $session);
114
        // Get the savings until the given session.
115
        $savings = $fund->savings()
116
            ->select('savings.*')
117
            ->join('members', 'members.id', '=', 'savings.member_id')
118
            ->join('member_defs', 'members.def_id', '=', 'member_defs.id')
119
            ->join('sessions', 'sessions.id', '=', 'savings.session_id')
120
            ->whereIn('sessions.id', $sessions->pluck('id'))
121
            ->orderBy('member_defs.name', 'asc')
122
            ->orderBy('sessions.day_date', 'asc')
123
            ->with(['session', 'member'])
124
            ->get();
125
126
        $distribution = new Distribution($sessions, $savings, $profitAmount);
127
        return $savings->count() === 0 ?  $distribution:
128
            $this->setDistributions($distribution);
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($fund, $session);
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