Bug 29016 [25.11.x]: (QA follow-up) Translate circulation overrides in info column
CIRCULATION rows whose info payload is a JSON object can carry `forced`
and `confirmations` arrays listing the override codes a librarian
acknowledged when checking out, renewing or otherwise circulating an
item (e.g. DEBT, AGE_RESTRICTION, BIBLIO_ALREADY_ISSUED). The old
server-rendered template ran these through the translate_log_override
SWITCH block in action-logs.inc; in the new AJAX-driven viewer the JS
default branch was just escapeHtml()-ing the raw JSON, leaving the
codes unlocalised and unreadable.
Port the override -> human-readable label map into str/viewlog.inc as
override_labels, mirroring the module/action/interface label maps
already there. Add a renderCircInfo() helper that recognises the JSON
shape and emits the same div.forced / div.confirmed blocks the old
template did, so the existing CSS continues to apply.
renderInfo() now falls through CIRCULATION -> renderCircInfo, the
generic record-info renderer (CATALOGUING / AUTHORITIES from bug 42032)
and finally escapeHtml(), so non-JSON payloads (item barcodes,
ID-only info, ...) keep displaying as before.
Test plan:
1. Enable IssueLog.
2. Trigger an override checkout (e.g. patron with debt, item not for
loan) and confirm.
3. Tools -> Log viewer, filter by module=Circulation.
4. The override row's Info column should show one or more localised
"Patron had debt", "Item was not for loan", ... lines instead of
a raw JSON blob.
5. Plain ISSUE / RETURN rows (no overrides) keep showing the raw
itemnumber / barcode payload as before.
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Jacob O'Mara <Jacob.omara@openfifth.co.uk>
This commit is contained in:
parent
78a9da8e33
commit
2b165d347e
|
|
@ -94,5 +94,52 @@
|
|||
CRON: _("Cron job"),
|
||||
UPGRADE: _("Upgrade"),
|
||||
};
|
||||
|
||||
/*
|
||||
* Translation map for circulation override codes that appear in the
|
||||
* `forced` and `confirmations` arrays of a CIRCULATION info column
|
||||
* JSON payload (see C4::Circulation::CanBookBeIssued and friends).
|
||||
*/
|
||||
var override_labels = {
|
||||
HOLD_POLICY_OVERRIDE: _("Hold policy override"),
|
||||
INVALID_DATE: _("Invalid date confirmed"),
|
||||
DEBT_GUARANTEES: _("Guarantees had debts"),
|
||||
DEBT_GUARANTORS: _("Guarantors had debts"),
|
||||
DEBT: _("Patron had debt"),
|
||||
USERBLOCKEDOVERDUE: _("Patron had overdues"),
|
||||
USERBLOCKEDNOENDDATE: _("Patron had an indefinite restriction"),
|
||||
ADDITIONAL_MATERIALS: _("Additional materials were confirmed"),
|
||||
RENEW_ISSUE: _("Renewal"),
|
||||
ISSUED_TO_ANOTHER: _("Item was issued to another patron prior to this checkout"),
|
||||
PATRON_CANT: _("Max renewals reached"),
|
||||
TOO_MANY: _("Too many checkouts"),
|
||||
PREVISSUE: _("Patron was notified of previous issues"),
|
||||
NOT_FOR_LOAN: _("Item was not for loan"),
|
||||
NOT_FOR_LOAN_FORCING: _("Item was not for loan"),
|
||||
ITEM_LOST: _("Item was reported lost"),
|
||||
BORRNOTSAMEBRANCH: _("Patron does not belong to this library"),
|
||||
RENTALCHARGE: _("Item had a rental charge"),
|
||||
RECALLED: _("Item was recalled"),
|
||||
RESERVE_WAITING: _("Hold waiting on item"),
|
||||
RESERVED: _("Hold placed on item"),
|
||||
TRANSFERRED: _("Item was transferred"),
|
||||
PROCESSING: _("Item was in processing"),
|
||||
BOOKED_EARLY: _("Item was booked and checked out early"),
|
||||
BOOKED_TO_ANOTHER: _("Item was booked to another patron"),
|
||||
AGE_RESTRICTION: _("Item has an age restriction"),
|
||||
HIGHHOLDS: _("Item had high holds"),
|
||||
BIBLIO_ALREADY_ISSUED: _("An item from this bibliographic record is already checked out"),
|
||||
DEBARRED: _("Patron is restricted"),
|
||||
RENEWAL_LIMIT: _("Renewal limit override"),
|
||||
UNSEEN_LIMIT: _("Override unseen item restriction"),
|
||||
AUTO_TOO_SOON: _("Override auto-renew too soon restriction"),
|
||||
AUTO_TOO_LATE: _("Override auto-renew too late restriction"),
|
||||
ACCOUNT_EXPIRED: _("Account expired override"),
|
||||
ON_RESERVE: _("Override renew hold for another"),
|
||||
TOO_MUCH: _("Override auto-renew too much owing restriction"),
|
||||
CURRENTISSUE: _("Item is currently checked out to this patron"),
|
||||
UNSEEN: _("Unseen renewal"),
|
||||
SEEN: _("Seen renewal"),
|
||||
};
|
||||
</script>
|
||||
<!-- / str/viewlog.inc -->
|
||||
|
|
|
|||
|
|
@ -1,3 +1,194 @@
|
|||
/**
|
||||
* Recursively collect changed entries from a Struct::Diff node.
|
||||
* Returns an array of { key, before, after } objects.
|
||||
* Nested diffs are flattened with dot-separated keys.
|
||||
*/
|
||||
function collectDiffEntries(node, prefix) {
|
||||
var entries = [];
|
||||
Object.keys(node).forEach(function (key) {
|
||||
var change = node[key];
|
||||
var fullKey = prefix ? prefix + "." + key : key;
|
||||
if ("O" in change || "N" in change) {
|
||||
entries.push({ key: fullKey, before: change.O, after: change.N });
|
||||
} else if ("A" in change) {
|
||||
entries.push({ key: fullKey, before: undefined, after: change.A });
|
||||
} else if ("R" in change) {
|
||||
entries.push({ key: fullKey, before: change.R, after: undefined });
|
||||
} else if ("D" in change) {
|
||||
entries = entries.concat(collectDiffEntries(change.D, fullKey));
|
||||
}
|
||||
});
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-print a parsed JSON value as nested <ul> markup. Arrays become
|
||||
* ordered lists preserving MARC-in-JSON field order; objects render their
|
||||
* keys. Used by both the struct-diff renderer and the catalog info renderer.
|
||||
*/
|
||||
function fmtJsonValue(v) {
|
||||
if (v === undefined || v === null) return "";
|
||||
if (typeof v !== "object") return $("<span>").text(String(v)).html();
|
||||
if (Array.isArray(v)) {
|
||||
var items = v
|
||||
.map(function (item) {
|
||||
return "<li>" + fmtJsonValue(item) + "</li>";
|
||||
})
|
||||
.join("");
|
||||
return '<ul class="diff-obj">' + items + "</ul>";
|
||||
}
|
||||
var items = Object.keys(v)
|
||||
.map(function (k) {
|
||||
return (
|
||||
"<li>" +
|
||||
$("<span>").text(k).html() +
|
||||
": " +
|
||||
fmtJsonValue(v[k]) +
|
||||
"</li>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
return '<ul class="diff-obj">' + items + "</ul>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the info column for a CATALOGUING or AUTHORITIES log entry.
|
||||
* The payload is "biblio <JSON>" or "authority <JSON>" where the JSON
|
||||
* is the pre-change (MODIFY) or final (DELETE) state of the record,
|
||||
* including a MARC-in-JSON `_marc` key. Returns rendered HTML, or null
|
||||
* if the text doesn't look like a record JSON payload (e.g. the bare
|
||||
* "biblio" / "authority" prefix logged on ADD).
|
||||
*/
|
||||
function renderRecordInfo(raw) {
|
||||
if (!raw) return null;
|
||||
var match = raw.match(/^(?:biblio|authority)\s+(\{[\s\S]*\})\s*$/);
|
||||
if (!match) return null;
|
||||
var parsed;
|
||||
try {
|
||||
parsed = JSON.parse(match[1]);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
return '<div class="loginfo-struct">' + fmtJsonValue(parsed) + "</div>";
|
||||
}
|
||||
|
||||
/* global override_labels */
|
||||
|
||||
/**
|
||||
* Render the info column for a CIRCULATION log entry whose payload is a
|
||||
* JSON object containing `forced` and/or `confirmations` arrays of
|
||||
* override codes. Returns rendered HTML, or null if the payload doesn't
|
||||
* look like a circulation JSON object.
|
||||
*/
|
||||
function renderCircInfo(raw) {
|
||||
if (!raw) return null;
|
||||
var trimmed = raw.replace(/^\s+|\s+$/g, "");
|
||||
if (trimmed.charAt(0) !== "{") return null;
|
||||
var parsed;
|
||||
try {
|
||||
parsed = JSON.parse(trimmed);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
if (!parsed || typeof parsed !== "object") return null;
|
||||
|
||||
var forced =
|
||||
Array.isArray(parsed.forced) && parsed.forced.length
|
||||
? parsed.forced
|
||||
: null;
|
||||
var confirmations =
|
||||
Array.isArray(parsed.confirmations) && parsed.confirmations.length
|
||||
? parsed.confirmations
|
||||
: null;
|
||||
if (!forced && !confirmations) return null;
|
||||
|
||||
function translate(code) {
|
||||
return override_labels[code] || escapeHtml(code);
|
||||
}
|
||||
|
||||
var html = "";
|
||||
if (forced) {
|
||||
html += forced
|
||||
.map(function (c) {
|
||||
return '<div class="forced">' + translate(c) + "</div>";
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
if (confirmations) {
|
||||
html += confirmations
|
||||
.map(function (c) {
|
||||
return '<div class="confirmed">' + translate(c) + "</div>";
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Struct::Diff JSON as a human-readable before/after table.
|
||||
* Falls back to a <pre> block for non-Struct::Diff JSON or plain text.
|
||||
*/
|
||||
function renderStructDiff(raw) {
|
||||
if (!raw) return "";
|
||||
var diff;
|
||||
try {
|
||||
diff = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
return $("<pre>").text(raw)[0].outerHTML;
|
||||
}
|
||||
if (!diff || !diff.D || typeof diff.D !== "object") {
|
||||
return $("<pre>").text(raw)[0].outerHTML;
|
||||
}
|
||||
|
||||
var entries = collectDiffEntries(diff.D, "");
|
||||
if (!entries.length) return "";
|
||||
|
||||
var rows = entries
|
||||
.map(function (e) {
|
||||
var before =
|
||||
e.before !== undefined
|
||||
? "<del>" + fmtJsonValue(e.before) + "</del>"
|
||||
: "";
|
||||
var after =
|
||||
e.after !== undefined
|
||||
? "<ins>" + fmtJsonValue(e.after) + "</ins>"
|
||||
: "";
|
||||
return (
|
||||
"<tr>" +
|
||||
'<td class="diff-key">' +
|
||||
$("<span>").text(e.key).html() +
|
||||
"</td>" +
|
||||
'<td class="diff-before">' +
|
||||
before +
|
||||
"</td>" +
|
||||
'<td class="diff-after">' +
|
||||
after +
|
||||
"</td>" +
|
||||
"</tr>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
|
||||
return (
|
||||
'<table class="struct-diff">' +
|
||||
"<thead><tr>" +
|
||||
"<th>" +
|
||||
__("Field") +
|
||||
"</th>" +
|
||||
"<th>" +
|
||||
__("Before") +
|
||||
"</th>" +
|
||||
"<th>" +
|
||||
__("After") +
|
||||
"</th>" +
|
||||
"</tr></thead>" +
|
||||
"<tbody>" +
|
||||
rows +
|
||||
"</tbody>" +
|
||||
"</table>"
|
||||
);
|
||||
}
|
||||
|
||||
function tickAll(section) {
|
||||
$("input[type='checkbox'][name='" + section + "']").prop("checked", true);
|
||||
$("#" + section.slice(0, -1) + "ALL").prop("checked", true);
|
||||
|
|
@ -202,8 +393,16 @@ function renderInfo(data, type, row) {
|
|||
);
|
||||
}
|
||||
|
||||
var rendered = renderRecordInfo(info);
|
||||
var body = rendered !== null ? rendered : escapeHtml(info);
|
||||
var body = null;
|
||||
if (mod == "CIRCULATION") {
|
||||
body = renderCircInfo(info);
|
||||
}
|
||||
if (body === null) {
|
||||
body = renderRecordInfo(info);
|
||||
}
|
||||
if (body === null) {
|
||||
body = escapeHtml(info);
|
||||
}
|
||||
return (
|
||||
'<div class="loginfo" id="loginfo' +
|
||||
escapeHtml(action_id) +
|
||||
|
|
|
|||
Loading…
Reference in New Issue