Skip to content
Snippets Groups Projects
Commit 4fd5069f authored by Dimitris Bozelos's avatar Dimitris Bozelos
Browse files

Issue #3364008 Support sending the branch in request headers

parent 188b3989
No related branches found
No related tags found
No related merge requests found
...@@ -7,10 +7,13 @@ namespace Drupal\acumatica\EntitySync\Api; ...@@ -7,10 +7,13 @@ namespace Drupal\acumatica\EntitySync\Api;
use Drupal\entity_sync\Client\ClientInterface; use Drupal\entity_sync\Client\ClientInterface;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use KrystalCode\Acumatica\Api\Discovery\Discovery; use KrystalCode\Acumatica\Api\Discovery\Discovery;
use KrystalCode\Acumatica\Api\OData\Filter; use KrystalCode\Acumatica\Api\OData\Filter;
use KrystalCode\Acumatica\Api\OData\FilterGroup; use KrystalCode\Acumatica\Api\OData\FilterGroup;
use KrystalCode\Acumatica\Api\Session\SessionManagerInterface; use KrystalCode\Acumatica\Api\Session\SessionManagerInterface;
use Psr\Http\Message\RequestInterface;
/** /**
* The API client Entity Sync adapter for the Acumatica object APIs. * The API client Entity Sync adapter for the Acumatica object APIs.
...@@ -121,10 +124,32 @@ class ObjectClient implements ClientInterface, ObjectClientInterface { ...@@ -121,10 +124,32 @@ class ObjectClient implements ClientInterface, ObjectClientInterface {
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* Support client options are:
* - branch (array, optional): An associative array that instructs the client
* to include the branch in the request headers. Supported array elements
* are:
* - mode (string, required): The calculation mode. Supported modes are
* `static` and `field_mapping`. The `static` mode will use the provided
* fixed value as the branch. The `field_mapping` mode will use one of the
* field values that were transformed as defined by the field mapping of
* the synchronization.
* - static (array, optional): Required if the calculation mode is `static`.
* An associative array containing a single element, keyed by `value` and
* containing the branch to use.
* - field_mapping (array, optional): Required if the calculation mode is
* `field_mapping`. An associative array containing a single element,
* keyed by `remote_name` and containing the remote field name to use as
* the branch. This should normally match the same property defined in the
* field mapping of the synchronization.
*
* @link https://help-2022r2.acumatica.com/(W(82))/Help?ScreenId=ShowWiki&pageid=9821cff9-4970-4153-a0f8-dbf5758133a7
*/ */
public function create(array $fields, array $options = []) { public function create(array $fields, array $options = []) {
// Handle attributes. // Handle attributes.
$fields = $this->buildAttributesField($fields); $fields = $this->buildAttributesField($fields);
// Handle branch, if requested in options.
[$fields, $branch] = $this->getBranch($fields, $options);
$namespace = $this->sessionManager $namespace = $this->sessionManager
->getConfiguration() ->getConfiguration()
...@@ -132,7 +157,10 @@ class ObjectClient implements ClientInterface, ObjectClientInterface { ...@@ -132,7 +157,10 @@ class ObjectClient implements ClientInterface, ObjectClientInterface {
$class = $namespace . '\\Model\\' . $this->objectType; $class = $namespace . '\\Model\\' . $this->objectType;
$object = new $class($fields); $object = new $class($fields);
$discovery = new Discovery(new Client(), $this->sessionManager); $discovery = new Discovery(
$this->getGuzzleClient($branch),
$this->sessionManager
);
$api = lcfirst($this->objectType); $api = lcfirst($this->objectType);
return $discovery return $discovery
->{$api}() ->{$api}()
...@@ -142,10 +170,36 @@ class ObjectClient implements ClientInterface, ObjectClientInterface { ...@@ -142,10 +170,36 @@ class ObjectClient implements ClientInterface, ObjectClientInterface {
/** /**
* {@inheritdoc} * {@inheritdoc}
*
* Support client options are:
* - branch (array, optional): An associative array that instructs the client
* to include the branch in the request headers. Supported array elements
* are:
* - mode (string, required): The calculation mode. Supported modes are
* `static` and `field_mapping`. The `static` mode will use the provided
* fixed value as the branch. The `field_mapping` mode will use one of the
* field values that were transformed as defined by the field mapping of
* the synchronization.
* - static (array, optional): Required if the calculation mode is `static`.
* An associative array containing a single element, keyed by `value` and
* containing the branch to use.
* - field_mapping (array, optional): Required if the calculation mode is
* `field_mapping`. An associative array containing a single element,
* keyed by `remote_name` and containing the remote field name to use as
* the branch. This should normally match the same property defined in the
* field mapping of the synchronization.
*
* According to documentation, setting the current branch should apply to
* update operations as well. Some testing on orders though worked only for
* create operations i.e. orders remained on the branch after update requests.
*
* @link https://help-2022r2.acumatica.com/(W(82))/Help?ScreenId=ShowWiki&pageid=9821cff9-4970-4153-a0f8-dbf5758133a7
*/ */
public function update($id, array $fields, array $options = []) { public function update($id, array $fields, array $options = []) {
// Handle attributes. // Handle attributes.
$fields = $this->buildAttributesField($fields); $fields = $this->buildAttributesField($fields);
// Handle branch, if requested in options.
[$fields, $branch] = $this->getBranch($fields, $options);
$namespace = $this->sessionManager $namespace = $this->sessionManager
->getConfiguration() ->getConfiguration()
...@@ -154,7 +208,10 @@ class ObjectClient implements ClientInterface, ObjectClientInterface { ...@@ -154,7 +208,10 @@ class ObjectClient implements ClientInterface, ObjectClientInterface {
$object = new $class($fields); $object = new $class($fields);
$object->setId($id); $object->setId($id);
$discovery = new Discovery(new Client(), $this->sessionManager); $discovery = new Discovery(
$this->getGuzzleClient($branch),
$this->sessionManager
);
$api = lcfirst($this->objectType); $api = lcfirst($this->objectType);
return $discovery return $discovery
->{$api}() ->{$api}()
...@@ -373,4 +430,128 @@ class ObjectClient implements ClientInterface, ObjectClientInterface { ...@@ -373,4 +430,128 @@ class ObjectClient implements ClientInterface, ObjectClientInterface {
return "datetimeoffset'" . $datetime->format('Y-m-d\TH:i:s') . ".000'"; return "datetimeoffset'" . $datetime->format('Y-m-d\TH:i:s') . ".000'";
} }
/**
* Calculates and returns the Acumatica branch based on the given options.
*
* This currently applies only create/update export operations.
*
* If the branch calculation mode is based on field mapping, the value will be
* taken from the transformed field values. The corresponding field value will
* be removed from the array of the transformed field values that is
* returned. This is because the branch is not needed to be sent in the
* fields, we just need the header.
*
* @param array $fields
* The associative array containing the transformed field values.
* @param array $options
* The client options.
*
* @return array
* A numerical array containing the updated field array as its first
* element, and the Acumatica branch as its second element (string, or NULL
* if no branch was detected).
*
* @throws \RuntimeException
* If the calculation mode relies on a field that was not found in the
* transformed fields.
*/
protected function getBranch(
array $fields,
array $options
): array {
if (!isset($options['branch'])) {
return [$fields, NULL];
}
$this->validateBranchOptions($options['branch']);
if ($options['branch']['mode'] === 'static') {
return [$fields, $options['branch']['static']['value']];
}
// Field mapping it is otherwise - see validation.
$field_name = $options['branch']['field_mapping']['remote_name'];
if (!isset($fields[$field_name])) {
throw new \RuntimeException(sprintf(
'Field "%s" not found in the calculated field values when executing branch detection.',
$field_name
));
}
$branch = $fields[$field_name];
unset($fields[$field_name]);
return [$fields, $branch];
}
/**
* Validates the given branch options.
*
* @param array $options
* The associative array containing the branch property of the client
* options.
*
* @throws \InvalidArgumentException
* When a property has an invalid value or when a required property is
* missing.
*/
protected function validateBranchOptions(array $options): void {
if (!isset($options['mode'])) {
throw new \InvalidArgumentException(
'The branch detection mode must be configured.'
);
}
switch ($options['mode']) {
case 'field_mapping':
if (!isset($options['field_mapping']['remote_name'])) {
throw new \InvalidArgumentException(
'The remote name of the field mapping item that provides the branch must be provided for field mapping-based branch detection.'
);
}
break;
case 'static':
if (!isset($options['static']['value'])) {
throw new \InvalidArgumentException(
'A value must be provided for static branch detection.'
);
}
break;
default:
throw new \InvalidArgumentException(sprintf(
'Unknown branch detection mode "%s".',
$options['mode']
));
}
}
/**
* Returns the instantiated Guzzle client for making requests.
*
* Currently, it adds the appropriate header if a branch is given.
*
* @param string|null $branch
* The Acumatica branch to use for the header, or NULL to not include the
* branch header.
*
* @return \GuzzleHttp\Client
* The instantiated Guzzle client.
*/
protected function getGuzzleClient(string|null $branch): Client {
if ($branch === NULL) {
return new Client();
}
// We use a middleware so that all requests sent by this client will have
// the header.
$handler = HandlerStack::create();
$handler->push(Middleware::mapRequest(
function (RequestInterface $request) use ($branch) {
return $request->withHeader('PX-CbApiBranch', $branch);
})
);
return new Client(['handler' => $handler]);
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment