Total Complexity | 51 |
Total Lines | 312 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Invoice often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Invoice, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class Invoice extends \App\Integrations\Wapro\Synchronizer |
||
19 | { |
||
20 | /** {@inheritdoc} */ |
||
21 | const NAME = 'LBL_INVOICE'; |
||
22 | |||
23 | /** {@inheritdoc} */ |
||
24 | const SEQUENCE = 5; |
||
25 | |||
26 | /** @var string[] Map for payment methods with WAPRO ERP */ |
||
27 | const PAYMENT_METHODS_MAP = [ |
||
28 | 'gotówka' => 'PLL_CASH', |
||
29 | 'przelew' => 'PLL_TRANSFER', |
||
30 | 'czek' => 'PLL_CHECK', |
||
31 | 'pobranie' => 'PLL_CASH_ON_DELIVERY', |
||
32 | ]; |
||
33 | |||
34 | /** {@inheritdoc} */ |
||
35 | protected $fieldMap = [ |
||
36 | 'ID_FIRMY' => ['fieldName' => 'multiCompanyId', 'fn' => 'findRelationship', 'tableName' => 'FIRMA', 'skipMode' => true], |
||
37 | 'ID_KONTRAHENTA' => ['fieldName' => 'accountid', 'fn' => 'findRelationship', 'tableName' => 'KONTRAHENT', 'skipMode' => true], |
||
38 | 'FORMA_PLATNOSCI' => ['fieldName' => 'payment_methods', 'fn' => 'convertPaymentMethods'], |
||
39 | 'UWAGI' => 'description', |
||
40 | 'KONTRAHENT_NAZWA' => ['fieldName' => 'company_name_a', 'fn' => 'decode'], |
||
41 | 'issueTime' => ['fieldName' => 'issue_time', 'fn' => 'convertDate'], |
||
42 | 'saleDate' => ['fieldName' => 'saledate', 'fn' => 'convertDate'], |
||
43 | 'paymentDate' => ['fieldName' => 'paymentdate', 'fn' => 'convertDate'], |
||
44 | ]; |
||
45 | |||
46 | /** {@inheritdoc} */ |
||
47 | public function process(): int |
||
48 | { |
||
49 | $query = (new \App\Db\Query())->select([ |
||
50 | 'ID_DOKUMENTU_HANDLOWEGO', 'ID_FIRMY', 'ID_KONTRAHENTA', 'ID_DOK_ORYGINALNEGO', |
||
51 | 'NUMER', 'FORMA_PLATNOSCI', 'UWAGI', 'KONTRAHENT_NAZWA', 'WARTOSC_NETTO', 'WARTOSC_BRUTTO', 'DOK_KOREKTY', 'DATA_KURS_WAL', 'DOK_WAL', 'SYM_WAL', |
||
52 | 'issueTime' => 'cast (dbo.DOKUMENT_HANDLOWY.DATA_WYSTAWIENIA - 36163 as datetime)', |
||
53 | 'saleDate' => 'cast (dbo.DOKUMENT_HANDLOWY.DATA_SPRZEDAZY - 36163 as datetime)', |
||
54 | 'paymentDate' => 'cast (dbo.DOKUMENT_HANDLOWY.TERMIN_PLAT - 36163 as datetime)', |
||
55 | 'currencyDate' => 'cast (dbo.DOKUMENT_HANDLOWY.DATA_KURS_WAL - 36163 as datetime)', |
||
56 | ])->from('dbo.DOKUMENT_HANDLOWY') |
||
57 | ->where(['ID_TYPU' => 1]); |
||
58 | $pauser = \App\Pauser::getInstance('WaproInvoiceLastId'); |
||
59 | if ($val = $pauser->getValue()) { |
||
60 | $query->andWhere(['>', 'ID_DOKUMENTU_HANDLOWEGO', $val]); |
||
61 | } |
||
62 | $lastId = $s = $e = $i = $u = 0; |
||
63 | foreach ($query->batch(100, $this->controller->getDb()) as $rows) { |
||
64 | $lastId = 0; |
||
65 | foreach ($rows as $row) { |
||
66 | $this->waproId = $row['ID_DOKUMENTU_HANDLOWEGO']; |
||
67 | $this->row = $row; |
||
68 | $this->skip = false; |
||
69 | try { |
||
70 | switch ($this->importRecord()) { |
||
71 | default: |
||
72 | case 0: |
||
73 | ++$s; |
||
74 | break; |
||
75 | case 1: |
||
76 | ++$u; |
||
77 | break; |
||
78 | case 2: |
||
79 | ++$i; |
||
80 | break; |
||
81 | } |
||
82 | $lastId = $this->waproId; |
||
83 | } catch (\Throwable $th) { |
||
84 | $this->logError($th); |
||
85 | ++$e; |
||
86 | } |
||
87 | } |
||
88 | $pauser->setValue($lastId); |
||
89 | if ($this->controller->cron && $this->controller->cron->checkTimeout()) { |
||
90 | break; |
||
91 | } |
||
92 | } |
||
93 | if (0 == $lastId) { |
||
94 | $pauser->destroy(); |
||
95 | } |
||
96 | $this->log("Create {$i} | Update {$u} | Skipped {$s} | Error {$e}"); |
||
97 | return $i + $u; |
||
98 | } |
||
99 | |||
100 | /** {@inheritdoc} */ |
||
101 | public function importRecord(): int |
||
102 | { |
||
103 | if ($id = $this->findInMapTable($this->waproId, 'DOKUMENT_HANDLOWY')) { |
||
104 | $this->recordModel = \Vtiger_Record_Model::getInstanceById($id, 'FInvoice'); |
||
105 | } else { |
||
106 | $this->recordModel = \Vtiger_Record_Model::getCleanInstance('FInvoice'); |
||
107 | $this->recordModel->setDataForSave([\App\Integrations\Wapro::RECORDS_MAP_TABLE_NAME => [ |
||
108 | 'wtable' => 'DOKUMENT_HANDLOWY', |
||
109 | ]]); |
||
110 | } |
||
111 | $this->recordModel->set('wapro_id', $this->waproId); |
||
112 | $this->recordModel->set('finvoice_status', 'PLL_UNASSIGNED'); |
||
113 | $this->recordModel->set('finvoice_type', 'PLL_DOMESTIC_INVOICE'); |
||
114 | $this->recordModel->set($this->recordModel->getModule()->getSequenceNumberFieldName(), $this->row['NUMER']); |
||
115 | $this->loadFromFieldMap(); |
||
116 | $this->loadDeliveryAddress('b'); |
||
117 | $this->loadInventory(); |
||
118 | if ($this->skip) { |
||
119 | return 0; |
||
120 | } |
||
121 | $this->recordModel->save(); |
||
122 | \App\Cache::save('WaproMapTable', "{$this->waproId}|DOKUMENT_HANDLOWY", $this->recordModel->getId()); |
||
123 | if ($id) { |
||
|
|||
124 | return $this->recordModel->getPreviousValue() ? 1 : 3; |
||
125 | } |
||
126 | return 2; |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * Convert payment method to system format. |
||
131 | * |
||
132 | * @param string $value |
||
133 | * @param array $params |
||
134 | * |
||
135 | * @return string |
||
136 | */ |
||
137 | protected function convertPaymentMethods(string $value, array $params): string |
||
138 | { |
||
139 | if (isset(self::PAYMENT_METHODS_MAP[$value])) { |
||
140 | return self::PAYMENT_METHODS_MAP[$value]; |
||
141 | } |
||
142 | $fieldModel = $this->recordModel->getField($params['fieldName']); |
||
143 | $key = array_search(mb_strtolower($value), array_map('mb_strtolower', $fieldModel->getPicklistValues())); |
||
144 | if (empty($key)) { |
||
145 | $fieldModel->setPicklistValues([$value]); |
||
146 | $key = $value; |
||
147 | } |
||
148 | return $key ?? ''; |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * Convert date to system format. |
||
153 | * |
||
154 | * @param string $value |
||
155 | * @param array $params |
||
156 | * |
||
157 | * @return string |
||
158 | */ |
||
159 | protected function convertDate(string $value, array $params): string |
||
160 | { |
||
161 | $value = explode(' ', $value); |
||
162 | return $value[0]; |
||
163 | } |
||
164 | |||
165 | /** |
||
166 | * Load delivery address. |
||
167 | * |
||
168 | * @param string $key |
||
169 | * |
||
170 | * @return void |
||
171 | */ |
||
172 | protected function loadDeliveryAddress(string $key): void |
||
173 | { |
||
174 | $row = (new \App\Db\Query())->select(['dbo.MIEJSCE_DOSTAWY.*'])->from('dbo.DOSTAWA') |
||
175 | ->leftJoin('dbo.MIEJSCE_DOSTAWY', 'dbo.DOSTAWA.ID_MIEJSCA_DOSTAWY = dbo.MIEJSCE_DOSTAWY.ID_MIEJSCA_DOSTAWY') |
||
176 | ->where(['dbo.DOSTAWA.ID_DOKUMENTU_HANDLOWEGO' => $this->row['DOK_KOREKTY'] ? $this->row['ID_DOK_ORYGINALNEGO'] : $this->waproId]) |
||
177 | ->one($this->controller->getDb()); |
||
178 | if ($row) { |
||
179 | $this->recordModel->set('addresslevel1' . $key, $this->convertCountry($row['SYM_KRAJU'])); |
||
180 | $this->recordModel->set('addresslevel5' . $key, $row['MIEJSCOWOSC']); |
||
181 | $this->recordModel->set('addresslevel7' . $key, $row['KOD_POCZTOWY']); |
||
182 | $this->recordModel->set('addresslevel8' . $key, $row['ULICA_LOKAL']); |
||
183 | $this->recordModel->set('company_name_' . $key, $row['FIRMA']); |
||
184 | if ($row['ODBIORCA']) { |
||
185 | [$firstName, $lastName] = explode(' ', $row['ODBIORCA'], 2); |
||
186 | $this->recordModel->set('first_name_' . $key, $firstName); |
||
187 | $this->recordModel->set('last_name_' . $key, $lastName); |
||
188 | } |
||
189 | $params = ['fieldName' => 'phone_' . $key]; |
||
190 | $phone = $this->convertPhone($row['TEL'], $params); |
||
191 | $this->recordModel->set($params['fieldName'], $phone); |
||
192 | } |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * Load inventory items. |
||
197 | * |
||
198 | * @return void |
||
199 | */ |
||
200 | protected function loadInventory(): void |
||
201 | { |
||
202 | $inventory = $this->getInventory(); |
||
203 | if (!$this->recordModel->isNew()) { |
||
204 | $oldInventory = $this->recordModel->getInventoryData(); |
||
205 | foreach ($oldInventory as $oldSeq => $oldItem) { |
||
206 | foreach ($inventory as $seq => $item) { |
||
207 | $same = true; |
||
208 | foreach ($item as $name => $value) { |
||
209 | if ($same) { |
||
210 | $same = isset($oldItem[$name]) && $value == $oldItem[$name]; |
||
211 | } |
||
212 | } |
||
213 | if ($same && $oldItem) { |
||
214 | $inventory[$seq] = $oldItem; |
||
215 | unset($oldInventory[$oldSeq]); |
||
216 | continue 2; |
||
217 | } |
||
218 | } |
||
219 | } |
||
220 | } |
||
221 | $this->recordModel->initInventoryData($inventory); |
||
222 | if (isset($inventory[0]['name'])) { |
||
223 | $this->recordModel->set('subject', \App\Record::getLabel($inventory[0]['name'], true) ?: '-'); |
||
224 | } |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Get inventory items. |
||
229 | * |
||
230 | * @return array |
||
231 | */ |
||
232 | protected function getInventory(): array |
||
233 | { |
||
234 | $currencyId = $this->getBaseCurrency()['currencyId']; |
||
235 | if (!empty($this->row['DOK_WAL'])) { |
||
236 | $currencyId = $this->convertCurrency($this->row['SYM_WAL'], []); |
||
237 | } |
||
238 | $currencyParam = \App\Json::encode($this->getCurrencyParam($currencyId)); |
||
239 | $dataReader = (new \App\Db\Query())->select(['ID_ARTYKULU', 'ILOSC', 'KOD_VAT', 'CENA_NETTO', 'JEDNOSTKA', 'OPIS', 'RABAT', 'RABAT2', 'CENA_NETTO_WAL']) |
||
240 | ->from('dbo.POZYCJA_DOKUMENTU_MAGAZYNOWEGO') |
||
241 | ->where(['ID_DOK_HANDLOWEGO' => $this->waproId]) |
||
242 | ->createCommand($this->controller->getDb())->query(); |
||
243 | $inventory = []; |
||
244 | while ($row = $dataReader->read()) { |
||
245 | $productId = $this->findRelationship($row['ID_ARTYKULU'], ['tableName' => 'ARTYKUL']); |
||
246 | if (!$productId) { |
||
247 | $productId = $this->addProduct($row['ID_ARTYKULU']); |
||
248 | } |
||
249 | $inventory[] = [ |
||
250 | 'name' => $productId, |
||
251 | 'qty' => $row['ILOSC'], |
||
252 | 'price' => $this->row['DOK_WAL'] ? $row['CENA_NETTO_WAL'] : $row['CENA_NETTO'], |
||
253 | 'comment1' => trim($row['OPIS']), |
||
254 | 'unit' => $this->convertUnitName($row['JEDNOSTKA'], ['fieldName' => 'usageunit', 'moduleName' => 'Products']), |
||
255 | 'discountmode' => 1, |
||
256 | 'discountparam' => \App\Json::encode([ |
||
257 | 'aggregationType' => ['individual', 'additional'], |
||
258 | 'individualDiscount' => empty((float) $row['RABAT']) ? 0 : (-$row['RABAT']), |
||
259 | 'individualDiscountType' => 'percentage', |
||
260 | 'additionalDiscount' => empty((float) $row['RABAT2']) ? 0 : (-$row['RABAT2']), |
||
261 | ]), |
||
262 | 'taxmode' => 1, |
||
263 | 'taxparam' => \App\Json::encode( |
||
264 | $this->getGlobalTax($row['KOD_VAT']) ? [ |
||
265 | 'aggregationType' => 'global', |
||
266 | 'globalTax' => (float) $row['KOD_VAT'], |
||
267 | ] : [ |
||
268 | 'aggregationType' => 'individual', |
||
269 | 'individualTax' => (float) $row['KOD_VAT'], |
||
270 | ] |
||
271 | ), |
||
272 | 'discount_aggreg' => 2, |
||
273 | 'currency' => $currencyId, |
||
274 | 'currencyparam' => $currencyParam, |
||
275 | ]; |
||
276 | } |
||
277 | return $inventory; |
||
278 | } |
||
279 | |||
280 | /** |
||
281 | * Get currency param. |
||
282 | * |
||
283 | * @param int $currencyId |
||
284 | * |
||
285 | * @return array |
||
286 | */ |
||
287 | protected function getCurrencyParam(int $currencyId): array |
||
288 | { |
||
289 | $baseCurrency = $this->getBaseCurrency(); |
||
290 | $defaultCurrencyId = $baseCurrency['default']['id']; |
||
291 | if (!empty($this->row['DATA_KURS_WAL'])) { |
||
292 | $date = $this->convertDate($this->row['currencyDate'], []); |
||
293 | } else { |
||
294 | $date = \vtlib\Functions::getLastWorkingDay(date('Y-m-d', strtotime('-1 day', strtotime($this->row['saleDate'])))); |
||
295 | } |
||
296 | $params = [ |
||
297 | $defaultCurrencyId => ['date' => $date, 'value' => 1.0, 'conversion' => 1.0], |
||
298 | ]; |
||
299 | $info = ['date' => $date]; |
||
300 | if ($currencyId != $defaultCurrencyId) { |
||
301 | if (empty($this->row['PRZELICZNIK_WAL'])) { |
||
302 | $value = \Settings_CurrencyUpdate_Module_Model::getCleanInstance()->getCRMConversionRate($currencyId, $defaultCurrencyId, $date); |
||
303 | $info['value'] = empty($value) ? 1.0 : round($value, 5); |
||
304 | $info['conversion'] = empty($value) ? 1.0 : round(1 / $value, 5); |
||
305 | } else { |
||
306 | $info['value'] = round($this->row['PRZELICZNIK_WAL'], 5); |
||
307 | $info['conversion'] = round(1 / $this->row['PRZELICZNIK_WAL'], 5); |
||
308 | } |
||
309 | $params[$currencyId] = $info; |
||
310 | } |
||
311 | return $params; |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Add a product when it does not exist in CRM. |
||
316 | * |
||
317 | * @param int $id |
||
318 | * |
||
319 | * @return int |
||
320 | */ |
||
321 | protected function addProduct(int $id): int |
||
322 | { |
||
323 | return $this->controller->getSynchronizer('Products')->importRecordById($id); |
||
324 | } |
||
325 | |||
326 | /** {@inheritdoc} */ |
||
327 | public function getCounter(): int |
||
330 | } |
||
331 | } |
||
332 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
integer
values, zero is a special case, in particular the following results might be unexpected: