feat: Implement admin-only specialties management interface
Playwright Tests / test (push) Canceled after 0s

- Added MailerAttachmentPolicy for validating email attachments against server rules.
- Introduced fis.specialties.js for managing hidden maintenance actions via an admin interface.
- Created fis.specialties.scss for styling the specialties management UI.
- Added German localization for specialties management in fis.specialties_txt_de.js.
- Developed CSS for specialties management in fis.specialties.css.
- Implemented server-side stored procedure fds__admin_backupAndClearInvoiceFile for backing up and clearing invoice files.
- Created fds__invoice_file_backups table to store backup records of invoice files.
This commit is contained in:
Stefan
2026-09-17 21:17:23 +02:00
parent 86f6a60860
commit 249a945f5e
59 changed files with 2720 additions and 457 deletions
+1
View File
@@ -0,0 +1 @@
let $spt={mdl:"Spezialfunktionen",intro:"Versteckte Wartungsfunktionen für Administratoren. Jede Aktion wird protokolliert (fds__admin_activity / Anwendungslog).",run:"Ausführen",working:"Wird ausgeführt…",failed:"Die Aktion ist fehlgeschlagen.",ifc_title:"Rechnungsdatei zurücksetzen (Soft-Delete)",ifc_desc:"Sichert die gespeicherte PDF-Datei einer finalisierten Rechnung (DB-Tabelle fds__invoice_file_backups + Kopie im Blob-Archiv) und entfernt sie aus der Rechnung. Beim nächsten Aufruf des Dokuments wird die PDF neu generiert (ADR 0025/0027).",ifc_id:"Interne Rechnungs-ID",ifc_id_ph:"z.B. W7VAL",ifc_id_note:"Die interne ID (Spalte Id), nicht die Rechnungsnummer R2026-….",ifc_btn:"Datei sichern und entfernen",ifc_confirm:'Die Rechnungsdatei von "{0}" wird gesichert und aus der Rechnung entfernt. Fortfahren?',ifc_ok:"Erledigt: Die Datei wurde gesichert und entfernt. Das Dokument wird beim nächsten Aufruf neu generiert.",ifc_ok_blob:"Blob-Backup: {0}",ifc_ok_noblob:"Kein Blob-Backup angelegt (Blob-Archiv deaktiviert, kein Blob vorhanden oder Kopie fehlgeschlagen — siehe Log).",ifc_reason:{invoice_not_found:"Rechnung nicht gefunden.",not_final:"Die Rechnung ist nicht finalisiert — nur finalisierte Rechnungen können zurückgesetzt werden (Entwürfe würden sonst dauerhaft als Entwurf gespeichert).",external:"Externe (MFR-)Dokumente können nicht neu generiert werden.",no_file:"Es ist keine Datei gespeichert — nichts zu tun.",db_error:"Datenbankfehler — Details im Anwendungslog (CodeReference AdminBackupClearInvoiceFile)."}},$spc={init2:function(){$ocms.getScript([],(function(){$spc.init3()}))},init3:function(){$fis.cf(!0),$fis.lf(!0),$("#activemodule").text($spt.mdl),$("#topbar").ocmsmenu([]);let e=$$.dc("spcfrm",$("#contentframe"));$$.dc("spc_intro",e).text($spt.intro);let t=$$.dc("spc_grid",e);$.each($spc.cards,(function(e,n){n(t)}))},card:function(e,t,n){let i=$$.dc("spc_card",e),r=$$.dc("spc_card_hd",i);$$.s(t).appendTo(r);let c=$$.dc("spc_card_bd",i);return n&&$$.dc("spc_card_desc",c).text(n),{card:i,body:c,form:$("<form/>",{class:"spc_form",novalidate:"novalidate"}).appendTo(c),result:$$.dc("spc_result",c).hide()}},field:function(e,t,n,i){i=i||{};let r=$$.dc("spc_field",e),c="spc_"+t;$$.lbl(n,{for:c}).appendTo(r);let o=$$.i({id:c,name:t,type:i.type||"text",placeholder:i.placeholder||""}).appendTo(r);return i.required&&o.attr("required","required"),i.pattern&&o.attr("pattern",i.pattern),i.note&&$$.dc("spc_field_note",r).text(i.note),o},showResult:function(e,t,n){e.empty().attr("class","spc_result "+(t?"spc_result_ok":"spc_result_err")),$.each([].concat(n||[]),(function(t,n){n&&$$.dc("spc_result_line",e).text(n)})),e.show()},submit:function(e,t){let n=$$.bbtn(t.button,"spc_btn spc_btn_primary").attr("type","submit").appendTo($$.dc("spc_actions",e.form));e.form.on("submit",(function(i){i.preventDefault();let r=e.form[0];if("function"==typeof r.reportValidity&&!r.reportValidity())return;let c=e.form.serializeObject(!0,{typedvalues:!1});("function"!=typeof t.confirm||confirm(t.confirm(c)))&&(e.result.hide(),n.prop("disabled",!0).text($spt.working),$ocms.postXT($.extend(t.run(c),{success:function(n){let i=t.report(n||{});$spc.showResult(e.result,i.ok,i.lines)},error:function(t){$spc.showResult(e.result,!1,[$spt.failed,t&&t.internalText||""])},complete:function(){n.prop("disabled",!1).text(t.button)}})))}))},cards:[function(e){let t=$spc.card(e,$spt.ifc_title,$spt.ifc_desc);$spc.field(t.form,"id",$spt.ifc_id,{required:!0,placeholder:$spt.ifc_id_ph,note:$spt.ifc_id_note,pattern:"[A-Za-z0-9]{1,10}"}),$spc.submit(t,{button:$spt.ifc_btn,confirm:function(e){return $spt.ifc_confirm.replace("{0}",e.id)},run:function(e){return{url:$ocms.url("admin/backupclearinvoicefile/"+encodeURIComponent((e.id||"").trim())),method:"post",data:{}}},report:function(e){let t=e.backup||{};return!0===t.success?{ok:!0,lines:[$spt.ifc_ok,t.blobBackupUri?$spt.ifc_ok_blob.replace("{0}",t.blobBackupUri):$spt.ifc_ok_noblob]}:{ok:!1,lines:[$spt.ifc_reason[t.reason]||$spt.failed+" ("+(t.reason||"?")+")"]}}})}]},$$spc={init2:$spc.init2,auth:{}};export default $$spc;