Explorar el Código

wip product update works

master
Daniel hace 3 años
padre
commit
b607ddde09
Se han modificado 13 ficheros con 1086 adiciones y 3397 borrados
  1. +2
    -1
      api/apiUpdateProducts.php
  2. +271
    -0
      api/listener/Product.php
  3. +1
    -1
      api/tokenData
  4. +0
    -809
      api2/Product.php
  5. +0
    -2097
      api2/ProductCollection.php
  6. +0
    -489
      api2/ProductCollectionItem.php
  7. +36
    -0
      src/Command/CmdUpdateProducts.php
  8. +122
    -0
      src/EventListener/PostCheckoutListener.php
  9. +170
    -0
      src/ExactApi/ApiExact.php
  10. +193
    -0
      src/ExactApi/IsotopeDatabaseHandler.php
  11. +267
    -0
      src/ExactApi/Product.php
  12. +23
    -0
      src/ExactApi/apiUpdateProducts.php
  13. +1
    -0
      src/ExactApi/tokenData

+ 2
- 1
api/apiUpdateProducts.php Ver fichero

@@ -6,5 +6,6 @@ require __DIR__ . '/IsotopeDatabaseHandler.php';
$apiExact = new ApiExact();
$databaseHandler = new IsotopeDatabaseHandler();
$products = $apiExact->getProducts();
$databaseHandler->processProducts($products);
var_dump($products);
//$databaseHandler->processProducts($products);


+ 271
- 0
api/listener/Product.php Ver fichero

@@ -0,0 +1,271 @@
<?php
/**
* @author Daniel Knudsen <d.knudsen@spawntree.de>
* @date 11.07.22
*/


class Product
{
const VAT = 0.19;

private $id;
private $standardSalesPrice;
private $salesVatCode;
private $salesVatCodeDescription;
private $description;
private $extraDescription;
private $isWebShopItem;
private $itemGroup;
private $itemGroupCode;
private $stock;
private $netWeight;
private $grossWeight;
private $code;

public static function getFields()
{
return [
'ID',
'StandardSalesPrice',
'SalesVatCode',
'SalesVatCodeDescription',
'Description',
'ExtraDescription',
'IsWebshopItem',
'ItemGroup',
'ItemGroupCode',
'Stock',
'NetWeight',
'GrossWeight',
'Code',
];
}

public static function createProductFromApiItem(array $item)
{
$res = new self();
$res->setId($item['ID']);
$res->setStandardSalesPrice($item['StandardSalesPrice']);
$res->setSalesVatCode($item['SalesVatCode']);
$res->setSalesVatCodeDescription($item['SalesVatCodeDescription']);
$res->setDescription($item['Description']);
$res->setExtraDescription($item['ExtraDescription']);
$res->setIsWebShopItem($item['IsWebshopItem']);
$res->setItemGroup($item['ItemGroup']);
$res->setItemGroupCode($item['ItemGroupCode']);
$res->setStock($item['Stock']);
$res->setNetWeight($item['NetWeight']);
$res->setGrossWeight($item['GrossWeight']);
$res->setCode($item['Code']);
return $res;
}

/**
* @return mixed
*/
public function getId()
{
return $this->id;
}

/**
* @param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}

/**
* @return mixed
*/
public function getStandardSalesPrice()
{
return $this->standardSalesPrice;
}

/**
* @param mixed $standardSalesPrice
*/
public function setStandardSalesPrice($standardSalesPrice)
{
$this->standardSalesPrice = $standardSalesPrice;
}

/**
* @return mixed
*/
public function getSalesVatCode()
{
return $this->salesVatCode;
}

/**
* @param mixed $salesVatCode
*/
public function setSalesVatCode($salesVatCode)
{
$this->salesVatCode = $salesVatCode;
}

/**
* @return mixed
*/
public function getSalesVatCodeDescription()
{
return $this->salesVatCodeDescription;
}

/**
* @param mixed $salesVatCodeDescription
*/
public function setSalesVatCodeDescription($salesVatCodeDescription)
{
$this->salesVatCodeDescription = $salesVatCodeDescription;
}

/**
* @return mixed
*/
public function getDescription()
{
return $this->description;
}

/**
* @param mixed $description
*/
public function setDescription($description)
{
$this->description = $description;
}

/**
* @return mixed
*/
public function getExtraDescription()
{
return $this->extraDescription;
}

/**
* @param mixed $extraDescription
*/
public function setExtraDescription($extraDescription)
{
$this->extraDescription = $extraDescription;
}

/**
* @return mixed
*/
public function getIsWebShopItem()
{
return $this->isWebShopItem;
}

/**
* @param mixed $isWebShopItem
*/
public function setIsWebShopItem($isWebShopItem)
{
$this->isWebShopItem = $isWebShopItem;
}

/**
* @return mixed
*/
public function getItemGroup()
{
return $this->itemGroup;
}

/**
* @param mixed $itemGroup
*/
public function setItemGroup($itemGroup)
{
$this->itemGroup = $itemGroup;
}

/**
* @return mixed
*/
public function getItemGroupCode()
{
return $this->itemGroupCode;
}

/**
* @param mixed $itemGroupCode
*/
public function setItemGroupCode($itemGroupCode)
{
$this->itemGroupCode = $itemGroupCode;
}

/**
* @return mixed
*/
public function getStock()
{
return $this->stock;
}

/**
* @param mixed $stock
*/
public function setStock($stock)
{
$this->stock = $stock;
}

/**
* @return mixed
*/
public function getNetWeight()
{
return $this->netWeight;
}

/**
* @param mixed $netWeight
*/
public function setNetWeight($netWeight)
{
$this->netWeight = $netWeight;
}

/**
* @return mixed
*/
public function getGrossWeight()
{
return $this->grossWeight;
}

/**
* @param mixed $grossWeight
*/
public function setGrossWeight($grossWeight)
{
$this->grossWeight = $grossWeight;
}

/**
* @return mixed
*/
public function getCode()
{
return $this->code;
}

/**
* @param mixed $code
*/
public function setCode($code)
{
$this->code = $code;
}
}

+ 1
- 1
api/tokenData Ver fichero

