BillImportForm   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 202
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 7
dl 0
loc 202
ccs 0
cts 104
cp 0
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A attributes() 0 4 1
A attributeLabels() 0 6 1
A rules() 0 6 1
B parse() 0 37 6
A splitLine() 0 9 2
A resolveClients() 0 6 1
A resolveTime() 0 18 6
B resolveType() 0 33 7
A convertClientToId() 0 8 2
1
<?php
2
/**
3
 * Finance module for HiPanel
4
 *
5
 * @link      https://github.com/hiqdev/hipanel-module-finance
6
 * @package   hipanel-module-finance
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2015-2019, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hipanel\modules\finance\forms;
12
13
use hipanel\modules\client\models\Client;
14
use hipanel\modules\finance\models\Bill;
15
use Yii;
16
use yii\base\InvalidValueException;
17
use yii\helpers\ArrayHelper;
18
19
/**
20
 * Class BillImportForm provides functionality to parse CSV data.
21
 *
22
 * Usage:
23
 *
24
 * ```php
25
 * $model = new BillImportForm([
26
 *     'billTypes' => [
27
 *         'deposit,webmoney' => 'WebMoney account deposit'
28
 *     ]
29
 * ]);
30
 *
31
 * $model->data = 'silverfire;now;10;usd;webmoney;test';
32
 * $models = $model->parse();
33
 *
34
 * foreach ($models as $model) {
35
 *     $model->save();
36
 * }
37
 * ```
38
 */
39
class BillImportForm extends \yii\base\Model
40
{
41
    /**
42
     * @var string
43
     */
44
    public $data;
45
    /**
46
     * @var array Array of possible bill types.
47
     * Key - full bill type
48
     * Value - bill type title
49
     */
50
    public $billTypes = [];
51
    /**
52
     * @var array map to find client id by login.
53
     * Key - login
54
     * Value - id
55
     */
56
    private $clientsMap = [];
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function attributes()
62
    {
63
        return ['data'];
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69
    public function attributeLabels()
70
    {
71
        return [
72
            'data' => Yii::t('hipanel:finance', 'Rows for import'),
73
        ];
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function rules()
80
    {
81
        return [
82
            ['data', 'safe'],
83
        ];
84
    }
85
86
    /**
87
     * Parses [[data]] attribute and creates [[Bill]] model from each line.
88
     *
89
     * @return Bill[]|false Array of [[Bill]] models on success or `false` on parsing error
90
     */
91
    public function parse()
92
    {
93
        $bills = [];
94
        $billTemplate = new Bill(['scenario' => Bill::SCENARIO_CREATE]);
95
96
        $lines = explode("\n", $this->data);
97
98
        try {
99
            foreach ($lines as $line) {
100
                $line = trim($line);
101
                if ($line === '') {
102
                    continue;
103
                }
104
105
                $bills[] = $bill = clone $billTemplate;
106
107
                list($client, $time, $sum, $currency, $type, $label) = $this->splitLine($line);
108
                $quantity = 1;
109
                $bill->setAttributes(compact('client', 'time', 'sum', 'currency', 'type', 'label', 'quantity'));
110
                $bill->populateRelation('charges', []);
111
            }
112
113
            $this->resolveClients(ArrayHelper::getColumn($bills, 'client'));
114
115
            foreach ($bills as $bill) {
116
                $bill->time = \Yii::$app->formatter->asDatetime($this->resolveTime($bill->time), 'php:d.m.Y H:i:s');
117
                $bill->type = $this->resolveType($bill->type);
118
                $bill->client_id = $this->convertClientToId($bill->client);
119
            }
120
        } catch (InvalidValueException $e) {
121
            $this->addError('data', $e->getMessage());
122
123
            return false;
124
        }
125
126
        return empty($bills) ? false : $bills;
127
    }
128
129
    /**
130
     * Splits $line for chunks by `;` character.
131
     * Ensures there are exactly 6 chunks.
132
     * Trims each value before return.
133
     *
134
     * @param string $line to be exploded
135
     * @return array
136
     */
137
    protected function splitLine($line)
138
    {
139
        $chunks = explode(';', $line);
140
        if (count($chunks) !== 6) {
141
            throw new InvalidValueException('Line "' . $line . '" is malformed');
142
        }
143
144
        return array_map('trim', $chunks);
145
    }
146
147
    /**
148
     * @param array $logins all logins used current import session to be pre-fetched
149
     * @void
150
     */
151
    private function resolveClients($logins)
152
    {
153
        $clients = Client::find()->where(['logins' => $logins])->limit(-1)->all();
154
        $this->clientsMap = array_combine(ArrayHelper::getColumn($clients, 'login'),
155
            ArrayHelper::getColumn($clients, 'id'));
156
    }
157
158
    /**
159
     * Resolves $time to a UNIX epoch timestamp.
160
     *
161
     * @param $time
162
     * @return int UNIX epoch timestamp
163
     */
164
    protected function resolveTime($time)
165
    {
166
        $timestamp = strtotime($time);
167
168
        if ($timestamp !== false) {
169
            return $timestamp;
170
        }
171
172
        if ($time === 'this' || $time === 'thisMonth') {
173
            return strtotime('first day of this month midnight');
174
        }
175
176
        if ($time === 'prev' || $time === 'prevMonth') {
177
            return strtotime('first day of last month midnight');
178
        }
179
180
        return time();
181
    }
182
183
    /**
184
     * Resolves payment $type to a normal form.
185
     *
186
     * @param string $type
187
     * @throws InvalidValueException
188
     * @return string
189
     */
190
    protected function resolveType($type)
191
    {
192
        $types = $this->billTypes;
193
194
        // Type is a normal key
195
        if (isset($types[$type])) {
196
            return $type;
197
        }
198
199
        // Type is a title of type instead of its key
200
        if (in_array($type, $types, true)) {
201
            return array_search($type, $types, true);
202
        }
203
204
        // Assuming only second part is passed. Match from the end
205
        $foundKey = null;
206
        foreach ($types as $key => $title) {
207
            list(, $name) = explode(',', $key);
208
            if ($name === $type) {
209
                if ($foundKey !== null) {
210
                    throw new InvalidValueException('Payment type "' . $type . '" is ambiguous');
211
                }
212
213
                $foundKey = $key;
214
            }
215
        }
216
217
        if ($foundKey) {
218
            return $foundKey;
219
        }
220
221
        throw new InvalidValueException('Payment type "' . $type . '" is not recognized');
222
    }
223
224
    /**
225
     * Converts client login to ID.
226
     * Note: [[resolveClients]]] must be called before calling this method.
227
     *
228
     * @param string $client
229
     * @return string|int
230
     * @see clientMap
231
     */
232
    protected function convertClientToId($client)
233
    {
234
        if (!isset($this->clientsMap[$client])) {
235
            throw new InvalidValueException('Client "' . $client . '" was not found');
236
        }
237
238
        return $this->clientsMap[$client];
239
    }
240
}
241