Skip to content

Commit 02a7cd6

Browse files
MM-25: Add logic for product prices and inventory synchronization (#50)
- Disabled field 'code' on editing for existing SalesChannel, to prevent issues with an incorrect mapping between salesChannel and website - Fixed issue with price sync - Fixed issue with remove remote data on integration deactivation - Fixed issue with updating SKU - Fixed issue with updating price when using default price
1 parent c7c231b commit 02a7cd6

73 files changed

Lines changed: 3140 additions & 906 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package/marello-magento2-bundle/src/Marello/Bundle/Magento2Bundle/Async/ClearInternalDataForDisabledIntegrationMessage.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
namespace Marello\Bundle\Magento2Bundle\Async;
44

5-
use Oro\Component\MessageQueue\Client\Message;
65
use Oro\Component\MessageQueue\Transport\MessageInterface;
76
use Oro\Component\MessageQueue\Util\JSON;
87

9-
class ClearInternalDataForDisabledIntegrationMessage extends Message implements IntegrationAwareMessageInterface
8+
class ClearInternalDataForDisabledIntegrationMessage implements IntegrationAwareMessageInterface
109
{
1110
public const INTEGRATION_ID = 'integration_id';
1211

package/marello-magento2-bundle/src/Marello/Bundle/Magento2Bundle/Async/RemoveRemoteDataForDisabledIntegrationMessage.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
namespace Marello\Bundle\Magento2Bundle\Async;
44

5-
use Oro\Component\MessageQueue\Client\Message;
65
use Oro\Component\MessageQueue\Transport\MessageInterface;
76
use Oro\Component\MessageQueue\Util\JSON;
87

9-
class RemoveRemoteDataForDisabledIntegrationMessage extends Message implements IntegrationAwareMessageInterface
8+
class RemoveRemoteDataForDisabledIntegrationMessage implements IntegrationAwareMessageInterface
109
{
1110
public const INTEGRATION_ID = 'integration_id';
1211
public const IS_REMOVED = 'is_removed';

package/marello-magento2-bundle/src/Marello/Bundle/Magento2Bundle/Async/RemoveRemoteProductForDisableIntegrationMessage.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
use Marello\Bundle\Magento2Bundle\Model\Magento2TransportSettings;
66
use Oro\Component\MessageQueue\Transport\MessageInterface;
77
use Oro\Component\MessageQueue\Util\JSON;
8-
use Oro\Component\MessageQueue\Client\Message;
98

10-
class RemoveRemoteProductForDisableIntegrationMessage extends Message implements IntegrationAwareMessageInterface
9+
class RemoveRemoteProductForDisableIntegrationMessage implements IntegrationAwareMessageInterface
1110
{
1211
public const INTEGRATION_ID = 'integration_id';
1312
public const IS_REMOVED = 'is_removed';

package/marello-magento2-bundle/src/Marello/Bundle/Magento2Bundle/Async/RemoveRemoteProductForDisableIntegrationProcessor.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ public function process(MessageInterface $message, SessionInterface $session)
6363
$result = $this
6464
->jobRunner
6565
->runDelayed($wrappedMessage->getJobId(), function () use ($wrappedMessage) {
66-
return $this->processRemovingProduct($wrappedMessage);
66+
$this->processRemovingProduct($wrappedMessage);
67+
68+
return true;
6769
});
6870
} catch (\Throwable $exception) {
6971
$context['exception'] = $exception;
@@ -89,8 +91,9 @@ public function process(MessageInterface $message, SessionInterface $session)
8991

9092
/**
9193
* @param RemoveRemoteProductForDisableIntegrationMessage $message
92-
* @throws RuntimeException
9394
* @throws RestException
95+
* @throws RuntimeException
96+
* @throws \Doctrine\ORM\NonUniqueResultException
9497
*/
9598
protected function processRemovingProduct(RemoveRemoteProductForDisableIntegrationMessage $message): void
9699
{
@@ -106,14 +109,12 @@ protected function processRemovingProduct(RemoveRemoteProductForDisableIntegrati
106109
);
107110

108111
if ($product === null) {
109-
return true;
112+
return;
110113
}
111114

112115
$productManager->remove($product);
113116
$productManager->flush();
114117
}
115-
116-
return true;
117118
}
118119

119120
/**

package/marello-magento2-bundle/src/Marello/Bundle/Magento2Bundle/Async/SalesChannelStateChangedMessage.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
namespace Marello\Bundle\Magento2Bundle\Async;
44

5-
use Oro\Component\MessageQueue\Client\Message;
65
use Oro\Component\MessageQueue\Transport\MessageInterface;
76
use Oro\Component\MessageQueue\Util\JSON;
87

9-
class SalesChannelStateChangedMessage extends Message implements IntegrationAwareMessageInterface
8+
class SalesChannelStateChangedMessage implements IntegrationAwareMessageInterface
109
{
1110
public const INTEGRATION_ID = 'integration_id';
1211
public const SALES_CHANNEL_ID_KEY = 'sales_channel_id';
@@ -129,7 +128,7 @@ public static function createFromMessage(MessageInterface $message): SalesChanne
129128
$messageData = JSON::decode($message->getBody());
130129

131130
$message = new SalesChannelStateChangedMessage(
132-
$message[self::INTEGRATION_ID],
131+
$messageData[self::INTEGRATION_ID],
133132
$messageData[self::SALES_CHANNEL_ID_KEY],
134133
$messageData[self::IS_ACTIVE_KEY],
135134
$messageData[self::CREATED_PRODUCT_IDS],

package/marello-magento2-bundle/src/Marello/Bundle/Magento2Bundle/Async/SalesChannelStateChangedProcessor.php

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
namespace Marello\Bundle\Magento2Bundle\Async;
44

55
use Doctrine\DBAL\Exception\RetryableException;
6-
use Marello\Bundle\Magento2Bundle\Batch\Step\ExclusiveItemStep;
7-
use Marello\Bundle\Magento2Bundle\Integration\Connector\ProductConnector;
6+
use Marello\Bundle\Magento2Bundle\Entity\Website;
7+
use Marello\Bundle\Magento2Bundle\Scheduler\ProductSchedulerInterface;
8+
use Marello\Bundle\SalesBundle\Entity\SalesChannel;
89
use Oro\Bundle\IntegrationBundle\Entity\Channel as Integration;
9-
use Oro\Component\DependencyInjection\ServiceLink;
1010
use Oro\Component\MessageQueue\Client\TopicSubscriberInterface;
1111
use Oro\Component\MessageQueue\Consumption\MessageProcessorInterface;
1212
use Oro\Component\MessageQueue\Job\JobRunner;
@@ -30,22 +30,22 @@ class SalesChannelStateChangedProcessor implements
3030
/** @var ManagerRegistry */
3131
protected $managerRegistry;
3232

33-
/** @var ServiceLink */
34-
protected $syncScheduler;
33+
/** @var ProductSchedulerInterface */
34+
protected $productScheduler;
3535

3636
/**
3737
* @param JobRunner $jobRunner
3838
* @param ManagerRegistry $managerRegistry
39-
* @param ServiceLink $syncScheduler
39+
* @param ProductSchedulerInterface $productScheduler
4040
*/
4141
public function __construct(
4242
JobRunner $jobRunner,
4343
ManagerRegistry $managerRegistry,
44-
ServiceLink $syncScheduler
44+
ProductSchedulerInterface $productScheduler
4545
) {
4646
$this->jobRunner = $jobRunner;
4747
$this->managerRegistry = $managerRegistry;
48-
$this->syncScheduler = $syncScheduler;
48+
$this->productScheduler = $productScheduler;
4949
}
5050

5151
/**
@@ -117,40 +117,40 @@ public static function getSubscribedTopics()
117117
*/
118118
protected function processChangedProducts(SalesChannelStateChangedMessage $message): void
119119
{
120-
foreach ($message->getRemovedProductIds() as $productId) {
121-
$this->syncScheduler->getService()->schedule(
122-
$message->getIntegrationId(),
123-
ProductConnector::TYPE,
124-
[
125-
'ids' => [$productId],
126-
ExclusiveItemStep::OPTION_KEY_EXCLUSIVE_STEP_NAME =>
127-
ProductConnector::EXPORT_STEP_DELETE_ON_CHANNEL
128-
]
129-
);
120+
$this->productScheduler->scheduleDeleteProductsOnChannel(
121+
$message->getIntegrationId(),
122+
$message->getRemovedProductIds()
123+
);
124+
125+
$salesChannel = $this->getSalesChannel($message);
126+
if (null === $salesChannel) {
127+
return;
130128
}
131129

132-
foreach ($message->getCreatedProductIds() as $productId) {
133-
$this->syncScheduler->getService()->schedule(
134-
$message->getIntegrationId(),
135-
ProductConnector::TYPE,
136-
[
137-
'ids' => [$productId],
138-
ExclusiveItemStep::OPTION_KEY_EXCLUSIVE_STEP_NAME =>
139-
ProductConnector::EXPORT_STEP_CREATE
140-
]
141-
);
142-
}
130+
$website = $salesChannel->getMagento2Websites()->first();
131+
132+
$this->productScheduler->scheduleCreateProductsOnChannel(
133+
$message->getIntegrationId(),
134+
$message->getCreatedProductIds()
135+
);
143136

144137
foreach ($message->getUpdatedProductIds() as $productId) {
145-
$this->syncScheduler->getService()->schedule(
138+
$this->productScheduler->scheduleUpdateProductOnChannel(
146139
$message->getIntegrationId(),
147-
ProductConnector::TYPE,
148-
[
149-
'ids' => [$productId],
150-
ExclusiveItemStep::OPTION_KEY_EXCLUSIVE_STEP_NAME =>
151-
ProductConnector::EXPORT_STEP_UPDATE
152-
]
140+
$productId
153141
);
142+
143+
/**
144+
* We should put website scope data for product in case if website is activated\re-activated,
145+
* because when product removes from website all product website scope data removes alongside to it.
146+
*/
147+
if ($website instanceof Website && $message->isActive()) {
148+
$this->productScheduler->scheduleUpdateWebsiteScopeDataProductOnChannel(
149+
$message->getIntegrationId(),
150+
$website->getId(),
151+
$productId
152+
);
153+
}
154154
}
155155
}
156156

@@ -167,4 +167,18 @@ protected function isIntegrationApplicable(SalesChannelStateChangedMessage $mess
167167

168168
return $integration && $integration->isEnabled();
169169
}
170+
171+
/**
172+
* @param SalesChannelStateChangedMessage $message
173+
* @return SalesChannel|null
174+
*/
175+
protected function getSalesChannel(SalesChannelStateChangedMessage $message): ?SalesChannel
176+
{
177+
/** @var SalesChannel $salesChannel */
178+
$salesChannel = $this->managerRegistry
179+
->getRepository(SalesChannel::class)
180+
->find($message->getSalesChannelId());
181+
182+
return $salesChannel;
183+
}
170184
}

package/marello-magento2-bundle/src/Marello/Bundle/Magento2Bundle/Async/SalesChannelsRemovedMessage.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
namespace Marello\Bundle\Magento2Bundle\Async;
44

5-
use Oro\Component\MessageQueue\Client\Message;
65
use Oro\Component\MessageQueue\Transport\MessageInterface;
76
use Oro\Component\MessageQueue\Util\JSON;
87

9-
class SalesChannelsRemovedMessage extends Message implements IntegrationAwareMessageInterface
8+
class SalesChannelsRemovedMessage implements IntegrationAwareMessageInterface
109
{
1110
public const INTEGRATION_ID = 'integration_id';
1211
public const UPDATED_PRODUCT_IDS_KEY = 'updated_product_ids';

package/marello-magento2-bundle/src/Marello/Bundle/Magento2Bundle/Async/SalesChannelsRemovedProcessor.php

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
namespace Marello\Bundle\Magento2Bundle\Async;
44

55
use Doctrine\DBAL\Exception\RetryableException;
6-
use Marello\Bundle\Magento2Bundle\Batch\Step\ExclusiveItemStep;
7-
use Marello\Bundle\Magento2Bundle\Integration\Connector\ProductConnector;
6+
use Marello\Bundle\Magento2Bundle\Scheduler\ProductSchedulerInterface;
87
use Oro\Bundle\IntegrationBundle\Entity\Channel as Integration;
9-
use Oro\Component\DependencyInjection\ServiceLink;
108
use Oro\Component\MessageQueue\Client\TopicSubscriberInterface;
119
use Oro\Component\MessageQueue\Consumption\MessageProcessorInterface;
1210
use Oro\Component\MessageQueue\Job\JobRunner;
@@ -29,22 +27,22 @@ class SalesChannelsRemovedProcessor implements
2927
/** @var ManagerRegistry */
3028
protected $managerRegistry;
3129

32-
/** @var ServiceLink */
33-
protected $syncScheduler;
30+
/** @var ProductSchedulerInterface */
31+
protected $productScheduler;
3432

3533
/**
3634
* @param JobRunner $jobRunner
3735
* @param ManagerRegistry $managerRegistry
38-
* @param ServiceLink $syncScheduler
36+
* @param ProductSchedulerInterface $productScheduler
3937
*/
4038
public function __construct(
4139
JobRunner $jobRunner,
4240
ManagerRegistry $managerRegistry,
43-
ServiceLink $syncScheduler
41+
ProductSchedulerInterface $productScheduler
4442
) {
4543
$this->jobRunner = $jobRunner;
4644
$this->managerRegistry = $managerRegistry;
47-
$this->syncScheduler = $syncScheduler;
45+
$this->productScheduler = $productScheduler;
4846
}
4947

5048
/**
@@ -108,29 +106,15 @@ function (JobRunner $jobRunner) use ($wrappedMessage) {
108106
*/
109107
protected function processChangedProducts(SalesChannelsRemovedMessage $message): void
110108
{
111-
foreach ($message->getRemovedProductIds() as $productId) {
112-
$this->syncScheduler->getService()->schedule(
113-
$message->getIntegrationId(),
114-
ProductConnector::TYPE,
115-
[
116-
'ids' => [$productId],
117-
ExclusiveItemStep::OPTION_KEY_EXCLUSIVE_STEP_NAME =>
118-
ProductConnector::EXPORT_STEP_DELETE_ON_CHANNEL
119-
]
120-
);
121-
}
122-
123-
foreach ($message->getUpdatedProductIds() as $productId) {
124-
$this->syncScheduler->getService()->schedule(
125-
$message->getIntegrationId(),
126-
ProductConnector::TYPE,
127-
[
128-
'ids' => [$productId],
129-
ExclusiveItemStep::OPTION_KEY_EXCLUSIVE_STEP_NAME =>
130-
ProductConnector::EXPORT_STEP_UPDATE
131-
]
132-
);
133-
}
109+
$this->productScheduler->scheduleDeleteProductsOnChannel(
110+
$message->getIntegrationId(),
111+
$message->getRemovedProductIds()
112+
);
113+
114+
$this->productScheduler->scheduleUpdateProductsOnChannel(
115+
$message->getIntegrationId(),
116+
$message->getRemovedProductIds()
117+
);
134118
}
135119

136120
/**
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Marello\Bundle\Magento2Bundle\Batch\Step;
4+
5+
use Akeneo\Bundle\BatchBundle\Entity\StepExecution;
6+
use Oro\Bundle\BatchBundle\Step\ItemStep;
7+
8+
/**
9+
* This logic provides functionality to use simple routing within step logic,
10+
* to collect all steps that can be use in scope of one connector, but run only required.
11+
*/
12+
class ActionItemStep extends ItemStep
13+
{
14+
/** @var string */
15+
public const OPTION_KEY_ACTION_NAME = 'actionName';
16+
17+
/** @var string */
18+
protected $actionName;
19+
20+
/**
21+
* @param string $stepName
22+
*/
23+
public function setActionName(string $stepName): void
24+
{
25+
$this->actionName = $stepName;
26+
}
27+
28+
/**
29+
* {@inheritDoc}
30+
*/
31+
public function doExecute(StepExecution $stepExecution)
32+
{
33+
$actionName = $stepExecution
34+
->getJobExecution()
35+
->getExecutionContext()
36+
->get(self::OPTION_KEY_ACTION_NAME);
37+
38+
if ($this->actionName === $actionName) {
39+
parent::doExecute($stepExecution);
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)