Files
kupshop/admin/class/class.StockInImport.php
2025-08-02 16:30:27 +02:00

391 lines
13 KiB
PHP

<?php
use KupShop\AdminBundle\Util\StockInProductOfSupplierService;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use Query\Operator;
class StockInImport
{
use DatabaseCommunication;
public $type;
public $errors = [];
public $default_settings = [
'type' => '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';
}
}
}