'0', 'vatDisplay' => '0', 'payment_method' => 'prevodem', 'multiplier' => '1', 'id_index' => 'invoice', ]; protected $required = [ 'number' => true, 'code' => true, ]; protected $tableName = 'stock_in'; private $index; protected $index_old; protected ?StoresInStore $storesInStore = null; protected ContextManager $contextManager; protected StockInProductOfSupplierService $stockInProductOfSupplierService; public function __construct() { if (findModule(Modules::STORES)) { $this->storesInStore = ServiceContainer::getService(StoresInStore::class); } $this->contextManager = ServiceContainer::getService(ContextManager::class); $this->stockInProductOfSupplierService = ServiceContainer::getService(StockInProductOfSupplierService::class); } public function recalcTotalPrice() { sqlQuery('UPDATE '.getTableName('stock_in').' s SET total_price=( SELECT COALESCE(SUM(si.quantity*si.price), 0)+s.transport_price-s.discount FROM '.getTableName('stock_in_items').' si WHERE si.id_stock_in=s.id ) WHERE s.id=:id', ['id' => $this->getID()]); } public function get_vars() { $vars = parent::get_vars(); $pageVars = getVal('body', $vars); $acn = $this->getAction(); $pageVars['paid'] = [ '1' => translate('priceType_withVat', 'stockIn'), '2' => translate('priceType_withoutVat', 'stockIn'), '0' => translate('priceType_bothVat', 'stockIn'), '3' => translate('priceType_foreignCurrency', 'stockIn'), ]; $sql = sqlQuery('SELECT id, name FROM '.getTableName('suppliers')); $pageVars['suppliers'] = ['' => '- Vyber dodavatele -']; foreach ($sql as $row) { $pageVars['suppliers'][$row['id']] = $row['name']; } if (findModule(Modules::STORES)) { $pageVars['stores'] = $this->storesInStore->getStoresNames(); } $pageVars['paymentMethods'] = [ 'dobirka' => translate('cod', 'stockIn'), 'prevodem' => translate('bankTransfer', 'stockIn'), ]; $ID = $this->getID(); if ($ID) { global $cfg; $id_supplier = $pageVars['data']['id_supplier']; $date_issued = $pageVars['data']['date_issued']; $fields = 'si.id, si.id_product, si.name, si.id_variation, si.quantity, p.title as product_title, pv.title variation_title, si.price, COALESCE(v.vat, si.vat) as vat, p.figure, COALESCE(pv.price, p.price) as web_price, COALESCE(pv.in_store, p.in_store) as web_in_store, p.discount as web_discount, COALESCE(pv.ean, p.ean) as ean, pos.code as code_supplier, pos.note as products_of_suppliers_note'; if (!empty($cfg['Modules']['products_variations']['variationCode'])) { $fields .= ', COALESCE(pv.code, p.code) as code'; } else { $fields .= ', p.code as code'; } if (!empty($cfg['Modules']['products']['note'])) { $fields .= ', COALESCE(pv.note, p.note) as note'; } if (findModule(Modules::STOCK_IN, Modules::SUB_WEIGHTED_PURCHASE_PRICE)) { $fields .= ', si.additional_costs'; } $fields .= ', (SELECT si2.price FROM stock_in_items si2 LEFT JOIN stock_in st2 ON si2.id_stock_in = st2.id WHERE st2.id_index = "invoice" AND st2.date_issued <= "'.$date_issued.'" AND st2.id != si.id_stock_in AND si2.id_product = si.id_product AND ((si.id_variation IS NULL AND si2.id_variation IS NULL) OR si2.id_variation = si.id_variation) ORDER BY st2.date_issued DESC, st2.id DESC LIMIT 1) AS prev_price'; $orderBy = 'p.id > 0, si.id DESC'; if ($this->getIndex() == 'future') { $orderBy = 'IF(products_of_suppliers_note IS NULL, 1, 0) ASC, '.$orderBy; } $SQL = sqlQuery("SELECT {$fields} FROM stock_in_items si LEFT JOIN products p ON p.id=si.id_product LEFT JOIN products_variations pv ON pv.id=si.id_variation LEFT JOIN products_of_suppliers pos ON p.id = pos.id_product AND pos.id_supplier = '".$id_supplier."' AND ((pv.id IS NULL AND pos.id_variation IS NULL) OR pos.id_variation = pv.id) LEFT JOIN vats v ON v.id=p.vat WHERE si.id_stock_in='".$ID."' GROUP BY si.id ORDER BY ".$orderBy.' '); $vat = getAdminVat()['value']; $data = &$pageVars['data']; $data['transportVat'] = $vat; $data['transport_price_vat'] = $data['transport_price'] * (1 + ($vat / 100)); $data['total_price_vat'] = $data['transport_price_vat'] - ($data['discount'] * (1 + ($vat / 100))); $this->unserializeCustomData($data); if (!empty($data['data']['parent_stock_in'])) { $data['data']['parent_stock_in'] = sqlQueryBuilder() ->select('*') ->from('stock_in') ->where(Operator::equals(['id' => $data['data']['parent_stock_in']])) ->execute()->fetch(); } if (findModule(Modules::STORES) && ($data['data']['id_store'] ?? false)) { $data['isExternalStore'] = ($this->storesInStore->getStores()[$data['data']['id_store']]['type'] ?? false) == StoresInStore::TYPE_EXTERNAL_STORE; } $totalQuantity = 0; $items = []; foreach ($SQL as $row) { $row['web_price'] = calcPrice($row['web_price'], 0, $row['web_discount'])->asFloat(); $row['price'] = floatval($row['price']); if ($row['web_price'] && $row['price']) { $row['rabat'] = ($row['web_price'] - $row['price']) / $row['price'] * 100; if ($row['rabat'] < 2) { $row['color'] = '#9b313b'; $row['bg_color'] = '#FDD7DB'; } elseif ($row['rabat'] < 10) { $row['color'] = '#9a7a2c'; $row['bg_color'] = '#fceecd'; } else { $row['color'] = '#699b31'; $row['bg_color'] = '#E5FCCD'; } if ($row['web_price'] > 0 && $row['web_price'] <= $row['price']) { $row['bg_color'] = '#FCC479'; } if ($row['rabat'] > 100) { $row['bg_color'] = '#DADDFC'; } } if ($row['figure'] != 'Y') { $row['bg_color'] = '#FF858D'; } $row['web_price_vat'] = $row['web_price'] * (1 + $row['vat'] / 100); $row['price_vat'] = $row['price'] * (1 + $row['vat'] / 100); $row['price_all'] = $row['price'] * $row['quantity']; $row['price_all_vat'] = $row['price'] * $row['quantity'] * (1 + $row['vat'] / 100); $row['additional_costs'] = floatval($row['additional_costs'] ?? null); $items[$row['id']] = $row; $data['total_price_vat'] += ($row['price'] * $row['quantity']) * (1 + $row['vat'] / 100); $totalQuantity += $row['quantity']; } $data['total_price_vat'] = round($data['total_price_vat'], 4); // to fix values like -9.0949470177293E-13 $pageVars['items'] = $items; $pageVars['totalQuantity'] = $totalQuantity; $pageVars['itemsCount'] = count($items); } // Import from file if (empty($pageVars['items']) && !empty($data['id_supplier'])) { $pageVars['import'] = new StockInImport($data); } $pageVars['paid2'] = ['1' => 'ANO', '0' => 'NE']; $pageVars['vatDisplay'] = getVal('vatDisplay'); if (isset($data['data']['currency_symbol']) && !empty($data['data']['currency_symbol'])) { $pageVars['data']['currency_symbol'] = $data['data']['currency_symbol']; } else { $pageVars['data']['currency_symbol'] = '€'; } if (is_string($pageVars['data']['data'] ?? false)) { $this->unserializeCustomData($pageVars['data']); } $vars['body'] = $pageVars; return $vars; } protected function getFields() { $index = $this->getIndex(); if (in_array($index, ['closure', 'future', 'preorder'])) { $this->required = []; } parent::getFields(); } public function createObject() { $data = parent::createObject(); if (empty($data['date_created'])) { $data['date_created'] = date('d-m-Y H:i:s'); } if (empty($data['date_issued'])) { if (in_array($data['id_index'], ['future', 'preorder'])) { $data['date_issued'] = date('d-m-Y', time() + 86400); } else { $data['date_issued'] = date('d-m-Y', time() - 86400); } } if (empty($data['date_expiration'])) { $data['date_expiration'] = date('d-m-Y', time() + (13 * 86400)); } return $data; } public function getData() { $data = parent::getData(); if (!empty($data['date_issued'])) { $data['date_issued'] = $this->prepareDate($data['date_issued']); } if (!empty($data['date_expiration'])) { $data['date_expiration'] = $this->prepareDate($data['date_expiration']); } if (isset($data['multiplier'])) { $this->preparePrice($data['multiplier']); } if (getVal('Submit') == 'recieved') { if ($this->getIndex() == 'future') { $this->index_old = 'future'; $this->index = 'invoice'; } $data['id_index'] = $this->index; } if (getVal('Submit')) { $this->serializeCustomData($data); } return $data; } public function handleUpdate() { $acn = $this->getAction(); $data = $this->getData(); $ID = $this->getID(); $oldMultiplier = null; $multiplier = $data['multiplier']; if ($acn == 'edit' && !empty($ID)) { $closed = returnSQLResult('SELECT closed FROM '.getTableName('stock_in')." s WHERE s.id='{$ID}' "); if ($closed) { $this->returnError('Nelze upravovat uzavřenou fakturu'); } // kvůli přepočtu CZK ceny při změně kurzu u naskladnění v cizí měně $vatDisplay = json_decode($data['data'], true)['vatDisplay'] ?? null; if ($vatDisplay and $vatDisplay == 3) { $oldMultiplier = sqlQueryBuilder() ->select('multiplier') ->from('stock_in') ->andWhere(Operator::equals(['id' => $ID])) ->execute()->fetchOne(); } } parent::handleUpdate(); if ($this->index_old == 'future' && $this->index == 'invoice') { // Naskladnění aktualizovat pouze v případě naskladnění, ne aktualizace faktury sqlQueryBuilder() ->update('stock_in') ->set('date_stock_in', 'NOW()') ->andWhere(Operator::equals(['id' => $ID])) ->execute(); } $object = $this->getObject(); if (empty($object['number']) && $object['id_index'] == 'invoice') { $number = returnSQLResult('SELECT MAX(number)+1 FROM '.getTableName('stock_in').' WHERE id_index="invoice"'); if (empty($number)) { $number = 1; } $this->updateSQL('stock_in', ['number' => $number], ['id' => $this->getID()]); } $stockin = getVal('stockin', $data, []); if (findModule(Modules::STOCK_IN, Modules::SUB_WEIGHTED_PURCHASE_PRICE)) { $this->appendAdditionalCosts($stockin, $object); } if (findModule(Modules::STOCK_IN, Modules::SUB_WEIGHTED_PURCHASE_PRICE)) { $this->productPriceWeigher = ServiceContainer::getService(ProductPriceWeigher::class); } if (isset($oldMultiplier) && $oldMultiplier != 0 && ($oldMultiplier != $multiplier)) { foreach ($stockin as $id => &$item) { $item['price'] = ($item['price'] / $oldMultiplier) * $multiplier; } } krsort($stockin, SORT_NUMERIC); sqlGetConnection()->transactional(function () use ($stockin) { foreach ($stockin as $id => $item) { $item['id'] = intval($id); if (!$id) { continue; } $item['quantity'] = str_replace(',', '.', $item['quantity']); $item = array_merge($this->getItem($item), $item); if ($this->index_old == 'future' && $this->index == 'invoice') { // naskladneni $item['recieved'] = true; } $this->recalculateWeightedPrice($item); if (!empty($item['delete'])) { $this->handleDeleteValue($item); continue; } if ($id < 0) { $this->handleAddValue($item); } else { $this->handleUpdateValue($item); } } $this->recalcTotalPrice(); }); $vatDisplay = '0'; if (getVal('vatDisplay')) { $vatDisplay = getVal('vatDisplay'); } $this->redirect(['vatDisplay' => $vatDisplay, 'ErrStr' => $this->getErrors()[0]]); return true; } public function getItem($item) { $row = sqlFetchAssoc(sqlQuery('SELECT si.id_product, si.id_variation, si.quantity as old_quantity FROM stock_in_items si WHERE si.id=:id AND si.id_product IS NOT NULL', ['id' => $item['id']])); return $row ? $row : []; } /** Předefinováno na Lashes **/ public function appendAdditionalCosts(&$stockin, $stockInRow) { $additionalsCosts = toDecimal($stockInRow['transport_price']); $totalPrice = \DecimalConstants::zero(); foreach ($stockin as $id => &$item) { $item['price'] = $this->preparePrice($item['price']); // fix Division by zero - convert string to float (quantity muze byt napr. "0.0000") $item['quantity'] = $this->preparePrice($item['quantity']); $item['additional_costs'] = 0; if (!$item['price'] || !$item['quantity']) { continue; } $totalPrice = $totalPrice->add(toDecimal($item['price'])->mul(toDecimal($item['quantity']))->abs()); } foreach ($stockin as $id => &$item) { if (!$item['price'] || !$item['quantity']) { continue; } $itemsPrice = toDecimal($item['price'])->mul(toDecimal($item['quantity']))->abs(); $itemsPiecesPerc = $itemsPrice->div($totalPrice); $item['additional_costs'] = $additionalsCosts->mul($itemsPiecesPerc)->div(toDecimal($item['quantity']))->asFloat(); } return $stockin; } public function handleDelete() { if (!findRight('INSTORE_STOCKIN_ERASE')) { redirect('launch.php?s=error.php&id=1'); } $ID = $this->getID(); $SQL = sqlQuery('SELECT si.id_product, si.id_variation, si.quantity FROM '.getTableName('stock_in_items')." si WHERE si.id_stock_in={$ID} AND si.id_product IS NOT NULL"); while (($row = sqlFetchAssoc($SQL)) !== false) { $this->storeIn($row['id_product'], $row['id_variation'], -$row['quantity']); } writeDownActivity(sprintf(translate('activityDeleted'), $ID)); // smazat sqlQuery('DELETE FROM '.getTableName('stock_in')." WHERE id='{$ID}' "); redirect("launch.php?s={$this->getName()}.php&acn=erased"); } public function handleDeleteValue($item) { if (!empty($item['id_product'])) { $this->storeIn($item['id_product'], $item['id_variation'], -$item['old_quantity']); } sqlQuery('DELETE FROM stock_in_items WHERE id=:id', ['id' => $item['id']]); } public function handleAddValue($item) { $data = $this->getData(); $ID = $this->getID(); $item['id_stock_in'] = $ID; $this->preparePrice($item['price']); $this->preparePrice($item['price_vat']); if (empty($item['price'])) { $item['price'] = 0; } if (empty($item['price_vat'])) { $item['price_vat'] = 0; } if (empty($item['quantity'])) { $item['quantity'] = 0; } $fields = ['id_stock_in', 'quantity', 'price', 'name', 'vat']; if (findModule(Modules::STOCK_IN, Modules::SUB_WEIGHTED_PURCHASE_PRICE)) { $fields[] = 'additional_costs'; } if (!empty($item['id_product'])) { if (empty(intval($item['id_product']))) { return false; } $fields[] = 'id_product'; $fields[] = 'id_variation'; if (!empty($item['id_product_text'])) { $item['name'] = $item['id_product_text']; } if (empty($item['id_variation'])) { $item['id_variation'] = null; } else { $item['name'] = Variations::fillInProductTitle($item['id_variation'], $item['name']); } $vat = returnSQLResult('SELECT v.vat FROM '.getTableName('products').' p JOIN '.getTableName('vats')." v ON v.id=p.vat WHERE p.id={$item['id_product']} "); $item['vat'] = (float) $vat; if (empty($item['price'])) { $item['price'] = $item['price_vat'] / (1 + ($vat / 100)); } } else { if (empty($item['product_text'])) { return false; } $vat = getAdminVat()['value']; if (empty($item['quantity'])) { $item['quantity'] = 1; } if (empty($price)) { $item['price'] = $item['price_vat'] / (1 + ($vat / 100)); } $item['name'] = $item['product_text']; $fields[] = 'name'; $item['vat'] = $vat; } $SQLfields = $this->getSQLFields($item, $fields); $this->insertSQL('stock_in_items', $SQLfields); if (!empty($item['id_product'])) { // Update store $this->storeIn($item['id_product'], $item['id_variation'], $item['quantity']); // Update suppliers code $item['supplier_code'] = trim($item['supplier_code']) ?: null; $GLOBALS['code'] = $item['supplier_code']; try { $this->stockInProductOfSupplierService->updateOrInsertProductSupplier($item, $data); } catch (Exception|\Doctrine\DBAL\Driver\Exception) { $this->addError("Nepodařilo se uložit kód dodavatele: {$item['supplier_code']}, položky: {$item['name']}"); } } } public function handleUpdateValue($item) { if (empty($item['price']) && $item['id_product']) { $vat = returnSQLResult('SELECT v.vat FROM products p JOIN vats v ON v.id=p.vat WHERE p.id=:id_product', $item); $item['price'] = $item['price_vat'] / (1 + ($vat / 100)); } else { $this->preparePrice($item['price']); } if ($item['id_product']) { if ($this->index_old == 'future' && $this->index == 'invoice') { $quantity = $item['quantity']; if (findModule(Modules::PRODUCTS, Modules::SUB_PRICE_BUY) && findModule(Modules::PRODUCTS, Modules::SUB_PRICE_BUY) !== 'do_not_update' && !findModule(Modules::STOCK_IN, Modules::SUB_WEIGHTED_PURCHASE_PRICE) ) { $qb = sqlQueryBuilder() ->update(($item['id_variation'] ?? false) ? 'products_variations' : 'products') ->set('price_buy', $item['price']); if ($item['id_variation'] ?? false) { $qb->andWhere(Operator::equals(['id' => $item['id_variation']])); } else { $qb->andWhere(Operator::equals(['id' => $item['id_product']])); } $qb->execute(); } // Naskladnění aktualizovat pouze v případě naskladnění, ne aktualizace faktury sqlQueryBuilder() ->update('products') ->set('date_stock_in', 'NOW()') ->andWhere(Operator::equals(['id' => $item['id_product']])) ->execute(); } else { $quantity = $item['quantity'] - $item['old_quantity']; } $this->storeIn($item['id_product'], $item['id_variation'], $quantity); } $SQLFields = [ 'quantity' => $item['quantity'], 'price' => $item['price'], ]; if (findModule(Modules::STOCK_IN, Modules::SUB_WEIGHTED_PURCHASE_PRICE)) { $SQLFields['additional_costs'] = $item['additional_costs']; } $this->updateSQL('stock_in_items', $SQLFields, ['id' => $item['id']]); $this->recalcTotalPrice(); } public function recalculateWeightedPrice($item) { $index = $this->getIndex(); if (in_array($index, ['closure', 'future', 'preorder']) || empty($item['price'])) { return false; } if (findModule(Modules::STOCK_IN, Modules::SUB_WEIGHTED_PURCHASE_PRICE)) { $this->productPriceWeigher->updateWeightedPurchasePrice($item); } } public function handleClose() { $ID = $this->getID(); sqlQuery('UPDATE '.getTableName('stock_in')." s SET s.closed=1 WHERE s.id={$ID}"); $ErrStr = "Záznam číslo {$ID} uzavřen."; // presmerovani redirect('launch.php?s=stockIn.php&acn=edit&refresh=parent&ID='.$ID.'&ErrStr='.$ErrStr); } public function handleImport() { $data = $this->getData(); $data['id'] = $this->getID(); $import = new StockInImport($data, $this->getIndex()); try { $import->load_supplier_settings(); } catch (Exception $e) { $this->returnError('Chyba importu: '.$e->getMessage()); } if (!$import->import($_FILES['import'])) { $this->returnError('Chyba importu: '.join(', ', $import->errors)); } $this->recalcTotalPrice(); $this->returnOK('Import proběhl: '.join(', ', $import->errors)); } public function handleCreateProduct() { $stockInID = $this->getID(); $itemID = getVal('itemID'); $success = false; if ($stockInID && $itemID) { try { sqlGetConnection()->transactional(function () use ($itemID, $stockInID, &$success) { if ($item = $this->selectSQL('stock_in_items', ['id' => $itemID])->fetch()) { $stockIn = $this->selectSQL('stock_in', ['id' => $stockInID])->fetch(); $name = $item['name']; preg_match('/\(kód: (.*?)\)/', $name, $match); if (!empty($match)) { $name = str_replace($match[0], '', $name); $code = $match[1]; $vatID = getAdminVat()['id']; $vat = getAdminVat()['value']; $insert = [ 'title' => $name, // 'code' => $code, 'vat' => $vatID, 'figure' => 'N', 'date_added' => date('Y-m-d H:i:s'), ]; if ($this->getIndex() == 'invoice') { $insert['in_store'] = $item['quantity'] ? $item['quantity'] : 0; } if (findModule(Modules::PRODUCTS, Modules::SUB_PRICE_BUY)) { $insert['price_buy'] = toDecimal($item['price'])->addVat($vat); } $this->insertSQL('products', $insert); $productID = sqlInsertId(); $this->updateSQL( 'stock_in_items', [ 'id_product' => $productID, 'vat' => $vat, ], ['id' => $itemID] ); $this->insertSQL( 'products_of_suppliers', [ 'id_product' => $productID, 'id_supplier' => $stockIn['id_supplier'], 'code' => $code, ] ); $success = true; } } }); if ($success) { $this->returnOK('Produkt byl úspěšně vytvořen'); } else { $this->returnError('Produkt se nepodařilo vytvořit'); } } catch (Exception $e) { if (strpos($e->getMessage(), '1062 Duplicate entry')) { $this->returnError('Nepodařilo se vytvořit produkt, protože produkt s takovým kódem už existuje.'); } else { $this->returnError('Při vytváření produktu se vyskytla chyba: '.$e->getMessage()); } } } } public function getIndex() { if (is_null($this->index)) { try { $this->index = $this->getObject()['id_index']; } catch (NotFoundHttpException) { $this->index = ''; } } return $this->index; } public function storeIn($id_product, $id_variation, $quantity) { $index = $this->getIndex(); if (in_array($index, ['closure', 'future', 'preorder'])) { return false; } sqlGetConnection()->transactional(function () use ($quantity, $id_variation, $id_product) { $stockIn = true; if (findModule(Modules::STORES)) { $data = $this->getCustomData(); $store_id = ($data['id_store'] ?? 1); $SQLfields = [ 'id_store' => $store_id, 'id_product' => $id_product, 'id_variation' => $id_variation, 'quantity' => $quantity, ]; ServiceContainer::getService(\KupShop\KupShopBundle\Util\LoggingContext::class)->activateStockIn($this->getID(), function () use ($SQLfields) { $this->storesInStore->updateStoreItem($SQLfields); }); $stores = $this->storesInStore->getStores(); $store = $stores[$store_id]; if ($store['type'] == StoresInStore::TYPE_EXTERNAL_STORE) { $stockIn = false; } } if ($stockIn) { $prod = new Product($id_product); $prod->storeIn($id_variation, $quantity); } }); return true; } public function hasRights($name = null) { switch ($name) { case Window::RIGHT_DUPLICATE: return false; case Window::RIGHT_DELETE: return findRight('INSTORE_STOCKIN_ERASE'); default: return parent::hasRights($name); } } }