AnalysisService::byDate()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 3
eloc 22
c 2
b 1
f 0
nc 3
nop 2
dl 0
loc 31
rs 9.568
1
<?php
2
3
namespace app\core\services;
4
5
use app\core\exceptions\InvalidArgumentException;
6
use app\core\models\Category;
7
use app\core\models\Record;
8
use app\core\models\Transaction;
9
use app\core\types\AnalysisDateType;
10
use app\core\types\DirectionType;
11
use app\core\types\TransactionType;
12
use Yii;
13
use yii\base\BaseObject;
14
use yii\base\InvalidConfigException;
15
use yiier\helpers\DateHelper;
16
use yiier\helpers\Setup;
17
18
/**
19
 *
20
 * @property-read array $recordOverview
21
 */
22
class AnalysisService extends BaseObject
23
{
24
    /**
25
     * @return array
26
     * @throws \Exception
27
     */
28
    public function getRecordOverview(): array
29
    {
30
        $items = [];
31
        foreach (AnalysisDateType::texts() as $key => $item) {
32
            $date = AnalysisService::getDateRange($key);
33
            $items[$key]['overview'] = $this->getRecordOverviewByDate($date);
34
            $items[$key]['key'] = $key;
35
            $items[$key]['text'] = $item;
36
        }
37
38
        return $items;
39
    }
40
41
    public function getRecordOverviewByDate(array $date): array
42
    {
43
        $conditions = [];
44
        if (count($date) == 2) {
45
            $conditions = ['between', 'date', $date[0], $date[1]];
46
        }
47
        $userId = \Yii::$app->user->id;
48
        $types = [TransactionType::EXPENSE, TransactionType::INCOME];
49
        $baseConditions = ['user_id' => $userId, 'transaction_type' => $types, 'exclude_from_stats' => false];
50
        $sum = Record::find()
51
            ->where($baseConditions)
52
            ->andWhere(['direction' => DirectionType::INCOME])
53
            ->andWhere($conditions)
54
            ->sum('amount_cent');
55
        $items['income'] = $sum ? (float)Setup::toYuan($sum) : 0;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$items was never initialized. Although not strictly required by PHP, it is generally a good practice to add $items = array(); before regardless.
Loading history...
56
57
        $sum = Record::find()
58
            ->where($baseConditions)
59
            ->andWhere(['direction' => DirectionType::EXPENSE])
60
            ->andWhere($conditions)
61
            ->sum('amount_cent');
62
        $items['expense'] = $sum ? (float)Setup::toYuan($sum) : 0;
63
64
        $items['surplus'] = (float)bcsub($items['income'], $items['expense'], 2);
65
66
        return $items;
67
    }
68
69
    public function getCategoryStatisticalData(array $date, int $transactionType)
70
    {
71
        $conditions = [];
72
        $items = [];
73
        if (count($date) == 2) {
74
            $conditions = ['between', 'date', $date[0], $date[1]];
75
        }
76
        $userId = \Yii::$app->user->id;
77
        $baseConditions = ['user_id' => $userId, 'transaction_type' => $transactionType];
78
        $categories = Category::find()->where($baseConditions)->asArray()->all();
79
80
        foreach ($categories as $key => $category) {
81
            $items[$key]['x'] = $category['name'];
82
            $sum = Record::find()
83
                ->where($baseConditions)
84
                ->andWhere(['category_id' => $category['id'], 'exclude_from_stats' => false])
85
                ->andWhere($conditions)
86
                ->sum('amount_cent');
87
            $items[$key]['y'] = $sum ? (float)Setup::toYuan($sum) : 0;
88
        }
89
90
        return $items;
91
    }
92
93
    /**
94
     * @param string $dateStr
95
     * @param int $transactionType
96
     * @return array
97
     * @throws InvalidConfigException
98
     */
99
    public function getRecordStatisticalData(string $dateStr, int $transactionType)
100
    {
101
        $dates = AnalysisDateType::getEveryDayByMonth($dateStr);
102
        $userId = \Yii::$app->user->id;
103
        $baseConditions = ['user_id' => $userId, 'transaction_type' => $transactionType, 'exclude_from_stats' => false];
104
        $items = [];
105
        foreach ($dates as $key => $date) {
106
            $items[$key]['x'] = sprintf("%02d", $key + 1);
107
            $sum = Record::find()
108
                ->where($baseConditions)
109
                ->andWhere(['between', 'date', $date[0], $date[1]])
110
                ->sum('amount_cent');
111
            $items[$key]['y'] = $sum ? (float)Setup::toYuan($sum) : 0;
112
        }
113
        return $items;
114
    }
115
116
117
    /**
118
     * @param $key
119
     * @return array
120
     * @throws \Exception
121
     */
122
    public static function getDateRange($key): array
123
    {
124
        $formatter = Yii::$app->formatter;
125
        $date = [];
126
        switch ($key) {
127
            case AnalysisDateType::TODAY:
128
                $date = [DateHelper::beginTimestamp(), DateHelper::endTimestamp()];
129
                break;
130
            case AnalysisDateType::YESTERDAY:
131
                $time = strtotime('-1 day');
132
                $date = [DateHelper::beginTimestamp($time), DateHelper::endTimestamp($time)];
133
                break;
134
            case AnalysisDateType::LAST_MONTH:
135
                $beginTime = $formatter->asDatetime(strtotime('-1 month'), 'php:01-m-Y');
136
                $endTime = $formatter->asDatetime('now', 'php:01-m-Y');
137
                $date = [DateHelper::beginTimestamp($beginTime), DateHelper::endTimestamp($endTime) - 3600 * 24];
138
                break;
139
            case AnalysisDateType::CURRENT_MONTH:
140
                $time = $formatter->asDatetime('now', 'php:01-m-Y');
141
                $date = [DateHelper::beginTimestamp($time), DateHelper::endTimestamp()];
142
                break;
143
        }
144
145
        return array_map(function ($i) use ($formatter) {
146
            return $formatter->asDatetime($i);
147
        }, $date);
148
    }
149
150
    /**
151
     * @param array $params
152
     * @return array
153
     * @throws InvalidArgumentException
154
     * @throws \Exception
155
     */
156
    public function byCategory(array $params)
157
    {
158
        $items = [];
159
        $categoriesMap = CategoryService::getCurrentMap();
160
        foreach ([TransactionType::EXPENSE, TransactionType::INCOME] as $type) {
161
            $data = $this->getBaseQuery($params)
162
                ->select([
163
                    'category_id',
164
                    'SUM(currency_amount_cent) AS currency_amount_cent'
165
                ])
166
                ->andWhere(['transaction_type' => $type])
167
                ->groupBy('category_id')
168
                ->asArray()
169
                ->all();
170
            $k = TransactionType::getName($type);
171
            $items['total'][$k] = 0;
172
            $items[$k] = [];
173
            foreach ($data as $key => $value) {
174
                $v['category_name'] = data_get($categoriesMap, $value['category_id'], 0);
175
                $v['currency_amount'] = (float)Setup::toYuan($value['currency_amount_cent']);
176
                $items['total'][$k] += $v['currency_amount'];
177
                $items[$k][] = $v;
178
            }
179
        }
180
        $items['total']['surplus'] = (float)bcsub(
181
            data_get($items['total'], TransactionType::getName(TransactionType::INCOME), 0),
0 ignored issues
show
Bug introduced by
It seems like data_get($items['total']...actionType::INCOME), 0) can also be of type null; however, parameter $num1 of bcsub() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

181
            /** @scrutinizer ignore-type */ data_get($items['total'], TransactionType::getName(TransactionType::INCOME), 0),
Loading history...
182
            data_get($items['total'], TransactionType::getName(TransactionType::EXPENSE), 0),
0 ignored issues
show
Bug introduced by
It seems like data_get($items['total']...ctionType::EXPENSE), 0) can also be of type null; however, parameter $num2 of bcsub() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

182
            /** @scrutinizer ignore-type */ data_get($items['total'], TransactionType::getName(TransactionType::EXPENSE), 0),
Loading history...
183
            2
184
        );
185
186
        return $items;
187
    }
188
189
    /**
190
     * @param array $params
191
     * @param string $format
192
     * @return array
193
     * @throws InvalidArgumentException
194
     * @throws \Exception
195
     */
196
    public function byDate(array $params, string $format)
197
    {
198
        $items = [];
199
        foreach ([TransactionType::EXPENSE, TransactionType::INCOME] as $type) {
200
            $data = $this->getBaseQuery($params)
201
                ->select([
202
                    "DATE_FORMAT(date, '{$format}') as m_date",
203
                    'SUM(currency_amount_cent) AS currency_amount_cent'
204
                ])
205
                ->andWhere(['transaction_type' => $type])
206
                ->groupBy('m_date')
207
                ->asArray()
208
                ->all();
209
210
            $k = TransactionType::getName($type);
211
            $items['total'][$k] = 0;
212
            $items[$k] = [];
213
            foreach ($data as $key => $value) {
214
                $v['date'] = $value['m_date'];
215
                $v['currency_amount'] = (float)Setup::toYuan($value['currency_amount_cent']);
216
                $items['total'][$k] += $v['currency_amount'];
217
                $items[$k][] = $v;
218
            }
219
        }
220
        $items['total']['surplus'] = (float)bcsub(
221
            data_get($items['total'], TransactionType::getName(TransactionType::INCOME), 0),
0 ignored issues
show
Bug introduced by
It seems like data_get($items['total']...actionType::INCOME), 0) can also be of type null; however, parameter $num1 of bcsub() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

221
            /** @scrutinizer ignore-type */ data_get($items['total'], TransactionType::getName(TransactionType::INCOME), 0),
Loading history...
222
            data_get($items['total'], TransactionType::getName(TransactionType::EXPENSE), 0),
0 ignored issues
show
Bug introduced by
It seems like data_get($items['total']...ctionType::EXPENSE), 0) can also be of type null; however, parameter $num2 of bcsub() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

