Files
itflow/agent/modals/software/software_renew.php
johnnyq a33f98ffa5 Licenses: rebuilt the license list with Renewing soon, Expired, Over seats and No seats assigned tabs, a seat bar (used against bought, red when over) and colour-coded renewal on each row, license type and renewal filters, optional key, purchase and order reference columns, search by vendor, order reference and individual keys, and bulk Renew, Archive, Restore and Delete; new license page (license.php) with renewal and over-seats status in the header, License and Vendor cards (key, order ref, purchase date, vendor account number, phone, email, website, hours, SLA, all copyable), notes that save on blur, and tabs for Seats (each device and user with the key they were given), Keys, Credentials, Documents, Files and History from the audit log; new license quick look used everywhere a license is shown; New and Edit share one tabbed form (Details, License, Seats, Notes) with device and user pickers, a live seat counter, renewal shortcuts and a template picker that replaces the separate Create from Template modal; Renew modal for one license or a bulk selection that moves the renewal date by a term or to a set date, previews the new date for each license and records a new order reference; Manage Seats modal; individual keys pasted one per line and handed out to devices and users, which gives them a seat, with the key returned to the pool when they come off the license; Renewal Ticket opens New Ticket with the subject and details filled in; New Document and Upload from a license link the result to it; devices, users, credentials and files now link to licenses through the shared link modal, which shows the seats left on each license and offers every license type, not just Device; the asset and contact pages and quick looks list licenses with their seats, renewal and the key to install, and get New License; the client overview gets a Licenses over seats card under Needs attention, starred licenses under Favorites and renewals that open the license page; global search finds licenses by name, vendor, order reference or key; the vendor quick look lists that vendor's licenses; the dashboard card opens the Renewing tab; the export gains seats used, individual keys, vendor, order reference and client columns; admin license templates share the license type list and show type icons. Uses the existing software_keys, key assignment, software_credentials and software_files tables. Fixes editing a Subscription or Open Source license clearing its license type, archiving a license removing all of its seats (seats are now kept, matching the API), license forms and seat handlers accepting another client's assets, contacts or vendor, a 500 on duplicate seat links and on forms missing fields, and an extra query per row in the export; license deletes now respect the destructive deletes setting. No DB change
2026-10-14 21:31:30 -04:00

146 lines
6.9 KiB
PHP

