'xlsx', 'fields' => [0 => 'code', 1 => 'name', 2 => 'quantity', 3 => 'ean', 4 => 'price', 5 => 'supplier_code'], 'search_in_products' => true, 'isdoc_search_in_ean' => true, 'encoding' => 'UTF-8', 'skip' => '1', 'piece_price' => true, 'default' => true, ]; public $supplier_settings; protected $separator = ';'; protected $skip = 0; protected $encoding; protected $fields = []; protected $stock_in; protected $check_file_name = false; protected $only_visible = false; protected $search_in_products = false; protected $multiple = 1; protected $cut_code; protected $ignore_code = false; protected $stockInIndex; protected StockInProductOfSupplierService $stockInProductOfSupplierService; public function __construct($stock_in, $stockInIndex = 'invoice') { $this->stock_in = $stock_in; $this->supplier_settings = sqlQueryBuilder()->select('s.import_settings') ->from('suppliers', 's') ->where(Operator::equals(['id' => $stock_in['id_supplier']])) ->execute() ->fetchColumn(); $this->stockInIndex = $stockInIndex; /* @var StockInProductOfSupplierService $supplierService */ $this->stockInProductOfSupplierService = ServiceContainer::getService(StockInProductOfSupplierService::class); } public function load_supplier_settings() { $settings = $this->supplier_settings != null ? json_decode($this->supplier_settings, true) : $this->default_settings; if ($this->supplier_settings && json_last_error()) { throw new Exception('Nelze nacist nastaveni importu: '.json_last_error_msg()); } foreach ((array) $settings as $key => $value) { $this->$key = $value; } } public function import($file) { if ($this->check_file_name) { $name = pathinfo($file['name'], PATHINFO_FILENAME); if ($name != $this->stock_in['code']) { return $this->add_error("Nesedí jméno souboru s číslem faktury: {$name} != {$this->stock_in['code']}"); } } if (empty($file['tmp_name'])) { return $this->add_error('Nebyl nahrán žádný soubor!'); } $method = "import_{$this->type}"; sqlStartTransaction(); $ret = $this->$method($file['tmp_name']); sqlFinishTransaction(); return $ret; } public function import_isdoc($file) { $xml = simplexml_load_file($file); $factorage = [ 'date_issued' => (string) $xml->IssueDate, 'note' => (string) $xml->note, 'date_expiration' => (string) $xml->PaymentMeans->Payment->Details->PaymentDueDate, 'total_price' => (string) $xml->LegalMonetaryTotal->TaxExclusiveAmount, ]; if (!$this->ignore_code) { $factorage['code'] = (string) $xml->ID; } $this->updateSQL( 'stock_in', $factorage, [ 'id' => $this->stock_in['id'], ] ); foreach ($xml->InvoiceLines->InvoiceLine as $invoiceLine) { $item = [ 'code' => (string) $invoiceLine->Item->SellersItemIdentification->ID, 'name' => (string) $invoiceLine->Item->Description, 'quantity' => (string) $invoiceLine->InvoicedQuantity, 'price' => (string) $invoiceLine->LineExtensionAmount, 'vat' => (string) $invoiceLine->ClassifiedTaxCategory->Percent, ]; if ($this->isdoc_search_in_ean ?? false) { $ean = array_filter([ (int) $invoiceLine->Item->CatalogueItemIdentification->ID ?? '', // tady by mel byt EAN dle dokumentace (int) $invoiceLine->Item->SellersItemIdentification->ID ?? '', // nekdo ma EAN tady ]); if ($ean) { $ean = (count($ean) == 1 ? reset($ean) : $ean); $item['ean'] = $ean; } } if ($item['quantity'] < 0) { $item['price'] = (string) $invoiceLine->UnitPrice; } $this->add_item($item); } return true; } public function import_isdocx($file) { $za = new ZipArchive(); $res = $za->open($file); if ($res !== true) { return false; } // finds the first .isdoc file and returns it or false $findIsdoc = function ($zipArchive) { // loop through all files in zip for ($i = 0; $i < $zipArchive->numFiles; $i++) { $stat = $zipArchive->statIndex($i); // if extension is .isdoc return the filename if (preg_match('/(.*\.isdoc)/', $stat['name']) === 1) { return $stat['name']; } } return false; }; $fileName = 'zip://'.$file.'#'.$findIsdoc($za); $za->close(); return $this->import_isdoc($fileName); } public function fopen_convert($fileName) { $fc = iconv($this->encoding, 'utf-8', file_get_contents($fileName)); $handle = fopen('php://memory', 'rw'); fwrite($handle, $fc); fseek($handle, 0); return $handle; } public function import_xlsx($file) { $xlsx = new AutomaticImportTransform($file); $data = $xlsx->GetXml(); $i = 0; foreach ($data->item as $row) { if ($i < $this->skip) { $i++; continue; } $arr = json_decode(json_encode($row), true)['col']; $arr = array_pad($arr, count($this->fields), null); $item = array_combine($this->fields, array_slice($arr, 0, count($this->fields))); // kontrola prázdného řádku if (!empty($item) && !is_array($item['quantity'])) { $this->add_item($item); } else { continue; } $i++; } return true; } public function import_csv($file) { if ($this->encoding) { $file = $this->fopen_convert($file); } else { $file = fopen($file, 'r'); } if (!$file) { exit('Nelze načíst soubor'); } $count = count($this->fields); while ($this->skip-- > 0) { fgetcsv($file, null, $this->separator); } while ($row = fgetcsv($file, null, $this->separator)) { $row = array_combine($this->fields, array_slice($row, 0, $count)); $this->add_item($row); } return true; } public function add_item($item) { /* * Funkce na uříznutí X počtu znaků z kodu. Stačí zadat v importu/exportu "cut" = 'začátek';'konec' * */ if ($this->cut_code) { $cut = explode(';', $this->cut_code); $item['code'] = substr($item['code'], $cut[0], $cut[1] ?? (strlen($item['code']) - $cut[0])); } $mapping = []; if (!empty($item['code'])) { $mapping = ['code' => $item['code']]; } if (!empty($item['ean'])) { $mapping['ean'] = $item['ean']; } if (!empty($mapping)) { $qb = sqlQueryBuilder()->select('pos.id_product', 'pos.id_variation', 'pos.import_multiplier', 'p.vat as id_vat') ->from('products_of_suppliers', 'pos') ->leftJoin('pos', 'products', 'p', 'pos.id_product=p.id') ->where(Operator::equals(['pos.id_supplier' => $this->stock_in['id_supplier']])); if ($this->only_visible) { $qb->andWhere(Operator::equals(['p.figure' => 'Y'])); } $operators = array_map(function ($key, $value) { if (is_array($value)) { return Operator::inIntArray($value, "pos.{$key}"); } return Operator::equals(["pos.{$key}" => $value]); }, array_keys($mapping), $mapping); $qb->andWhere(Operator::orX($operators)); $product = $qb->execute()->fetch(); if (!$product && $this->search_in_products) { $qb = sqlQueryBuilder()->select('p.id AS id_product, pv.id AS id_variation') ->fromProducts() ->joinVariationsOnProducts(); $productsMapping = array_merge( Mapping::mapKeys($mapping, function ($key, $value) {return ["p.{$key}", $value]; }), Mapping::mapKeys($mapping, function ($key, $value) {return ["pv.{$key}", $value]; }) ); if (isset($productsMapping['pv.code']) && !findModule(Modules::PRODUCTS_VARIATIONS, Modules::SUB_CODE)) { unset($productsMapping['pv.code']); } $operators = array_map(function ($key, $value) { if (is_array($value)) { return Operator::inStringArray($value, $key); } return Operator::equals([$key => $value]); }, array_keys($productsMapping), $productsMapping); $qb->andWhere(Operator::orX($operators)); if ($this->only_visible) { $qb->andWhere(Operator::equals(['p.figure' => 'Y'])); } $product = $qb->execute()->fetch(); if ($product) { $product['import_multiplier'] = 1; } } } else { $product = null; $mapping['code'] = translate('unknown'); $mapping['ean'] = translate('unknown'); } if (!$product || $product['import_multiplier'] == 0) { $product = ['id_product' => null, 'id_variation' => null, 'name' => "{$item['name']} (kód: {$mapping['code']}, ean: {$mapping['ean']})", 'import_multiplier' => 1, 'id_vat' => 0]; } else { $product['name'] = null; } $product['id_stock_in'] = $this->stock_in['id']; $product['quantity'] = round((float) $item['quantity'] * (float) $product['import_multiplier'] * (float) $this->multiple); $product['vat'] = empty($item['vat']) ? getVat($product['id_vat']) : $item['vat']; if (empty($item['price']) && !empty($item['price_with_vat'])) { $price = toDecimal($this->preparePrice($item['price_with_vat']))->removeVat($product['vat']); } else { $price = toDecimal($this->preparePrice($item['price'])); } // if isset price multiplier if (isset($this->stock_in['multiplier'])) { $price = $price->mul(toDecimal($this->stock_in['multiplier'])); } unset($product['id_vat']); if (isset($this->piece_price)) { $product['price'] = $price; } else { $product['price'] = $product['quantity'] > 0 ? $price->div(toDecimal($product['quantity'])) : $price; } if ($this->insertSQL('stock_in_items', $product, ['import_multiplier']) <= 0) { return $this->add_error('Cannot add '.print_r($product, true)); } if ($product['id_product'] > 0 && !in_array($this->stockInIndex, ['future', 'preorder'])) { $prod = new Product($product['id_product']); $prod->storeIn($product['id_variation'], $product['quantity']); } $product['supplier_code'] = !empty($item['supplier_code']) ? trim($item['supplier_code']) : null; $stockInData = getVal('data'); try { $this->stockInProductOfSupplierService->updateOrInsertProductSupplier($product, $stockInData); } catch (Exception|\Doctrine\DBAL\Driver\Exception) { $this->add_error("Nepodařilo se uložit kód dodavatele '{$item['code']}' položky: {$item['name']}"); } } private function add_error($string) { $this->errors[] = $string; return false; } } if (!function_exists('json_last_error_msg')) { function json_last_error_msg() { switch (json_last_error()) { case JSON_ERROR_NONE: return 'No errors'; case JSON_ERROR_DEPTH: return 'Maximum stack depth exceeded'; case JSON_ERROR_STATE_MISMATCH: return 'Underflow or the modes mismatch'; case JSON_ERROR_CTRL_CHAR: return 'Unexpected control character found'; case JSON_ERROR_SYNTAX: return 'Syntax error, malformed JSON'; case JSON_ERROR_UTF8: return 'Malformed UTF-8 characters, possibly incorrectly encoded'; default: return 'Unknown error'; } } }