222
            /** @scrutinizer ignore-type */ data_get($items['total'], TransactionType::getName(TransactionType::EXPENSE), 0),
Loading history...
223
            2
224
        );
225
226
        return $items;
227
    }
228
229
230
    /**
231
     * @param array $params
232
     * @return \yii\db\ActiveQuery
233
     * @throws \Exception
234
     */
235
    protected function getBaseQuery(array $params)
236
    {
237
        $baseConditions = ['user_id' => Yii::$app->user->id,];
238
        $condition = ['category_id' => request('category_id'), 'type' => request('transaction_type')];
239
        $query = Transaction::find()->where($baseConditions)->andFilterWhere($condition);
240
        if (isset($params['keyword']) && $searchKeywords = trim($params['keyword'])) {
241
            $query->andWhere(
242
                "MATCH(`description`, `tags`, `remark`) AGAINST ('*$searchKeywords*' IN BOOLEAN MODE)"
243
            );
244
        }
245
        if (($date = explode('~', data_get($params, 'date'))) && count($date) == 2) {
0 ignored issues
show
Bug introduced by
It seems like data_get($params, 'date') can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

245
        if (($date = explode('~', /** @scrutinizer ignore-type */ data_get($params, 'date'))) && count($date) == 2) {
Loading history...
246
            $start = $date[0] . ' 00:00:00';
247
            $end = $date[1] . ' 23:59:59';
248
            $query->andWhere(['between', 'date', $start, $end]);
249
        }
250
        $transactionIds = $query->column();
251
252
        return Record::find()
253
            ->where($baseConditions)
254
            ->andWhere([
255
                'transaction_id' => $transactionIds,
256
                'exclude_from_stats' => (int)false,
257
            ])
258
            ->andFilterWhere([
259
                'account_id' => request('account_id'),
260
                'source' => request('source'),
261
            ]);
262
    }
263
}
264