@@ -1 +1 @@
{"access_token":"stampDE001.gAAAAATOqLu_FvySDkT1YxBYQOSbyTqVC-jQWfCBmfomFxKEuiSUp2H5XYFwHOjuZ_cbURYObp_CuoijjuXPHYAvgR5GjxZVLpnI33116cIBqeKkdnQ2617l-DfjsiPlSO9ilA5CtrV75A1ZhxtWJqJ5uZynH0eT0ZB8LytI81Fg2p4tVAIAAIAAAAAqO6Xcq6ab64TEJDE2QyPHpfueoxrCkLlFVkxbiXgoANGrrSTu9qQtOTYyszkuc3sGZSdg1zD8eJLx8sL0zlTy7BYRYgTmiUhLZhECjG_7PfpObMuaz_Q4NOlIeyGOX23B6p-lbSZ_HHcVx1XzDgSry7MSIcFZGIqUWqr4jWW6EDqU0Q9A1ypbkKAPxh77V68LrHAzIm8yjoQell4GBnNSVOwNWKcLgmqkznUIsywr-Kq6dvfs_Ziy0nu5DR6PbpYLIVY4g1eCE4ncfgtPYsndKKuSIOisf8pYn_7NaLfSxvYWVwFxuTHw3roRufszbE-toAsUzfBVv2mQjKdyop9xRri3Adh3vfsEAKEyksZdsduCjFEPu6KoSv_pgQfExSURkHKj0gOX8_GjPtcbT4OWZ3JuZ7n9-4U5uSV-HHOvd7T2ojgQ0lFcVukuwHq086KTMNGzcBPfM-_vcBYAx1nRRpupQL2Np0fm85iycZwycSKAFydpcMeB1VaP5PYkVLiLOsXBPy4SMM0_-c51NykUm338m8SwAVeVQXN3VYcT0kJwUsyMdOtYoDvXUL-5p_NafKwwPwdrgpIwnjjO3s37O43RsGWaY8kp42y2I4lQbMwcqTde_bOcPCRQWium4OB__fkFaiCJ5eE5EGcbPUVzjEfW_Lhr8yrkRacFfut9YqCTVuGxZepi0eyX3FHk_-qAtXTBav11EZ40G5slokVlyPu63pQfhpSrsNIGZv-j5YIsN5MqjHn7HgtbS0UMRUAQERHvDkz6ddfbkqwjOgYE","token_type":"bearer","expires_in":"600","refresh_token":"stampDE001.vhK0!IAAAAIMZSdkYxeTLXpkXHeNZCQAgV_DZ3JM_sqN9Y81kYggu8QEAAAE1Ecbn6_tJNnyZQn4ktg02ypEuBII4NQc3bgVgWB1lM0IZaxRyKB5C9AVOCqh9ewKECN3eNMP0C0P2QVzNyoBsOHrK_QHqqypFPBfs2fPG2psKUngqf4nTWfGWpjnaeevGqll88v4_Cka58o-Kmlrv2Ho_uc3Tjl4gE6GeLF870gyWnW1Qx3AJUTx4730yswUr1YwEODVTl0WBSJ2LEqELKvx4MQ603b642QkevFCrH6ttZBef-ufEKcRpT7Fic9JHcSdUEJCGrhsGe__Ah0dS2WtxNfhD3m7YgGpxBqSjCJXWvXzBpBd9UsZ6vCuF0CP2EVvSZ_HS3iTgD5UOBDoQkTn8kt65TixrpQWZE5j2-vfgoQy9CbPv8aO75XT4gZGPtjij7GAdXNc17ZH-P_3N5CFmLnycGyfPNdnS58PP4nkYcl6LzVF5pBOBj2mIZa9Ic7W1fcSWDePaGC_WjVQvuV18tjvsMxp738XNTPdZfta5V769cSq1tuRblIyLYIJhhYhmprqg1KADYkcNC7RHYSVOHbds83VORW9gvgjFLCWt15TLhJaGbydgaMMUGHlZ4gb8YFMCzbRiq_20_K8iP_J5BdP2omIeHCNnH6360wmJybbgcFlYv0FZNDXUwkRxaTsREpfSvDUzZHCsk0Od","expiry_time":1657818416}
{"access_token":"stampDE001.gAAAALz6N4YQffUT_PsUt5T0HjUWldTzhSYiO3CUjBSEayLfCTPdWlFQZ7houvHkiJ5jDlRFLGflu6K_7VnRNtYmkBNRTz3P30sOtLmqGX2B7J3EJ3uSoApS-AEw4kzKk-FitabCwiTgdP9UbXaAnbfqqZEaXWmLmXQLlLBEOv4AJEvsVAIAAIAAAADSS-GjXrYGVJnWsv-jussxX5iQvsELp6Pj0JfWjxMd7AZaTFhADLGSvQndUodg2EeGmQ65dcgxE-gu6ZVdRwUg3u8yFpwDyoGTxqRojOA0pUDz1NGYifXvU_uw49jFYIjT5NhDwUeW-7LtZAnkU-GXqTJXeUy20cPXX7pw5uI6h-DAimSj9rrQTOZVOVAIfLhT-ojj0HFmUQsON0FJ_4ZulHoUggehSj4ECTdNM1sE9HP9cUMwuT46ccuqfyTu01HxkBxuwbPwUFf6VbyenqMjx8kuPrDNhhDD7h3XBnEYnUB5S9gobBGzkBxowi3PE83rhgpHQ1cyq6q76yXK0UOKzYVqyTGlQX6fbH95Ftv8CO7XhI5hQVOHDvadJxKZLtkVTrj9c0VlyduBi6s_VtmMhNw8dWDJqBrDSJOnzmEsXzvcvXMVXj0rLVHOGK54V_RPgFL9ieLLFvHs80X15xl90FvmILpm8BUp1J8hC5ruCGK_LZNsjIzJkPDjTmWCu3ys7WkvMMG79a28S4bgQDXmrgBhLH4PwLYz_r-XLvp7FeFXmL4NFsyQUQULCZjLkHZ45IcrlbLNKSO69QSDCS2orVj5_Ta7koRQ2zTDL34dLmXfk9RP1zVinc7fFN6RTPc_HYFqEjAHiu14Wsq1fMcNPmA9mWIVFXIVJBK6E9t-OxUkHXnvGy_wBIGZvK4qMakFisAeqmbQ61RlFYvzcE-8gUbCx0KU1ZG0TbutmJPr8mFEMWdVBk83Xlm4oaItp6BYA9pKgMciBucPq156qQ0G","token_type":"bearer","expires_in":"600","refresh_token":"stampDE001.vhK0!IAAAACdtr4d9DYfTnyyeJaJvOI0bouLOlyz4z5SU6-U1dG7D8QEAAAEjZ3c-4haAJg1MGAzHhNBKsuCqEJS71izk6qHzC8cEgxzDi5yTB1-UwsRWD8gUZEqepx55CP426XFUBQQzs8C7C_he3pRWXMZ3-qNAKAFcGtL4xJUEjnhHT-8oTswEZUo95U1WBXeAlY2jvQlL2TWlRfXwWWyE1qbcPLI6EHDQfjFnyMk-qFzmwUMjRtxv42FQODq3kkyqJWQm9yzqCJcYF1VpPKOn4NuEvoOgR0PEia3MccvSsRP9EParcpcWIEe45bEZ3jizx6G1vwSmHVNOvKfI45gQksw4EYWZl9TsS2d1AtGAuqGxkJ8JWD6fQHU5aOx0IuSAbcghZbMj25hMenJOCEAx8mehcg-QwDfAFMV0zAoSIhoZ0FZAV25y_fAEiGEz7nZUMkkZkO3wsxTjLkQslA5YrGNHz1VZyHNbQBgsNm2iP-piYpmYcSV2Z91f6oj_N-jLYB5T4vyhQSsw9Fm0Zy97kl0A59NCjkM3EHw3Z6twDF_rQkpqdes40y5PLp2TZrmkmW8moIIYU5L0SMQ0T0t7MnsrHp3GhaKSvFKNcQNUNU8eZfQDr04diEauKmQnGvGy-rXYbhap3kGtLmRZ3jZ8EhC5YXnn1cpejmIacBloOFZ-DdbUG3m86N78LJRVUv06Ca5b-MZjmbtB","expiry_time":1657899700}

+ 0
- 809
api2/Product.php Ver fichero

@@ -1,809 +0,0 @@
<?php

/*
* Isotope eCommerce for Contao Open Source CMS
*
* Copyright (C) 2009 - 2019 terminal42 gmbh & Isotope eCommerce Workgroup
*
* @link https://isotopeecommerce.org
* @license https://opensource.org/licenses/lgpl-3.0.html
*/

namespace Isotope\Model;

use Contao\Database;
use Contao\Date;
use Contao\DcaExtractor;
use Contao\Model;
use Isotope\Interfaces\IsotopeProduct;
use Isotope\Model\Attribute;
use Isotope\RequestCache\Filter;
use Model\Collection;

/**
* The basic Isotope product model
*
* @property int $id
* @property int $pid
* @property int $gid
* @property int $tstamp
* @property string $language
* @property int $dateAdded
* @property int $type
* @property array $pages
* @property array $orderPages
* @property array $inherit
* @property bool $fallback
* @property string $alias
* @property string $sku
* @property string $name
* @property string $teaser
* @property string $description
* @property string $meta_title
* @property string $meta_description
* @property string $meta_keywords
* @property bool $shipping_exempt
* @property array $images
* @property bool $protected
* @property array $groups
* @property bool $guests
* @property array $cssID
* @property bool $published
* @property string $start
* @property string $stop
*/
abstract class Product extends TypeAgent implements IsotopeProduct
{

/**
* Table name
* @var string
*/
protected static $strTable = 'tl_iso_product';

/**
* Interface to validate attribute
* @var string
*/
protected static $strInterface = '\Isotope\Interfaces\IsotopeProduct';

/**
* List of types (classes) for this model
* @var array
*/
protected static $arrModelTypes = array();

/**
* Currently active product (LIFO queue)
* @var array
*/
protected static $arrActive = array();

/**
* Get categories (pages) assigned to this product
*
* @param bool $blnPublished Only return published categories (pages)
*
* @return array
*/
abstract public function getCategories($blnPublished = false);

/**
* Get product that is currently active (needed e.g. for insert tag replacement)
*
* @return IsotopeProduct|null
*/
public static function getActive()
{
return 0 === \count(static::$arrActive) ? null : end(static::$arrActive);
}

/**
* Set product that is currently active (needed e.g. for insert tag replacement)
*
* @param IsotopeProduct $objProduct
*/
public static function setActive(IsotopeProduct $objProduct)
{
static::$arrActive[] = $objProduct;
}

/**
* Unset product that is currently active (prevent later use of it)
*/
public static function unsetActive()
{
array_pop(static::$arrActive);
}

/**
* Find all published products
*
* @param array $arrOptions
*
* @return Collection|Product[]|null
*/
public static function findPublished(array $arrOptions = array())
{
return static::findPublishedBy(array(), array(), $arrOptions);
}

/**
* Find published products by condition
*
* @param mixed $arrColumns
* @param mixed $arrValues
* @param array $arrOptions
*
* @return Collection|Product[]|null
*/
public static function findPublishedBy($arrColumns, $arrValues, array $arrOptions = array())
{
$t = static::$strTable;

$arrValues = (array) $arrValues;

if (!\is_array($arrColumns)) {
$arrColumns = array(static::$strTable . '.' . $arrColumns . '=?');
}

// Add publish check to $arrColumns as the first item to enable SQL keys
if (BE_USER_LOGGED_IN !== true) {
$time = Date::floorToMinute();
array_unshift(
$arrColumns,
"$t.published='1' AND ($t.start='' OR $t.start<'$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "')"
);
}

return static::findBy($arrColumns, $arrValues, $arrOptions);
}

/**
* Find a single product by primary key
*
* @param int $intId
* @param array $arrOptions
*
* @return static|null
*/
public static function findPublishedByPk($intId, array $arrOptions = array())
{
$arrOptions = array_merge(
array(
'return' => 'Model'
),
$arrOptions
);

return static::findPublishedBy(static::$strPk, (int) $intId, $arrOptions);
}

/**
* Find a single product by its ID or alias
*
* @param mixed $varId The ID or alias
* @param array $arrOptions An optional options array
*
* @return static|null The model or null if the result is empty
*/
public static function findPublishedByIdOrAlias($varId, array $arrOptions = array())
{
$t = static::$strTable;

$arrColumns = array("($t.id=? OR $t.alias=?)");
$arrValues = array(is_numeric($varId) ? $varId : 0, $varId);

$arrOptions = array_merge(
array(
'limit' => 1,
'return' => 'Model'
),
$arrOptions
);

return static::findPublishedBy($arrColumns, $arrValues, $arrOptions);
}

/**
* Find products by IDs
*
* @param array $arrIds
* @param array $arrOptions
*
* @return Product[]|Collection
*/
public static function findPublishedByIds(array $arrIds, array $arrOptions = array())
{
if (0 === \count($arrIds)) {
return null;
}

return static::findPublishedBy(
array(static::$strTable . '.id IN (' . implode(',', array_map('intval', $arrIds)) . ')'),
null,
$arrOptions
);
}

/**
* Return collection of published product variants by product PID
*
* @param int $intPid
* @param array $arrOptions
*
* @return Collection|Product[]|null
*/
public static function findPublishedByPid($intPid, array $arrOptions = array())
{
return static::findPublishedBy('pid', (int) $intPid, $arrOptions);
}

/**
* Return collection of published products by categories
*
* @param array $arrCategories
* @param array $arrOptions
*
* @return Collection|Product[]|null
*/
public static function findPublishedByCategories(array $arrCategories, array $arrOptions = array())
{
return static::findPublishedBy(
array('c.page_id IN (' . implode(',', array_map('intval', $arrCategories)) . ')'),
null,
$arrOptions
);
}

/**
* Find a single frontend-available product by primary key
*
* @param int $intId
* @param array $arrOptions
*
* @return static|null
*/
public static function findAvailableByPk($intId, array $arrOptions = array())
{
$objProduct = static::findPublishedByPk($intId, $arrOptions);

if (null === $objProduct || !$objProduct->isAvailableInFrontend()) {
return null;
}

return $objProduct;
}

/**
* Find a single frontend-available product by its ID or alias
*
* @param mixed $varId The ID or alias
* @param array $arrOptions An optional options array
*
* @return Product|null The model or null if the result is empty
*/
public static function findAvailableByIdOrAlias($varId, array $arrOptions = array())
{
$objProduct = static::findPublishedByIdOrAlias($varId, $arrOptions);

if (null === $objProduct || !$objProduct->isAvailableInFrontend()) {
return null;
}

return $objProduct;
}

/**
* Find frontend-available products by IDs
*
* @param array $arrIds
* @param array $arrOptions
*
* @return Collection|Product[]|null
*/
public static function findAvailableByIds(array $arrIds, array $arrOptions = array())
{
$objProducts = static::findPublishedByIds($arrIds, $arrOptions);

if (null === $objProducts) {
return null;
}

$arrProducts = [];
foreach ($objProducts as $objProduct) {
if ($objProduct->isAvailableInFrontend()) {
$arrProducts[] = $objProduct;
}
}

if (0 === \count($arrProducts)) {
return null;
}

return new Collection($arrProducts, static::$strTable);
}

/**
* Find frontend-available products by condition
*
* @param mixed $arrColumns
* @param mixed $arrValues
* @param array $arrOptions
*
* @return Collection
*/
public static function findAvailableBy($arrColumns, $arrValues, array $arrOptions = array())
{
$objProducts = static::findPublishedBy($arrColumns, $arrValues, $arrOptions);

if (null === $objProducts) {
return null;
}

$arrProducts = [];
foreach ($objProducts as $objProduct) {
if ($objProduct->isAvailableInFrontend()) {
$arrProducts[] = $objProduct;
}
}

if (0 === \count($arrProducts)) {
return null;
}

return new Collection($arrProducts, static::$strTable);
}

/**
* Find variant of a product
*
* @param IsotopeProduct $objProduct
* @param array $arrVariant
* @param array $arrOptions
*
* @return Model|null
*/
public static function findVariantOfProduct(
IsotopeProduct $objProduct,
array $arrVariant,
array $arrOptions = array()
) {
$t = static::$strTable;

$arrColumns = array(
"$t.id IN (" . implode(',', $objProduct->getVariantIds()) . ')',
"$t." . implode("=? AND $t.", array_keys($arrVariant)) . '=?'
);

$arrOptions = array_merge(
array(
'limit' => 1,
'column' => $arrColumns,
'value' => $arrVariant,
'return' => 'Model'
),
$arrOptions
);

return static::find($arrOptions);
}

/**
* Finds the default variant of a product.
*
* @param IsotopeProduct $objProduct
* @param array $arrOptions
*
* @return static|null
*/
public static function findDefaultVariantOfProduct(IsotopeProduct $objProduct, array $arrOptions = array())
{
static $cache;

if (null === $cache) {
$cache = [];
$data = Database::getInstance()->execute(
"SELECT id, pid FROM tl_iso_product WHERE pid>0 AND language='' AND fallback='1'"
);

while ($data->next()) {
$cache[$data->pid] = $data->id;
}
}

$defaultId = $cache[$objProduct->getProductId()];

if ($defaultId < 1 || !\in_array($defaultId, $objProduct->getVariantIds())) {
return null;
}

return static::findByPk($defaultId, $arrOptions);
}

/**
* Returns the number of published products.
*
* @param array $arrOptions
*
* @return int
*/
public static function countPublished(array $arrOptions = array())
{
return static::countPublishedBy(array(), array(), $arrOptions);
}

/**
* Return the number of products matching certain criteria
*
* @param mixed $arrColumns
* @param mixed $arrValues
* @param array $arrOptions
*
* @return int
*/
public static function countPublishedBy($arrColumns, $arrValues, array $arrOptions = array())
{
$t = static::$strTable;

$arrValues = (array) $arrValues;

if (!\is_array($arrColumns)) {
$arrColumns = array(static::$strTable . '.' . $arrColumns . '=?');
}

// Add publish check to $arrColumns as the first item to enable SQL keys
if (BE_USER_LOGGED_IN !== true) {
$time = Date::floorToMinute();
array_unshift(
$arrColumns,
"
$t.published='1'
AND ($t.start='' OR $t.start<'$time')
AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "')
"
);
}

return static::countBy($arrColumns, $arrValues, $arrOptions);
}

/**
* Gets the number of translation records in the product table.
* Mostly useful to see if there are any translations at all to optimize queries.
*
* @return int
*/
public static function countTranslatedProducts()
{
static $result;

if (null === $result) {
$result = Database::getInstance()->query(
"SELECT COUNT(*) AS total FROM tl_iso_product WHERE language!=''"
)->total;
}

return $result;
}

/**
* Return a model or collection based on the database result type
*
* @param array $arrOptions
*
* @return Product|Product[]|Collection|null
*/
protected static function find(array $arrOptions)
{
$arrOptions['group'] = static::getTable() . '.id' . (null === ($arrOptions['group'] ?? null) ? '' : ', '.$arrOptions['group']);

$objProducts = parent::find($arrOptions);

if (null === $objProducts) {
return null;
}

/** @var Filter[] $arrFilters */
$arrFilters = $arrOptions['filters'] ?? null;
$arrSorting = $arrOptions['sorting'] ?? null;

$hasFilters = \is_array($arrFilters) && 0 !== \count($arrFilters);
$hasSorting = \is_array($arrSorting) && 0 !== \count($arrSorting);

if ($hasFilters || $hasSorting) {

/** @var static[] $arrProducts */
$arrProducts = $objProducts->getModels();

if ($hasFilters) {
$arrProducts = array_filter($arrProducts, function ($objProduct) use ($arrFilters) {
$arrGroups = [];

foreach ($arrFilters as $objFilter) {
$blnMatch = $objFilter->matches($objProduct);

if ($objFilter->hasGroup()) {
$arrGroups[$objFilter->getGroup()] = $arrGroups[$objFilter->getGroup()] ? : $blnMatch;
} elseif (!$blnMatch) {
return false;
}
}

return !\in_array(false, $arrGroups, true);
});
}

// $arrProducts can be empty if the filter removed all records
if ($hasSorting && 0 !== \count($arrProducts)) {
$arrParam = array();
$arrData = array();

foreach ($arrSorting as $strField => $arrConfig) {
foreach ($arrProducts as $objProduct) {

// Both SORT_STRING and SORT_REGULAR are case sensitive, strings starting with a capital letter
// will come before strings starting with a lowercase letter. To perform a case insensitive
// search, force the sorting order to be determined by a lowercase copy of the original value.

// Temporary fix for price attribute (see #945)
if ('price' === $strField) {
if (null !== $objProduct->getPrice()) {
$arrData[$strField][$objProduct->id] = $objProduct->getPrice()->getAmount();
} else {
$arrData[$strField][$objProduct->id] = 0;
}
} else {
$arrData[$strField][$objProduct->id] = strtolower(
str_replace('"', '', $objProduct->$strField)
);
}
}

$arrParam[] = &$arrData[$strField];
$arrParam[] = $arrConfig[0];
$arrParam[] = $arrConfig[1];
}

// Add product array as the last item.
// This will sort the products array based on the sorting of the passed in arguments.
$arrParam[] = &$arrProducts;
\call_user_func_array('array_multisort', $arrParam);
}

$objProducts = new Collection($arrProducts, static::$strTable);
}

return $objProducts;
}

/**
* Return select statement to load product data including multilingual fields
*
* @param array $arrOptions an array of columns
*
* @return string
*/
protected static function buildFindQuery(array $arrOptions)
{
$objBase = DcaExtractor::getInstance($arrOptions['table']);
$hasTranslations = (static::countTranslatedProducts() > 0);
$hasVariants = (ProductType::countByVariants() > 0);

$arrJoins = array();
$arrFields = array(
$arrOptions['table'] . '.*',
"'" . str_replace('-', '_', $GLOBALS['TL_LANGUAGE']) . "' AS language",
);

if ($hasVariants) {
$arrFields[] = sprintf(
'IF(%s.pid>0, parent.type, %s.type) AS type',
$arrOptions['table'],
$arrOptions['table']
);
}

if ($hasTranslations) {
foreach (Attribute::getMultilingualFields() as $attribute) {
$arrFields[] = "IFNULL(translation.$attribute, " . $arrOptions['table'] . ".$attribute) AS $attribute";
}
}

foreach (Attribute::getFetchFallbackFields() as $attribute) {
$arrFields[] = "{$arrOptions['table']}.$attribute AS {$attribute}_fallback";
}

if ($hasTranslations) {
$arrJoins[] = sprintf(
" LEFT OUTER JOIN %s translation ON %s.id=translation.pid AND translation.language='%s'",
$arrOptions['table'],
$arrOptions['table'],
str_replace('-', '_', $GLOBALS['TL_LANGUAGE'])
);

$arrOptions['group'] = (null === $arrOptions['group'] ? '' : $arrOptions['group'].', ') . 'translation.id';
}

if ($hasVariants) {
$arrJoins[] = sprintf(
' LEFT OUTER JOIN %s parent ON %s.pid=parent.id',
$arrOptions['table'],
$arrOptions['table']
);
}

$arrFields[] = 'GROUP_CONCAT(c.page_id) AS product_categories';
$arrJoins[] = sprintf(
' LEFT OUTER JOIN %s c ON %s=c.pid',
ProductCategory::getTable(),
($hasVariants ? "IFNULL(parent.id, {$arrOptions['table']}.id)" : "{$arrOptions['table']}.id")
);

if ('c.sorting' === ($arrOptions['order'] ?? '')) {
$arrFields[] = 'c.sorting';

$arrOptions['group'] = (null === $arrOptions['group'] ? '' : $arrOptions['group'].', ') . 'c.id';
}

if ($objBase->hasRelations()) {
$intCount = 0;

foreach ($objBase->getRelations() as $strKey => $arrConfig) {
// Automatically join the single-relation records
if (('eager' === $arrConfig['load'] || ($arrOptions['eager'] ?? false))
&& ('hasOne' === $arrConfig['type'] || 'belongsTo' === $arrConfig['type'])
) {
if (\is_array($arrOptions['joinAliases'] ?? null)
&& ($key = array_search($arrConfig['table'], $arrOptions['joinAliases'], true)) !== false
) {
$strJoinAlias = $key;
unset($arrOptions['joinAliases'][$key]);
} else {
++$intCount;
$strJoinAlias = 'j' . $intCount;
}

$objRelated = DcaExtractor::getInstance($arrConfig['table']);

foreach ($objRelated->getFields() as $strField => $config) {
$arrFields[] = $strJoinAlias . '.' . $strField . ' AS ' . $strKey . '__' . $strField;
}

$arrJoins[] = sprintf(
' LEFT JOIN %s %s ON %s.%s=%s.id',
$arrConfig['table'],
$strJoinAlias,
$arrOptions['table'],
$strKey,
$strJoinAlias
);
}
}
}

// Generate the query
$strQuery = 'SELECT ' . implode(', ', $arrFields) . ' FROM ' . $arrOptions['table'] . implode('', $arrJoins);

// Where condition
if (!\is_array($arrOptions['column'] ?? null)) {
$arrOptions['column'] = array($arrOptions['table'] . '.' . $arrOptions['column'] . '=?');
}

// The model must never find a language record
$strQuery .= " WHERE {$arrOptions['table']}.language='' AND " . implode(' AND ', $arrOptions['column']);

// Group by
if (($arrOptions['group'] ?? null) !== null) {
$strQuery .= ' GROUP BY ' . $arrOptions['group'];
}

// Order by
if (($arrOptions['order'] ?? null) !== null) {
$strQuery .= ' ORDER BY ' . $arrOptions['order'];
}

return $strQuery;
}

/**
* Build a query based on the given options to count the number of products.
*
* @param array $arrOptions The options array
*
* @return string
*/
protected static function buildCountQuery(array $arrOptions)
{
$hasTranslations = (static::countTranslatedProducts() > 0);
$hasVariants = (ProductType::countByVariants() > 0);

$arrJoins = array();
$arrFields = array(
$arrOptions['table'] . '.id',
"'" . str_replace('-', '_', $GLOBALS['TL_LANGUAGE']) . "' AS language",
);

if ($hasVariants) {
$arrFields[] = sprintf(
'IF(%s.pid>0, parent.type, %s.type) AS type',
$arrOptions['table'],
$arrOptions['table']
);
}

if ($hasTranslations) {
foreach (Attribute::getMultilingualFields() as $attribute) {
$arrFields[] = "IFNULL(translation.$attribute, " . $arrOptions['table'] . ".$attribute) AS $attribute";
}
}

if ($hasTranslations) {
$arrJoins[] = sprintf(
" LEFT OUTER JOIN %s translation ON %s.id=translation.pid AND translation.language='%s'",
$arrOptions['table'],
$arrOptions['table'],
str_replace('-', '_', $GLOBALS['TL_LANGUAGE'])
);

$arrOptions['group'] = (null === $arrOptions['group'] ? '' : $arrOptions['group'].', ') . 'translation.id, tl_iso_product.id';
}

if ($hasVariants) {
$arrJoins[] = sprintf(
' LEFT OUTER JOIN %s parent ON %s.pid=parent.id',
$arrOptions['table'],
$arrOptions['table']
);
}

$arrJoins[] = sprintf(
' LEFT OUTER JOIN %s c ON %s=c.pid',
ProductCategory::getTable(),
($hasVariants ? "IFNULL(parent.id, {$arrOptions['table']}.id)" : "{$arrOptions['table']}.id")
);

// Generate the query
$strWhere = '';
$strQuery = '
SELECT
' . implode(', ', $arrFields) . '
FROM ' . $arrOptions['table'] . implode('', $arrJoins);

// Where condition
if (!empty($arrOptions['column'])) {
if (!\is_array($arrOptions['column'])) {
$arrOptions['column'] = array($arrOptions['table'] . '.' . $arrOptions['column'] . '=?');
}

$strWhere = ' AND ' . implode(' AND ', $arrOptions['column']);
}

// The model must never find a language record
$strQuery .= " WHERE {$arrOptions['table']}.language=''" . $strWhere;

// Group by
if ($arrOptions['group'] !== null) {
$strQuery .= ' GROUP BY ' . $arrOptions['group'];
}

return 'SELECT COUNT(*) AS count FROM ('.$strQuery.') c1';
}

/**
* Return select statement to load product data including multilingual fields
*
* @param array $arrOptions an array of columns
* @param array $arrJoinAliases an array of table join aliases
*
* @return string
*
* @deprecated use buildFindQuery introduced in Contao 3.3
*/
protected static function buildQueryString($arrOptions, $arrJoinAliases = array('t' => 'tl_iso_producttype'))
{
$arrOptions['joinAliases'] = $arrJoinAliases;

return static::buildFindQuery((array) $arrOptions);
}
}

+ 0
- 2097
api2/ProductCollection.php
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 0
- 489
api2/ProductCollectionItem.php Ver fichero

@@ -1,489 +0,0 @@
<?php

/*
* Isotope eCommerce for Contao Open Source CMS
*
* Copyright (C) 2009 - 2019 terminal42 gmbh & Isotope eCommerce Workgroup
*
* @link https://isotopeecommerce.org
* @license https://opensource.org/licenses/lgpl-3.0.html
*/

namespace Isotope\Model;

use Contao\Database;
use Contao\Model;
use Contao\StringUtil;
use Contao\System;
use Haste\Data\Plain;
use Isotope\Interfaces\IsotopeProduct;
use Isotope\Interfaces\IsotopeProductWithOptions;
use Isotope\Isotope;


/**
* ProductCollectionItem represents an item in a product collection.
*
* @property int $id
* @property int $pid
* @property int $tstamp
* @property int $product_id
* @property string $type
* @property string $sku
* @property string $name
* @property mixed $configuration
* @property int $quantity
* @property float $price
* @property float $tax_free_price
* @property string $tax_id
* @property int $jumpTo
*/
class ProductCollectionItem extends \Model
{

/**
* Name of the current table
* @var string
*/
protected static $strTable = 'tl_iso_product_collection_item';

/**
* Cache the current product
* @var IsotopeProduct|IsotopeProductWithOptions|false
*/
protected $objProduct = false;

/**
* Cache downloads for the collection item
* @var array
*/
protected $arrDownloads;

/**
* Errors
* @var array
*/
protected $arrErrors = [];

/**
* True if product collection is locked
* @var bool
*/
protected $blnLocked = false;


/**
* Check if collection item is available
*
* @return bool
*/
public function isAvailable()
{
if ($this->isLocked()) {
return true;
}

if (isset($GLOBALS['ISO_HOOKS']['itemIsAvailable']) && \is_array($GLOBALS['ISO_HOOKS']['itemIsAvailable'])) {
foreach ($GLOBALS['ISO_HOOKS']['itemIsAvailable'] as $callback) {
$available = System::importStatic($callback[0])->{$callback[1]}($this);

// If return value is boolean then we accept it as result
if (true === $available || false === $available) {
return $available;
}
}
}

if (!$this->hasProduct() || !$this->getProduct()->isAvailableForCollection($this->getRelated('pid'))) {
return false;
}

$arrConfig = $this->getOptions();
foreach ($this->getProduct()->getOptions() as $k => $v) {
if ($arrConfig[$k] !== $v) {
return false;
}
}

return true;
}

/**
* Return true if product collection item is locked
*/
public function isLocked()
{
return $this->blnLocked;
}

/**
* Lock item, necessary if product collection is locked
*/
public function lock()
{
$this->blnLocked = true;
}

/**
* Delete downloads when deleting product collection item
*
* @return int
*/
public function delete()
{
$intId = $this->id;
$intAffected = parent::delete();

if ($intAffected) {
Database::getInstance()->query("DELETE FROM tl_iso_product_collection_download WHERE pid=$intId");
}

return $intAffected;
}

/**
* Get the product related to this item
*
* @param bool $blnNoCache
*
* @return IsotopeProduct|null
*/
public function getProduct($blnNoCache = false)
{
if (false === $this->objProduct || true === $blnNoCache) {

$this->objProduct = null;

/** @var string|\Isotope\Model\Product $strClass */
$strClass = Product::getClassForModelType($this->type);

if ($strClass == '' || !class_exists($strClass)) {
System::log('Error creating product object of type "' . $this->type . '"', __METHOD__, TL_ERROR);

return null;
}

try {
$this->objProduct = $strClass::findByPk($this->product_id);
} catch (\Exception $e) {
$this->objProduct = null;
$this->addError($e->getMessage());
}

if (null !== $this->objProduct && $this->objProduct instanceof IsotopeProductWithOptions) {
try {
if ($this->objProduct instanceof Model) {
$this->objProduct = clone $this->objProduct;
$this->objProduct->preventSaving(false);
$this->objProduct->id = $this->product_id;
}

$this->objProduct->setOptions($this->getOptions());
} catch (\RuntimeException $e) {
$this->addError($GLOBALS['TL_LANG']['ERR']['collectionItemNotAvailable']);
}
}
}

return $this->objProduct;
}

/**
* Return boolean flag if product could be loaded
*
* @return bool
*/
public function hasProduct()
{
return (null !== $this->getProduct());
}

/**
* Get product SKU. Automatically falls back to the collection item table if product is not found.
*
* @return string
*/
public function getSku()
{
return (string) ($this->isLocked() || !$this->hasProduct()) ? $this->sku : $this->getProduct()->getSku();
}

/**
* Get product name. Automatically falls back to the collection item table if product is not found.
*
* @return string
*/
public function getName()
{
return (string) ($this->isLocked() || !$this->hasProduct()) ? $this->name : $this->getProduct()->getName();
}

/**
* Returns key-value array for variant-enabled and customer editable attributes.
*
* @return array
*
* @deprecated Use getOptions()
*/
public function getAttributes()
{
return $this->getOptions();
}

/**
* Returns key-value array for variant-enabled and customer editable attributes.
*
* @return array
*/
public function getOptions()
{
$arrConfig = StringUtil::deserialize($this->configuration);

return \is_array($arrConfig) ? $arrConfig : [];
}

/**
* Get product configuration
*
* @return array
*
* @deprecated Deprecated since Isotope 2.4, to be removed in Isotope 3.0. Use getOptions() instead.
*/
public function getConfiguration()
{
$arrConfig = StringUtil::deserialize($this->configuration);

if (empty($arrConfig) || !\is_array($arrConfig)) {
return array();
}

if ($this->hasProduct()) {
return Isotope::formatProductConfiguration($arrConfig, $this->getProduct());

} else {
foreach ($arrConfig as $k => $v) {
$arrConfig[$k] = new Plain($v, $k);
}

return $arrConfig;
}
}

/**
* Get product price. Automatically falls back to the collection item table if product is not found.
*
* @return string
*/
public function getPrice()
{
if ($this->isLocked() || !$this->hasProduct()) {
return $this->price;
}

$objPrice = $this->getProduct()->getPrice($this->getRelated('pid'));

if (null === $objPrice) {
return '';
}

return $objPrice->getAmount((int) $this->quantity, $this->getOptions());
}

/**
* Get tax free product price. Automatically falls back to the collection item table if product is not found.
*
* @return string
*/
public function getTaxFreePrice()
{
if ($this->isLocked() || !$this->hasProduct()) {
return $this->tax_free_price;
}

$objPrice = $this->getProduct()->getPrice($this->getRelated('pid'));

if (null === $objPrice) {
return '';
}

return $objPrice->getNetAmount((int) $this->quantity, $this->getOptions());
}

/**
* Get original product price. Automatically falls back to the collection item table if product is not found.
*
* @return string
*/
public function getOriginalPrice()
{
if ($this->isLocked() || !$this->hasProduct()) {
return $this->price;
}

$objPrice = $this->getProduct()->getPrice($this->getRelated('pid'));

if (null === $objPrice) {
return '';
}

return $objPrice->getOriginalAmount((int) $this->quantity, $this->getOptions());
}

/**
* Get product price multiplied by the requested product quantity
*
* @return string
*/
public function getTotalPrice()
{
return (string) ($this->getPrice() * (int) $this->quantity);
}

/**
* Get original product price multiplied by the requested product quantity
*
* @return string
*/
public function getTotalOriginalPrice()
{
return (string) ($this->getOriginalPrice() * (int) $this->quantity);
}

/**
* Get tax free product price multiplied by the requested product quantity
*
* @return string
*/
public function getTaxFreeTotalPrice()
{
return (string) ($this->getTaxFreePrice() * (int) $this->quantity);
}

/**
* Return downloads associated with this product collection item
*
* @return ProductCollectionDownload[]
*/
public function getDownloads()
{
if (null === $this->arrDownloads) {
$this->arrDownloads = array();

$objDownloads = ProductCollectionDownload::findBy('pid', $this->id);

if (null !== $objDownloads) {
while ($objDownloads->next()) {
$this->arrDownloads[] = $objDownloads->current();
}
}
}

return $this->arrDownloads;
}

/**
* Increase quantity of product collection item
*
* @param int $intQuantity
*
* @return self
*/
public function increaseQuantityBy($intQuantity)
{
$time = time();

Database::getInstance()->query("
UPDATE tl_iso_product_collection_item
SET tstamp=$time, quantity=(quantity+" . (int) $intQuantity . ')
WHERE id=' . $this->id
);

$this->tstamp = $time;
$this->quantity = Database::getInstance()
->query("SELECT quantity FROM tl_iso_product_collection_item WHERE id=" . $this->id)
->quantity
;

return $this;
}

/**
* Decrease quantity of product collection item
*
* @param int $intQuantity
*
* @return self
*/
public function decreaseQuantityBy($intQuantity)
{
if (($this->quantity - $intQuantity) < 1) {
throw new \UnderflowException('Quantity of product collection item cannot be less than 1.');
}

$time = time();

Database::getInstance()->query("
UPDATE tl_iso_product_collection_item
SET tstamp=$time, quantity=(quantity-" . (int) $intQuantity . ')
WHERE id=' . $this->id
);

$this->tstamp = $time;
$this->quantity = Database::getInstance()
->query('SELECT quantity FROM tl_iso_product_collection_item WHERE id=' . $this->id)
->quantity
;

return $this;
}

/**
* Calculate the sum of a database column
*
* @param string $strField
* @param mixed $strColumn
* @param mixed $varValue
*
* @return int
*/
public static function sumBy($strField, $strColumn = null, $varValue = null)
{
$strQuery = "SELECT SUM($strField) AS sum FROM tl_iso_product_collection_item";

if ($strColumn !== null) {
$strQuery .= ' WHERE ' . (\is_array($strColumn) ? implode(' AND ', $strColumn) : static::$strTable . '.' . $strColumn . "=?");
}

return (int) Database::getInstance()->prepare($strQuery)->execute($varValue)->sum;
}

/**
* Add an error message
*
* @param string $strError
*/
public function addError($strError)
{
$this->arrErrors[] = $strError;
}

/**
* Return true if the collection item has errors
*
* @return bool
*/
public function hasErrors()
{
return 0 !== \count($this->arrErrors);
}

/**
* Return the errors array
*
* @return array
*/
public function getErrors()
{
return $this->arrErrors;
}
}

+ 36
- 0
src/Command/CmdUpdateProducts.php Ver fichero

@@ -0,0 +1,36 @@
<?php
namespace App\Command;

use App\ExactApi\IsotopeDatabaseHandler;
use App\ExactApi\ApiExact;
use Doctrine\DBAL\Connection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class CmdUpdateProducts extends Command
{
protected static $defaultName = 'app:update-products';
protected $connection;
private $statusCode = 0;

public function __construct(Connection $connection)
{
$this->connection = $connection;
parent::__construct();
}

protected function configure(): void
{
$this->setDescription('Updates products from Exact-Online to Contao-Isotope tables.');
}

protected function execute(InputInterface $input, OutputInterface $output): ?int
{
$apiConnector = new ApiExact();
$products = $apiConnector->getProducts();
$handler = new IsotopeDatabaseHandler($this->connection);
$handler->processProducts($products);
return $this->statusCode;
}
}

+ 122
- 0
src/EventListener/PostCheckoutListener.php Ver fichero

@@ -0,0 +1,122 @@
<?php

namespace App\EventListener;


use Contao\CoreBundle\ServiceAnnotation\Hook;
use Contao\FrontendTemplate;
use Contao\Module;
use App\ExactApi\ApiExact;
use Isotope\Model\Address;
use Isotope\Model\Product;
use Isotope\Model\ProductCollection\Order;
use Isotope\Model\ProductCollectionItem;

class PostCheckoutListener
{
const WAREHOUSE_ID = 'd8a6a9b8-d0ac-4d36-8d00-bd420d0d81f5';

/**
* @Hook("postCheckout")
*/
// public function __invoke(int $userId, array $userData, Module $module): void
public function __invoke(Order $order, $item): void
{
$apiExact = new ApiExact();

//ob_start();
$salesOrderItems = [];
/** @var ProductCollectionItem $orderItem */
foreach($order->getItems() as $orderItem) {
$salesOrderItems[] = $this->createOrderItem($orderItem);
// /** @var Product $orderItem */
// $product = $orderItem->getProduct();
// var_dump($orderItem->id);
// var_dump($orderItem->sku);
// var_dump($orderItem->quantity);
// var_dump($orderItem->tax_free_price);
//
// var_dump($product->id);
// var_dump($product->name);
// var_dump($product->is_set);

}
//var_dump($apiExact->test());
// file_put_contents('dan.txt', ob_get_contents());
// ob_end_clean();



$shippingCustomer = null;
// if ($this->compareAddresses() === false) {
// $shippingCustomer = $apiExact->createCustomer(
// $this->createCustomerApiData($order->getShippingAddress())
// );
// }
//
$billingCustomer = $apiExact->createCustomer(
$this->createCustomerApiData($order->getBillingAddress())
);
$apiExact->createSalesOrder($this->createSalesOrderData($order, $billingCustomer, $shippingCustomer));

}

private function compareAddresses(Order $order)
{
return
$order->getBillingAddress()->firstname === $order->getShippingAddress()->firstname &&
$order->getBillingAddress()->lastname === $order->getShippingAddress()->lastname &&
$order->getBillingAddress()->street_1 === $order->getShippingAddress()->street_1 &&
$order->getBillingAddress()->postal === $order->getShippingAddress()->postal &&
$order->getBillingAddress()->city === $order->getShippingAddress()->city &&
$order->getBillingAddress()->company === $order->getShippingAddress()->company &&
$order->getBillingAddress()->phone === $order->getShippingAddress()->phone &&
$order->getBillingAddress()->email === $order->getShippingAddress()->email;
}

private function createCustomerApiData(Address $address)
{
return [
'Name' => $address->firstname . ' ' . $address->lastname,
'AddressLine1' => $address->street_1,
'AddressLine2' => $address->company,
'City' => $address->city,
'Postcode' => $address->postal,
'Email' => $address->email,
'Country' => 'DE',
'Phone' => $address->phone,
'Status' => 'C',
];
}

private function createOrderItem(ProductCollectionItem $orderItem)
{
/** @var Product $orderItem */
$product = $orderItem->getProduct();
return [
'Description' => $product->name,
'Item' => $product->exact_id,
'UnitPrice' => (float) $orderItem->tax_free_price / (int) $orderItem->quantity,
'Quantity' => $orderItem->quantity
];
}

private function createSalesOrderData(Order $order, $billingCustomer, $salesOrderItems, $shippingCustomer = null)
{
$date = new \DateTime('now');
$res = [
'Description' => $billingCustomer['Name'],
'OrderDate' => $date->format('m/d/Y H:i:s'),
'OrderedBy' => $billingCustomer['ID'],
'WarehouseID' => self::WAREHOUSE_ID,
'SalesOrderLines' => [
$salesOrderItems
]
];
if ($shippingCustomer !== null) {
$res['DeliverTo'] = $shippingCustomer['ID'];
}

return $res;
}
}

+ 170
- 0
src/ExactApi/ApiExact.php Ver fichero

@@ -0,0 +1,170 @@
<?php

namespace App\ExactApi;

use App\ExactApi\Product;

class ApiExact
{
const DIVISION = '58687';
const API_URL = "https://start.exactonline.de/api";
const API_URL_WNR = self::API_URL . "/v1/" . self::DIVISION;
const CLIENT_ID = "5a04d118-349c-4750-aac3-fa3a386999c6";
const CLIENT_SECRET = "E7Wuqcsp4Lih";

const ACCESS_TOKEN_VALID_DURATION = 600 - 50;
const ITEM_GROUP_UUID = 'df17bdaf-2af7-4e9f-8d60-326e36b57764';
const PRODUCT_CODE_PREFIX = "WR";

public function getAccessToken()
{
$tokenData = json_decode(file_get_contents(__DIR__ . '/tokenData', true));
ob_start();
var_dump($tokenData);
ob_end_clean();

if (!property_exists($tokenData, 'expiry_time') || (int)$tokenData->expiry_time < time()) {
$postData = array(
'grant_type' => 'refresh_token',
'refresh_token' => $tokenData->refresh_token,
'client_id' => self::CLIENT_ID,
'client_secret' => self::CLIENT_SECRET
);

$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => self::API_URL . '/oauth2/token',
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HTTPHEADER => array('Content-Type: application/x-www-form-urlencoded'),
CURLOPT_POSTFIELDS => http_build_query($postData)
));

$response = curl_exec($ch);
curl_close($ch);
$jsonResult = json_decode($response);

if (property_exists($jsonResult, 'error')) {
$msg = $jsonResult->error;
throw new \Exception($msg);
}
$jsonResult->expiry_time = time() + self::ACCESS_TOKEN_VALID_DURATION;
file_put_contents(__DIR__ . '/tokenData', json_encode($jsonResult));

$tokenData = json_decode(file_get_contents(__DIR__ . '/tokenData', true));
}
return $tokenData->access_token;
}

public function getProducts()
{
$baseUrl = self::API_URL_WNR . "/bulk/Logistics/Items?";
$fields = [
'ID',
'StandardSalesPrice',
'SalesVatCode',
'SalesVatCodeDescription',
'Description',
'ExtraDescription',
'IsWebshopItem',
'ItemGroup',
'ItemGroupCode',
'Stock',
'NetWeight',
'GrossWeight',
'Code'
];
$filter = "\$filter=ItemGroup eq guid'".self::ITEM_GROUP_UUID."'";
$select = "&\$select=".$this->getFieldString($fields);
$requestUrl = $baseUrl . $filter . $select;
$response = $this->getApiData($requestUrl);
$responseArr = json_decode($response, 1)['d']['results'];

$res = [];
foreach ($responseArr as $productItem) {
if (
array_key_exists('IsWebshopItem', $productItem) &&
$productItem['IsWebshopItem'] === 1 &&
array_key_exists('Code', $productItem) &&
stripos(strtoupper($productItem['Code']), self::PRODUCT_CODE_PREFIX) === 0
) {
$res[] = Product::createProductFromApiItem($productItem);
}
}
return $res;
}

public function createCustomer($customerData)
{
$url = self::API_URL_WNR . "/crm/Accounts";
return json_decode($this->postApiData($url, $customerData));
}

public function createSalesOrder($salesOrderData)
{
$url = self::API_URL_WNR . "/salesorder/SalesOrders";
// $parameters = [
// 'Description' => 'Daniel Knudsen',
// 'OrderDate' => '07/13/2022 17:00:00',
// 'OrderedBy' => '9ba706a3-d6f5-40c8-8d4f-e55a37692cef',
// 'WarehouseID' => 'd8a6a9b8-d0ac-4d36-8d00-bd420d0d81f5',
// 'SalesOrderLines' => [
// [
// 'Description' => 'TEST: Reinigungstuch grün',
// 'Item' => '9dcd8b50-5de6-4f15-a51a-c51282292383',
// 'UnitPrice' => '2.45',
// 'Quantity' => '3'
// ],
// [
// 'Description' => 'TEST: McQuade´s E-Bike Kettenrückführ - Tool',
// 'Item' => '0e04113f-1299-4d3c-b65e-01e83ca5b3b2',
// 'UnitPrice' => '476.00',
// 'Quantity' => '1'
// ],
// ]
// ];
return json_decode($this->postApiData($url, $salesOrderData));
}

private function getApiData($url)
{
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => array('Accept: application/json' , "Authorization: Bearer " . $this->getAccessToken() ),
CURLOPT_RETURNTRANSFER => TRUE,
));
$res = curl_exec($ch);
curl_close($ch);
return $res;
}

private function postApiData($url, $data)
{
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json',
'Accept: application/json',
"Authorization: Bearer " . $this->getAccessToken() ),
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => json_encode($data)
));
$res = curl_exec($ch);
curl_close($ch);
return $res;
}

private function getFieldString($fields)
{
$res = '';
$cnt = count($fields);
$i = 1;
foreach ($fields as $field) {
$res .= $cnt !== $i ? $field . ',' : $field;
$i++;
}
return $res;
}
}

+ 193
- 0
src/ExactApi/IsotopeDatabaseHandler.php Ver fichero

@@ -0,0 +1,193 @@
<?php

namespace App\ExactApi;

use App\ExactApi\Product;
use Doctrine\DBAL\Connection;

class IsotopeDatabaseHandler
{
private $connection;

public function __construct(Connection $connection)
{
$this->connection = $connection;
}

public function processProducts(array $products)
{
$this->connection->beginTransaction();

$sql = "SELECT * FROM `tl_iso_product`";
$stmt = $this->connection->query($sql);
$dbProducts = $stmt->fetchAll(\PDO::FETCH_ASSOC);

$sql = "SELECT * FROM `tl_iso_product_price`";
$stmt = $this->connection->query($sql);
$dbProductPrices = $stmt->fetchAll(\PDO::FETCH_ASSOC);

$dbProductsBySku = [];
foreach ($dbProducts as $prod) {
$dbProductsBySku[$prod['sku']] = $prod;
}

$dbProductsPricesByPid = [];
foreach ($dbProductPrices as $price) {
$dbProductsPricesByPid[$price['pid']] = $price;
}

/** @var Product $product */
foreach ($products as $product) {

if (array_key_exists($product->getCode(), $dbProductsBySku)) {
$dbProduct = $dbProductsBySku[$product->getCode()];
$dbPrice = $dbProductsPricesByPid[$dbProduct['id']];
$this->updateProduct($product, $dbProduct['id']);
$this->updatePriceTier($product, $dbPrice['id']);

} else {
$productId = $this->createProduct($product);
$productPriceId = $this->createPrice($product, $productId);
$this->createPriceTier($product, $productPriceId);
}
}
$this->connection->commit();
}

private function updateProduct(Product $product, $productDbId)
{
$shippingWeight = $this->convertShippingWeight($product);
$sql =
"UPDATE `tl_iso_product` SET
tstamp = ".time().",
shipping_weight = '$shippingWeight'
WHERE `id` = ".$productDbId;
$this->connection->query($sql);
}

private function updatePriceTier(Product $product, $priceDbId)
{
$price = $product->getStandardSalesPrice() * (1 + Product::VAT);
$sql =
"UPDATE `tl_iso_product_pricetier` SET
tstamp = ".time().",
price = $price
WHERE `pid` = ".$priceDbId;
$this->connection->query($sql);
}

private function createProduct(Product $product)
{
$alias = 'exact-product-'.$product->getCode();
$shippingWeight = $this->convertShippingWeight($product);
$sql =
"INSERT into `tl_iso_product` (
pid,
gid,
tstamp,
language,
dateAdded,
type,
fallback,
alias,
gtin,
sku,
name,
description,
meta_title,
baseprice,
shipping_weight,
shipping_exempt,
shipping_pickup,
shipping_price,
protected,
guests,
cssID,
published,
start,
stop,
exact_id
) VALUES (
0,
0,
".time().",
'',
".time().",
2,
'',
'$alias',
'',
'".$product->getCode()."',
'".$product->getDescription()."',
'".$product->getExtraDescription()."',
'',
'',
'$shippingWeight',
'',
'',
0.00,
'',
'',
'',
'0',
'',
'',
'".$product->getId()."'
)";
$this->connection->query($sql);
return $this->connection->lastInsertId();
}

private function createPrice(Product $product, $productDbId)
{
$sql =
"INSERT INTO `tl_iso_product_price` (
pid,
tstamp,
tax_class,
config_id,
member_group,
start,
stop
) VALUES (
$productDbId,
".time().",
1,
0,
0,
'',
''
)";
$this->connection->query($sql);
return $this->connection->lastInsertId();
}

private function createPriceTier(Product $product, $productPriceDbId)
{
$price = $product->getStandardSalesPrice() * (1 + Product::VAT);
$sql =
"INSERT INTO `tl_iso_product_pricetier` (
pid,
tstamp,
min,
price
) VALUES (
$productPriceDbId,
".time().",
1,
$price
)";
$this->connection->query($sql);
return $this->connection->lastInsertId();
}

private function convertShippingWeight(Product $product)
{
$shippingWeight = $product->getGrossWeight() !== '' ?
$product->getGrossWeight() :
$product->getNetWeight();
return ((int) $shippingWeight === 0 || $shippingWeight === '') ?
'' :
'a:2:{s:4:"unit";s:2:"kg";s:5:"value";s:3:"' . $shippingWeight . '";}';
}
}

+ 267
- 0
src/ExactApi/Product.php Ver fichero

@@ -0,0 +1,267 @@
<?php
namespace App\ExactApi;

class Product
{
const VAT = 0.19;

private $id;
private $standardSalesPrice;
private $salesVatCode;
private $salesVatCodeDescription;
private $description;
private $extraDescription;
private $isWebShopItem;
private $itemGroup;
private $itemGroupCode;
private $stock;
private $netWeight;
private $grossWeight;
private $code;

public static function getFields()
{
return [
'ID',
'StandardSalesPrice',
'SalesVatCode',
'SalesVatCodeDescription',
'Description',
'ExtraDescription',
'IsWebshopItem',
'ItemGroup',
'ItemGroupCode',
'Stock',
'NetWeight',
'GrossWeight',
'Code',
];
}

public static function createProductFromApiItem(array $item)
{
$res = new self();
$res->setId($item['ID']);
$res->setStandardSalesPrice($item['StandardSalesPrice']);
$res->setSalesVatCode($item['SalesVatCode']);
$res->setSalesVatCodeDescription($item['SalesVatCodeDescription']);
$res->setDescription($item['Description']);
$res->setExtraDescription($item['ExtraDescription']);
$res->setIsWebShopItem($item['IsWebshopItem']);
$res->setItemGroup($item['ItemGroup']);
$res->setItemGroupCode($item['ItemGroupCode']);
$res->setStock($item['Stock']);
$res->setNetWeight($item['NetWeight']);
$res->setGrossWeight($item['GrossWeight']);
$res->setCode($item['Code']);
return $res;
}

/**
* @return mixed
*/
public function getId()
{
return $this->id;
}

/**
* @param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}

/**
* @return mixed
*/
public function getStandardSalesPrice()
{
return $this->standardSalesPrice;
}

/**
* @param mixed $standardSalesPrice
*/
public function setStandardSalesPrice($standardSalesPrice)
{
$this->standardSalesPrice = $standardSalesPrice;
}

/**
* @return mixed
*/
public function getSalesVatCode()
{
return $this->salesVatCode;
}

/**
* @param mixed $salesVatCode
*/
public function setSalesVatCode($salesVatCode)
{
$this->salesVatCode = $salesVatCode;
}

/**
* @return mixed
*/
public function getSalesVatCodeDescription()
{
return $this->salesVatCodeDescription;
}

/**
* @param mixed $salesVatCodeDescription
*/
public function setSalesVatCodeDescription($salesVatCodeDescription)
{
$this->salesVatCodeDescription = $salesVatCodeDescription;
}

/**
* @return mixed
*/
public function getDescription()
{
return $this->description;
}

/**
* @param mixed $description
*/
public function setDescription($description)
{
$this->description = $description;
}

/**
* @return mixed
*/
public function getExtraDescription()
{
return $this->extraDescription;
}

/**
* @param mixed $extraDescription
*/
public function setExtraDescription($extraDescription)
{
$this->extraDescription = $extraDescription;
}

/**
* @return mixed
*/
public function getIsWebShopItem()
{
return $this->isWebShopItem;
}

/**
* @param mixed $isWebShopItem
*/
public function setIsWebShopItem($isWebShopItem)
{
$this->isWebShopItem = $isWebShopItem;
}

/**
* @return mixed
*/
public function getItemGroup()
{
return $this->itemGroup;
}

/**
* @param mixed $itemGroup
*/
public function setItemGroup($itemGroup)
{
$this->itemGroup = $itemGroup;
}

/**
* @return mixed
*/
public function getItemGroupCode()
{
return $this->itemGroupCode;
}

/**
* @param mixed $itemGroupCode
*/
public function setItemGroupCode($itemGroupCode)
{
$this->itemGroupCode = $itemGroupCode;
}

/**
* @return mixed
*/
public function getStock()
{
return $this->stock;
}

/**
* @param mixed $stock
*/
public function setStock($stock)
{
$this->stock = $stock;
}

/**
* @return mixed
*/
public function getNetWeight()
{
return $this->netWeight;
}

/**
* @param mixed $netWeight
*/
public function setNetWeight($netWeight)
{
$this->netWeight = $netWeight;
}

/**
* @return mixed
*/
public function getGrossWeight()
{
return $this->grossWeight;
}

/**
* @param mixed $grossWeight
*/
public function setGrossWeight($grossWeight)
{
$this->grossWeight = $grossWeight;
}

/**
* @return mixed
*/
public function getCode()
{
return $this->code;
}

/**
* @param mixed $code
*/
public function setCode($code)
{
$this->code = $code;
}
}

+ 23
- 0
src/ExactApi/apiUpdateProducts.php Ver fichero

@@ -0,0 +1,23 @@
<?php
//require __DIR__ . '/ApiExact.php';
//require __DIR__ . '/IsotopeDatabaseHandler.php';

namespace App\ExactApi;
//use App\ExactApi\ApiExact;
//use App\ExactApi\IsotopeDatabaseHandler;

//use App\ExactApi\ApiExact as ApiExact;
//use App\ExactApi\IsotopeDatabaseHandler as IsotopeDatabaseHandler;

//include_once('App\ExactApi\ApiExact.php');
//include_once('App\ExactApi\IsotopeDatabaseHandler.php');

require_once './ApiExact.php';
require_once './IsotopeDatabaseHandler.php';

$apiConnector = new ApiExact();
$databaseHandler = new IsotopeDatabaseHandler();

//echo $apiConnector->getAccessToken() . "\n";
$products = $apiConnector->getProducts();
$databaseHandler->processProducts($products);

+ 1
- 0
src/ExactApi/tokenData Ver fichero

@@ -0,0 +1 @@
{"access_token":"stampDE001.gAAAAKLeomGUJnO_59CX7KmwE4tkt1bPxLR7EfU5E2eHjj6LeU2dAPfkRstKdlJJoudf3y6ehyKwF5wQQ1SljbA5sRrGwQsw4M876o0RRtxVHwyR6_QSoM7kP3qj7HfJLEDWjY-KrlJf_rlKtZZ2sRGJkX9jbd-4XDA_uqQECkf2NnIfVAIAAIAAAAC2ADvWhSjIjtyvs4_rhH9HzqaVKUbQthUoKf6StMSX4O5GzilHhM2cQCQJly_7hbpKHJMe4o167cPYIQLTeOilQdlQh21nhGykWfOKtXG31h210WtT2bg8Kc0MtrHQGi_f161wE_xINPXr-H7GTcXLe4klIGUkv0kmmmUacNoDUDfU1fPRTCCFvmkRnE7iBzd116aY9XMVw6mDucFExI9U-dLOD8IFWCPa9U6DSd50T7WoglDHC8Enh-J92C1c8kAhCCiGyA5GHyb9S1eJAnHFDYzOl9y0pwtIHekBJq-LxpzAbVieBUZrXEsTJnD7eiHvKoy9g_Bl-jOVgPTJT29n0dFDUClE_iQ6MGydAiEVKoyhQO20W7EJg64G8l6ZHZrd8r1J36TZ4jmdZ8E8_wqw3MzYGZm4vEXK4G0Lf2-j5uD0eh3aKPQCCdn_jEiCaEABQ-bsvd9sQazNNHbDwzl82GZdlhY8sqln14NpNloahOxhY9-jKu5nT9IBN2EftT-VoLNWp_E4ydPltM_7SS0Xro9j8nf7xNjpBDa__GeR2QSg_He5VR6u2n4lUbU1Qzks2W38RiCA3jXrQ-T6FCtiiaf5rhMzudqeq2c94Ggbn1g7BD_3S9xUFeA5kmSCkn1NZ3RzSqmkMq4IVPcbsJFYeoCiuZ1gYcvSOoBMHN8mjZ8j3xpPEvzJSX39arD5IeNqrLZDXuEU5aORiRnqjN6kPC4w1GQbbMsEL94r8tYGy8VkW7bstxkar7vb2J8vOPhLamN0mcxJdTZCCJ8tj18e","token_type":"bearer","expires_in":"600","refresh_token":"stampDE001.vhK0!IAAAANKu8TC2-MBN5wFyro7uwanpxXBsRFy7YkYSPaM9jMCm8QEAAAEVEJUYfzoc3Q1hatgTMAOm__pmwjTgdkOyzb7qUj7oRvir1XftD_cb5eE7J-06Q0SxWegFPfULR65PDc6Id0pDXZbugqaKhK24Gg9Gr0x74g4EW8HMYJ0PsW1uqvpOb_ph9mQxYllDQf598_uPZvuvxAMN4NOipKiHEXn9tExbn-rmJdrV9bwa3fAm5b2ALbzfBEKCuzuwLkrjH3cheaMC2TaksJ6M2we9IeAezapsyOVEl6QtnhY67L3Irp_bUV_SCZ6H-gbPDX3mFujxyw-lvRoC-ZcgIpM8EuWC3pN24abFp7Qaj8wE2X9_oAgvoJgn42lHl-x6M-QChdf78f6wiCPquk3OwwuBYsjmiJrHFQt4a6nRS-Y_A-J8IOdwq-_KZkvNTY5UI1ZmiGH5pvIDfaXTcvPn6r3C-sQecdImJM43Y_tgZ9a6p2gwPi8zNZSkXfwQH5bfal6tBcH43_8eGSp_M-VbLb7TotZfJgEIePSlZ--VV1aTaRMJ78_JBloFWvQabvhYEvvuvRaePbdGRgXzj95KgQeW8DzBXE6_zHSmhwkUEv11xgXimH3BFToSV_F25ZND7H3OkVM1-M8Jhbvrzh4SR5wRV5WfzC83Mk58HmDtI7xR0owYAu-9mtidFsK8mRC3nJzZSOWdGenZ","expiry_time":1757726518}

Cargando…
Cancelar
Guardar