Record::attributeLabels()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 16
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 18
rs 9.7333
1
<?php
2
3
namespace app\core\models;
4
5
use app\core\exceptions\CannotOperateException;
6
use app\core\services\AccountService;
7
use app\core\services\RecurrenceService;
8
use app\core\types\DirectionType;
9
use app\core\types\RecordSource;
10
use Yii;
11
use yii\behaviors\TimestampBehavior;
12
use yii\db\ActiveRecord;
13
use yii\helpers\ArrayHelper;
14
use yiier\helpers\DateHelper;
15
use yiier\helpers\Setup;
16
17
/**
18
 * This is the model class for table "{{%record}}".
19
 *
20
 * @property int $id
21
 * @property int $user_id
22
 * @property int $account_id
23
 * @property int $transaction_type
24
 * @property int $category_id
25
 * @property int $amount_cent
26
 * @property int $currency_amount_cent
27
 * @property string $currency_code
28
 * @property int|null $transaction_id
29
 * @property int $direction
30
 * @property string $date
31
 * @property int $source
32
 * @property int $exclude_from_stats
33
 * @property string|null $created_at
34
 * @property string|null $updated_at
35
 *
36
 * @property float $amount
37
 * @property-read Account $account
38
 * @property-read Category $category
39
 * @property-read Transaction $transaction
40
 */
41
class Record extends ActiveRecord
42
{
43
    /**
44
     * {@inheritdoc}
45
     */
46
    public static function tableName()
47
    {
48
        return '{{%record}}';
49
    }
50
51
    /**
52
     * @inheritdoc
53
     * @throws \yii\base\InvalidConfigException
54
     */
55
    public function behaviors()
56
    {
57
        return [
58
            [
59
                'class' => TimestampBehavior::class,
60
                'value' => Yii::$app->formatter->asDatetime('now')
61
            ],
62
        ];
63
    }
64
65
66
    public function transactions()
67
    {
68
        return [
69
            self::SCENARIO_DEFAULT => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
70
        ];
71
    }
72
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function rules()
78
    {
79
        return [
80
            [
81
                [
82
                    'user_id',
83
                    'account_id',
84
                    'transaction_type',
85
                    'category_id',
86
                    'currency_amount_cent',
87
                    'currency_code',
88
                    'direction',
89
                ],
90
                'required'
91
            ],
92
            [
93
                [
94
                    'user_id',
95
                    'account_id',
96
                    'transaction_type',
97
                    'category_id',
98
                    'amount_cent',
99
                    'currency_amount_cent',
100
                    'transaction_id',
101
                    'direction'
102
                ],
103
                'integer'
104
            ],
105
            ['direction', 'in', 'range' => [DirectionType::INCOME, DirectionType::EXPENSE]],
106
            ['source', 'in', 'range' => array_keys(RecordSource::names())],
107
            [['date'], 'datetime', 'format' => 'php:Y-m-d H:i'],
108
            ['exclude_from_stats', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true],
109
        ];
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function attributeLabels()
116
    {
117
        return [
118
            'id' => Yii::t('app', 'ID'),
119
            'user_id' => Yii::t('app', 'User ID'),
120
            'account_id' => Yii::t('app', 'Account ID'),
121
            'transaction_type' => Yii::t('app', 'Transaction Type'),
122
            'category_id' => Yii::t('app', 'Category ID'),
123
            'amount_cent' => Yii::t('app', 'Amount Cent'),
124
            'currency_amount_cent' => Yii::t('app', 'Currency Amount Cent'),
125
            'currency_code' => Yii::t('app', 'Currency Code'),
126
            'transaction_id' => Yii::t('app', 'Transaction ID'),
127
            'direction' => Yii::t('app', 'Direction'),
128
            'date' => Yii::t('app', 'Date'),
129
            'source' => Yii::t('app', 'Source'),
130
            'exclude_from_stats' => Yii::t('app', 'Exclude From Stats'),
131
            'created_at' => Yii::t('app', 'Created At'),
132
            'updated_at' => Yii::t('app', 'Updated At'),
133
        ];
134
    }
135
136
137
    public function getTransaction()
138
    {
139
        return $this->hasOne(Transaction::class, ['id' => 'transaction_id']);
140
    }
141
142
    public function getAccount()
143
    {
144
        return $this->hasOne(Account::class, ['id' => 'account_id']);
145
    }
146
147
    public function getCategory()
148
    {
149
        return $this->hasOne(Category::class, ['id' => 'category_id']);
150
    }
151
152
    /**
153
     * @param bool $insert
154
     * @return bool
155
     * @throws \Throwable
156
     */
157
    public function beforeSave($insert)
158
    {
159
        if (parent::beforeSave($insert)) {
160
            $this->exclude_from_stats = $this->exclude_from_stats ?: 0;
161
            $this->source = $this->source ?: RecordSource::WEB;
162
            if (!$this->amount_cent) {
163
                if ($this->currency_code == user('base_currency_code')) {
164
                    $this->amount_cent = $this->currency_amount_cent;
165
                } else {
166
                    // $this->amount_cent = $this->currency_amount_cent;
167
                    // todo 计算汇率
168
                }
169
            }
170
            return true;
171
        } else {
172
            return false;
173
        }
174
    }
175
176
177
    /**
178
     * @param bool $insert
179
     * @param array $changedAttributes
180
     * @throws \yii\db\Exception|\Throwable
181
     */
182
    public function afterSave($insert, $changedAttributes)
183
    {
184
        parent::afterSave($insert, $changedAttributes);
185
        if ($this->transaction_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->transaction_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
186
            // Exclude balance adjustment transaction type
187
            AccountService::updateAccountBalance($this->account_id);
188
            $accountId = data_get($changedAttributes, 'account_id');
189
            if ($accountId && $accountId !== $this->account_id) {
190
                AccountService::updateAccountBalance($accountId);
191
            }
192
        }
193
    }
194
195
    /**
196
     * @return bool
197
     * @throws CannotOperateException
198
     */
199
    public function beforeDelete()
200
    {
201
        if (RecurrenceService::countByTransactionId($this->transaction_id, $this->user_id)) {
0 ignored issues
show
Bug introduced by
It seems like $this->transaction_id can also be of type null; however, parameter $transactionId of app\core\services\Recurr...:countByTransactionId() does only seem to accept integer, 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

201
        if (RecurrenceService::countByTransactionId(/** @scrutinizer ignore-type */ $this->transaction_id, $this->user_id)) {
Loading history...
202
            throw new CannotOperateException(Yii::t('app', 'Cannot be deleted because it has been used.'));
203
        }
204
        return parent::beforeDelete();
205
    }
206
207
    /**
208
     * @throws \Throwable
209
     * @throws \yii\db\Exception
210
     * @throws \yii\db\StaleObjectException
211
     */
212
    public function afterDelete()
213
    {
214
        parent::afterDelete();
215
        if ($this->transaction_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->transaction_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
216
            if ($transaction = Transaction::find()->where(['id' => $this->transaction_id])->one()) {
217
                $transaction->delete();
218
            }
219
            $record = Record::find()
220
                ->where(['user_id' => $this->user_id, 'transaction_id' => $this->transaction_id])
221
                ->one();
222
            if ($record) {
223
                $record->delete();
224
            }
225
        }
226
        $this->account_id ? AccountService::updateAccountBalance($this->account_id) : null;
227
    }
228
229
    /**
230
     * @return array
231
     */
232
    public function fields()
233
    {
234
        $fields = parent::fields();
235
        unset($fields['currency_amount_cent'], $fields['user_id'], $fields['amount_cent']);
236
237
238
        $fields['direction'] = function (self $model) {
239
            return DirectionType::getName($model->direction);
240
        };
241
242
        $fields['currency_amount'] = function (self $model) {
243
            return Setup::toYuan($model->currency_amount_cent);
244
        };
245
246
        $fields['amount'] = function (self $model) {
247
            return Setup::toYuan($model->amount_cent);
248
        };
249
250
        $fields['transaction'] = function (self $model) {
251
            return $model->transaction ? ArrayHelper::merge(
252
                ArrayHelper::toArray($model->transaction),
253
                ['exclude_from_stats' => (bool)$model->exclude_from_stats]
254
            ) : null;
255
        };
256
257
        $fields['category'] = function (self $model) {
258
            return $model->category;
259
        };
260
261
        $fields['source_text'] = function (self $model) {
262
            return RecordSource::getName($model->source);
263
        };
264
265
        $fields['account'] = function (self $model) {
266
            return $model->account;
267
        };
268
269
        $fields['date'] = function (self $model) {
270
            return DateHelper::datetimeToIso8601($model->date);
271
        };
272
273
        $fields['exclude_from_stats'] = function (self $model) {
274
            return (bool)$model->exclude_from_stats;
275
        };
276
277
        $fields['created_at'] = function (self $model) {
278
            return DateHelper::datetimeToIso8601($model->created_at);
0 ignored issues
show
Bug introduced by
It seems like $model->created_at can also be of type null; however, parameter $dateStr of yiier\helpers\DateHelper::datetimeToIso8601() 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

278
            return DateHelper::datetimeToIso8601(/** @scrutinizer ignore-type */ $model->created_at);
Loading history...
279
        };
280
281
        $fields['updated_at'] = function (self $model) {
282
            return DateHelper::datetimeToIso8601($model->updated_at);
0 ignored issues
show
Bug introduced by
It seems like $model->updated_at can also be of type null; however, parameter $dateStr of yiier\helpers\DateHelper::datetimeToIso8601() 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

282
            return DateHelper::datetimeToIso8601(/** @scrutinizer ignore-type */ $model->updated_at);
Loading history...
283
        };
284
285
        return $fields;
286
    }
287
}
288