forked from fa/breadcrumb-the-shire
New domain detail page showing customer info, contract data, linked handovers, updates section, and related domains for the same customer. Data loaded async with skeleton loading states. Includes DomainDetailService and tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
227 lines
7.7 KiB
PHP
227 lines
7.7 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Module\Helpdesk\Service;
|
|
|
|
use MintyPHP\Module\Helpdesk\Repository\HandoverRepository;
|
|
use MintyPHP\Module\Helpdesk\Repository\UpdateRepository;
|
|
|
|
/**
|
|
* Service for loading domain detail data (master data, customer, contract, handovers).
|
|
*
|
|
* Provides loadDomain() for the synchronous page load and loadDomainDetails()
|
|
* for the async data endpoint.
|
|
*/
|
|
class DomainDetailService
|
|
{
|
|
public function __construct(
|
|
private readonly BcODataGateway $bcODataGateway,
|
|
private readonly EffectiveHelpdeskSettingsService $settingsGateway,
|
|
private readonly HandoverRepository $handoverRepository,
|
|
private readonly ?UpdateRepository $updateRepository = null
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Load domain master data (1 OData call).
|
|
*
|
|
* Used by the main page load — customer, contract, handovers loaded async.
|
|
*
|
|
* @api Called from domain($id) action
|
|
* @return array{status: string, domain?: array<string, mixed>, error?: string}
|
|
*/
|
|
public function loadDomain(string $domainNo): array
|
|
{
|
|
$domainNo = trim($domainNo);
|
|
if ($domainNo === '') {
|
|
return ['status' => 'not_found'];
|
|
}
|
|
|
|
if (!$this->settingsGateway->isConfigured()) {
|
|
return ['status' => 'not_configured', 'error' => 'BC connection not configured'];
|
|
}
|
|
|
|
try {
|
|
$domain = $this->bcODataGateway->getDomain($domainNo);
|
|
} catch (\Throwable) {
|
|
return ['status' => 'error', 'error' => 'BC connection failed'];
|
|
}
|
|
|
|
if ($domain === null) {
|
|
return ['status' => 'not_found'];
|
|
}
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'domain' => $domain,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Load all detail data for a domain (for async data endpoint).
|
|
*
|
|
* @api Called from domain-detail-data endpoint
|
|
* @return array{
|
|
* ok: bool,
|
|
* customer?: array<string, mixed>|null,
|
|
* contract?: array<string, mixed>|null,
|
|
* handovers?: list<array<string, mixed>>,
|
|
* handover_stats?: array{total: int, draft: int, in_progress: int, completed: int, archived: int},
|
|
* related_domains?: list<array<string, mixed>>,
|
|
* error?: string
|
|
* }
|
|
*/
|
|
public function loadDomainDetails(string $domainNo, string $customerNo, int $tenantId): array
|
|
{
|
|
$domainNo = trim($domainNo);
|
|
$customerNo = trim($customerNo);
|
|
|
|
if ($domainNo === '') {
|
|
return ['ok' => false, 'error' => 'Missing domain number'];
|
|
}
|
|
|
|
$customer = null;
|
|
if ($customerNo !== '') {
|
|
try {
|
|
$customer = $this->bcODataGateway->getCustomer($customerNo);
|
|
} catch (\Throwable) {
|
|
// Best-effort — continue without customer data
|
|
}
|
|
}
|
|
|
|
$contract = $this->loadContractForDomain($domainNo);
|
|
|
|
$handovers = $this->handoverRepository->findByDomainNo($tenantId, $domainNo);
|
|
$handoverStats = $this->aggregateHandoverStats($handovers);
|
|
|
|
$relatedDomains = [];
|
|
if ($customerNo !== '') {
|
|
try {
|
|
$allCustomerDomains = $this->bcODataGateway->getDomainsForCustomer($customerNo);
|
|
$relatedDomains = array_values(array_filter(
|
|
$allCustomerDomains,
|
|
static fn (array $d): bool => trim((string) ($d['No'] ?? '')) !== $domainNo
|
|
));
|
|
} catch (\Throwable) {
|
|
// Best-effort — continue without related domains
|
|
}
|
|
}
|
|
|
|
$updates = [];
|
|
if ($this->updateRepository !== null) {
|
|
$updates = $this->prepareUpdatesForResponse(
|
|
$this->updateRepository->findByDomainNo($tenantId, $domainNo)
|
|
);
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'customer' => $customer,
|
|
'contract' => $contract,
|
|
'handovers' => $this->prepareHandoversForResponse($handovers),
|
|
'handover_stats' => $handoverStats,
|
|
'updates' => $updates,
|
|
'related_domains' => $relatedDomains,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Load contract line info for a specific domain from BC.
|
|
*
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
private function loadContractForDomain(string $domainNo): ?array
|
|
{
|
|
try {
|
|
$contractLines = $this->bcODataGateway->listDomainContractLines();
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($contractLines as $line) {
|
|
$lineNo = trim((string) ($line['No'] ?? ''));
|
|
if ($lineNo === $domainNo) {
|
|
return [
|
|
'contract_type' => trim((string) ($line['PI_Header_Type'] ?? '')),
|
|
'contract_no' => trim((string) ($line['Header_No'] ?? '')),
|
|
'contract_description' => trim((string) ($line['PI_Header_Description'] ?? '')),
|
|
'header_state' => trim((string) ($line['PI_Header_State'] ?? '')),
|
|
'line_state' => trim((string) ($line['Line_State'] ?? '')),
|
|
];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>> $handovers
|
|
* @return array{total: int, draft: int, in_progress: int, completed: int, archived: int}
|
|
*/
|
|
private function aggregateHandoverStats(array $handovers): array
|
|
{
|
|
$stats = ['total' => 0, 'draft' => 0, 'in_progress' => 0, 'completed' => 0, 'archived' => 0];
|
|
|
|
foreach ($handovers as $handover) {
|
|
$stats['total']++;
|
|
$status = trim((string) ($handover['status'] ?? ''));
|
|
if (isset($stats[$status])) {
|
|
$stats[$status]++;
|
|
}
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Prepare update rows for the JSON response.
|
|
*
|
|
* @param list<array<string, mixed>> $updates
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
private function prepareUpdatesForResponse(array $updates): array
|
|
{
|
|
$prepared = [];
|
|
foreach ($updates as $update) {
|
|
$categoryCode = (string) ($update['category_code'] ?? '');
|
|
$status = (string) ($update['status'] ?? '');
|
|
$prepared[] = [
|
|
'ticket_no' => (string) ($update['ticket_no'] ?? ''),
|
|
'category_code' => $categoryCode,
|
|
'category_label' => UpdateService::categoryLabel($categoryCode),
|
|
'category_variant' => UpdateService::categoryVariant($categoryCode),
|
|
'gitea_path' => (string) ($update['gitea_path'] ?? ''),
|
|
'status' => $status,
|
|
'status_label' => UpdateService::statusLabel($status),
|
|
'status_variant' => UpdateService::statusVariant($status),
|
|
'created_at' => (string) ($update['created_at'] ?? ''),
|
|
];
|
|
}
|
|
|
|
return $prepared;
|
|
}
|
|
|
|
/**
|
|
* Prepare handover rows for the JSON response (strip internal fields).
|
|
*
|
|
* @param list<array<string, mixed>> $handovers
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
private function prepareHandoversForResponse(array $handovers): array
|
|
{
|
|
$prepared = [];
|
|
foreach ($handovers as $handover) {
|
|
$prepared[] = [
|
|
'id' => (int) ($handover['id'] ?? 0),
|
|
'product_code' => (string) ($handover['product_code'] ?? ''),
|
|
'product_name' => (string) ($handover['product_name'] ?? ''),
|
|
'product_bc_description' => (string) ($handover['product_bc_description'] ?? ''),
|
|
'status' => (string) ($handover['status'] ?? ''),
|
|
'created_by_name' => (string) ($handover['created_by_name'] ?? ''),
|
|
'created_at' => (string) ($handover['created_at'] ?? ''),
|
|
];
|
|
}
|
|
|
|
return $prepared;
|
|
}
|
|
}
|