first commit
This commit is contained in:
390
admin/class/class.StockInImport.php
Normal file
390
admin/class/class.StockInImport.php
Normal file
@@ -0,0 +1,390 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user