Completed
Push — master ( 0211a3...9747b2 )
by Dmitry
15:35
created

BillImportForm::splitLine()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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