Passed
Push — developer ( b6ebe7...0bf5e9 )
by Mariusz
47:54 queued 29:05
created

Synchronizer::findRelationship()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 9
rs 10
cc 3
nc 3
nop 2
1
<?php
2
/**
3
 * WAPRO ERP base synchronizer file.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 5.0 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author    Mariusz Krzaczkowski <[email protected]>
10
 */
11
12
namespace App\Integrations\Wapro;
13
14
/**
15
 * WAPRO ERP base synchronizer class.
16
 */
17
abstract class Synchronizer
18
{
19
	/** @var string Provider name | File name. */
20
	const NAME = null;
21
22
	/** @var string Module name. */
23
	const MODULE_NAME = null;
24
25
	/** @var string Priority order. */
26
	const SEQUENCE = null;
27
28
	/** @var string Class name. */
29
	public $className;
30
31
	/** @var string[] Map of fields integrating with WAPRO ERP */
32
	protected $fieldMap = [];
33
34
	/** @var \App\Integrations\Wapro Controller instance. */
35
	protected $controller;
36
37
	/** @var \Vtiger_Record_Model Record model instance. */
38
	protected $recordModel;
39
40
	/** @var array Record row. */
41
	protected $row;
42
43
	/** @var int WAPRO ERP record ID. */
44
	protected $waproId;
45
46
	/** @var bool The flag to skip record creation. */
47
	protected $skip;
48
49
	/** @var bool Information on currency configuration. */
50
	protected static $currency;
51
52
	/**
53
	 * Synchronizer constructor.
54
	 *
55
	 * @param \App\Integrations\Wapro $controller
56
	 */
57
	public function __construct(\App\Integrations\Wapro $controller)
58
	{
59
		$this->controller = $controller;
60
		$this->className = substr(static::class, strrpos(static::class, '\\') + 1);
61
		if (isset($controller->customConfig[$this->className])) {
62
			$this->fieldMap = $controller->customConfig[$this->className];
63
		}
64
	}
65
66
	/**
67
	 * Main function to execute synchronizer.
68
	 *
69
	 * @return int
70
	 */
71
	abstract public function process(): int;
72
73
	/**
74
	 * Import record.
75
	 *
76
	 * @return int
77
	 */
78
	abstract public function importRecord(): int;
79
80
	/**
81
	 * Get number of records.
82
	 *
83
	 * @return int
84
	 */
85
	abstract public function getCounter(): int;
86
87
	/**
88
	 * Function to get provider name.
89
	 *
90
	 * @return string provider name
91
	 */
92
	public function getName(): string
93
	{
94
		return $this::NAME;
95
	}
96
97
	/**
98
	 * Find the crm ID for the integration record.
99
	 *
100
	 * @param int    $id
101
	 * @param string $table
102
	 *
103
	 * @return int|null
104
	 */
105
	public function findInMapTable(int $id, string $table): ?int
106
	{
107
		$cacheKey = "$id|$table";
108
		if (\App\Cache::has('WaproMapTable', $cacheKey)) {
109
			return \App\Cache::get('WaproMapTable', $cacheKey);
110
		}
111
		$crmId = (new \App\Db\Query())->select(['crmid'])->from(\App\Integrations\Wapro::RECORDS_MAP_TABLE_NAME)->where(['wtable' => $table, 'wid' => $id])->scalar();
112
		\App\Cache::save('WaproMapTable', $cacheKey, $crmId);
113
		return $crmId ?: null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $crmId ?: null could return the type string which is incompatible with the type-hinted return integer|null. Consider adding an additional type-check to rule them out.
Loading history...
114
	}
115
116
	/**
117
	 * Add error log to db.
118
	 *
119
	 * @param \Throwable $ex
120
	 *
121
	 * @return void
122
	 */
123
	public function logError(\Throwable $ex): void
124
	{
125
		\App\Log::error("Error during import record in {$this->className}:\n{$ex->__toString()}", 'Integrations/Wapro');
126
		\App\DB::getInstance('log')->createCommand()
127
			->insert(\App\Integrations\Wapro::LOG_TABLE_NAME, [
128
				'time' => date('Y-m-d H:i:s'),
129
				'category' => $this->className,
130
				'message' => \App\TextUtils::textTruncate($ex->getMessage(), 255),
131
				'error' => true,
132
				'trace' => \App\TextUtils::textTruncate("WAPRO ID: {$this->waproId} \n{$ex->__toString()}", 65535)
133
			])->execute();
134
	}
135
136
	/**
137
	 * Add log to db.
138
	 *
139
	 * @param string $message
140
	 *
141
	 * @return void
142
	 */
143
	public function log(string $message): void
144
	{
145
		\App\DB::getInstance('log')->createCommand()
146
			->insert(\App\Integrations\Wapro::LOG_TABLE_NAME, [
147
				'time' => date('Y-m-d H:i:s'),
148
				'category' => $this->className,
149
				'message' => \App\TextUtils::textTruncate($message, 255),
150
				'error' => false,
151
			])->execute();
152
	}
153
154
	/**
155
	 * Load data from DB based on field map.
156
	 *
157
	 * @return void
158
	 */
159
	protected function loadFromFieldMap(): void
160
	{
161
		foreach ($this->fieldMap as $wapro => $crm) {
162
			if (\array_key_exists($wapro, $this->row)) {
163
				if (null === $this->row[$wapro]) {
164
					continue;
165
				}
166
				if (\is_array($crm)) {
167
					$value = $this->{$crm['fn']}($this->row[$wapro], $crm); // it cannot be on the lower line because it is a reference
168
					if ($this->skip) {
169
						break;
170
					}
171
					$this->recordModel->set($crm['fieldName'], trim($value));
172
				} else {
173
					$this->recordModel->set($crm, trim($this->row[$wapro]));
174
				}
175
			} else {
176
				\App\Log::error("No column {$wapro} in the {$this->className}", 'Integrations/Wapro');
177
			}
178
		}
179
	}
180
181
	/**
182
	 * Convert phone to system format.
183
	 *
184
	 * @param string $value
185
	 * @param array  $params
186
	 *
187
	 * @return string
188
	 */
189
	protected function convertPhone(string $value, array &$params): string
190
	{
191
		if ($fieldModel = $this->recordModel->getField($params['fieldName'])) {
192
			$details = $fieldModel->getUITypeModel()->getPhoneDetails($value, 'PL');
0 ignored issues
show
Bug introduced by
The method getPhoneDetails() does not exist on Vtiger_Base_UIType. It seems like you code against a sub-type of Vtiger_Base_UIType such as Vtiger_Phone_UIType. ( Ignorable by Annotation )

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

192
			$details = $fieldModel->getUITypeModel()->/** @scrutinizer ignore-call */ getPhoneDetails($value, 'PL');
Loading history...
193
			$value = $details['number'];
194
			if ($params['fieldName'] !== $details['fieldName']) {
195
				$params['fieldName'] = $details['fieldName'];
196
			}
197
		}
198
		return $value;
199
	}
200
201
	/**
202
	 * Convert country to system format.
203
	 *
204
	 * @param string $value
205
	 * @param array  $params
206
	 *
207
	 * @return string|null
208
	 */
209
	protected function convertCountry(string $value, array $params = []): ?string
210
	{
211
		$value = trim($value);
212
		return $value ? \App\Fields\Country::getCountryName($value) : null;
213
	}
214
215
	/**
216
	 * Convert currency to system format.
217
	 *
218
	 * @param string $value
219
	 * @param array  $params
220
	 *
221
	 * @return int
222
	 */
223
	protected function convertCurrency(string $value, array $params): int
224
	{
225
		$currencyId = \App\Fields\Currency::getIdByCode($value);
226
		if (empty($currencyId)) {
227
			$currencyId = \App\Fields\Currency::addCurrency($value);
228
		}
229
		return $currencyId ?? 0;
230
	}
231
232
	/**
233
	 * Find relationship.
234
	 *
235
	 * @param string $value
236
	 * @param array  $params
237
	 *
238
	 * @return int
239
	 */
240
	protected function findByRelationship(string $value, array $params): int
241
	{
242
		if ($id = $this->findInMapTable($value, $params['tableName'])) {
0 ignored issues
show
Bug introduced by
$value of type string is incompatible with the type integer expected by parameter $id of App\Integrations\Wapro\S...nizer::findInMapTable(). ( Ignorable by Annotation )

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

242
		if ($id = $this->findInMapTable(/** @scrutinizer ignore-type */ $value, $params['tableName'])) {
Loading history...
243
			return $id;
244
		}
245
		if (!empty($params['skipMode'])) {
246
			$this->skip = true;
247
		}
248
		return 0;
249
	}
250
251
	/**
252
	 * Get information about base currency.
253
	 *
254
	 * @return array
255
	 */
256
	protected function getBaseCurrency(): array
257
	{
258
		if (isset(self::$currency)) {
259
			return self::$currency;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::currency returns the type boolean which is incompatible with the type-hinted return array.
Loading history...
260
		}
261
		$wapro = (new \App\Db\Query())->from('dbo.WALUTA_BAZOWA_V')->one($this->controller->getDb());
262
		return self::$currency = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('wapro' => $wapro,...Currency::getDefault()) of type array<string,array|boolean|integer> is incompatible with the declared type boolean of property $currency.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
263
			'wapro' => $wapro,
264
			'currencyId' => $this->convertCurrency($wapro['SYM_WALUTY'], []),
265
			'default' => \App\Fields\Currency::getDefault(),
266
		];
267
	}
268
269
	/**
270
	 * Convert unit name to system format.
271
	 *
272
	 * @param string $value
273
	 * @param array  $params
274
	 *
275
	 * @return string
276
	 */
277
	protected function convertUnitName(string $value, array $params): string
278
	{
279
		$value = trim($value, '.');
280
		$picklistValues = \App\Fields\Picklist::getValuesName($params['fieldName']);
281
		$return = \in_array($value, $picklistValues);
282
		if (!$return) {
283
			foreach ($picklistValues as $picklistValue) {
284
				if (\App\Language::translate($picklistValue, $params['moduleName']) === $value) {
285
					$return = true;
286
					$value = $picklistValue;
287
					break;
288
				}
289
			}
290
		}
291
		return $return ? $value : '';
292
	}
293
294
	/**
295
	 * Get global tax from value.
296
	 *
297
	 * @param string $value
298
	 * @param bool   $addIfNotExist
299
	 *
300
	 * @return string
301
	 */
302
	protected function getGlobalTax(string $value, bool $addIfNotExist = false): string
303
	{
304
		$value = (float) $value;
305
		$taxes = '';
306
		foreach (\Vtiger_Inventory_Model::getGlobalTaxes() as $key => $tax) {
307
			if (\App\Validator::floatIsEqual($tax['value'], $value)) {
308
				$taxes = $key;
309
				break;
310
			}
311
		}
312
		if ($addIfNotExist && empty($taxes)) {
313
			$taxModel = new \Settings_Inventory_Record_Model();
314
			$taxModel->setData([
315
				'name' => $value,
316
				'value' => $value,
317
				'status' => 0,
318
				'default' => 0,
319
			])
320
				->setType('Taxes');
321
			$taxes = $taxModel->save();
322
		}
323
		return $taxes;
324
	}
325
326
	/**
327
	 * Convert unit name to system format.
328
	 *
329
	 * @param string $value
330
	 * @param array  $params
331
	 *
332
	 * @return string
333
	 */
334
	protected function decode(string $value, array $params): string
335
	{
336
		return trim(preg_replace('/[\x{0081}\n]/u', ' ', \App\Purifier::decodeHtml($value)));
337
	}
338
339
	/**
340
	 * Search for user in activity.
341
	 *
342
	 * @param int    $id
343
	 * @param string $table
344
	 *
345
	 * @return int|null
346
	 */
347
	public function searchUserInActivity(int $id, string $table): ?int
348
	{
349
		$cacheKey = "$id|$table";
350
		if (\App\Cache::has('WaproUser', $cacheKey)) {
351
			return \App\Cache::get('WaproUser', $cacheKey);
352
		}
353
		$userId = null;
354
		$erpId = (new \App\Db\Query())->select(['ID_UZYTKOWNIKA'])->from('dbo.AKTYWNOSC_UZYTKOWNIKA')
355
			->where(['ID_ZAPISU' => $id, 'TYP_ZAPISU' => $table])->scalar($this->controller->getDb());
356
		if ($erpId) {
357
			$userId = $this->getUser($erpId);
0 ignored issues
show
Bug introduced by
$erpId of type string is incompatible with the type integer expected by parameter $erpId of App\Integrations\Wapro\Synchronizer::getUser(). ( Ignorable by Annotation )

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

357
			$userId = $this->getUser(/** @scrutinizer ignore-type */ $erpId);
Loading history...
358
		}
359
		\App\Cache::save('WaproUser', $cacheKey, $userId);
360
		return $userId;
361
	}
362
363
	/**
364
	 * Find CRM User ID by ID from ERP.
365
	 *
366
	 * @param int $erpId
367
	 *
368
	 * @return int|null
369
	 */
370
	protected function getUser(int $erpId): ?int
371
	{
372
		if (\App\Cache::has('WaproUserMap', '')) {
373
			$users = \App\Cache::get('WaproUserMap', '');
374
		} else {
375
			$erpUsers = (new \App\Db\Query())->select(['ID_UZYTKOWNIKA', 'IDENTYFIKATOR'])
376
				->from('dbo.UZYTKOWNIK')->createCommand($this->controller->getDb())->queryAllByGroup();
377
			$dataReader = (new \App\Db\Query())->select(['wapro_user', 'id'])->from('vtiger_users')
378
				->where(['and', ['not', ['wapro_user' => null]], ['<>', 'wapro_user', '']])
379
				->createCommand()->query();
380
			$crmUsers = [];
381
			while ($row = $dataReader->read()) {
382
				$erpIds = explode(',', $row['wapro_user']);
383
				foreach ($erpIds as $erpId) {
384
					$crmUsers[$erpId] = $row['id'];
385
				}
386
			}
387
			$users = [];
388
			foreach ($erpUsers as $id => $ident) {
389
				if (isset($crmUsers[$ident])) {
390
					$users[$id] = $crmUsers[$ident];
391
				}
392
			}
393
			\App\Cache::save('WaproUserMap', '', $users);
394
		}
395
		return $users[$erpId] ?? null;
396
	}
397
}
398