commerce_amws-8.x-1.x-dev/modules/shipping/src/EntitySync/FeedPostOrderFulfillmentDataClient.php
modules/shipping/src/EntitySync/FeedPostOrderFulfillmentDataClient.php
<?php
namespace Drupal\commerce_amws_shipping\EntitySync;
use Drupal\commerce_amws\Entity\StoreInterface;
use Drupal\commerce_amws\MachineName\Field\Operation as AmwsOperationField;
use Drupal\commerce_amws_feed\Content\XmlBuilderInterface as FeedContentBuilderInterface;
use Drupal\entity_sync\Client\ClientInterface as EntitySyncClientInterface;
use Drupal\entity_sync\Entity\OperationInterface;
use Drupal\entity_sync\MachineName\Field\Operation as OperationField;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use SellingPartnerApi\Api\FeedsApi;
use SellingPartnerApi\Document;
use SellingPartnerApi\FeedType;
use SellingPartnerApi\Model\Feeds\CreateFeedDocumentSpecification;
use SellingPartnerApi\Model\Feeds\CreateFeedSpecification;
use SellingPartnerApi\Model\Feeds\Feed;
/**
* Adapter that bridges Entity Sync to the Amazon MWS SPI-API client.
*
* phpcs:disable
* @I Extract common functionality to a base feed client
* type : task
* priority : normal
* labels : modularity
* phpcs:enable
*/
class FeedPostOrderFulfillmentDataClient implements EntitySyncClientInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The XML feed content builder.
*
* @var \Drupal\commerce_amws_feed\Content\XmlBuilderInterface
*/
protected $feedContentBuilder;
/**
* The Feeds API.
*
* @var \SellingPartnerApi\Api\FeedsApi
*/
protected $feedsApi;
/**
* The Amazon MWS store that the feed will be submitted to.
*
* @var \Drupal\commerce_amws\Entity\StoreInterface
*/
protected $amwsStore;
/**
* Constructs a new FeedPostOrderFulfilmentDataClient object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\commerce_amws_feed\Content\XmlBuilderInterface $feed_content_builder
* The XML feed content builder.
* @param \SellingPartnerApi\Api\FeedsApi $feeds_api
* The Feeds API client.
* @param \Drupal\commerce_amws\Entity\StoreInterface $amws_store
* The Amazon MWS store that the feed will be submitted to.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
FeedContentBuilderInterface $feed_content_builder,
FeedsApi $feeds_api,
StoreInterface $amws_store
) {
$this->entityTypeManager = $entity_type_manager;
$this->feedContentBuilder = $feed_content_builder;
$this->feedsApi = $feeds_api;
$this->amwsStore = $amws_store;
}
/**
* {@inheritdoc}
*/
public function importList(array $filters = [], array $options = []) {
throw new \Exception(
'Importing a list of Amazon MWS shipments is not supported.'
);
}
/**
* {@inheritdoc}
*/
public function importEntity($id) {
throw new \Exception('Importing an Amazon MWS shipment is not supported.');
}
/**
* {@inheritdoc}
*/
public function create(array $fields) {
$feed_type = FeedType::POST_ORDER_FULFILLMENT_DATA;
$content = $this->feedContentBuilder->build(
$this->buildContent($fields)
);
// First, we create the document that will be submitted with the feed, and
// upload its content.
$document_response = $this->feedsApi->createFeedDocument(
new CreateFeedDocumentSpecification(
['content_type' => $feed_type['contentType']]
)
);
$feed_document_id = $document_response->getFeedDocumentId();
$document = new Document($document_response, $feed_type);
$document->upload($content);
// Then, we create and submit the feed.
$feed_response = $this->feedsApi->createFeed(
new CreateFeedSpecification(
[
'feed_type' => $feed_type['name'],
'marketplace_ids' => [$this->amwsStore->getMarketplaceId()],
'input_feed_document_id' => $feed_document_id,
]
)
);
$feed_id = $feed_response->getFeedId();
// We store the feed and document IDs in the response so that even
// subscribers can store them in the operation entity fields.
$response = new \stdClass();
$response->feedId = $feed_id;
$response->feedDocumentId = $feed_document_id;
return $response;
}
/**
* {@inheritdoc}
*/
public function update($id, array $fields) {
throw new \Exception(
'Updating an already submitted Feed for an Amazon MWS shipment is not supported. A new Feed should be submitted instead.'
);
}
/**
* Fetches and responds to updates of the operation in the remote system.
*
* @param \Drupal\entity_sync\Entity\OperationInterface $operation
* The operation.
* phpcs:disable
* @I Considering creating a generic interface method in Entity Sync
* type : task
* priority : normal
* labels : modularity
*
* @I Design might be more consistent if we only fetch and format results here
* type : task
* priority : normal
* labels : code-structure
* phpcs:enable
*/
public function fetchFeedUpdates(OperationInterface $operation) {
$remote_id_field = $operation->get(OperationField::REMOTE_ID);
// If for whatever reason do no have the feed ID we cannot get the feed
// updates anymore. Move the operation to the disconnected state so that the
// update manager does not keep picking it up as pending updates.
if ($remote_id_field->isEmpty()) {
$operation->get(OperationField::STATE)
->first()
->applyTransitionById('disconnect');
$this->entityTypeManager
->getStorage('entity_sync_operation')
->save($operation);
return;
}
$feed = $this->feedsApi->getFeed(
$remote_id_field->first()->getValue()['value']
);
$remote_state = $feed->getProcessingStatus();
$transition_id = $this->getOperationTransitionFromRemoteState(
$operation,
$remote_state
);
// If we are on the same state i.e. processing has not progressed yet on the
// remote system there's no updates to make to the operation.
if (!$transition_id) {
return;
}
$operation->set(
AmwsOperationField::RESULT_FEED_DOCUMENT_ID,
$feed->getResultFeedDocumentId()
);
$operation->set(OperationField::REMOTE_STATE, $remote_state);
$operation->get(OperationField::STATE)
->first()
->applyTransitionById($transition_id);
$this->entityTypeManager
->getStorage('entity_sync_operation')
->save($operation);
}
/**
* Builds the content for the feed document.
*
* @param array $fields
* An associative array containing the values required for building the
* feed content.
*
* @return array
* The content in array format, as required for passing it to the XML
* builder.
*/
protected function buildContent(array $fields) {
$message = [
'OrderFulfillment' => [
'AmazonOrderID' => $fields['AmazonOrderID'],
],
];
if ($fields['FulfillmentDate']) {
$message['OrderFulfillment']['FulfillmentDate'] = $fields['FulfillmentDate'];
}
if ($fields['FulfillmentData']) {
$message['OrderFulfillment']['FulfillmentData'] = $fields['FulfillmentData'];
}
if ($fields['Item']) {
$message['OrderFulfillment']['Item'] = $fields['Item'];
}
return [
'Header' => [
'MerchantIdentifier' => $fields['MerchantIdentifier'],
],
'MessageType' => 'OrderFulfillment',
'Message' => [$message],
];
}
/**
* Determines the transition that should be applied to the given operation.
*
* @param \Drupal\entity_sync\Entity\OperationInterface $operation
* The operation.
* @param string $remote_state
* The remote state of the operation.
*
* @return string|null
* The ID of the transition, of NULL if no transition should be applied to
* the operation.
*/
protected function getOperationTransitionFromRemoteState(
OperationInterface $operation,
string $remote_state
) {
// If we are here we must be in either the `submission_completed` or
// in the `feed_in_progress` state.
$state = $operation->get(OperationField::STATE)->first()->getId();
switch ($remote_state) {
// If the remote state is `IN_QUEUE` the local state must still be
// `submission_completed`. There's no transition to be made.
case Feed::PROCESSING_STATUS_IN_QUEUE:
return;
// If the remote state is `IN_PROGRESS` the local state must still be
// `submission_completed` or already `feed_in_progress`. We transition to
// `feed_in_progress` in the former case.
case Feed::PROCESSING_STATUS_IN_PROGRESS:
return $state === 'submission_completed' ? 'process_feed' : NULL;
// If the remote state is `IN_PROGRESS` the local state must still be
// `submission_completed`. We transition to `feed_cancelled`.
case Feed::PROCESSING_STATUS_CANCELLED:
return 'cancel_feed';
// In all other cases the local state must be either
// `submission_completed` or `feed_in_progress`. We transition according
// to the remote state.
case Feed::PROCESSING_STATUS_DONE:
return 'complete_feed';
case Feed::PROCESSING_STATUS_FATAL:
return 'fail_feed';
}
}
}