<?php
/*
* Renew one license (?id=) or the ones ticked on the list (?software_ids[]=).
* Move each renewal date on by a term - from its current date, or from today
* if there is none or it has passed - or set one date for all of them. The
* new date for each shows before you save. Optionally record the new order
* or invoice reference.
*/
require_once '../../../includes/modal_header.php';
enforceUserPermission('module_support', 2);
$ids = isset($_GET['id']) ? [intval($_GET['id'])] : array_map('intval', (array) ($_GET['software_ids'] ?? []));
$licenses = [];
foreach (array_unique($ids) as $software_id) {
$row = softwareRow($software_id);
if (!$row) {
continue;
}
$client_id = intval($row['software_client_id']);
enforceClientAccess();
$licenses[] = $row;
}
if (!$licenses) {
echo json_encode(['error' => 'Tick the licenses to renew first.']);
exit;
}
$single = count($licenses) === 1;
$default_term = '1y';
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title text-truncate"><i class="fas fa-fw fa-redo-alt me-2"></i>Renew <?= $single ? '<strong>' . escapeHtml($licenses[0]['software_name']) . '</strong>' : '<strong>' . count($licenses) . '</strong> licenses' ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off" data-renew-form>
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<?php foreach ($licenses as $row) { ?><input type="hidden" name="software_ids[]" value="<?= intval($row['software_id']) ?>"><?php } ?>
<input type="hidden" name="renew_mode" value="extend" data-renew-mode>
<div class="modal-body">
<label class="form-label">Renew for</label>
<div class="d-flex flex-wrap gap-2 mb-2" role="group" aria-label="Term">
<?php foreach (SOFTWARE_RENEWAL_TERMS as $term => $term_label) { ?>
<input type="radio" class="btn-check" name="renew_term" id="renewTerm<?= $term ?>" value="<?= $term ?>" <?= $term === $default_term ? 'checked' : '' ?>>
<label class="btn btn-outline-primary btn-sm" for="renewTerm<?= $term ?>"><?= $term_label ?></label>
<?php } ?>
<input type="radio" class="btn-check" name="renew_term" id="renewTermDate" value="date">
<label class="btn btn-outline-secondary btn-sm" for="renewTermDate"><i class="fas fa-calendar-alt me-1"></i>A date</label>
</div>
<div class="mb-3" data-renew-date-box hidden>
<input type="date" class="form-control" name="renew_date" max="2999-12-31" data-renew-date>
</div>
<div class="form-text mb-3">Each date moves on from its current renewal date, or from today if it has already passed.</div>
<div class="table-responsive border rounded mb-3">
<table class="table table-sm align-middle mb-0">
<thead class="table-light text-nowrap"><tr><th class="ps-2">License</th><th>Renews now</th><th class="pe-2">New date</th></tr></thead>
<tbody>
<?php foreach ($licenses as $row) {
$renewal = softwareRenewal($row['software_expire']);
$next = [];
foreach (array_keys(SOFTWARE_RENEWAL_TERMS) as $term) {
$next[$term] = softwareRenewalExtended($row['software_expire'], $term);
} ?>
<tr>
<td class="ps-2">
<div class="d-flex align-items-center gap-2">
<?= softwareTile($row['software_type'], 'sm') ?>
<div class="min-w-0">
<div class="fw-semibold text-truncate"><?= escapeHtml($row['software_name']) ?></div>
<?php if (!$single) { ?><div class="small text-body-secondary text-truncate"><?= escapeHtml($row['client_name']) ?></div><?php } ?>
</div>
</div>
</td>
<td class="text-nowrap">
<?php if ($renewal) { ?>
<?= escapeHtml($renewal['date']) ?>
<div class="small text-<?= $renewal['tone'] === 'success' ? 'body-secondary' : $renewal['tone'] . '-emphasis' ?>"><?= escapeHtml($renewal['text']) ?></div>
<?php } else { ?>
<span class="text-body-secondary">No date</span>
<?php } ?>
</td>
<td class="text-nowrap fw-semibold text-success-emphasis pe-2" data-renew-next='<?= escapeHtml(json_encode($next)) ?>'><?= $next[$default_term] ?></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<label class="form-label">New order / invoice reference</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-fw fa-receipt"></i></span>
<input type="text" class="form-control" name="purchase_reference" maxlength="200" placeholder="Leave blank to keep <?= $single && $licenses[0]['software_purchase_reference'] ? escapeHtml($licenses[0]['software_purchase_reference']) : 'what each has' ?>">
</div>
</div>
<div class="modal-footer">
<button type="submit" name="renew_software" class="btn btn-primary text-bold"><i class="fas fa-redo-alt me-2"></i>Renew</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Cancel</button>
</div>
</form>
<script>
(function () {
var form = document.querySelector('form[data-renew-form]:not([data-wired])');
if (!form) {
return;
}
form.dataset.wired = '1';
var mode = form.querySelector('[data-renew-mode]');
var dateBox = form.querySelector('[data-renew-date-box]');
var date = form.querySelector('[data-renew-date]');
function update() {
var term = form.querySelector('[name="renew_term"]:checked').value;
var byDate = term === 'date';
mode.value = byDate ? 'date' : 'extend';
dateBox.hidden = !byDate;
date.required = byDate;
form.querySelectorAll('[data-renew-next]').forEach(function (cell) {
cell.textContent = byDate ? (date.value || '-') : JSON.parse(cell.dataset.renewNext)[term];
});
}
form.querySelectorAll('[name="renew_term"]').forEach(function (radio) {
radio.addEventListener('change', function () {
update();
if (radio.value === 'date') {
date.focus();
}
});
});
date.addEventListener('input', update);
update();
})();
</script>
<?php
require_once '../../../includes/modal_footer.php';