| 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!=, orswitchconditions), values of different types might be equal.For
integervalues, zero is a special case, in particular the following results might be unexpected: