|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* (c) shopware AG <[email protected]> |
|
4
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
5
|
|
|
* file that was distributed with this source code. |
|
6
|
|
|
*/ |
|
7
|
|
|
|
|
8
|
|
|
namespace ShopwarePlugins\Connect\Components; |
|
9
|
|
|
|
|
10
|
|
|
use Shopware\Bundle\SearchBundle\Sorting\ReleaseDateSorting; |
|
11
|
|
|
use Shopware\Connect\Gateway; |
|
12
|
|
|
use Shopware\Components\Model\CategoryDenormalization; |
|
13
|
|
|
use Shopware\Connect\ProductToShop as ProductToShopBase; |
|
14
|
|
|
use Shopware\Connect\Struct\Product; |
|
15
|
|
|
use Shopware\Models\Article\Article as ProductModel; |
|
16
|
|
|
use Shopware\Models\Article\Detail as DetailModel; |
|
17
|
|
|
use Shopware\Models\Attribute\Article as AttributeModel; |
|
18
|
|
|
use Shopware\Components\Model\ModelManager; |
|
19
|
|
|
use Shopware\Connect\Struct\PriceRange; |
|
20
|
|
|
use Shopware\Connect\Struct\ProductUpdate; |
|
21
|
|
|
use Shopware\CustomModels\Connect\ProductStreamAttribute; |
|
22
|
|
|
use Shopware\Models\Customer\Group; |
|
23
|
|
|
use Shopware\Connect\Struct\Property; |
|
24
|
|
|
use Shopware\Models\ProductStream\ProductStream; |
|
25
|
|
|
use Shopware\Models\Property\Group as PropertyGroup; |
|
26
|
|
|
use Shopware\Models\Property\Option as PropertyOption; |
|
27
|
|
|
use Shopware\Models\Property\Value as PropertyValue; |
|
28
|
|
|
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamRepository; |
|
29
|
|
|
use ShopwarePlugins\Connect\Components\ProductStream\ProductStreamService; |
|
30
|
|
|
use ShopwarePlugins\Connect\Components\Translations\LocaleMapper; |
|
31
|
|
|
use ShopwarePlugins\Connect\Components\Gateway\ProductTranslationsGateway; |
|
32
|
|
|
use ShopwarePlugins\Connect\Components\Marketplace\MarketplaceGateway; |
|
33
|
|
|
use ShopwarePlugins\Connect\Components\Utils\UnitMapper; |
|
34
|
|
|
use Shopware\CustomModels\Connect\Attribute as ConnectAttribute; |
|
35
|
|
|
use Shopware\Models\Article\Image; |
|
36
|
|
|
use Shopware\Models\Article\Supplier; |
|
37
|
|
|
use Shopware\Models\Tax\Tax; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* The interface for products imported *from* connect *to* the local shop |
|
41
|
|
|
* |
|
42
|
|
|
* @category Shopware |
|
43
|
|
|
* @package Shopware\Plugins\SwagConnect |
|
44
|
|
|
*/ |
|
45
|
|
|
class ProductToShop implements ProductToShopBase |
|
46
|
|
|
{ |
|
47
|
|
|
/** |
|
48
|
|
|
* @var Helper |
|
49
|
|
|
*/ |
|
50
|
|
|
private $helper; |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* @var ModelManager |
|
54
|
|
|
*/ |
|
55
|
|
|
private $manager; |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* @var \ShopwarePlugins\Connect\Components\Config |
|
59
|
|
|
*/ |
|
60
|
|
|
private $config; |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* @var ImageImport |
|
64
|
|
|
*/ |
|
65
|
|
|
private $imageImport; |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* @var \ShopwarePlugins\Connect\Components\VariantConfigurator |
|
69
|
|
|
*/ |
|
70
|
|
|
private $variantConfigurator; |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* @var MarketplaceGateway |
|
74
|
|
|
*/ |
|
75
|
|
|
private $marketplaceGateway; |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* @var ProductTranslationsGateway |
|
79
|
|
|
*/ |
|
80
|
|
|
private $productTranslationsGateway; |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* @var \Shopware\Models\Shop\Repository |
|
84
|
|
|
*/ |
|
85
|
|
|
private $shopRepository; |
|
86
|
|
|
|
|
87
|
|
|
private $localeRepository; |
|
88
|
|
|
|
|
89
|
|
|
/** |
|
90
|
|
|
* @var CategoryResolver |
|
91
|
|
|
*/ |
|
92
|
|
|
private $categoryResolver; |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* @var \Shopware\Connect\Gateway |
|
96
|
|
|
*/ |
|
97
|
|
|
private $connectGateway; |
|
98
|
|
|
|
|
99
|
|
|
/** |
|
100
|
|
|
* @var \Enlight_Event_EventManager |
|
101
|
|
|
*/ |
|
102
|
|
|
private $eventManager; |
|
103
|
|
|
|
|
104
|
|
|
/** |
|
105
|
|
|
* @var CategoryDenormalization |
|
106
|
|
|
*/ |
|
107
|
|
|
private $categoryDenormalization; |
|
108
|
|
|
|
|
109
|
|
|
/** |
|
110
|
|
|
* @param Helper $helper |
|
111
|
|
|
* @param ModelManager $manager |
|
112
|
|
|
* @param ImageImport $imageImport |
|
113
|
|
|
* @param \ShopwarePlugins\Connect\Components\Config $config |
|
114
|
|
|
* @param VariantConfigurator $variantConfigurator |
|
115
|
|
|
* @param \ShopwarePlugins\Connect\Components\Marketplace\MarketplaceGateway $marketplaceGateway |
|
116
|
|
|
* @param ProductTranslationsGateway $productTranslationsGateway |
|
117
|
|
|
* @param CategoryResolver $categoryResolver |
|
118
|
|
|
* @param Gateway $connectGateway |
|
119
|
|
|
* @param \Enlight_Event_EventManager $eventManager |
|
120
|
|
|
* @param CategoryDenormalization $categoryDenormalization |
|
121
|
|
|
*/ |
|
122
|
|
|
public function __construct( |
|
123
|
|
|
Helper $helper, |
|
124
|
|
|
ModelManager $manager, |
|
125
|
|
|
ImageImport $imageImport, |
|
126
|
|
|
Config $config, |
|
127
|
|
|
VariantConfigurator $variantConfigurator, |
|
128
|
|
|
MarketplaceGateway $marketplaceGateway, |
|
129
|
|
|
ProductTranslationsGateway $productTranslationsGateway, |
|
130
|
|
|
CategoryResolver $categoryResolver, |
|
131
|
|
|
Gateway $connectGateway, |
|
132
|
|
|
\Enlight_Event_EventManager $eventManager, |
|
133
|
|
|
CategoryDenormalization $categoryDenormalization |
|
134
|
|
|
) { |
|
135
|
|
|
$this->helper = $helper; |
|
136
|
|
|
$this->manager = $manager; |
|
137
|
|
|
$this->config = $config; |
|
138
|
|
|
$this->imageImport = $imageImport; |
|
139
|
|
|
$this->variantConfigurator = $variantConfigurator; |
|
140
|
|
|
$this->marketplaceGateway = $marketplaceGateway; |
|
141
|
|
|
$this->productTranslationsGateway = $productTranslationsGateway; |
|
142
|
|
|
$this->categoryResolver = $categoryResolver; |
|
143
|
|
|
$this->connectGateway = $connectGateway; |
|
144
|
|
|
$this->eventManager = $eventManager; |
|
145
|
|
|
$this->categoryDenormalization = $categoryDenormalization; |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* Start transaction |
|
150
|
|
|
* |
|
151
|
|
|
* Starts a transaction, which includes all insertOrUpdate and delete |
|
152
|
|
|
* operations, as well as the revision updates. |
|
153
|
|
|
* |
|
154
|
|
|
* @return void |
|
155
|
|
|
*/ |
|
156
|
|
|
public function startTransaction() |
|
157
|
|
|
{ |
|
158
|
|
|
$this->manager->getConnection()->beginTransaction(); |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
/** |
|
162
|
|
|
* Commit transaction |
|
163
|
|
|
* |
|
164
|
|
|
* Commits the transactions, once all operations are queued. |
|
165
|
|
|
* |
|
166
|
|
|
* @return void |
|
167
|
|
|
*/ |
|
168
|
|
|
public function commit() |
|
169
|
|
|
{ |
|
170
|
|
|
$this->manager->getConnection()->commit(); |
|
171
|
|
|
} |
|
172
|
|
|
|
|
173
|
|
|
/** |
|
174
|
|
|
* Import or update given product |
|
175
|
|
|
* |
|
176
|
|
|
* Store product in your shop database as an external product. The |
|
177
|
|
|
* associated sourceId |
|
178
|
|
|
* |
|
179
|
|
|
* @param Product $product |
|
180
|
|
|
*/ |
|
181
|
|
|
public function insertOrUpdate(Product $product) |
|
182
|
|
|
{ |
|
183
|
|
|
/** @var Product $product */ |
|
184
|
|
|
$product = $this->eventManager->filter( |
|
185
|
|
|
'Connect_ProductToShop_InsertOrUpdate_Before', |
|
186
|
|
|
$product |
|
187
|
|
|
); |
|
188
|
|
|
|
|
189
|
|
|
// todo@dn: Set dummy values and make product inactive |
|
190
|
|
|
if (empty($product->title) || empty($product->vendor)) { |
|
191
|
|
|
return; |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
if (!empty($product->sku)) { |
|
195
|
|
|
$number = 'SC-' . $product->shopId . '-' . $product->sku; |
|
196
|
|
|
$duplicatedDetail = $this->helper->getDetailByNumber($number); |
|
197
|
|
|
if ($duplicatedDetail |
|
198
|
|
|
&& $this->helper->getConnectAttributeByModel($duplicatedDetail)->getSourceId() != $product->sourceId |
|
199
|
|
|
) { |
|
200
|
|
|
$this->deleteDetail($duplicatedDetail); |
|
201
|
|
|
} |
|
202
|
|
|
} else { |
|
203
|
|
|
$number = 'SC-' . $product->shopId . '-' . $product->sourceId; |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
$detail = $this->helper->getArticleDetailModelByProduct($product); |
|
207
|
|
|
$detail = $this->eventManager->filter( |
|
208
|
|
|
'Connect_Merchant_Get_Article_Detail_After', |
|
209
|
|
|
$detail, |
|
210
|
|
|
[ |
|
211
|
|
|
'product' => $product, |
|
212
|
|
|
'subject' => $this |
|
213
|
|
|
] |
|
214
|
|
|
); |
|
215
|
|
|
|
|
216
|
|
|
$isMainVariant = false; |
|
217
|
|
|
if ($detail === null) { |
|
218
|
|
|
$active = $this->config->getConfig('activateProductsAutomatically', false) ? true : false; |
|
219
|
|
|
if ($product->groupId !== null) { |
|
220
|
|
|
$model = $this->helper->getArticleByRemoteProduct($product); |
|
221
|
|
|
if (!$model instanceof \Shopware\Models\Article\Article) { |
|
|
|
|
|
|
222
|
|
|
$model = $this->helper->createProductModel($product); |
|
223
|
|
|
$model->setActive($active); |
|
224
|
|
|
$isMainVariant = true; |
|
225
|
|
|
} |
|
226
|
|
|
} else { |
|
227
|
|
|
$model = $this->helper->getConnectArticleModel($product->sourceId, $product->shopId); |
|
228
|
|
|
if (!$model instanceof \Shopware\Models\Article\Article) { |
|
|
|
|
|
|
229
|
|
|
$model = $this->helper->createProductModel($product); |
|
230
|
|
|
$model->setActive($active); |
|
231
|
|
|
} |
|
232
|
|
|
} |
|
233
|
|
|
|
|
234
|
|
|
$detail = new DetailModel(); |
|
235
|
|
|
$detail->setActive($model->getActive()); |
|
236
|
|
|
$this->manager->persist($detail); |
|
237
|
|
|
$detail->setArticle($model); |
|
238
|
|
|
$model->getDetails()->add($detail); |
|
239
|
|
|
if (!empty($product->variant)) { |
|
240
|
|
|
$this->variantConfigurator->configureVariantAttributes($product, $detail); |
|
241
|
|
|
} |
|
242
|
|
|
} else { |
|
243
|
|
|
$model = $detail->getArticle(); |
|
244
|
|
|
// fix for isMainVariant flag |
|
245
|
|
|
// in connect attribute table |
|
246
|
|
|
$mainDetail = $model->getMainDetail(); |
|
247
|
|
|
if ($detail->getId() === $mainDetail->getId()) { |
|
248
|
|
|
$isMainVariant = true; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
if (empty($product->variant) && $model->getConfiguratorSet()) { |
|
252
|
|
|
$this->manager->getConnection()->executeQuery( |
|
253
|
|
|
'UPDATE s_articles SET configurator_set_id = NULL WHERE id = ?', |
|
254
|
|
|
[$model->getId()] |
|
255
|
|
|
); |
|
256
|
|
|
} |
|
257
|
|
|
} |
|
258
|
|
|
|
|
259
|
|
|
$detail->setNumber($number); |
|
260
|
|
|
|
|
261
|
|
|
/** @var \Shopware\Models\Category\Category $category */ |
|
262
|
|
|
foreach ($model->getCategories() as $category) { |
|
263
|
|
|
$attribute = $category->getAttribute(); |
|
264
|
|
|
if (!$attribute) { |
|
265
|
|
|
continue; |
|
266
|
|
|
} |
|
267
|
|
|
|
|
268
|
|
|
if ($attribute->getConnectImported()) { |
|
269
|
|
|
$model->removeCategory($category); |
|
270
|
|
|
} |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
$detailAttribute = $detail->getAttribute(); |
|
274
|
|
|
if (!$detailAttribute) { |
|
275
|
|
|
$detailAttribute = new AttributeModel(); |
|
276
|
|
|
$detail->setAttribute($detailAttribute); |
|
277
|
|
|
$model->setAttribute($detailAttribute); |
|
278
|
|
|
$detailAttribute->setArticle($model); |
|
279
|
|
|
$detailAttribute->setArticleDetail($detail); |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
|
|
$connectAttribute = $this->helper->getConnectAttributeByModel($detail) ?: new ConnectAttribute; |
|
283
|
|
|
// configure main variant and groupId |
|
284
|
|
|
if ($isMainVariant === true) { |
|
285
|
|
|
$connectAttribute->setIsMainVariant(true); |
|
286
|
|
|
} |
|
287
|
|
|
$connectAttribute->setGroupId($product->groupId); |
|
288
|
|
|
|
|
289
|
|
|
list($updateFields, $flag) = $this->getUpdateFields($model, $detail, $connectAttribute, $product); |
|
290
|
|
|
/* |
|
291
|
|
|
* Make sure, that the following properties are set for |
|
292
|
|
|
* - new products |
|
293
|
|
|
* - products that have been configured to receive these updates |
|
294
|
|
|
*/ |
|
295
|
|
|
if ($updateFields['name']) { |
|
296
|
|
|
$model->setName($product->title); |
|
297
|
|
|
} |
|
298
|
|
|
if ($updateFields['shortDescription']) { |
|
299
|
|
|
$model->setDescription($product->shortDescription); |
|
300
|
|
|
} |
|
301
|
|
|
if ($updateFields['longDescription']) { |
|
302
|
|
|
$model->setDescriptionLong($product->longDescription); |
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
if ($updateFields['additionalDescription']) { |
|
306
|
|
|
$detailAttribute->setConnectProductDescription($product->additionalDescription); |
|
307
|
|
|
} |
|
308
|
|
|
|
|
309
|
|
|
$this->saveVat($product, $model); |
|
310
|
|
|
|
|
311
|
|
|
if ($product->vendor !== null) { |
|
312
|
|
|
$repo = $this->manager->getRepository('Shopware\Models\Article\Supplier'); |
|
313
|
|
|
$supplier = $repo->findOneBy(['name' => $product->vendor]); |
|
314
|
|
|
if ($supplier === null) { |
|
315
|
|
|
$supplier = $this->createSupplier($product->vendor); |
|
316
|
|
|
} |
|
317
|
|
|
$model->setSupplier($supplier); |
|
318
|
|
|
} |
|
319
|
|
|
|
|
320
|
|
|
//set product properties |
|
321
|
|
|
$this->applyProductProperties($model, $product); |
|
322
|
|
|
|
|
323
|
|
|
// apply marketplace attributes |
|
324
|
|
|
$detailAttribute = $this->applyMarketplaceAttributes($detailAttribute, $product); |
|
325
|
|
|
|
|
326
|
|
|
$connectAttribute->setShopId($product->shopId); |
|
327
|
|
|
$connectAttribute->setSourceId($product->sourceId); |
|
328
|
|
|
$connectAttribute->setExportStatus(null); |
|
329
|
|
|
$connectAttribute->setPurchasePrice($product->purchasePrice); |
|
330
|
|
|
$connectAttribute->setFixedPrice($product->fixedPrice); |
|
331
|
|
|
$connectAttribute->setStream($product->stream); |
|
332
|
|
|
|
|
333
|
|
|
// store product categories to connect attribute |
|
334
|
|
|
$connectAttribute->setCategory($product->categories); |
|
335
|
|
|
|
|
336
|
|
|
$connectAttribute->setLastUpdateFlag($flag); |
|
337
|
|
|
// store purchasePriceHash and offerValidUntil |
|
338
|
|
|
$connectAttribute->setPurchasePriceHash($product->purchasePriceHash); |
|
339
|
|
|
$connectAttribute->setOfferValidUntil($product->offerValidUntil); |
|
340
|
|
|
|
|
341
|
|
|
$detail->setInStock($product->availability); |
|
342
|
|
|
$detail->setEan($product->ean); |
|
343
|
|
|
$detail->setShippingTime($product->deliveryWorkDays); |
|
344
|
|
|
$releaseDate = new \DateTime(); |
|
345
|
|
|
$releaseDate->setTimestamp($product->deliveryDate); |
|
346
|
|
|
$detail->setReleaseDate($releaseDate); |
|
347
|
|
|
$detail->setMinPurchase($product->minPurchaseQuantity); |
|
348
|
|
|
|
|
349
|
|
|
// some shops have feature "sell not in stock", |
|
350
|
|
|
// then end customer should be able to by the product with stock = 0 |
|
351
|
|
|
$shopConfiguration = $this->connectGateway->getShopConfiguration($product->shopId); |
|
352
|
|
|
if ($shopConfiguration && $shopConfiguration->sellNotInStock) { |
|
353
|
|
|
$model->setLastStock(false); |
|
354
|
|
|
} else { |
|
355
|
|
|
$model->setLastStock(true); |
|
356
|
|
|
} |
|
357
|
|
|
|
|
358
|
|
|
// if connect product has unit |
|
359
|
|
|
// find local unit with units mapping |
|
360
|
|
|
// and add to detail model |
|
361
|
|
|
if (array_key_exists('unit', $product->attributes) && $product->attributes['unit']) { |
|
362
|
|
|
$detailAttribute->setConnectRemoteUnit($product->attributes['unit']); |
|
363
|
|
|
if ($this->config->getConfig($product->attributes['unit']) == null) { |
|
364
|
|
|
$this->config->setConfig($product->attributes['unit'], '', null, 'units'); |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
|
|
/** @var \ShopwarePlugins\Connect\Components\Utils\UnitMapper $unitMapper */ |
|
368
|
|
|
$unitMapper = new UnitMapper($this->config, $this->manager); |
|
369
|
|
|
|
|
370
|
|
|
$shopwareUnit = $unitMapper->getShopwareUnit($product->attributes['unit']); |
|
371
|
|
|
|
|
372
|
|
|
/** @var \Shopware\Models\Article\Unit $unit */ |
|
373
|
|
|
$unit = $this->helper->getUnit($shopwareUnit); |
|
374
|
|
|
$detail->setUnit($unit); |
|
375
|
|
|
$detail->setPurchaseUnit($product->attributes['quantity']); |
|
376
|
|
|
$detail->setReferenceUnit($product->attributes['ref_quantity']); |
|
377
|
|
|
} else { |
|
378
|
|
|
$detail->setUnit(null); |
|
379
|
|
|
$detail->setPurchaseUnit(null); |
|
380
|
|
|
$detail->setReferenceUnit(null); |
|
381
|
|
|
} |
|
382
|
|
|
|
|
383
|
|
|
// set dimension |
|
384
|
|
|
if (array_key_exists('dimension', $product->attributes) && $product->attributes['dimension']) { |
|
385
|
|
|
$dimension = explode('x', $product->attributes['dimension']); |
|
386
|
|
|
$detail->setLen($dimension[0]); |
|
387
|
|
|
$detail->setWidth($dimension[1]); |
|
388
|
|
|
$detail->setHeight($dimension[2]); |
|
389
|
|
|
} else { |
|
390
|
|
|
$detail->setLen(null); |
|
391
|
|
|
$detail->setWidth(null); |
|
392
|
|
|
$detail->setHeight(null); |
|
393
|
|
|
} |
|
394
|
|
|
|
|
395
|
|
|
// set weight |
|
396
|
|
|
if (array_key_exists('weight', $product->attributes) && $product->attributes['weight']) { |
|
397
|
|
|
$detail->setWeight($product->attributes['weight']); |
|
398
|
|
|
} |
|
399
|
|
|
|
|
400
|
|
|
//set package unit |
|
401
|
|
|
if (array_key_exists(Product::ATTRIBUTE_PACKAGEUNIT, $product->attributes)) { |
|
402
|
|
|
$detail->setPackUnit($product->attributes[Product::ATTRIBUTE_PACKAGEUNIT]); |
|
403
|
|
|
} |
|
404
|
|
|
|
|
405
|
|
|
//set basic unit |
|
406
|
|
|
if (array_key_exists(Product::ATTRIBUTE_BASICUNIT, $product->attributes)) { |
|
407
|
|
|
$detail->setMinPurchase($product->attributes[Product::ATTRIBUTE_BASICUNIT]); |
|
408
|
|
|
} |
|
409
|
|
|
|
|
410
|
|
|
//set manufacturer no. |
|
411
|
|
|
if (array_key_exists(Product::ATTRIBUTE_MANUFACTURERNUMBER, $product->attributes)) { |
|
412
|
|
|
$detail->setSupplierNumber($product->attributes[Product::ATTRIBUTE_MANUFACTURERNUMBER]); |
|
413
|
|
|
} |
|
414
|
|
|
|
|
415
|
|
|
// Whenever a product is updated, store a json encoded list of all fields that are updated optionally |
|
416
|
|
|
// This way a customer will be able to apply the most recent changes any time later |
|
417
|
|
|
$connectAttribute->setLastUpdate(json_encode([ |
|
418
|
|
|
'shortDescription' => $product->shortDescription, |
|
419
|
|
|
'longDescription' => $product->longDescription, |
|
420
|
|
|
'additionalDescription' => $product->additionalDescription, |
|
421
|
|
|
'purchasePrice' => $product->purchasePrice, |
|
422
|
|
|
'image' => $product->images, |
|
423
|
|
|
'variantImages' => $product->variantImages, |
|
424
|
|
|
'price' => $product->price * ($product->vat + 1), |
|
425
|
|
|
'name' => $product->title, |
|
426
|
|
|
'vat' => $product->vat |
|
427
|
|
|
])); |
|
428
|
|
|
|
|
429
|
|
|
if ($model->getMainDetail() === null) { |
|
430
|
|
|
$model->setMainDetail($detail); |
|
431
|
|
|
} |
|
432
|
|
|
|
|
433
|
|
|
if ($detail->getAttribute() === null) { |
|
434
|
|
|
$detail->setAttribute($detailAttribute); |
|
435
|
|
|
$detailAttribute->setArticle($model); |
|
436
|
|
|
} |
|
437
|
|
|
|
|
438
|
|
|
$connectAttribute->setArticle($model); |
|
439
|
|
|
$connectAttribute->setArticleDetail($detail); |
|
440
|
|
|
|
|
441
|
|
|
$this->eventManager->notify( |
|
442
|
|
|
'Connect_Merchant_Saving_ArticleAttribute_Before', |
|
443
|
|
|
[ |
|
444
|
|
|
'subject' => $this, |
|
445
|
|
|
'connectAttribute' => $connectAttribute |
|
446
|
|
|
] |
|
447
|
|
|
); |
|
448
|
|
|
|
|
449
|
|
|
$this->manager->persist($model); |
|
450
|
|
|
$this->manager->flush(); |
|
451
|
|
|
|
|
452
|
|
|
$this->categoryResolver->storeRemoteCategories($product->categories, $model->getId()); |
|
453
|
|
|
$categories = $this->categoryResolver->resolve($product->categories); |
|
454
|
|
|
if (count($categories) > 0) { |
|
455
|
|
|
$detailAttribute->setConnectMappedCategory(true); |
|
456
|
|
|
} |
|
457
|
|
|
|
|
458
|
|
|
$this->manager->persist($connectAttribute); |
|
459
|
|
|
$this->manager->persist($detail); |
|
460
|
|
|
//article has to be flushed |
|
461
|
|
|
$this->manager->persist($detailAttribute); |
|
462
|
|
|
$this->manager->flush(); |
|
463
|
|
|
|
|
464
|
|
|
$this->categoryDenormalization->disableTransactions(); |
|
465
|
|
|
foreach ($categories as $category) { |
|
466
|
|
|
$this->categoryDenormalization->addAssignment($model->getId(), $category); |
|
467
|
|
|
$this->manager->getConnection()->executeQuery( |
|
468
|
|
|
'INSERT IGNORE INTO `s_articles_categories` (`articleID`, `categoryID`) VALUES (?,?)', |
|
469
|
|
|
[$model->getId(), $category] |
|
470
|
|
|
); |
|
471
|
|
|
} |
|
472
|
|
|
$this->categoryDenormalization->enableTransactions(); |
|
473
|
|
|
|
|
474
|
|
|
$defaultCustomerGroup = $this->helper->getDefaultCustomerGroup(); |
|
475
|
|
|
// Only set prices, if fixedPrice is active or price updates are configured |
|
476
|
|
|
if (count($detail->getPrices()) == 0 || $connectAttribute->getFixedPrice() || $updateFields['price']) { |
|
477
|
|
|
$this->setPrice($model, $detail, $product); |
|
478
|
|
|
} |
|
479
|
|
|
// If the price is not being update, update the purchasePrice anyway |
|
480
|
|
|
$this->setPurchasePrice($detail, $product->purchasePrice, $defaultCustomerGroup); |
|
481
|
|
|
|
|
482
|
|
|
$this->manager->clear(); |
|
483
|
|
|
|
|
484
|
|
|
$this->addArticleTranslations($model, $product); |
|
485
|
|
|
|
|
486
|
|
|
//clear cache for that article |
|
487
|
|
|
$this->helper->clearArticleCache($model->getId()); |
|
488
|
|
|
|
|
489
|
|
|
if ($updateFields['image']) { |
|
490
|
|
|
// Reload the model in order to not to work an the already flushed model |
|
491
|
|
|
$model = $this->helper->getArticleModelByProduct($product); |
|
492
|
|
|
// import only global images for article |
|
493
|
|
|
$this->imageImport->importImagesForArticle(array_diff($product->images, $product->variantImages), $model); |
|
|
|
|
|
|
494
|
|
|
// Reload the article detail model in order to not to work an the already flushed model |
|
495
|
|
|
$detail = $this->helper->getArticleDetailModelByProduct($product); |
|
496
|
|
|
// import only specific images for variant |
|
497
|
|
|
$this->imageImport->importImagesForDetail($product->variantImages, $detail); |
|
|
|
|
|
|
498
|
|
|
} |
|
499
|
|
|
|
|
500
|
|
|
$this->eventManager->notify( |
|
501
|
|
|
'Connect_ProductToShop_InsertOrUpdate_After', |
|
502
|
|
|
[ |
|
503
|
|
|
'connectProduct' => $product, |
|
504
|
|
|
'shopArticleDetail' => $detail |
|
505
|
|
|
] |
|
506
|
|
|
); |
|
507
|
|
|
|
|
508
|
|
|
$stream = $this->getOrCreateStream($product); |
|
509
|
|
|
$this->addProductToStream($stream, $model); |
|
510
|
|
|
} |
|
511
|
|
|
|
|
512
|
|
|
/** |
|
513
|
|
|
* @param ProductModel $article |
|
514
|
|
|
* @param Product $product |
|
515
|
|
|
*/ |
|
516
|
|
|
private function applyProductProperties(ProductModel $article, Product $product) |
|
517
|
|
|
{ |
|
518
|
|
|
if (empty($product->properties)) { |
|
519
|
|
|
return; |
|
520
|
|
|
} |
|
521
|
|
|
|
|
522
|
|
|
/** @var Property $firstProperty */ |
|
523
|
|
|
$firstProperty = reset($product->properties); |
|
524
|
|
|
$groupRepo = $this->manager->getRepository(PropertyGroup::class); |
|
525
|
|
|
$group = $groupRepo->findOneBy(['name' => $firstProperty->groupName]); |
|
526
|
|
|
|
|
527
|
|
|
if (!$group) { |
|
528
|
|
|
$group = new PropertyGroup(); |
|
529
|
|
|
$group->setName($firstProperty->groupName); |
|
530
|
|
|
$group->setComparable($firstProperty->comparable); |
|
531
|
|
|
$group->setSortMode($firstProperty->sortMode); |
|
532
|
|
|
$group->setPosition($firstProperty->groupPosition); |
|
533
|
|
|
|
|
534
|
|
|
$attribute = new \Shopware\Models\Attribute\PropertyGroup(); |
|
535
|
|
|
$attribute->setPropertyGroup($group); |
|
536
|
|
|
$attribute->setConnectIsRemote(true); |
|
537
|
|
|
$group->setAttribute($attribute); |
|
538
|
|
|
|
|
539
|
|
|
$this->manager->persist($attribute); |
|
540
|
|
|
$this->manager->persist($group); |
|
541
|
|
|
$this->manager->flush(); |
|
542
|
|
|
} |
|
543
|
|
|
|
|
544
|
|
|
$propertyValues = $article->getPropertyValues(); |
|
545
|
|
|
$propertyValues->clear(); |
|
546
|
|
|
$this->manager->persist($article); |
|
547
|
|
|
$this->manager->flush(); |
|
548
|
|
|
|
|
549
|
|
|
$article->setPropertyGroup($group); |
|
550
|
|
|
|
|
551
|
|
|
$optionRepo = $this->manager->getRepository(PropertyOption::class); |
|
552
|
|
|
$valueRepo = $this->manager->getRepository(PropertyValue::class); |
|
553
|
|
|
|
|
554
|
|
|
foreach ($product->properties as $property) { |
|
555
|
|
|
$option = $optionRepo->findOneBy(['name' => $property->option]); |
|
556
|
|
|
$optionExists = $option instanceof PropertyOption; |
|
|
|
|
|
|
557
|
|
|
if (!$option) { |
|
558
|
|
|
$option = new PropertyOption(); |
|
559
|
|
|
$option->setName($property->option); |
|
560
|
|
|
$option->setFilterable($property->filterable); |
|
561
|
|
|
|
|
562
|
|
|
$attribute = new \Shopware\Models\Attribute\PropertyOption(); |
|
563
|
|
|
$attribute->setPropertyOption($option); |
|
564
|
|
|
$attribute->setConnectIsRemote(true); |
|
565
|
|
|
$option->setAttribute($attribute); |
|
566
|
|
|
|
|
567
|
|
|
$this->manager->persist($option); |
|
568
|
|
|
$this->manager->flush($option); |
|
569
|
|
|
} |
|
570
|
|
|
|
|
571
|
|
|
if (!$optionExists || !$value = $valueRepo->findOneBy(['option' => $option, 'value' => $property->value])) { |
|
572
|
|
|
$value = new PropertyValue($option, $property->value); |
|
573
|
|
|
$value->setPosition($property->valuePosition); |
|
574
|
|
|
|
|
575
|
|
|
$attribute = new \Shopware\Models\Attribute\PropertyValue(); |
|
576
|
|
|
$attribute->setPropertyValue($value); |
|
577
|
|
|
$attribute->setConnectIsRemote(true); |
|
578
|
|
|
$value->setAttribute($attribute); |
|
579
|
|
|
|
|
580
|
|
|
$this->manager->persist($value); |
|
581
|
|
|
} |
|
582
|
|
|
|
|
583
|
|
|
if (!$propertyValues->contains($value)) { |
|
584
|
|
|
//add only new values |
|
585
|
|
|
$propertyValues->add($value); |
|
586
|
|
|
} |
|
587
|
|
|
|
|
588
|
|
|
$filters = [ |
|
589
|
|
|
['property' => 'options.name', 'expression' => '=', 'value' => $property->option], |
|
590
|
|
|
['property' => 'groups.name', 'expression' => '=', 'value' => $property->groupName], |
|
591
|
|
|
]; |
|
592
|
|
|
|
|
593
|
|
|
$query = $groupRepo->getPropertyRelationQuery($filters, null, 1, 0); |
|
594
|
|
|
$relation = $query->getOneOrNullResult(); |
|
595
|
|
|
|
|
596
|
|
|
if (!$relation) { |
|
597
|
|
|
$group->addOption($option); |
|
598
|
|
|
$this->manager->persist($group); |
|
599
|
|
|
$this->manager->flush($group); |
|
600
|
|
|
} |
|
601
|
|
|
} |
|
602
|
|
|
|
|
603
|
|
|
$article->setPropertyValues($propertyValues); |
|
604
|
|
|
|
|
605
|
|
|
$this->manager->persist($article); |
|
606
|
|
|
$this->manager->flush(); |
|
607
|
|
|
} |
|
608
|
|
|
|
|
609
|
|
|
/** |
|
610
|
|
|
* @param Product $product |
|
611
|
|
|
* @return ProductStream |
|
612
|
|
|
*/ |
|
613
|
|
|
private function getOrCreateStream(Product $product) |
|
614
|
|
|
{ |
|
615
|
|
|
/** @var ProductStreamRepository $repo */ |
|
616
|
|
|
$repo = $this->manager->getRepository(ProductStreamAttribute::class); |
|
617
|
|
|
$stream = $repo->findConnectByName($product->stream); |
|
618
|
|
|
|
|
619
|
|
|
if (!$stream) { |
|
620
|
|
|
$stream = new ProductStream(); |
|
621
|
|
|
$stream->setName($product->stream); |
|
622
|
|
|
$stream->setType(ProductStreamService::STATIC_STREAM); |
|
623
|
|
|
$stream->setSorting(json_encode( |
|
624
|
|
|
[ReleaseDateSorting::class => ['direction' => 'desc']] |
|
625
|
|
|
)); |
|
626
|
|
|
|
|
627
|
|
|
//add attributes |
|
628
|
|
|
$attribute = new \Shopware\Models\Attribute\ProductStream(); |
|
629
|
|
|
$attribute->setProductStream($stream); |
|
630
|
|
|
$attribute->setConnectIsRemote(true); |
|
631
|
|
|
$stream->setAttribute($attribute); |
|
632
|
|
|
|
|
633
|
|
|
$this->manager->persist($attribute); |
|
634
|
|
|
$this->manager->persist($stream); |
|
635
|
|
|
$this->manager->flush(); |
|
636
|
|
|
} |
|
637
|
|
|
|
|
638
|
|
|
return $stream; |
|
639
|
|
|
} |
|
640
|
|
|
|
|
641
|
|
|
/** |
|
642
|
|
|
* @param ProductStream $stream |
|
643
|
|
|
* @param ProductModel $article |
|
644
|
|
|
* @throws \Doctrine\DBAL\DBALException |
|
645
|
|
|
*/ |
|
646
|
|
|
private function addProductToStream(ProductStream $stream, ProductModel $article) |
|
647
|
|
|
{ |
|
648
|
|
|
$conn = $this->manager->getConnection(); |
|
649
|
|
|
$sql = 'INSERT INTO `s_product_streams_selection` (`stream_id`, `article_id`) |
|
650
|
|
|
VALUES (:streamId, :articleId) |
|
651
|
|
|
ON DUPLICATE KEY UPDATE stream_id = :streamId, article_id = :articleId'; |
|
652
|
|
|
$stmt = $conn->prepare($sql); |
|
653
|
|
|
$stmt->execute([':streamId' => $stream->getId(), ':articleId' => $article->getId()]); |
|
654
|
|
|
} |
|
655
|
|
|
|
|
656
|
|
|
/** |
|
657
|
|
|
* Set detail purchase price with plain SQL |
|
658
|
|
|
* Entity usage throws exception when error handlers are disabled |
|
659
|
|
|
* |
|
660
|
|
|
* @param ProductModel $article |
|
661
|
|
|
* @param DetailModel $detail |
|
662
|
|
|
* @param Product $product |
|
663
|
|
|
* @throws \Doctrine\DBAL\DBALException |
|
664
|
|
|
*/ |
|
665
|
|
|
private function setPrice(ProductModel $article, DetailModel $detail, Product $product) |
|
666
|
|
|
{ |
|
667
|
|
|
// set price via plain SQL because shopware throws exception |
|
668
|
|
|
// undefined index: key when error handler is disabled |
|
669
|
|
|
$customerGroup = $this->helper->getDefaultCustomerGroup(); |
|
670
|
|
|
|
|
671
|
|
|
if (!empty($product->priceRanges)) { |
|
672
|
|
|
$this->setPriceRange($article, $detail, $product->priceRanges, $customerGroup); |
|
673
|
|
|
|
|
674
|
|
|
return; |
|
675
|
|
|
} |
|
676
|
|
|
|
|
677
|
|
|
$id = $this->manager->getConnection()->fetchColumn( |
|
678
|
|
|
'SELECT id FROM `s_articles_prices` |
|
679
|
|
|
WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?', |
|
680
|
|
|
[$customerGroup->getKey(), 1, 'beliebig', $article->getId(), $detail->getId()] |
|
681
|
|
|
); |
|
682
|
|
|
|
|
683
|
|
|
// todo@sb: test update prices |
|
684
|
|
|
if ($id > 0) { |
|
685
|
|
|
$this->manager->getConnection()->executeQuery( |
|
686
|
|
|
'UPDATE `s_articles_prices` SET `price` = ?, `baseprice` = ? WHERE `id` = ?', |
|
687
|
|
|
[$product->price, $product->purchasePrice, $id] |
|
688
|
|
|
); |
|
689
|
|
|
} else { |
|
690
|
|
|
$this->manager->getConnection()->executeQuery( |
|
691
|
|
|
'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`, `baseprice`) |
|
692
|
|
|
VALUES (?, 1, "beliebig", ?, ?, ?, ?);', |
|
693
|
|
|
[$customerGroup->getKey(), $article->getId(), $detail->getId(), $product->price, $product->purchasePrice] |
|
694
|
|
|
); |
|
695
|
|
|
} |
|
696
|
|
|
} |
|
697
|
|
|
|
|
698
|
|
|
/** |
|
699
|
|
|
* @param ProductModel $article |
|
700
|
|
|
* @param DetailModel $detail |
|
701
|
|
|
* @param array $priceRanges |
|
702
|
|
|
* @param Group $group |
|
703
|
|
|
* @throws \Doctrine\DBAL\ConnectionException |
|
704
|
|
|
* @throws \Exception |
|
705
|
|
|
*/ |
|
706
|
|
|
private function setPriceRange(ProductModel $article, DetailModel $detail, array $priceRanges, Group $group) |
|
707
|
|
|
{ |
|
708
|
|
|
$this->manager->getConnection()->beginTransaction(); |
|
709
|
|
|
|
|
710
|
|
|
try { |
|
711
|
|
|
// We always delete the prices, |
|
712
|
|
|
// because we can not know which record is update |
|
713
|
|
|
$this->manager->getConnection()->executeQuery( |
|
714
|
|
|
'DELETE FROM `s_articles_prices` WHERE `articleID` = ? AND `articledetailsID` = ?', |
|
715
|
|
|
[$article->getId(), $detail->getId()] |
|
716
|
|
|
); |
|
717
|
|
|
|
|
718
|
|
|
/** @var PriceRange $priceRange */ |
|
719
|
|
|
foreach ($priceRanges as $priceRange) { |
|
720
|
|
|
$priceTo = $priceRange->to == PriceRange::ANY ? 'beliebig' : $priceRange->to; |
|
721
|
|
|
|
|
722
|
|
|
//todo: maybe batch insert if possible? |
|
723
|
|
|
$this->manager->getConnection()->executeQuery( |
|
724
|
|
|
'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`) |
|
725
|
|
|
VALUES (?, ?, ?, ?, ?, ?);', |
|
726
|
|
|
[ |
|
727
|
|
|
$group->getKey(), |
|
728
|
|
|
$priceRange->from, |
|
729
|
|
|
$priceTo, |
|
730
|
|
|
$article->getId(), |
|
731
|
|
|
$detail->getId(), |
|
732
|
|
|
$priceRange->price |
|
733
|
|
|
] |
|
734
|
|
|
); |
|
735
|
|
|
} |
|
736
|
|
|
$this->manager->getConnection()->commit(); |
|
737
|
|
|
} catch (\Exception $e) { |
|
738
|
|
|
$this->manager->getConnection()->rollBack(); |
|
739
|
|
|
throw new \Exception($e->getMessage()); |
|
740
|
|
|
} |
|
741
|
|
|
} |
|
742
|
|
|
|
|
743
|
|
|
/** |
|
744
|
|
|
* Adds translation record for given article |
|
745
|
|
|
* |
|
746
|
|
|
* @param ProductModel $article |
|
747
|
|
|
* @param Product $sdkProduct |
|
748
|
|
|
*/ |
|
749
|
|
|
private function addArticleTranslations(ProductModel $article, Product $sdkProduct) |
|
750
|
|
|
{ |
|
751
|
|
|
/** @var \Shopware\Connect\Struct\Translation $translation */ |
|
752
|
|
|
foreach ($sdkProduct->translations as $key => $translation) { |
|
753
|
|
|
/** @var \Shopware\Models\Shop\Locale $locale */ |
|
754
|
|
|
$locale = $this->getLocaleRepository()->findOneBy(['locale' => LocaleMapper::getShopwareLocale($key)]); |
|
755
|
|
|
/** @var \Shopware\Models\Shop\Shop $shop */ |
|
756
|
|
|
$shop = $this->getShopRepository()->findOneBy(['locale' => $locale]); |
|
757
|
|
|
if (!$shop) { |
|
758
|
|
|
continue; |
|
759
|
|
|
} |
|
760
|
|
|
|
|
761
|
|
|
$this->productTranslationsGateway->addArticleTranslation($translation, $article->getId(), $shop->getId()); |
|
762
|
|
|
} |
|
763
|
|
|
} |
|
764
|
|
|
|
|
765
|
|
|
/** |
|
766
|
|
|
* dsadsa |
|
767
|
|
|
* @return \Shopware\Components\Model\ModelRepository |
|
768
|
|
|
*/ |
|
769
|
|
|
private function getLocaleRepository() |
|
770
|
|
|
{ |
|
771
|
|
|
if (!$this->localeRepository) { |
|
772
|
|
|
$this->localeRepository = $this->manager->getRepository('Shopware\Models\Shop\Locale'); |
|
773
|
|
|
} |
|
774
|
|
|
|
|
775
|
|
|
return $this->localeRepository; |
|
776
|
|
|
} |
|
777
|
|
|
|
|
778
|
|
|
private function getShopRepository() |
|
779
|
|
|
{ |
|
780
|
|
|
if (!$this->shopRepository) { |
|
781
|
|
|
$this->shopRepository = $this->manager->getRepository('Shopware\Models\Shop\Shop'); |
|
782
|
|
|
} |
|
783
|
|
|
|
|
784
|
|
|
return $this->shopRepository; |
|
785
|
|
|
} |
|
786
|
|
|
|
|
787
|
|
|
/** |
|
788
|
|
|
* Delete product or product variant with given shopId and sourceId. |
|
789
|
|
|
* |
|
790
|
|
|
* Only the combination of both identifies a product uniquely. Do NOT |
|
791
|
|
|
* delete products just by their sourceId. |
|
792
|
|
|
* |
|
793
|
|
|
* You might receive delete requests for products, which are not available |
|
794
|
|
|
* in your shop. Just ignore them. |
|
795
|
|
|
* |
|
796
|
|
|
* @param string $shopId |
|
797
|
|
|
* @param string $sourceId |
|
798
|
|
|
* @return void |
|
799
|
|
|
*/ |
|
800
|
|
|
public function delete($shopId, $sourceId) |
|
801
|
|
|
{ |
|
802
|
|
|
$detail = $this->helper->getArticleDetailModelByProduct(new Product([ |
|
803
|
|
|
'shopId' => $shopId, |
|
804
|
|
|
'sourceId' => $sourceId, |
|
805
|
|
|
])); |
|
806
|
|
|
if ($detail === null) { |
|
807
|
|
|
return; |
|
808
|
|
|
} |
|
809
|
|
|
|
|
810
|
|
|
$this->deleteDetail($detail); |
|
811
|
|
|
} |
|
812
|
|
|
|
|
813
|
|
|
/** |
|
814
|
|
|
* @param DetailModel $detailModel |
|
815
|
|
|
*/ |
|
816
|
|
|
private function deleteDetail(DetailModel $detailModel) |
|
817
|
|
|
{ |
|
818
|
|
|
$this->eventManager->notify( |
|
819
|
|
|
'Connect_Merchant_Delete_Product_Before', |
|
820
|
|
|
[ |
|
821
|
|
|
'subject' => $this, |
|
822
|
|
|
'articleDetail' => $detailModel |
|
823
|
|
|
] |
|
824
|
|
|
); |
|
825
|
|
|
|
|
826
|
|
|
$article = $detailModel->getArticle(); |
|
827
|
|
|
// Not sure why, but the Attribute can be NULL |
|
828
|
|
|
$attribute = $this->helper->getConnectAttributeByModel($detailModel); |
|
829
|
|
|
$this->manager->remove($detailModel); |
|
830
|
|
|
|
|
831
|
|
|
if ($attribute) { |
|
832
|
|
|
$this->manager->remove($attribute); |
|
833
|
|
|
} |
|
834
|
|
|
|
|
835
|
|
|
// if removed variant is main variant |
|
836
|
|
|
// find first variant which is not main and mark it |
|
837
|
|
|
if ($detailModel->getKind() === 1) { |
|
838
|
|
|
/** @var \Shopware\Models\Article\Detail $variant */ |
|
839
|
|
|
foreach ($article->getDetails() as $variant) { |
|
840
|
|
|
if ($variant->getId() != $detailModel->getId()) { |
|
841
|
|
|
$variant->setKind(1); |
|
842
|
|
|
$article->setMainDetail($variant); |
|
843
|
|
|
$connectAttribute = $this->helper->getConnectAttributeByModel($variant); |
|
844
|
|
|
if (!$connectAttribute) { |
|
845
|
|
|
continue; |
|
846
|
|
|
} |
|
847
|
|
|
$connectAttribute->setIsMainVariant(true); |
|
848
|
|
|
$this->manager->persist($connectAttribute); |
|
849
|
|
|
$this->manager->persist($article); |
|
850
|
|
|
$this->manager->persist($variant); |
|
851
|
|
|
break; |
|
852
|
|
|
} |
|
853
|
|
|
} |
|
854
|
|
|
} |
|
855
|
|
|
|
|
856
|
|
|
if (count($details = $article->getDetails()) === 1) { |
|
857
|
|
|
$details->clear(); |
|
858
|
|
|
$this->manager->remove($article); |
|
859
|
|
|
} |
|
860
|
|
|
|
|
861
|
|
|
// Do not remove flush. It's needed when remove article, |
|
862
|
|
|
// because duplication of ordernumber. Even with remove before |
|
863
|
|
|
// persist calls mysql throws exception "Duplicate entry" |
|
864
|
|
|
$this->manager->flush(); |
|
865
|
|
|
// always clear entity manager, because $article->getDetails() returns |
|
866
|
|
|
// more than 1 detail, but all of them were removed except main one. |
|
867
|
|
|
$this->manager->clear(); |
|
868
|
|
|
} |
|
869
|
|
|
|
|
870
|
|
|
/** |
|
871
|
|
|
* Get array of update info for the known fields |
|
872
|
|
|
* |
|
873
|
|
|
* @param $model |
|
874
|
|
|
* @param $detail |
|
875
|
|
|
* @param $attribute |
|
876
|
|
|
* @param $product |
|
877
|
|
|
* @return array |
|
878
|
|
|
*/ |
|
879
|
|
|
public function getUpdateFields($model, $detail, $attribute, $product) |
|
880
|
|
|
{ |
|
881
|
|
|
// This also defines the flags of these fields |
|
882
|
|
|
$fields = $this->helper->getUpdateFlags(); |
|
883
|
|
|
$flagsByName = array_flip($fields); |
|
884
|
|
|
|
|
885
|
|
|
$flag = 0; |
|
886
|
|
|
$output = []; |
|
887
|
|
|
foreach ($fields as $key => $field) { |
|
888
|
|
|
// Don't handle the imageInitialImport flag |
|
889
|
|
|
if ($field == 'imageInitialImport') { |
|
890
|
|
|
continue; |
|
891
|
|
|
} |
|
892
|
|
|
|
|
893
|
|
|
// If this is a new product |
|
894
|
|
|
if (!$model->getId() && $field == 'image' && !$this->config->getConfig('importImagesOnFirstImport', false)) { |
|
895
|
|
|
$output[$field] = false; |
|
896
|
|
|
$flag |= $flagsByName['imageInitialImport']; |
|
897
|
|
|
continue; |
|
898
|
|
|
} |
|
899
|
|
|
|
|
900
|
|
|
$updateAllowed = $this->isFieldUpdateAllowed($field, $model, $attribute); |
|
901
|
|
|
$output[$field] = $updateAllowed; |
|
902
|
|
|
if (!$updateAllowed && $this->hasFieldChanged($field, $model, $detail, $product)) { |
|
|
|
|
|
|
903
|
|
|
$flag |= $key; |
|
904
|
|
|
} |
|
905
|
|
|
} |
|
906
|
|
|
|
|
907
|
|
|
return [$output, $flag]; |
|
908
|
|
|
} |
|
909
|
|
|
|
|
910
|
|
|
/** |
|
911
|
|
|
* Determine if a given field has changed |
|
912
|
|
|
* |
|
913
|
|
|
* @param $field |
|
914
|
|
|
* @param ProductModel $model |
|
915
|
|
|
* @param DetailModel $detail |
|
916
|
|
|
* @param Product $product |
|
917
|
|
|
* @return bool |
|
918
|
|
|
*/ |
|
919
|
|
|
public function hasFieldChanged($field, ProductModel $model, DetailModel $detail, Product $product) |
|
920
|
|
|
{ |
|
921
|
|
|
switch ($field) { |
|
922
|
|
|
case 'shortDescription': |
|
923
|
|
|
return $model->getDescription() != $product->shortDescription; |
|
924
|
|
|
case 'longDescription': |
|
925
|
|
|
return $model->getDescriptionLong() != $product->longDescription; |
|
926
|
|
|
case 'additionalDescription': |
|
927
|
|
|
return $detail->getAttribute()->getConnectProductDescription() != $product->additionalDescription; |
|
928
|
|
|
case 'name': |
|
929
|
|
|
return $model->getName() != $product->title; |
|
930
|
|
|
case 'image': |
|
931
|
|
|
return count($model->getImages()) != count($product->images); |
|
932
|
|
|
case 'price': |
|
933
|
|
|
$prices = $detail->getPrices(); |
|
934
|
|
|
if (empty($prices)) { |
|
935
|
|
|
return true; |
|
936
|
|
|
} |
|
937
|
|
|
$price = $prices->first(); |
|
938
|
|
|
if (!$price) { |
|
939
|
|
|
return true; |
|
940
|
|
|
} |
|
941
|
|
|
|
|
942
|
|
|
return $prices->first()->getPrice() != $product->price; |
|
943
|
|
|
} |
|
944
|
|
|
|
|
945
|
|
|
throw new \InvalidArgumentException('Unrecognized field'); |
|
946
|
|
|
} |
|
947
|
|
|
|
|
948
|
|
|
/** |
|
949
|
|
|
* Helper method to determine if a given $fields may/must be updated. |
|
950
|
|
|
* This method will check for the model->id in order to determine, if it is a new entity. Therefore |
|
951
|
|
|
* this method cannot be used after the model in question was already flushed. |
|
952
|
|
|
* |
|
953
|
|
|
* @param $field |
|
954
|
|
|
* @param $model ProductModel |
|
955
|
|
|
* @param $attribute ConnectAttribute |
|
956
|
|
|
* @throws \RuntimeException |
|
957
|
|
|
* @return bool|null |
|
958
|
|
|
*/ |
|
959
|
|
|
public function isFieldUpdateAllowed($field, ProductModel $model, ConnectAttribute $attribute) |
|
960
|
|
|
{ |
|
961
|
|
|
$allowed = [ |
|
962
|
|
|
'ShortDescription', |
|
963
|
|
|
'LongDescription', |
|
964
|
|
|
'AdditionalDescription', |
|
965
|
|
|
'Image', |
|
966
|
|
|
'Price', |
|
967
|
|
|
'Name', |
|
968
|
|
|
]; |
|
969
|
|
|
|
|
970
|
|
|
// Always allow updates for new models |
|
971
|
|
|
if (!$model->getId()) { |
|
972
|
|
|
return true; |
|
973
|
|
|
} |
|
974
|
|
|
|
|
975
|
|
|
$field = ucfirst($field); |
|
976
|
|
|
$attributeGetter = 'getUpdate' . $field; |
|
977
|
|
|
$configName = 'overwriteProduct' . $field; |
|
978
|
|
|
|
|
979
|
|
|
if (!in_array($field, $allowed)) { |
|
980
|
|
|
throw new \RuntimeException("Unknown update field {$field}"); |
|
981
|
|
|
} |
|
982
|
|
|
|
|
983
|
|
|
$attributeValue = $attribute->$attributeGetter(); |
|
984
|
|
|
|
|
985
|
|
|
|
|
986
|
|
|
|
|
987
|
|
|
// If the value is 'null' or 'inherit', the behaviour will be inherited from the global configuration |
|
988
|
|
|
// Once we have a supplier based configuration, we need to take it into account here |
|
989
|
|
|
if ($attributeValue == null || $attributeValue == 'inherit') { |
|
990
|
|
|
return $this->config->getConfig($configName, true); |
|
991
|
|
|
} |
|
992
|
|
|
|
|
993
|
|
|
return $attributeValue == 'overwrite'; |
|
994
|
|
|
} |
|
995
|
|
|
|
|
996
|
|
|
/** |
|
997
|
|
|
* Read product attributes mapping and set to shopware attribute model |
|
998
|
|
|
* |
|
999
|
|
|
* @param AttributeModel $detailAttribute |
|
1000
|
|
|
* @param Product $product |
|
1001
|
|
|
* @return AttributeModel |
|
1002
|
|
|
*/ |
|
1003
|
|
|
private function applyMarketplaceAttributes(AttributeModel $detailAttribute, Product $product) |
|
1004
|
|
|
{ |
|
1005
|
|
|
$detailAttribute->setConnectReference($product->sourceId); |
|
1006
|
|
|
$detailAttribute->setConnectArticleShipping($product->shipping); |
|
1007
|
|
|
//todo@sb: check if connectAttribute matches position of the marketplace attribute |
|
1008
|
|
|
array_walk($product->attributes, function ($value, $key) use ($detailAttribute) { |
|
1009
|
|
|
$shopwareAttribute = $this->marketplaceGateway->findShopwareMappingFor($key); |
|
|
|
|
|
|
1010
|
|
|
if (strlen($shopwareAttribute) > 0) { |
|
1011
|
|
|
$setter = 'set' . ucfirst($shopwareAttribute); |
|
1012
|
|
|
$detailAttribute->$setter($value); |
|
1013
|
|
|
} |
|
1014
|
|
|
}); |
|
1015
|
|
|
|
|
1016
|
|
|
return $detailAttribute; |
|
1017
|
|
|
} |
|
1018
|
|
|
|
|
1019
|
|
|
/** |
|
1020
|
|
|
* @param $vendor |
|
1021
|
|
|
* @return Supplier |
|
1022
|
|
|
*/ |
|
1023
|
|
|
private function createSupplier($vendor) |
|
1024
|
|
|
{ |
|
1025
|
|
|
$supplier = new Supplier(); |
|
1026
|
|
|
|
|
1027
|
|
|
if (is_array($vendor)) { |
|
1028
|
|
|
$supplier->setName($vendor['name']); |
|
1029
|
|
|
$supplier->setDescription($vendor['description']); |
|
1030
|
|
|
if (array_key_exists('url', $vendor) && $vendor['url']) { |
|
1031
|
|
|
$supplier->setLink($vendor['url']); |
|
1032
|
|
|
} |
|
1033
|
|
|
|
|
1034
|
|
|
$supplier->setMetaTitle($vendor['page_title']); |
|
1035
|
|
|
|
|
1036
|
|
|
if (array_key_exists('logo_url', $vendor) && $vendor['logo_url']) { |
|
1037
|
|
|
$this->imageImport->importImageForSupplier($vendor['logo_url'], $supplier); |
|
1038
|
|
|
} |
|
1039
|
|
|
} else { |
|
1040
|
|
|
$supplier->setName($vendor); |
|
1041
|
|
|
} |
|
1042
|
|
|
|
|
1043
|
|
|
//sets supplier attributes |
|
1044
|
|
|
$attr = new \Shopware\Models\Attribute\ArticleSupplier(); |
|
1045
|
|
|
$attr->setConnectIsRemote(true); |
|
1046
|
|
|
|
|
1047
|
|
|
$supplier->setAttribute($attr); |
|
1048
|
|
|
|
|
1049
|
|
|
return $supplier; |
|
1050
|
|
|
} |
|
1051
|
|
|
|
|
1052
|
|
|
/** |
|
1053
|
|
|
* Set detail purchase price with plain SQL |
|
1054
|
|
|
* Entity usage throws exception when error handlers are disabled |
|
1055
|
|
|
* |
|
1056
|
|
|
* @param DetailModel $detail |
|
1057
|
|
|
* @param float $purchasePrice |
|
1058
|
|
|
* @param Group $defaultGroup |
|
1059
|
|
|
* @throws \Doctrine\DBAL\DBALException |
|
1060
|
|
|
*/ |
|
1061
|
|
|
private function setPurchasePrice(DetailModel $detail, $purchasePrice, Group $defaultGroup) |
|
1062
|
|
|
{ |
|
1063
|
|
|
if (method_exists($detail, 'setPurchasePrice')) { |
|
1064
|
|
|
$this->manager->getConnection()->executeQuery( |
|
1065
|
|
|
'UPDATE `s_articles_details` SET `purchaseprice` = ? WHERE `id` = ?', |
|
1066
|
|
|
[$purchasePrice, $detail->getId()] |
|
1067
|
|
|
); |
|
1068
|
|
|
} else { |
|
1069
|
|
|
$id = $this->manager->getConnection()->fetchColumn( |
|
1070
|
|
|
'SELECT id FROM `s_articles_prices` |
|
1071
|
|
|
WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?', |
|
1072
|
|
|
[$defaultGroup->getKey(), 1, 'beliebig', $detail->getArticleId(), $detail->getId()] |
|
1073
|
|
|
); |
|
1074
|
|
|
|
|
1075
|
|
|
if ($id > 0) { |
|
1076
|
|
|
$this->manager->getConnection()->executeQuery( |
|
1077
|
|
|
'UPDATE `s_articles_prices` SET `baseprice` = ? WHERE `id` = ?', |
|
1078
|
|
|
[$purchasePrice, $id] |
|
1079
|
|
|
); |
|
1080
|
|
|
} else { |
|
1081
|
|
|
$this->manager->getConnection()->executeQuery( |
|
1082
|
|
|
'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `baseprice`) |
|
1083
|
|
|
VALUES (?, 1, "beliebig", ?, ?, ?);', |
|
1084
|
|
|
[$defaultGroup->getKey(), $detail->getArticleId(), $detail->getId(), $purchasePrice] |
|
1085
|
|
|
); |
|
1086
|
|
|
} |
|
1087
|
|
|
} |
|
1088
|
|
|
} |
|
1089
|
|
|
|
|
1090
|
|
|
public function update($shopId, $sourceId, ProductUpdate $product) |
|
1091
|
|
|
{ |
|
1092
|
|
|
// find article detail id |
|
1093
|
|
|
$articleDetailId = $this->manager->getConnection()->fetchColumn( |
|
1094
|
|
|
'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?', |
|
1095
|
|
|
[$sourceId, $shopId] |
|
1096
|
|
|
); |
|
1097
|
|
|
|
|
1098
|
|
|
$this->eventManager->notify( |
|
1099
|
|
|
'Connect_Merchant_Update_GeneralProductInformation', |
|
1100
|
|
|
[ |
|
1101
|
|
|
'subject' => $this, |
|
1102
|
|
|
'shopId' => $shopId, |
|
1103
|
|
|
'sourceId' => $sourceId, |
|
1104
|
|
|
'articleDetailId' => $articleDetailId |
|
1105
|
|
|
] |
|
1106
|
|
|
); |
|
1107
|
|
|
|
|
1108
|
|
|
// update purchasePriceHash, offerValidUntil and purchasePrice in connect attribute |
|
1109
|
|
|
$this->manager->getConnection()->executeUpdate( |
|
1110
|
|
|
'UPDATE s_plugin_connect_items SET purchase_price_hash = ?, offer_valid_until = ?, purchase_price = ? |
|
1111
|
|
|
WHERE source_id = ? AND shop_id = ?', |
|
1112
|
|
|
[ |
|
1113
|
|
|
$product->purchasePriceHash, |
|
1114
|
|
|
$product->offerValidUntil, |
|
1115
|
|
|
$product->purchasePrice, |
|
1116
|
|
|
$sourceId, |
|
1117
|
|
|
$shopId, |
|
1118
|
|
|
] |
|
1119
|
|
|
); |
|
1120
|
|
|
|
|
1121
|
|
|
// update stock in article detail |
|
1122
|
|
|
// update prices |
|
1123
|
|
|
// if purchase price is stored in article detail |
|
1124
|
|
|
// update it together with stock |
|
1125
|
|
|
// since shopware 5.2 |
|
1126
|
|
|
if (method_exists('Shopware\Models\Article\Detail', 'getPurchasePrice')) { |
|
1127
|
|
|
$this->manager->getConnection()->executeUpdate( |
|
1128
|
|
|
'UPDATE s_articles_details SET instock = ?, purchaseprice = ? WHERE id = ?', |
|
1129
|
|
|
[$product->availability, $product->purchasePrice, $articleDetailId] |
|
1130
|
|
|
); |
|
1131
|
|
|
} else { |
|
1132
|
|
|
$this->manager->getConnection()->executeUpdate( |
|
1133
|
|
|
'UPDATE s_articles_details SET instock = ? WHERE id = ?', |
|
1134
|
|
|
[$product->availability, $articleDetailId] |
|
1135
|
|
|
); |
|
1136
|
|
|
} |
|
1137
|
|
|
$this->manager->getConnection()->executeUpdate( |
|
1138
|
|
|
"UPDATE s_articles_prices SET price = ?, baseprice = ? WHERE articledetailsID = ? AND pricegroup = 'EK'", |
|
1139
|
|
|
[$product->price, $product->purchasePrice, $articleDetailId] |
|
1140
|
|
|
); |
|
1141
|
|
|
} |
|
1142
|
|
|
|
|
1143
|
|
|
public function changeAvailability($shopId, $sourceId, $availability) |
|
1144
|
|
|
{ |
|
1145
|
|
|
// find article detail id |
|
1146
|
|
|
$articleDetailId = $this->manager->getConnection()->fetchColumn( |
|
1147
|
|
|
'SELECT article_detail_id FROM s_plugin_connect_items WHERE source_id = ? AND shop_id = ?', |
|
1148
|
|
|
[$sourceId, $shopId] |
|
1149
|
|
|
); |
|
1150
|
|
|
|
|
1151
|
|
|
$this->eventManager->notify( |
|
1152
|
|
|
'Connect_Merchant_Update_GeneralProductInformation', |
|
1153
|
|
|
[ |
|
1154
|
|
|
'subject' => $this, |
|
1155
|
|
|
'shopId' => $shopId, |
|
1156
|
|
|
'sourceId' => $sourceId, |
|
1157
|
|
|
'articleDetailId' => $articleDetailId |
|
1158
|
|
|
] |
|
1159
|
|
|
); |
|
1160
|
|
|
|
|
1161
|
|
|
// update stock in article detail |
|
1162
|
|
|
$this->manager->getConnection()->executeUpdate( |
|
1163
|
|
|
'UPDATE s_articles_details SET instock = ? WHERE id = ?', |
|
1164
|
|
|
[$availability, $articleDetailId] |
|
1165
|
|
|
); |
|
1166
|
|
|
} |
|
1167
|
|
|
|
|
1168
|
|
|
/** |
|
1169
|
|
|
* @inheritDoc |
|
1170
|
|
|
*/ |
|
1171
|
|
|
public function makeMainVariant($shopId, $sourceId, $groupId) |
|
1172
|
|
|
{ |
|
1173
|
|
|
//find article detail which should be selected as main one |
|
1174
|
|
|
$newMainDetail = $this->helper->getConnectArticleDetailModel($sourceId, $shopId); |
|
1175
|
|
|
if (!$newMainDetail) { |
|
1176
|
|
|
return; |
|
1177
|
|
|
} |
|
1178
|
|
|
|
|
1179
|
|
|
/** @var \Shopware\Models\Article\Article $article */ |
|
1180
|
|
|
$article = $newMainDetail->getArticle(); |
|
1181
|
|
|
|
|
1182
|
|
|
$this->eventManager->notify( |
|
1183
|
|
|
'Connect_Merchant_Update_ProductMainVariant_Before', |
|
1184
|
|
|
[ |
|
1185
|
|
|
'subject' => $this, |
|
1186
|
|
|
'shopId' => $shopId, |
|
1187
|
|
|
'sourceId' => $sourceId, |
|
1188
|
|
|
'articleId' => $article->getId(), |
|
1189
|
|
|
'articleDetailId' => $newMainDetail->getId() |
|
1190
|
|
|
] |
|
1191
|
|
|
); |
|
1192
|
|
|
|
|
1193
|
|
|
// replace current main detail with new one |
|
1194
|
|
|
$currentMainDetail = $article->getMainDetail(); |
|
1195
|
|
|
$currentMainDetail->setKind(2); |
|
1196
|
|
|
$newMainDetail->setKind(1); |
|
1197
|
|
|
$article->setMainDetail($newMainDetail); |
|
1198
|
|
|
|
|
1199
|
|
|
$this->manager->persist($newMainDetail); |
|
1200
|
|
|
$this->manager->persist($currentMainDetail); |
|
1201
|
|
|
$this->manager->persist($article); |
|
1202
|
|
|
$this->manager->flush(); |
|
1203
|
|
|
} |
|
1204
|
|
|
|
|
1205
|
|
|
/** |
|
1206
|
|
|
* @param Product $product |
|
1207
|
|
|
* @param ProductModel $model |
|
1208
|
|
|
*/ |
|
1209
|
|
|
private function saveVat(Product $product, $model) |
|
1210
|
|
|
{ |
|
1211
|
|
|
if ($product->vat !== null) { |
|
1212
|
|
|
$repo = $this->manager->getRepository('Shopware\Models\Tax\Tax'); |
|
1213
|
|
|
$taxRate = round($product->vat * 100, 2); |
|
1214
|
|
|
/** @var \Shopware\Models\Tax\Tax $tax */ |
|
1215
|
|
|
$tax = $repo->findOneBy(['tax' => $taxRate]); |
|
1216
|
|
|
if (!$tax) { |
|
1217
|
|
|
$tax = new Tax(); |
|
1218
|
|
|
$tax->setTax($taxRate); |
|
1219
|
|
|
//this is to get rid of zeroes behind the decimal point |
|
1220
|
|
|
$name = strval(round($taxRate, 2)) . '%'; |
|
1221
|
|
|
$tax->setName($name); |
|
1222
|
|
|
$this->manager->persist($tax); |
|
1223
|
|
|
} |
|
1224
|
|
|
$model->setTax($tax); |
|
1225
|
|
|
} |
|
1226
|
|
|
} |
|
1227
|
|
|
} |
|
1228
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.jsonfile (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.jsonto be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
requireorrequire-devsection?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceofchecks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.