feat(helpdesk): add handover revision history with timeline, diff, and restore
Every save creates an immutable revision snapshot. The aside shows a commit-graph-style timeline (newest first) with vertical line, circle nodes, version numbers, change type, user, and date. Clicking a past revision renders it readonly with inline git-diff-style highlights (changed/added/removed with tinted backgrounds). Compare mode allows diffing any two arbitrary revisions. MANAGE users can restore old versions, which creates a new revision preserving full history. New: HandoverRevisionService, HandoverRevisionRepository, migration 006, 14 PHPUnit tests, DE/EN translations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,10 +6,13 @@
|
||||
* $schemaFields — list of field definitions from schema_snapshot['fields']
|
||||
* $fieldValues — associative array of current field values (key => value)
|
||||
* $readonly — bool, if true all inputs are disabled
|
||||
* $fieldDiff — (optional) associative array of diffs: key => ['type' => added|removed|changed, 'old' => ..., 'new' => ...]
|
||||
*/
|
||||
$schemaFields = is_array($schemaFields ?? null) ? $schemaFields : [];
|
||||
$fieldValues = is_array($fieldValues ?? null) ? $fieldValues : [];
|
||||
$fieldDiff = is_array($fieldDiff ?? null) ? $fieldDiff : [];
|
||||
$readonly = !empty($readonly);
|
||||
$hasDiff = $fieldDiff !== [];
|
||||
?>
|
||||
<?php if ($schemaFields === []): ?>
|
||||
<p class="app-muted"><?php e(t('No protocol fields defined.')); ?></p>
|
||||
@@ -21,6 +24,8 @@ $readonly = !empty($readonly);
|
||||
$required = !empty($field['required']);
|
||||
$value = $key !== '' ? (string) ($fieldValues[$key] ?? '') : '';
|
||||
$inputId = 'handover-field-' . $index;
|
||||
$diff = ($key !== '' && isset($fieldDiff[$key])) ? $fieldDiff[$key] : null;
|
||||
$diffClass = $diff !== null ? ' handover-diff-' . ($diff['type'] ?? '') : '';
|
||||
?>
|
||||
<?php if ($type === 'heading'): ?>
|
||||
<h3 class="handover-form-heading"><?php e($label); ?></h3>
|
||||
@@ -29,50 +34,69 @@ $readonly = !empty($readonly);
|
||||
<p class="app-muted"><?php e($label); ?></p>
|
||||
|
||||
<?php elseif ($type === 'checkbox'): ?>
|
||||
<label class="handover-form-checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="<?php e($inputId); ?>"
|
||||
name="field_<?php e($key); ?>"
|
||||
value="1"
|
||||
<?php if ($value === '1'): ?> checked<?php endif; ?>
|
||||
<?php if ($readonly): ?> disabled<?php endif; ?>
|
||||
>
|
||||
<?php e($label); ?>
|
||||
<?php if ($required): ?><span class="handover-form-required"> *</span><?php endif; ?>
|
||||
</label>
|
||||
<div class="handover-diff-field<?php e($diffClass); ?>">
|
||||
<label class="handover-form-checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="<?php e($inputId); ?>"
|
||||
name="field_<?php e($key); ?>"
|
||||
value="1"
|
||||
<?php if ($value === '1'): ?> checked<?php endif; ?>
|
||||
<?php if ($readonly): ?> disabled<?php endif; ?>
|
||||
>
|
||||
<?php e($label); ?>
|
||||
<?php if ($required): ?><span class="handover-form-required"> *</span><?php endif; ?>
|
||||
</label>
|
||||
<?php if ($diff !== null): ?>
|
||||
<span class="handover-diff-indicator" aria-label="<?php e(t('Changed')); ?>">
|
||||
<?php if ($diff['type'] === 'changed'): ?>
|
||||
<small class="app-muted"><?php e(t('was: %s', ($diff['old'] ?? '') === '1' ? t('Yes') : t('No'))); ?></small>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php elseif ($type === 'select'): ?>
|
||||
<label for="<?php e($inputId); ?>">
|
||||
<?php e($label); ?>
|
||||
<?php if ($required): ?><span class="handover-form-required"> *</span><?php endif; ?>
|
||||
</label>
|
||||
<select
|
||||
id="<?php e($inputId); ?>"
|
||||
name="field_<?php e($key); ?>"
|
||||
<?php if ($required): ?> aria-required="true"<?php endif; ?>
|
||||
<?php if ($readonly): ?> disabled<?php endif; ?>
|
||||
>
|
||||
<option value=""><?php e(t('Please select...')); ?></option>
|
||||
<?php foreach (($field['options'] ?? []) as $opt): ?>
|
||||
<option value="<?php e((string) ($opt['value'] ?? '')); ?>"<?php if ($value === (string) ($opt['value'] ?? '')): ?> selected<?php endif; ?>>
|
||||
<?php e((string) ($opt['label'] ?? '')); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="handover-diff-field<?php e($diffClass); ?>">
|
||||
<label for="<?php e($inputId); ?>">
|
||||
<?php e($label); ?>
|
||||
<?php if ($required): ?><span class="handover-form-required"> *</span><?php endif; ?>
|
||||
</label>
|
||||
<select
|
||||
id="<?php e($inputId); ?>"
|
||||
name="field_<?php e($key); ?>"
|
||||
<?php if ($required): ?> aria-required="true"<?php endif; ?>
|
||||
<?php if ($readonly): ?> disabled<?php endif; ?>
|
||||
>
|
||||
<option value=""><?php e(t('Please select...')); ?></option>
|
||||
<?php foreach (($field['options'] ?? []) as $opt): ?>
|
||||
<option value="<?php e((string) ($opt['value'] ?? '')); ?>"<?php if ($value === (string) ($opt['value'] ?? '')): ?> selected<?php endif; ?>>
|
||||
<?php e((string) ($opt['label'] ?? '')); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if ($diff !== null && $diff['type'] === 'changed'): ?>
|
||||
<small class="app-muted handover-diff-indicator" aria-label="<?php e(t('Changed')); ?>"><?php e(t('was: %s', (string) ($diff['old'] ?? ''))); ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php elseif ($type === 'textarea'): ?>
|
||||
<label for="<?php e($inputId); ?>">
|
||||
<?php e($label); ?>
|
||||
<?php if ($required): ?><span class="handover-form-required"> *</span><?php endif; ?>
|
||||
</label>
|
||||
<textarea
|
||||
id="<?php e($inputId); ?>"
|
||||
name="field_<?php e($key); ?>"
|
||||
rows="4"
|
||||
<?php if ($required): ?> aria-required="true"<?php endif; ?>
|
||||
<?php if ($readonly): ?> disabled<?php endif; ?>
|
||||
><?php e($value); ?></textarea>
|
||||
<div class="handover-diff-field<?php e($diffClass); ?>">
|
||||
<label for="<?php e($inputId); ?>">
|
||||
<?php e($label); ?>
|
||||
<?php if ($required): ?><span class="handover-form-required"> *</span><?php endif; ?>
|
||||
</label>
|
||||
<textarea
|
||||
id="<?php e($inputId); ?>"
|
||||
name="field_<?php e($key); ?>"
|
||||
rows="4"
|
||||
<?php if ($required): ?> aria-required="true"<?php endif; ?>
|
||||
<?php if ($readonly): ?> disabled<?php endif; ?>
|
||||
><?php e($value); ?></textarea>
|
||||
<?php if ($diff !== null && $diff['type'] === 'changed'): ?>
|
||||
<small class="app-muted handover-diff-indicator" aria-label="<?php e(t('Changed')); ?>"><?php e(t('was: %s', (string) ($diff['old'] ?? ''))); ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<?php
|
||||
@@ -82,18 +106,23 @@ $readonly = !empty($readonly);
|
||||
default => 'text',
|
||||
};
|
||||
?>
|
||||
<label for="<?php e($inputId); ?>">
|
||||
<?php e($label); ?>
|
||||
<?php if ($required): ?><span class="handover-form-required"> *</span><?php endif; ?>
|
||||
</label>
|
||||
<input
|
||||
type="<?php e($inputType); ?>"
|
||||
id="<?php e($inputId); ?>"
|
||||
name="field_<?php e($key); ?>"
|
||||
value="<?php e($value); ?>"
|
||||
<?php if ($required): ?> aria-required="true"<?php endif; ?>
|
||||
<?php if ($readonly): ?> disabled<?php endif; ?>
|
||||
>
|
||||
<div class="handover-diff-field<?php e($diffClass); ?>">
|
||||
<label for="<?php e($inputId); ?>">
|
||||
<?php e($label); ?>
|
||||
<?php if ($required): ?><span class="handover-form-required"> *</span><?php endif; ?>
|
||||
</label>
|
||||
<input
|
||||
type="<?php e($inputType); ?>"
|
||||
id="<?php e($inputId); ?>"
|
||||
name="field_<?php e($key); ?>"
|
||||
value="<?php e($value); ?>"
|
||||
<?php if ($required): ?> aria-required="true"<?php endif; ?>
|
||||
<?php if ($readonly): ?> disabled<?php endif; ?>
|
||||
>
|
||||
<?php if ($diff !== null && $diff['type'] === 'changed'): ?>
|
||||
<small class="app-muted handover-diff-indicator" aria-label="<?php e(t('Changed')); ?>"><?php e(t('was: %s', (string) ($diff['old'] ?? ''))); ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use MintyPHP\Buffer;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Module\Helpdesk\HelpdeskAuthorizationPolicy;
|
||||
use MintyPHP\Module\Helpdesk\Service\HandoverRevisionService;
|
||||
use MintyPHP\Module\Helpdesk\Service\HandoverService;
|
||||
use MintyPHP\Router;
|
||||
use MintyPHP\Session;
|
||||
@@ -16,13 +17,15 @@ $request = requestInput();
|
||||
$returnTarget = requestResolveReturnTarget();
|
||||
$closeTarget = requestResolveReturnTarget('helpdesk/handovers');
|
||||
$handoverId = (int) ($id ?? 0);
|
||||
$editTarget = requestPathWithReturnTarget('helpdesk/handovers/edit/' . $handoverId, $returnTarget);
|
||||
$editBasePath = 'helpdesk/handovers/edit/' . $handoverId;
|
||||
$editTarget = requestPathWithReturnTarget($editBasePath, $returnTarget);
|
||||
|
||||
$session = app(SessionStoreInterface::class)->all();
|
||||
$tenantId = (int) ($session['current_tenant']['id'] ?? 0);
|
||||
$userId = (int) ($session['user']['id'] ?? 0);
|
||||
|
||||
$service = app(HandoverService::class);
|
||||
$revisionService = app(HandoverRevisionService::class);
|
||||
$handover = $service->findById($tenantId, $handoverId);
|
||||
|
||||
if ($handover === null) {
|
||||
@@ -47,6 +50,7 @@ $allowedStatuses = $service->getAllowedStatuses($permissionLevel);
|
||||
$schemaRaw = (string) ($handover['schema_snapshot'] ?? '{}');
|
||||
$schema = json_decode($schemaRaw, true);
|
||||
$schemaFields = is_array($schema) ? ($schema['fields'] ?? []) : [];
|
||||
$schemaVersion = is_array($schema) ? (int) ($schema['version'] ?? 1) : 1;
|
||||
|
||||
// Parse field values
|
||||
$fieldValuesRaw = (string) ($handover['field_values'] ?? '{}');
|
||||
@@ -57,6 +61,31 @@ if (!is_array($fieldValues)) {
|
||||
|
||||
$errorBag = formErrors();
|
||||
|
||||
// Handle restore POST
|
||||
if ($request->isMethod('POST') && (string) $request->body('action', '') === 'restore') {
|
||||
if (!Session::checkCsrfToken()) {
|
||||
Flash::error(t('Form expired, please try again'), $editTarget, 'csrf_expired');
|
||||
Router::redirect($editTarget);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$restoreRevision = (int) $request->body('restore_revision', 0);
|
||||
$restoreResult = $revisionService->restoreRevision($tenantId, $handoverId, $restoreRevision, $userId, $permissionLevel);
|
||||
|
||||
if ($restoreResult['ok']) {
|
||||
Flash::success(t('Version restored'), $editTarget, 'revision_restored');
|
||||
} else {
|
||||
$firstError = reset($restoreResult['errors']) ?: t('Failed to restore');
|
||||
Flash::error($firstError, $editTarget, 'revision_restore_failed');
|
||||
}
|
||||
|
||||
Router::redirect($editTarget);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle normal save POST
|
||||
if ($request->isMethod('POST') && $canEdit) {
|
||||
if (!Session::checkCsrfToken()) {
|
||||
Flash::error(t('Form expired, please try again'), $editTarget, 'csrf_expired');
|
||||
@@ -80,17 +109,34 @@ if ($request->isMethod('POST') && $canEdit) {
|
||||
}
|
||||
}
|
||||
|
||||
$previousStatus = $currentStatus;
|
||||
$updateResult = $service->updateFields($tenantId, $handoverId, $submittedValues, $userId, $permissionLevel);
|
||||
$errorBag->merge($updateResult['errors'] ?? []);
|
||||
|
||||
// Handle status change
|
||||
$newStatus = (string) $request->body('status', '');
|
||||
$statusChanged = false;
|
||||
if ($newStatus !== '' && $newStatus !== $currentStatus) {
|
||||
$statusResult = $service->changeStatus($tenantId, $handoverId, $newStatus, $userId, $permissionLevel);
|
||||
$errorBag->merge($statusResult['errors'] ?? []);
|
||||
if (empty($statusResult['errors'])) {
|
||||
$statusChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errorBag->hasAny()) {
|
||||
// Create a single revision for the combined save
|
||||
$finalStatus = $statusChanged ? $newStatus : $currentStatus;
|
||||
$service->createRevisionAfterSave(
|
||||
$tenantId,
|
||||
$handoverId,
|
||||
$submittedValues,
|
||||
$finalStatus,
|
||||
$previousStatus,
|
||||
$schemaVersion,
|
||||
$userId
|
||||
);
|
||||
|
||||
$handover = $service->findById($tenantId, $handoverId);
|
||||
$fieldValues = $submittedValues;
|
||||
$currentStatus = (string) ($handover['status'] ?? $currentStatus);
|
||||
@@ -111,11 +157,61 @@ if ($request->isMethod('POST') && $canEdit) {
|
||||
$validationSummaryErrors = $errorBag->toArray();
|
||||
$errors = $errorBag->toFlatList();
|
||||
|
||||
// Load revision data
|
||||
$revisions = $revisionService->listByHandover($tenantId, $handoverId);
|
||||
$activeRevisionNumber = (int) $request->query('revision', 0);
|
||||
$compareRevisionNumber = (int) $request->query('compare', 0);
|
||||
$isViewingRevision = $activeRevisionNumber > 0;
|
||||
$activeRevision = null;
|
||||
$fieldDiff = [];
|
||||
|
||||
if ($isViewingRevision) {
|
||||
$activeRevision = $revisionService->findRevision($tenantId, $handoverId, $activeRevisionNumber);
|
||||
if ($activeRevision === null) {
|
||||
// Invalid revision number — fall back to current
|
||||
$isViewingRevision = false;
|
||||
$activeRevisionNumber = 0;
|
||||
} else {
|
||||
// Load this revision's field values
|
||||
$revisionFieldValues = json_decode((string) ($activeRevision['field_values'] ?? '{}'), true);
|
||||
if (!is_array($revisionFieldValues)) {
|
||||
$revisionFieldValues = [];
|
||||
}
|
||||
|
||||
// Compute diff
|
||||
if ($compareRevisionNumber > 0) {
|
||||
// Compare against a specific revision
|
||||
$compareRevision = $revisionService->findRevision($tenantId, $handoverId, $compareRevisionNumber);
|
||||
if ($compareRevision !== null) {
|
||||
$compareValues = json_decode((string) ($compareRevision['field_values'] ?? '{}'), true);
|
||||
if (!is_array($compareValues)) {
|
||||
$compareValues = [];
|
||||
}
|
||||
$fieldDiff = $revisionService->computeDiff($compareValues, $revisionFieldValues);
|
||||
}
|
||||
} elseif ($activeRevisionNumber > 1) {
|
||||
// Compare against previous revision
|
||||
$prevRevision = $revisionService->findRevision($tenantId, $handoverId, $activeRevisionNumber - 1);
|
||||
if ($prevRevision !== null) {
|
||||
$prevValues = json_decode((string) ($prevRevision['field_values'] ?? '{}'), true);
|
||||
if (!is_array($prevValues)) {
|
||||
$prevValues = [];
|
||||
}
|
||||
$fieldDiff = $revisionService->computeDiff($prevValues, $revisionFieldValues);
|
||||
}
|
||||
}
|
||||
|
||||
$fieldValues = $revisionFieldValues;
|
||||
$currentStatus = (string) ($activeRevision['status'] ?? $currentStatus);
|
||||
}
|
||||
}
|
||||
|
||||
$productCode = (string) ($handover['product_code'] ?? '');
|
||||
$product = app(\MintyPHP\Module\Helpdesk\Service\SoftwareProductService::class)->findByCode($productCode);
|
||||
$productName = trim((string) ($product['name'] ?? '')) !== ''
|
||||
? $product['name']
|
||||
: (trim((string) ($product['bc_description'] ?? '')) !== '' ? $product['bc_description'] : $productCode);
|
||||
$canViewSoftwareProducts = $authService->authorize(HelpdeskAuthorizationPolicy::ABILITY_SOFTWARE_PRODUCTS_MANAGE, $actorContext)->isAllowed();
|
||||
$titleText = t('Handover') . ' ' . $productName;
|
||||
Buffer::set('title', $titleText);
|
||||
Buffer::set('style_groups', json_encode(['helpdesk']));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use MintyPHP\Module\Helpdesk\Service\HandoverRevisionService;
|
||||
use MintyPHP\Module\Helpdesk\Service\HandoverService;
|
||||
|
||||
$handover = is_array($handover ?? null) ? $handover : [];
|
||||
@@ -11,9 +12,17 @@ $closeTarget = $closeTarget ?? 'helpdesk/handovers';
|
||||
$canEdit = $canEdit ?? false;
|
||||
$allowedStatuses = is_array($allowedStatuses ?? null) ? $allowedStatuses : [];
|
||||
$currentStatus = $currentStatus ?? 'draft';
|
||||
$revisions = is_array($revisions ?? null) ? $revisions : [];
|
||||
$isViewingRevision = !empty($isViewingRevision);
|
||||
$activeRevisionNumber = $activeRevisionNumber ?? 0;
|
||||
$compareRevisionNumber = $compareRevisionNumber ?? 0;
|
||||
$fieldDiff = is_array($fieldDiff ?? null) ? $fieldDiff : [];
|
||||
$canManage = $canManage ?? false;
|
||||
|
||||
$formId = 'handover-form';
|
||||
$readonly = !$canEdit;
|
||||
$readonly = !$canEdit || $isViewingRevision;
|
||||
$editTarget = $editTarget ?? ('helpdesk/handovers/edit/' . ($handover['id'] ?? 0));
|
||||
$editBasePath = $editBasePath ?? ('helpdesk/handovers/edit/' . ($handover['id'] ?? 0));
|
||||
?>
|
||||
<div class="app-details-container">
|
||||
<section>
|
||||
@@ -24,7 +33,7 @@ $readonly = !$canEdit;
|
||||
'title' => $titleText ?? t('Handover'),
|
||||
'backHref' => $closeTarget,
|
||||
'backTitle' => t('Back to list'),
|
||||
'actions' => $canEdit ? [
|
||||
'actions' => ($canEdit && !$isViewingRevision) ? [
|
||||
[
|
||||
'form' => $formId,
|
||||
'name' => 'action',
|
||||
@@ -44,9 +53,22 @@ $readonly = !$canEdit;
|
||||
require templatePath('partials/app-details-titlebar.phtml');
|
||||
?>
|
||||
|
||||
<?php if ($isViewingRevision): ?>
|
||||
<div class="handover-revision-banner">
|
||||
<p>
|
||||
<i class="bi bi-clock-history"></i>
|
||||
<?php e(t('Viewing version %d', $activeRevisionNumber)); ?>
|
||||
<?php if ($compareRevisionNumber > 0): ?>
|
||||
— <?php e(t('compared with version %d', $compareRevisionNumber)); ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<a href="<?php e(lurl($editBasePath)); ?>" role="button" class="outline secondary small"><?php e(t('Back to current')); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require templatePath('partials/app-details-validation-summary.phtml'); ?>
|
||||
|
||||
<?php if ($canEdit): ?>
|
||||
<?php if ($canEdit && !$isViewingRevision): ?>
|
||||
<form id="<?php e($formId); ?>" method="post" data-standard-detail-form="1">
|
||||
<div class="app-tabs" data-tabs data-app-component="tabs" data-tabs-param="tab" data-tabs-storage-key="helpdesk-handover-form">
|
||||
<div class="app-tabs-nav">
|
||||
@@ -75,19 +97,30 @@ $readonly = !$canEdit;
|
||||
|
||||
<aside id="app-details-aside-section">
|
||||
<div class="app-details-aside-section">
|
||||
<?php
|
||||
$debitorNo = trim((string) ($handover['debitor_no'] ?? ''));
|
||||
$debitorName = trim((string) ($handover['debitor_name'] ?? ''));
|
||||
$canViewSoftwareProducts = $canViewSoftwareProducts ?? false;
|
||||
$productCode = $handover['product_code'] ?? '';
|
||||
?>
|
||||
<hgroup>
|
||||
<h2>#<?php e($handover['id'] ?? ''); ?></h2>
|
||||
<p><?php e(t('Handover')); ?></p>
|
||||
<h2>
|
||||
<?php if ($debitorNo !== ''): ?>
|
||||
<a href="<?php e(lurl('helpdesk/debitor/' . rawurlencode($debitorNo))); ?>"><?php e($debitorName !== '' ? $debitorName : $debitorNo); ?></a>
|
||||
<?php else: ?>
|
||||
<?php e($debitorName !== '' ? $debitorName : '—'); ?>
|
||||
<?php endif; ?>
|
||||
</h2>
|
||||
<p>
|
||||
<?php if ($canViewSoftwareProducts && $productCode !== ''): ?>
|
||||
<a href="<?php e(lurl('helpdesk/software-products/edit/' . rawurlencode($productCode))); ?>"><?php e($productName ?? $productCode); ?></a>
|
||||
<?php else: ?>
|
||||
<?php e($productName ?? $productCode); ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</hgroup>
|
||||
|
||||
<div class="badge-list">
|
||||
<span class="badge" data-variant="<?php e(HandoverService::statusVariant($currentStatus)); ?>">
|
||||
<?php e(HandoverService::statusLabel($currentStatus)); ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?php if ($canEdit && $allowedStatuses && count($allowedStatuses) > 1): ?>
|
||||
<hr>
|
||||
<?php if (!$isViewingRevision && $canEdit && $allowedStatuses && count($allowedStatuses) > 1): ?>
|
||||
<label for="handover-status"><small><?php e(t('Status')); ?></small></label>
|
||||
<select id="handover-status" name="status" form="<?php e($formId); ?>">
|
||||
<?php foreach ($allowedStatuses as $status): ?>
|
||||
@@ -96,37 +129,65 @@ $readonly = !$canEdit;
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php else: ?>
|
||||
<div class="badge-list">
|
||||
<span class="badge" data-variant="<?php e(HandoverService::statusVariant($currentStatus)); ?>">
|
||||
<?php e(HandoverService::statusLabel($currentStatus)); ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$debitorNo = trim((string) ($handover['debitor_no'] ?? ''));
|
||||
$debitorName = trim((string) ($handover['debitor_name'] ?? ''));
|
||||
?>
|
||||
<details name="handover-aside" open>
|
||||
<summary><?php e(t('Customer')); ?></summary>
|
||||
<hr>
|
||||
<p>
|
||||
<?php if ($debitorNo !== ''): ?>
|
||||
<a href="<?php e(lurl('helpdesk/debitor/' . rawurlencode($debitorNo))); ?>"><?php e($debitorName !== '' ? $debitorName : $debitorNo); ?></a>
|
||||
<?php else: ?>
|
||||
<?php e($debitorName); ?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<?php if ($debitorNo !== '' && $debitorName !== ''): ?>
|
||||
<p><small><?php e(t('Debitor No.')); ?></small><br><?php e($debitorNo); ?></p>
|
||||
<?php endif; ?>
|
||||
</details>
|
||||
<hr>
|
||||
|
||||
<details name="handover-aside" open>
|
||||
<summary><?php e(t('Software product')); ?></summary>
|
||||
<?php if ($revisions !== []): ?>
|
||||
<details name="handover-aside" open>
|
||||
<summary><?php e(t('History')); ?></summary>
|
||||
<hr>
|
||||
<?php $latestRevNum = (int) ($revisions[0]['revision'] ?? 0); ?>
|
||||
<div class="handover-revision-timeline">
|
||||
<?php foreach ($revisions as $rev):
|
||||
$revNum = (int) ($rev['revision'] ?? 0);
|
||||
$isActive = $revNum === $activeRevisionNumber;
|
||||
$isCurrent = $revNum === $latestRevNum;
|
||||
$revUrl = lurl($editBasePath . '?revision=' . $revNum);
|
||||
$compareUrl = lurl($editBasePath . '?revision=' . $latestRevNum . '&compare=' . $revNum);
|
||||
?>
|
||||
<div class="handover-revision-item<?php if ($isActive): ?> is-active<?php endif; ?>"
|
||||
<?php if ($isActive): ?>aria-current="true"<?php endif; ?>>
|
||||
<a href="<?php e($revUrl); ?>" class="handover-revision-link">
|
||||
<span class="handover-revision-number">
|
||||
<?php e('v' . $revNum); ?>
|
||||
<?php if ($isCurrent): ?>
|
||||
<span class="badge small" data-variant="neutral"><?php e(t('Current')); ?></span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<span class="handover-revision-meta">
|
||||
<span class="handover-revision-change"><?php e(HandoverRevisionService::changeTypeLabel((string) ($rev['change_type'] ?? ''))); ?></span>
|
||||
<span><?php e($rev['changed_by_label'] ?? ('#' . ($rev['changed_by'] ?? ''))); ?> · <?php e(dt($rev['created_at'] ?? '')); ?></span>
|
||||
</span>
|
||||
</a>
|
||||
<?php if (!$isCurrent): ?>
|
||||
<a href="<?php e($compareUrl); ?>" class="handover-revision-compare" aria-label="<?php e(t('Compare with current')); ?>" data-tooltip="<?php e(t('Compare with current')); ?>">
|
||||
<i class="bi bi-arrow-left-right"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($isViewingRevision && $canManage && $activeRevisionNumber < (int) ($revisions[0]['revision'] ?? 0)): ?>
|
||||
<form method="post" class="handover-revision-restore">
|
||||
<input type="hidden" name="action" value="restore">
|
||||
<input type="hidden" name="restore_revision" value="<?php e($activeRevisionNumber); ?>">
|
||||
<?php \MintyPHP\Session::getCsrfInput(); ?>
|
||||
<button type="submit" class="secondary outline small" onclick="return confirm('<?php e(t('Restore this version?')); ?>');">
|
||||
<i class="bi bi-arrow-counterclockwise"></i> <?php e(t('Restore this version')); ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</details>
|
||||
<hr>
|
||||
<p><?php e($productName ?? ($handover['product_code'] ?? '')); ?></p>
|
||||
<?php if (($productName ?? '') !== ($handover['product_code'] ?? '')): ?>
|
||||
<p><small><?php e(t('Code')); ?></small><br><span class="badge" data-variant="neutral"><?php e($handover['product_code'] ?? ''); ?></span></p>
|
||||
<?php endif; ?>
|
||||
</details>
|
||||
<hr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$asideAuditDetailsName = 'handover-aside';
|
||||
|
||||
Reference in New Issue
Block a user