feat: Enhance error logging for PDF preview failures and SQL timeouts
- Implemented a new method `ShapeTableNames` to gather entity and navigation table names, ensuring no blanks or duplicates are included. - Updated `FdsMfrClient` to handle cases where schema queries return no tables, logging appropriate warnings and preventing misleading errors. - Introduced `PdfPreviewAsync` in `IntranetController` to manage PDF preview rendering, capturing detailed logs for failures and notifying users with readable error messages. - Added comprehensive unit tests for error logging and table name shaping to ensure robustness and correctness. - Documented decision to persist user action failures to the `fds__admin_logdebug` table, enhancing support diagnostics.
This commit is contained in:
@@ -162,6 +162,9 @@ let $ict = {
|
||||
pna: 'Diese Seite ist in der Vorschau nicht verfügbar',
|
||||
tpe: 'Die Anzahl von {0} Seiten wird aktuell nicht unterstützt',
|
||||
eis: 'Der Rechnungsentwurf konnte nicht gespeichert werden.',
|
||||
pvf: 'Die PDF-Vorschau konnte nicht erstellt werden.',
|
||||
pvto: 'Der Server hat nicht rechtzeitig geantwortet (Zeitüberschreitung). Bitte versuchen Sie es erneut.',
|
||||
pvgone: 'Der Entwurf ist auf dem Server nicht mehr vorhanden (Sitzung abgelaufen). Bitte öffnen Sie den Entwurf erneut.',
|
||||
iss: 'Zwischenstand speichern.',
|
||||
p13b: 'USt -> §13b',
|
||||
setm: 'Set-Preisanzeige',
|
||||
@@ -692,6 +695,29 @@ $inv.eM = (r, re, opt) => {
|
||||
}
|
||||
return $('#topbar').ocmsmenu(m); /* empty Array => empty menu */
|
||||
};
|
||||
/* ── PDF preview failure handling (shared by every image-preview call) ────────
|
||||
A preview render can legitimately take longer than the 30s postXT default (Spire
|
||||
rasterises every page), and postXT swallows a timeout silently — the user would
|
||||
sit in front of a frozen editor with no message. So previews get a longer timeout
|
||||
and one handler that always tells the user something: the server's description
|
||||
header when it sent one (the backend logs the failure and puts the reason there),
|
||||
a timeout hint when the request timed out, the generic text otherwise. */
|
||||
$inv.pdfPreviewTimeout = 120; /* seconds */
|
||||
$inv.pdfPreviewFailed = function (jqXHR, textStatus) {
|
||||
if ((jqXHR || {}).internalCode === 11110) { $ocms.login.dlg(); return; }
|
||||
let desc = ((jqXHR || {}).internalText || '').trim();
|
||||
if (desc === '' && textStatus === 'timeout') { desc = $ict.pvto; }
|
||||
if (desc === '' && (jqXHR || {}).status === 410) { desc = $ict.pvgone; }
|
||||
alert(($ict.pvf || $t.f1) + (desc !== '' ? '\n' + desc : ''));
|
||||
};
|
||||
/* postXT never calls `error` for a timeout (it returns early), so the timeout is only
|
||||
observable from `complete`; this builds the complete handler for a preview call. */
|
||||
$inv.pdfPreviewComplete = function (layout) {
|
||||
return (jqXHR, textStatus) => {
|
||||
if (layout) { layout.rC('freeze'); }
|
||||
if (textStatus === 'timeout') { $inv.pdfPreviewFailed(jqXHR, textStatus); }
|
||||
};
|
||||
};
|
||||
/* ── Backend-authoritative draft editing controller (ADR 0006/0007) ───────────
|
||||
The server holds the truth for an invoice draft in an in-memory session; this
|
||||
object seeds it, sends single edits as deltas, and renders the totals footer +
|
||||
@@ -928,7 +954,7 @@ $inv.d = {
|
||||
if ($fis.ValidateEmail(email) === false) { if (bool(confirm($ict.ivE + $ict.ivEc), false) === false) { return; } }
|
||||
l.aC('freeze');
|
||||
$ocms.postXT({
|
||||
url: $ocms.url('inv/dpreview'), data: { token: t }, success: (response) => {
|
||||
url: $ocms.url('inv/dpreview'), data: { token: t }, timeout: $inv.pdfPreviewTimeout, success: (response) => {
|
||||
l.rC('freeze');
|
||||
let c = $$.dc('imagecollection pdfpreview'), vhr = Math.round(vh() * 0.88), total = response.total;
|
||||
if (total > 10) { $$.dc('note warn', c).text($ict.tpe); }
|
||||
@@ -953,7 +979,7 @@ $inv.d = {
|
||||
},
|
||||
cancel: function (e) { if (confirm($ict.cdI)) { $inv.d.close(); $inv.rReload(); } }
|
||||
});
|
||||
}, error: () => { l.rC('freeze'); alert($ict.eis); }
|
||||
}, error: $inv.pdfPreviewFailed, complete: $inv.pdfPreviewComplete(l)
|
||||
});
|
||||
},
|
||||
/* Zwischenspeichern: flush the cache to the DB (no re-upload); stay in the editor. */
|
||||
@@ -1116,7 +1142,7 @@ $inv.rd = {
|
||||
if ($fis.ValidateEmail(email) === false) { if (bool(confirm($ict.ivE + $ict.ivEc), false) === false) { return; } }
|
||||
l.aC('freeze');
|
||||
$ocms.postXT({
|
||||
url: $ocms.url('rem/dpreview'), data: { token: t }, success: (response) => {
|
||||
url: $ocms.url('rem/dpreview'), data: { token: t }, timeout: $inv.pdfPreviewTimeout, success: (response) => {
|
||||
l.rC('freeze');
|
||||
let c = $$.dc('imagecollection pdfpreview'), vhr = Math.round(vh() * 0.88);
|
||||
$.each(response.img || [], (ii, img) => { $$.dc('pdfp', c).append($$.img(img).css('max-height', (vhr - rpx(6)).toString() + 'px')); });
|
||||
@@ -1139,7 +1165,7 @@ $inv.rd = {
|
||||
},
|
||||
cancel: function (e) { if (confirm($ict.cdI)) { $inv.rd.close(); $inv.rReload(); } }
|
||||
});
|
||||
}, error: () => { l.rC('freeze'); alert($t.f1); }
|
||||
}, error: $inv.pdfPreviewFailed, complete: $inv.pdfPreviewComplete(l)
|
||||
});
|
||||
},
|
||||
/* Zwischenspeichern: flush the cache to the DB (no re-upload); stay in the editor. */
|
||||
@@ -2116,7 +2142,7 @@ $inv.disp = (id, typ) => {
|
||||
}
|
||||
if (u !== '') {
|
||||
$ocms.postXT({
|
||||
url: $ocms.url(u), data: { id: id || '', typ: 'img' }, success: (response) => {
|
||||
url: $ocms.url(u), data: { id: id || '', typ: 'img' }, timeout: $inv.pdfPreviewTimeout, success: (response) => {
|
||||
let c = $$.dc('imagecollection pdfpreview'), vhr = Math.round(vh() * 0.88), invid = response.id;
|
||||
$.each(response.img || [], function (ii, img) {
|
||||
$$.dc('pdfp', c).append($$.img(img).css('max-height', (vhr - rpx(6)).toString() + 'px'));
|
||||
@@ -2124,13 +2150,13 @@ $inv.disp = (id, typ) => {
|
||||
$ocms.dlg(c, {
|
||||
size: [vhr, Math.round(vw() * 0.88)], zindex_min: 50, form: false, exclusive:false
|
||||
});
|
||||
}
|
||||
}, error: $inv.pdfPreviewFailed, complete: $inv.pdfPreviewComplete()
|
||||
});
|
||||
}
|
||||
};
|
||||
$inv.jdbn = function (ev) {
|
||||
$ocms.postXT({
|
||||
url: $ocms.url('inv/rdocn'), data: { name: ev.data.id || '', typ: 'img' }, success: (response) => {
|
||||
url: $ocms.url('inv/rdocn'), data: { name: ev.data.id || '', typ: 'img' }, timeout: $inv.pdfPreviewTimeout, success: (response) => {
|
||||
let c = $$.dc('imagecollection pdfpreview'), vhr = Math.round(vh() * 0.88), invid = response.id;
|
||||
$.each(response.img || [], function (ii, img) {
|
||||
$$.dc('pdfp', c).append($$.img(img).css('max-height', (vhr - rpx(6)).toString() + 'px'));
|
||||
@@ -2138,7 +2164,7 @@ $inv.jdbn = function (ev) {
|
||||
$ocms.dlg(c, {
|
||||
size: [vhr, Math.round(vw() * 0.88)], zindex: 50, form: false
|
||||
});
|
||||
}
|
||||
}, error: $inv.pdfPreviewFailed, complete: $inv.pdfPreviewComplete()
|
||||
});
|
||||
};
|
||||
$inv.sp13b = () => {
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -162,6 +162,9 @@ let $ict = {
|
||||
pna: 'Diese Seite ist in der Vorschau nicht verfügbar',
|
||||
tpe: 'Die Anzahl von {0} Seiten wird aktuell nicht unterstützt',
|
||||
eis: 'Der Rechnungsentwurf konnte nicht gespeichert werden.',
|
||||
pvf: 'Die PDF-Vorschau konnte nicht erstellt werden.',
|
||||
pvto: 'Der Server hat nicht rechtzeitig geantwortet (Zeitüberschreitung). Bitte versuchen Sie es erneut.',
|
||||
pvgone: 'Der Entwurf ist auf dem Server nicht mehr vorhanden (Sitzung abgelaufen). Bitte öffnen Sie den Entwurf erneut.',
|
||||
iss: 'Zwischenstand speichern.',
|
||||
p13b: 'USt -> §13b',
|
||||
setm: 'Set-Preisanzeige',
|
||||
@@ -673,6 +676,29 @@ $inv.eM = (r, re, opt) => {
|
||||
}
|
||||
return $('#topbar').ocmsmenu(m); /* empty Array => empty menu */
|
||||
};
|
||||
/* ── PDF preview failure handling (shared by every image-preview call) ────────
|
||||
A preview render can legitimately take longer than the 30s postXT default (Spire
|
||||
rasterises every page), and postXT swallows a timeout silently — the user would
|
||||
sit in front of a frozen editor with no message. So previews get a longer timeout
|
||||
and one handler that always tells the user something: the server's description
|
||||
header when it sent one (the backend logs the failure and puts the reason there),
|
||||
a timeout hint when the request timed out, the generic text otherwise. */
|
||||
$inv.pdfPreviewTimeout = 120; /* seconds */
|
||||
$inv.pdfPreviewFailed = function (jqXHR, textStatus) {
|
||||
if ((jqXHR || {}).internalCode === 11110) { $ocms.login.dlg(); return; }
|
||||
let desc = ((jqXHR || {}).internalText || '').trim();
|
||||
if (desc === '' && textStatus === 'timeout') { desc = $ict.pvto; }
|
||||
if (desc === '' && (jqXHR || {}).status === 410) { desc = $ict.pvgone; }
|
||||
alert(($ict.pvf || $t.f1) + (desc !== '' ? '\n' + desc : ''));
|
||||
};
|
||||
/* postXT never calls `error` for a timeout (it returns early), so the timeout is only
|
||||
observable from `complete`; this builds the complete handler for a preview call. */
|
||||
$inv.pdfPreviewComplete = function (layout) {
|
||||
return (jqXHR, textStatus) => {
|
||||
if (layout) { layout.rC('freeze'); }
|
||||
if (textStatus === 'timeout') { $inv.pdfPreviewFailed(jqXHR, textStatus); }
|
||||
};
|
||||
};
|
||||
/* ── Backend-authoritative draft editing controller (ADR 0006/0007) ───────────
|
||||
The server holds the truth for an invoice draft in an in-memory session; this
|
||||
object seeds it, sends single edits as deltas, and renders the totals footer +
|
||||
@@ -909,7 +935,7 @@ $inv.d = {
|
||||
if ($fis.ValidateEmail(email) === false) { if (bool(confirm($ict.ivE + $ict.ivEc), false) === false) { return; } }
|
||||
l.aC('freeze');
|
||||
$ocms.postXT({
|
||||
url: $ocms.url('inv/dpreview'), data: { token: t }, success: (response) => {
|
||||
url: $ocms.url('inv/dpreview'), data: { token: t }, timeout: $inv.pdfPreviewTimeout, success: (response) => {
|
||||
l.rC('freeze');
|
||||
let c = $$.dc('imagecollection pdfpreview'), vhr = Math.round(vh() * 0.88), total = response.total;
|
||||
if (total > 10) { $$.dc('note warn', c).text($ict.tpe); }
|
||||
@@ -934,7 +960,7 @@ $inv.d = {
|
||||
},
|
||||
cancel: function (e) { if (confirm($ict.cdI)) { $inv.d.close(); $inv.rReload(); } }
|
||||
});
|
||||
}, error: () => { l.rC('freeze'); alert($ict.eis); }
|
||||
}, error: $inv.pdfPreviewFailed, complete: $inv.pdfPreviewComplete(l)
|
||||
});
|
||||
},
|
||||
/* Zwischenspeichern: flush the cache to the DB (no re-upload); stay in the editor. */
|
||||
@@ -1097,7 +1123,7 @@ $inv.rd = {
|
||||
if ($fis.ValidateEmail(email) === false) { if (bool(confirm($ict.ivE + $ict.ivEc), false) === false) { return; } }
|
||||
l.aC('freeze');
|
||||
$ocms.postXT({
|
||||
url: $ocms.url('rem/dpreview'), data: { token: t }, success: (response) => {
|
||||
url: $ocms.url('rem/dpreview'), data: { token: t }, timeout: $inv.pdfPreviewTimeout, success: (response) => {
|
||||
l.rC('freeze');
|
||||
let c = $$.dc('imagecollection pdfpreview'), vhr = Math.round(vh() * 0.88);
|
||||
$.each(response.img || [], (ii, img) => { $$.dc('pdfp', c).append($$.img(img).css('max-height', (vhr - rpx(6)).toString() + 'px')); });
|
||||
@@ -1120,7 +1146,7 @@ $inv.rd = {
|
||||
},
|
||||
cancel: function (e) { if (confirm($ict.cdI)) { $inv.rd.close(); $inv.rReload(); } }
|
||||
});
|
||||
}, error: () => { l.rC('freeze'); alert($t.f1); }
|
||||
}, error: $inv.pdfPreviewFailed, complete: $inv.pdfPreviewComplete(l)
|
||||
});
|
||||
},
|
||||
/* Zwischenspeichern: flush the cache to the DB (no re-upload); stay in the editor. */
|
||||
@@ -2097,7 +2123,7 @@ $inv.disp = (id, typ) => {
|
||||
}
|
||||
if (u !== '') {
|
||||
$ocms.postXT({
|
||||
url: $ocms.url(u), data: { id: id || '', typ: 'img' }, success: (response) => {
|
||||
url: $ocms.url(u), data: { id: id || '', typ: 'img' }, timeout: $inv.pdfPreviewTimeout, success: (response) => {
|
||||
let c = $$.dc('imagecollection pdfpreview'), vhr = Math.round(vh() * 0.88), invid = response.id;
|
||||
$.each(response.img || [], function (ii, img) {
|
||||
$$.dc('pdfp', c).append($$.img(img).css('max-height', (vhr - rpx(6)).toString() + 'px'));
|
||||
@@ -2105,13 +2131,13 @@ $inv.disp = (id, typ) => {
|
||||
$ocms.dlg(c, {
|
||||
size: [vhr, Math.round(vw() * 0.88)], zindex_min: 50, form: false, exclusive:false
|
||||
});
|
||||
}
|
||||
}, error: $inv.pdfPreviewFailed, complete: $inv.pdfPreviewComplete()
|
||||
});
|
||||
}
|
||||
};
|
||||
$inv.jdbn = function (ev) {
|
||||
$ocms.postXT({
|
||||
url: $ocms.url('inv/rdocn'), data: { name: ev.data.id || '', typ: 'img' }, success: (response) => {
|
||||
url: $ocms.url('inv/rdocn'), data: { name: ev.data.id || '', typ: 'img' }, timeout: $inv.pdfPreviewTimeout, success: (response) => {
|
||||
let c = $$.dc('imagecollection pdfpreview'), vhr = Math.round(vh() * 0.88), invid = response.id;
|
||||
$.each(response.img || [], function (ii, img) {
|
||||
$$.dc('pdfp', c).append($$.img(img).css('max-height', (vhr - rpx(6)).toString() + 'px'));
|
||||
@@ -2119,7 +2145,7 @@ $inv.jdbn = function (ev) {
|
||||
$ocms.dlg(c, {
|
||||
size: [vhr, Math.round(vw() * 0.88)], zindex: 50, form: false
|
||||
});
|
||||
}
|
||||
}, error: $inv.pdfPreviewFailed, complete: $inv.pdfPreviewComplete()
|
||||
});
|
||||
};
|
||||
$inv.sp13b = () => {
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user