freescout2ant/Services/CustomerArchivesApiClient.php
Claude 6eb4e8dfc2
Add API selection between MitarbeiterWebservice and Customer Archives/Stocks
Introduces an "Archive API" setting that switches both reads and writes
between the legacy MitarbeiterWebservice API and the new Customer Archives
(write) + Stocks (read) APIs.

- New write strategy (ArchiveWriterInterface + factory): legacy header-based
  writer and a Customer Archives JSON writer (CreateArchiveRequestDto, files as
  base64 data URIs, one entry per attachment), backed by CustomerArchivesApiClient.
- New read strategy (CrmReadClientInterface + factory): legacy delegate and a
  StocksReadClient that normalizes Stocks responses (customers/names,
  communications, contracts, contract lines/states) to the legacy shape so the
  controller/frontend stay unchanged.
- Hybrid: customer lookup by e-mail stays on MitarbeiterWebservice because the
  Stocks API cannot search by e-mail.
- Settings: ameise_api + ameise_archive_is_public dropdowns, env-backed base
  URLs for both new APIs (test/live), German translations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfJdbw4dRvJdcihTkKt69T
2026-06-25 13:23:16 +00:00

88 lines
3.2 KiB
PHP

<?php
namespace Modules\AmeiseModule\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException as Exception;
/**
* Low-level client for the new Customer Archives API
* (POST /api/customers/{customerId}/archive-entries).
*/
class CustomerArchivesApiClient
{
private $base_url;
private $tokenService;
private $ameiseLogStatus;
private $client;
public function __construct(TokenService $tokenService)
{
$this->tokenService = $tokenService;
$this->base_url = rtrim(
config('ameisemodule.ameise_mode') == 'test'
? config('ameisemodule.ameise_archive_base_url_test')
: config('ameisemodule.ameise_archive_base_url_live'),
'/'
);
$this->ameiseLogStatus = config('ameisemodule.ameise_log_status');
$this->client = new Client();
}
/**
* Create a single archive entry for the given customer.
*/
public function createArchiveEntry($customerId, array $payload): bool
{
try {
$token = $this->tokenService->getAccessToken();
if ($this->tokenHasError($token)) {
$this->ameiseLogStatus && \Helper::log('conversation_archive', 'Archive entry skipped because token contains an error.');
return false;
}
$url = $this->base_url . '/api/customers/' . rawurlencode((string) $customerId) . '/archive-entries';
$this->ameiseLogStatus && \Helper::log('conversation_archive', 'Sending POST request to: ' . $url);
$response = $this->client->request('POST', $url, [
'headers' => [
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => $payload,
'http_errors' => false,
]);
$code = $response->getStatusCode();
$this->ameiseLogStatus && \Helper::log('conversation_archive', 'Response status: ' . $code);
if ($code >= 200 && $code < 300) {
return true;
}
if ($code === 401) {
$this->tokenService->disconnectAmeise();
} else {
$this->ameiseLogStatus && \Helper::log('conversation_archive', 'Request failed with status code: ' . $code);
$this->ameiseLogStatus && \Helper::log('conversation_archive', 'Error response: ' . substr((string) $response->getBody(), 0, 2000));
}
} catch (Exception $e) {
$body = $e->hasResponse() ? (string) $e->getResponse()->getBody() : '';
$this->ameiseLogStatus && \Helper::log('conversation_archive', 'Error body: ' . substr($body, 0, 2000));
$this->ameiseLogStatus && \Helper::logException($e, 'conversation_archive');
if ($e->getCode() === 401) {
$this->tokenService->disconnectAmeise();
}
}
return false;
}
private function tokenHasError($token): bool
{
return $token && is_string($token)
&& ($decoded = json_decode($token, true))
&& isset($decoded['error']);
}
}