Creating Participation refunds
This commit is contained in:
@@ -2,47 +2,38 @@
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\ParticipantInvoice\ParticipantInvoiceTokens;
|
||||
use App\Models\DocumentAsset;
|
||||
use App\Models\DocumentTemplate;
|
||||
use App\Models\DocumentTypeCatalog;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DocumentTemplatesGetController extends CommonController
|
||||
{
|
||||
/** Blöcke, die Layout-HTML bzw. CSS enthalten und deshalb im Quelltext-Editor gepflegt werden. */
|
||||
private const array SOURCE_BLOCKS = [
|
||||
DocumentTemplate::BLOCK_LAYOUT,
|
||||
DocumentTemplate::BLOCK_STYLE,
|
||||
];
|
||||
|
||||
private const array BLOCK_LABELS = [
|
||||
DocumentTemplate::BLOCK_LAYOUT => 'Seitengerüst',
|
||||
DocumentTemplate::BLOCK_STYLE => 'Gestaltung (CSS)',
|
||||
'header_sender_return' => 'Rücksendezeile',
|
||||
'header_recipient' => 'Empfängeranschrift',
|
||||
'emblem' => 'Emblem',
|
||||
'logo' => 'Logo',
|
||||
'sender_data' => 'Absenderangaben',
|
||||
'subject' => 'Betreff und Referenzdaten',
|
||||
DocumentTemplate::BLOCK_BODY => 'Rechnungsinhalt',
|
||||
'footer' => 'Grußformel und Fußnote',
|
||||
];
|
||||
|
||||
public function __invoke(): JsonResponse
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$blocks = DocumentTemplate::forType(DocumentTemplate::TYPE_PARTICIPANT_INVOICE)
|
||||
$documentType = (string) $request->input('type', DocumentTypeCatalog::default());
|
||||
|
||||
if (!DocumentTypeCatalog::has($documentType)) {
|
||||
abort(422, 'Unbekannte Dokumentart.');
|
||||
}
|
||||
|
||||
$blocks = DocumentTemplate::forType($documentType)
|
||||
->values()
|
||||
->map(fn(DocumentTemplate $block): array => [
|
||||
'block' => $block->block,
|
||||
'label' => self::BLOCK_LABELS[$block->block] ?? $block->block,
|
||||
'label' => DocumentTypeCatalog::blockLabel($documentType, $block->block),
|
||||
'content' => (string) $block->content,
|
||||
'editable' => $block->editable,
|
||||
'source' => in_array($block->block, self::SOURCE_BLOCKS, true),
|
||||
'source' => in_array($block->block, DocumentTypeCatalog::SOURCE_BLOCKS, true),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'documentType' => $documentType,
|
||||
'documentTypes' => DocumentTypeCatalog::options(),
|
||||
'blocks' => $blocks,
|
||||
// Die Bilder gelten für alle Dokumentarten -- sie hängen nicht am Typ.
|
||||
'assets' => DocumentAsset::orderBy('name')->get()->map(fn(DocumentAsset $asset): array => [
|
||||
'name' => $asset->name,
|
||||
'label' => $asset->label,
|
||||
@@ -50,7 +41,7 @@ class DocumentTemplatesGetController extends CommonController
|
||||
'token' => '{asset:' . $asset->name . '}',
|
||||
'preview' => $asset->toDataUri(),
|
||||
]),
|
||||
'tokenGroups' => ParticipantInvoiceTokens::groups(),
|
||||
'tokenGroups' => DocumentTypeCatalog::tokenGroups($documentType),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\ParticipantInvoice\ParticipantInvoiceTokens;
|
||||
use App\Models\DocumentTemplate;
|
||||
use App\Models\DocumentTypeCatalog;
|
||||
use App\Providers\DocumentTemplateRenderProvider;
|
||||
use App\Providers\PdfGenerateAndDownloadProvider;
|
||||
use App\Scopes\CommonController;
|
||||
@@ -17,10 +16,16 @@ class DocumentTemplatesPreviewController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
$documentType = (string) $request->input('type', DocumentTypeCatalog::default());
|
||||
|
||||
if (!DocumentTypeCatalog::has($documentType)) {
|
||||
abort(422, 'Unbekannte Dokumentart.');
|
||||
}
|
||||
|
||||
$html = new DocumentTemplateRenderProvider(
|
||||
DocumentTemplate::TYPE_PARTICIPANT_INVOICE,
|
||||
$documentType,
|
||||
(array) $request->input('blocks', []),
|
||||
)->render(ParticipantInvoiceTokens::sample());
|
||||
)->render(DocumentTypeCatalog::sampleTokens($documentType));
|
||||
|
||||
return response(PdfGenerateAndDownloadProvider::fromHtml($html, 'portrait'), 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Domains\Admin\Controllers;
|
||||
|
||||
use App\Domains\Admin\Actions\UpdateDocumentTemplate\UpdateDocumentTemplateAction;
|
||||
use App\Domains\Admin\Actions\UpdateDocumentTemplate\UpdateDocumentTemplateRequest;
|
||||
use App\Models\DocumentTemplate;
|
||||
use App\Models\DocumentTypeCatalog;
|
||||
use App\Scopes\CommonController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -13,8 +13,16 @@ class DocumentTemplatesUpdateController extends CommonController
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$documentType = (string) $request->input('type', DocumentTypeCatalog::default());
|
||||
|
||||
// Die Dokumentart bestimmt, welche Zeilen geschrieben werden -- ungeprüft weitergereicht wäre
|
||||
// sie ein Weg, in beliebige Vorlagen zu schreiben.
|
||||
if (!DocumentTypeCatalog::has($documentType)) {
|
||||
abort(422, 'Unbekannte Dokumentart.');
|
||||
}
|
||||
|
||||
$action = new UpdateDocumentTemplateAction(new UpdateDocumentTemplateRequest(
|
||||
documentType: DocumentTemplate::TYPE_PARTICIPANT_INVOICE,
|
||||
documentType: $documentType,
|
||||
blocks: (array) $request->input('blocks', []),
|
||||
));
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ const blocks = ref([])
|
||||
const assets = ref([])
|
||||
const tokenGroups = ref({})
|
||||
|
||||
// Die Vorlagen sind nach Dokumentart getrennt; der Umschalter lädt jeweils deren Blöcke neu.
|
||||
const documentType = ref(null)
|
||||
const documentTypes = ref([])
|
||||
|
||||
const activeBlock = ref(null)
|
||||
const saving = ref(false)
|
||||
|
||||
@@ -33,12 +37,15 @@ function selectBlock(block) {
|
||||
onMounted(load)
|
||||
|
||||
async function load() {
|
||||
const data = await request('/api/v1/admin/document-templates', {method: 'GET'})
|
||||
const query = documentType.value ? '?type=' + encodeURIComponent(documentType.value) : ''
|
||||
const data = await request('/api/v1/admin/document-templates' + query, {method: 'GET'})
|
||||
if (!data) {
|
||||
toast.error('Die Vorlage konnte nicht geladen werden.')
|
||||
return
|
||||
}
|
||||
|
||||
documentType.value = data.documentType
|
||||
documentTypes.value = data.documentTypes ?? []
|
||||
blocks.value = data.blocks ?? []
|
||||
assets.value = data.assets ?? []
|
||||
tokenGroups.value = data.tokenGroups ?? {}
|
||||
@@ -47,6 +54,15 @@ async function load() {
|
||||
await refreshPreview()
|
||||
}
|
||||
|
||||
/** Beim Wechsel der Dokumentart alles neu holen -- Blöcke und Platzhalter sind je Art andere. */
|
||||
async function selectDocumentType(type) {
|
||||
if (type === documentType.value) return
|
||||
|
||||
documentType.value = type
|
||||
caret.value = null
|
||||
await load()
|
||||
}
|
||||
|
||||
/** Nur editierbare Blöcke werden gesendet; der generierte Rechnungsinhalt bleibt unverändert. */
|
||||
function editableBlocks() {
|
||||
return Object.fromEntries(
|
||||
@@ -60,7 +76,7 @@ async function save() {
|
||||
try {
|
||||
const response = await request('/api/v1/admin/document-templates', {
|
||||
method: 'POST',
|
||||
body: {blocks: editableBlocks()},
|
||||
body: {type: documentType.value, blocks: editableBlocks()},
|
||||
})
|
||||
|
||||
if (response?.status === 'success') {
|
||||
@@ -83,7 +99,7 @@ async function refreshPreview() {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content ?? '',
|
||||
},
|
||||
body: JSON.stringify({blocks: editableBlocks()}),
|
||||
body: JSON.stringify({type: documentType.value, blocks: editableBlocks()}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -217,14 +233,28 @@ function onFileChosen(event) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminAppLayout title="Rechnungsvorlage">
|
||||
<AdminAppLayout title="Dokumentvorlagen">
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px;">
|
||||
<p class="intro">
|
||||
Die Vorlage gilt für <strong>alle Mandanten</strong>; die eingesetzten Werte kommen je
|
||||
Rechnung aus dem jeweiligen Mandanten. Sie liegt in der Datenbank und wird von einem
|
||||
Die Vorlagen gelten für <strong>alle Mandanten</strong>; die eingesetzten Werte kommen je
|
||||
Dokument aus dem jeweiligen Mandanten. Sie liegen in der Datenbank und werden von einem
|
||||
Update nicht überschrieben.
|
||||
</p>
|
||||
|
||||
<div class="type-switch">
|
||||
<label for="document-type">Dokumentart</label>
|
||||
<select
|
||||
id="document-type"
|
||||
class="form-input"
|
||||
:value="documentType"
|
||||
@change="selectDocumentType($event.target.value)"
|
||||
>
|
||||
<option v-for="type in documentTypes" :key="type.value" :value="type.value">
|
||||
{{ type.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
<!-- ── Blöcke und Editor ── -->
|
||||
<div class="editor-column">
|
||||
@@ -243,7 +273,7 @@ function onFileChosen(event) {
|
||||
|
||||
<div v-if="current" class="editor-panel">
|
||||
<p v-if="!current.editable" class="notice">
|
||||
Dieser Block wird beim Erzeugen der Rechnung aus den Daten der Anmeldung
|
||||
Dieser Block wird beim Erzeugen des Dokuments aus den Daten der Anmeldung
|
||||
zusammengesetzt und lässt sich nicht bearbeiten.
|
||||
</p>
|
||||
|
||||
@@ -360,6 +390,24 @@ function onFileChosen(event) {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.type-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.type-switch label {
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.type-switch select {
|
||||
width: auto;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
|
||||
Reference in New Issue
Block a user