Transaction::getFromAccount()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace app\core\models;
4
5
use app\core\services\TagService;
6
use app\core\services\TransactionService;
7
use app\core\types\RecordSource;
8
use app\core\types\ReimbursementStatus;
9
use app\core\types\TransactionStatus;
10
use app\core\types\TransactionType;
11
use Yii;
12
use yii\behaviors\TimestampBehavior;
13
use yiier\helpers\DateHelper;
14
use yiier\helpers\Setup;
15
use yiier\validators\ArrayValidator;
16
use yiier\validators\MoneyValidator;
17
18
/**
19
 * This is the model class for table "{{%transaction}}".
20
 *
21
 * @property int $id
22
 * @property int $user_id
23
 * @property int $from_account_id
24
 * @property int|null $to_account_id
25
 * @property int $type
26
 * @property int $category_id
27
 * @property int $amount_cent
28
 * @property int $currency_amount_cent
29
 * @property string $currency_code
30
 * @property string|array $tags Multiple choice use,
31
 * @property string|null $description
32
 * @property string|null $remark
33
 * @property string|null $image
34
 * @property int|null $status
35
 * @property int|null $reimbursement_status
36
 * @property int|null $rating
37
 * @property string $date
38
 * @property string|null $created_at
39
 * @property string|null $updated_at
40
 *
41
 * @property-read Category $category
42
 * @property-read Account $fromAccount
43
 * @property-read Record[] $records
44
 * @property-read Account $toAccount
45
 */
46
class Transaction extends \yii\db\ActiveRecord
47
{
48
    /**
49
     * @var integer
50
     */
51
    public $amount;
52
53
    /**
54
     * @var string
55
     */
56
    public $source;
57
58
    /**
59
     * @var bool
60
     */
61
    public $exclude_from_stats;
62
63
    /**
64
     * @var integer
65
     */
66
    public $currency_amount;
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public static function tableName()
72
    {
73
        return '{{%transaction}}';
74
    }
75
76
    public function transactions()
77
    {
78
        return [
79
            self::SCENARIO_DEFAULT => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
80
            TransactionType::getName(TransactionType::INCOME) => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
81
            TransactionType::getName(TransactionType::EXPENSE) => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
82
            TransactionType::getName(TransactionType::TRANSFER) => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
83
        ];
84
    }
85
86
87
    public function beforeValidate()
88
    {
89
        if (parent::beforeValidate()) {
90
            $this->scenario = $this->type;
91
            return true;
92
        }
93
        return false;
94
    }
95
96
    /**
97
     * @inheritdoc
98
     * @throws \yii\base\InvalidConfigException
99
     */
100
    public function behaviors()
101
    {
102
        return [
103
            [
104
                'class' => TimestampBehavior::class,
105
                'value' => Yii::$app->formatter->asDatetime('now')
106
            ],
107
        ];
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function rules()
114
    {
115
        return [
116
            [['type', 'category_id', 'currency_amount', 'currency_code'], 'required'],
117
            [
118
                'to_account_id',
119
                'required',
120
                'on' => [
121
                    TransactionType::getName(TransactionType::INCOME),
122
                    TransactionType::getName(TransactionType::TRANSFER),
123
                ]
124
            ],
125
            [
126
                'from_account_id',
127
                'required',
128
                'on' => [
129
                    TransactionType::getName(TransactionType::EXPENSE),
130
                    TransactionType::getName(TransactionType::TRANSFER),
131
                ]
132
            ],
133
            [
134
                [
135
                    'user_id',
136
                    'from_account_id',
137
                    'to_account_id',
138
                    'category_id',
139
                    'amount_cent',
140
                    'currency_amount_cent',
141
                    'rating'
142
                ],
143
                'integer'
144
            ],
145
            [['description', 'remark'], 'trim'],
146
            ['type', 'in', 'range' => TransactionType::names()],
147
            ['reimbursement_status', 'in', 'range' => ReimbursementStatus::names()],
148
            ['status', 'in', 'range' => TransactionStatus::names()],
149
            [
150
                'currency_amount',
151
                'compare',
152
                'compareValue' => 0,
153
                'operator' => '>',
154
                'message' => Yii::t('app', 'Accounting failed, the amount must be greater than 0.')
155
            ],
156
            [['amount', 'currency_amount'], MoneyValidator::class], //todo message
157
158
            [['date'], 'datetime', 'format' => 'php:Y-m-d H:i'],
159
            [['currency_code'], 'string', 'max' => 3],
160
            [['description', 'remark', 'image'], 'string', 'max' => 255],
161
            ['tags', ArrayValidator::class],
162
            ['source', 'in', 'range' => array_keys(RecordSource::names())],
163
            ['exclude_from_stats', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true],
164
        ];
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function attributeLabels()
171
    {
172
        return [
173
            'id' => Yii::t('app', 'ID'),
174
            'user_id' => Yii::t('app', 'User ID'),
175
            'from_account_id' => Yii::t('app', 'From Account ID'),
176
            'to_account_id' => Yii::t('app', 'To Account ID'),
177
            'type' => Yii::t('app', 'Type'),
178
            'category_id' => Yii::t('app', 'Category ID'),
179
            'amount_cent' => Yii::t('app', 'Amount Cent'),
180
            'currency_amount_cent' => Yii::t('app', 'Currency Amount Cent'),
181
            'currency_code' => Yii::t('app', 'Currency Code'),
182
            'tags' => Yii::t('app', 'Tags'),
183
            'description' => Yii::t('app', 'Description'),
184
            'remark' => Yii::t('app', 'Remark'),
185
            'image' => Yii::t('app', 'Image'),
186
            'status' => Yii::t('app', 'Status'),
187
            'reimbursement_status' => Yii::t('app', 'Reimbursement Status'),
188
            'rating' => Yii::t('app', 'Rating'),
189
            'date' => Yii::t('app', 'Date'),
190
            'created_at' => Yii::t('app', 'Created At'),
191
            'updated_at' => Yii::t('app', 'Updated At'),
192
        ];
193
    }
194
195
196
    /**
197
     * @param bool $insert
198
     * @return bool
199
     * @throws \Throwable
200
     * @throws \app\core\exceptions\InvalidArgumentException
201
     */
202
    public function beforeSave($insert)
203
    {
204
        if (parent::beforeSave($insert)) {
205
            if ($insert) {
206
                $this->user_id = Yii::$app->user->id;
0 ignored issues
show
Documentation Bug introduced by
It seems like Yii::app->user->id can also be of type string. However, the property $user_id is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
207
            }
208
            $this->reimbursement_status = is_null($this->reimbursement_status) ?
209
                ReimbursementStatus::NONE : ReimbursementStatus::toEnumValue($this->reimbursement_status);
210
            $this->status = is_null($this->status) ?
211
                TransactionStatus::DONE : TransactionStatus::toEnumValue($this->status);
212
            $this->type = TransactionType::toEnumValue($this->type);
213
214
            $this->currency_amount_cent = Setup::toFen($this->currency_amount);
215
            if ($this->currency_code == Yii::$app->user->identity['base_currency_code']) {
216
                $this->amount_cent = $this->currency_amount_cent;
217
            } else {
218
                // $this->amount_cent = $this->currency_amount_cent;
219
                // todo 计算汇率
220
            }
221
            $this->tags ? TransactionService::createTags($this->tags) : null;
0 ignored issues
show
Bug introduced by
It seems like $this->tags can also be of type string; however, parameter $tags of app\core\services\TransactionService::createTags() does only seem to accept array, 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
            $this->tags ? TransactionService::createTags(/** @scrutinizer ignore-type */ $this->tags) : null;
Loading history...
222
            if ($this->description) {
223
                $this->tags = array_merge((array)$this->tags, TransactionService::matchTagsByDesc($this->description));
224
            }
225
226
            $this->tags = $this->tags ? implode(',', array_unique($this->tags)) : null;
0 ignored issues
show
Bug introduced by
It seems like $this->tags can also be of type string; however, parameter $array of array_unique() does only seem to accept array, 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

226
            $this->tags = $this->tags ? implode(',', array_unique(/** @scrutinizer ignore-type */ $this->tags)) : null;
Loading history...
227
            return true;
228
        } else {
229
            return false;
230
        }
231
    }
232
233
234
    /**
235
     * @param bool $insert
236
     * @param array $changedAttributes
237
     * @throws \yii\db\Exception|\Throwable
238
     */
239
    public function afterSave($insert, $changedAttributes)
240
    {
241
        parent::afterSave($insert, $changedAttributes);
242
        if (!$insert) {
243
            TransactionService::deleteRecord($this, $changedAttributes);
244
        }
245
        TransactionService::createUpdateRecord($this);
246
        $oldTags = data_get($changedAttributes, 'tags', '');
247
248
        $tags = explode(',', $this->tags) + explode(',', $oldTags);
0 ignored issues
show
Bug introduced by
It seems like $this->tags can also be of type array; 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

248
        $tags = explode(',', /** @scrutinizer ignore-type */ $this->tags) + explode(',', $oldTags);
Loading history...
Bug introduced by
It seems like $oldTags 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

248
        $tags = explode(',', $this->tags) + explode(',', /** @scrutinizer ignore-type */ $oldTags);
Loading history...
249
        if ($tags = array_unique($tags)) {
250
            TagService::updateCounters($tags);
251
        }
252
    }
253
254
    public function getCategory()
255
    {
256
        return $this->hasOne(Category::class, ['id' => 'category_id']);
257
    }
258
259
    public function getFromAccount()
260
    {
261
        return $this->hasOne(Account::class, ['id' => 'from_account_id']);
262
    }
263
264
    public function getRecords()
265
    {
266
        return $this->hasMany(Record::class, ['transaction_id' => 'id']);
267
    }
268
269
    public function getToAccount()
270
    {
271
        return $this->hasOne(Account::class, ['id' => 'to_account_id']);
272
    }
273
274
    public function extraFields()
275
    {
276
        return ['toAccount', 'fromAccount', 'category'];
277
    }
278
279
    /**
280
     * @return array
281
     */
282
    public function fields()
283
    {
284
        $fields = parent::fields();
285
        unset($fields['currency_amount_cent'], $fields['user_id'], $fields['amount_cent']);
286
287
        $fields['currency_amount'] = function (self $model) {
288
            return Setup::toYuan($model->currency_amount_cent);
289
        };
290
291
        $fields['amount'] = function (self $model) {
292
            return Setup::toYuan($model->amount_cent);
293
        };
294
295
        $fields['type'] = function (self $model) {
296
            return TransactionType::getName($model->type);
297
        };
298
299
        $fields['type_text'] = function (self $model) {
300
            return data_get(TransactionType::texts(), $model->type);
301
        };
302
303
        $fields['tags'] = function (self $model) {
304
            return $model->tags ? explode(',', $model->tags) : [];
0 ignored issues
show
Bug introduced by
It seems like $model->tags can also be of type array; 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

304
            return $model->tags ? explode(',', /** @scrutinizer ignore-type */ $model->tags) : [];
Loading history...
305
        };
306
307
        $fields['reimbursement_status'] = function (self $model) {
308
            return ReimbursementStatus::getName($model->reimbursement_status);
0 ignored issues
show
Bug introduced by
It seems like $model->reimbursement_status can also be of type null; however, parameter $v of app\core\types\BaseType::getName() 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

308
            return ReimbursementStatus::getName(/** @scrutinizer ignore-type */ $model->reimbursement_status);
Loading history...
309
        };
310
311
        $fields['status'] = function (self $model) {
312
            return TransactionStatus::getName($model->status);
0 ignored issues
show
Bug introduced by
It seems like $model->status can also be of type null; however, parameter $v of app\core\types\BaseType::getName() 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

312
            return TransactionStatus::getName(/** @scrutinizer ignore-type */ $model->status);
Loading history...
313
        };
314
315
        $fields['date'] = function (self $model) {
316
            return DateHelper::datetimeToIso8601($model->date);
317
        };
318
319
        $fields['created_at'] = function (self $model) {
320
            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

320
            return DateHelper::datetimeToIso8601(/** @scrutinizer ignore-type */ $model->created_at);
Loading history...
321
        };
322
323
        $fields['updated_at'] = function (self $model) {
324
            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

324
            return DateHelper::datetimeToIso8601(/** @scrutinizer ignore-type */ $model->updated_at);
Loading history...
325
        };
326
327
        return $fields;
328
    }
329
}
330