feat: Enhance club payment claims and task automation
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 1m0s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 1m0s
- Added `paid_amount_cents` column to `club_payment_claims` for better tracking of payments. - Implemented compatibility checks for the new column in `clubPaymentClaimService` and `clubTaskAutomationService`. - Updated various views to handle read-only states when editing is not allowed. - Refactored forms in `ClubAccountsView`, `ClubInvoicesView`, `ClubTasksView`, and `ClubCommunicationView` to use factory functions for cleaner code. - Introduced a new migration script to add the `paid_amount_cents` column if it doesn't exist and initialize it for existing paid claims. - Created a detailed plan for enhancing club features and ensuring stability in existing modules.
This commit is contained in:
@@ -166,6 +166,7 @@
|
||||
<div class="section-header">
|
||||
<h3>{{ form.id ? 'Konto bearbeiten' : 'Neues Konto' }}</h3>
|
||||
</div>
|
||||
<p v-if="!canEdit" class="state-banner">Lesemodus aktiv. Konten können geprüft, aber nicht bearbeitet werden.</p>
|
||||
|
||||
<form class="account-form" @submit.prevent="submitAccount">
|
||||
<label>
|
||||
@@ -261,6 +262,7 @@
|
||||
<div class="section-header">
|
||||
<h3>{{ transactionForm.id ? 'Buchung bearbeiten' : 'Neue Buchung' }}</h3>
|
||||
</div>
|
||||
<p v-if="!canEdit" class="state-banner">Lesemodus aktiv. Kontobewegungen bleiben sichtbar, neue Buchungen sind gesperrt.</p>
|
||||
|
||||
<form class="account-form" @submit.prevent="submitTransaction">
|
||||
<div class="account-form-grid">
|
||||
@@ -453,6 +455,25 @@ function createEmptyTransactionForm(accountId = '') {
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptyAccountForm() {
|
||||
return {
|
||||
id: null,
|
||||
name: '',
|
||||
accountHolder: '',
|
||||
bankName: '',
|
||||
iban: '',
|
||||
bic: '',
|
||||
accountType: 'bank',
|
||||
usageType: 'general',
|
||||
currencyCode: 'EUR',
|
||||
allowSepaCollections: false,
|
||||
allowOutgoingPayments: true,
|
||||
isDefault: false,
|
||||
status: 'active',
|
||||
notes: '',
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ClubAccountsView',
|
||||
components: {
|
||||
@@ -475,22 +496,7 @@ export default {
|
||||
accountType: '',
|
||||
search: '',
|
||||
},
|
||||
form: {
|
||||
id: null,
|
||||
name: '',
|
||||
accountHolder: '',
|
||||
bankName: '',
|
||||
iban: '',
|
||||
bic: '',
|
||||
accountType: 'bank',
|
||||
usageType: 'general',
|
||||
currencyCode: 'EUR',
|
||||
allowSepaCollections: false,
|
||||
allowOutgoingPayments: true,
|
||||
isDefault: false,
|
||||
status: 'active',
|
||||
notes: '',
|
||||
},
|
||||
form: createEmptyAccountForm(),
|
||||
transactionForm: createEmptyTransactionForm(),
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
@@ -553,13 +559,7 @@ export default {
|
||||
immediate: true,
|
||||
async handler(newClub) {
|
||||
if (!newClub) {
|
||||
this.accounts = [];
|
||||
this.transactions = [];
|
||||
this.selectedAccountId = null;
|
||||
this.selectedTransactionId = null;
|
||||
this.resetForm();
|
||||
this.resetTransactionForm();
|
||||
this.loadError = '';
|
||||
this.clearAccountsState();
|
||||
return;
|
||||
}
|
||||
await this.loadAccounts();
|
||||
@@ -618,6 +618,16 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearAccountsState() {
|
||||
this.accounts = [];
|
||||
this.transactions = [];
|
||||
this.paymentClaims = [];
|
||||
this.selectedAccountId = null;
|
||||
this.selectedTransactionId = null;
|
||||
this.form = createEmptyAccountForm();
|
||||
this.transactionForm = createEmptyTransactionForm();
|
||||
this.loadError = '';
|
||||
},
|
||||
applyRouteQuery() {
|
||||
const routeAccountId = this.$route?.query?.accountId;
|
||||
const routeStatus = typeof this.$route?.query?.status === 'string' ? this.$route.query.status : '';
|
||||
@@ -715,26 +725,12 @@ export default {
|
||||
},
|
||||
resetForm() {
|
||||
this.selectedAccountId = null;
|
||||
this.form = {
|
||||
id: null,
|
||||
name: '',
|
||||
accountHolder: '',
|
||||
bankName: '',
|
||||
iban: '',
|
||||
bic: '',
|
||||
accountType: 'bank',
|
||||
usageType: 'general',
|
||||
currencyCode: 'EUR',
|
||||
allowSepaCollections: false,
|
||||
allowOutgoingPayments: true,
|
||||
isDefault: false,
|
||||
status: 'active',
|
||||
notes: '',
|
||||
};
|
||||
this.form = createEmptyAccountForm();
|
||||
this.resetTransactionForm('');
|
||||
},
|
||||
resetTransactionForm() {
|
||||
resetTransactionForm(accountId = this.selectedAccountId || '') {
|
||||
this.selectedTransactionId = null;
|
||||
this.transactionForm = createEmptyTransactionForm(this.selectedAccountId || '');
|
||||
this.transactionForm = createEmptyTransactionForm(accountId);
|
||||
},
|
||||
async loadPaymentClaims() {
|
||||
if (!this.currentClub) {
|
||||
@@ -760,13 +756,15 @@ export default {
|
||||
await this.loadPaymentClaims();
|
||||
this.applyRouteQuery();
|
||||
if (this.selectedAccountId && !this.selectedAccount) {
|
||||
this.selectedAccountId = null;
|
||||
this.resetForm();
|
||||
}
|
||||
if (this.selectedTransactionId && !this.selectedTransaction) {
|
||||
this.selectedTransactionId = null;
|
||||
this.resetTransactionForm();
|
||||
}
|
||||
if (!this.selectedAccountId && this.accounts.length > 0) {
|
||||
this.selectedAccountId = this.accounts[0].id;
|
||||
} else if (this.accounts.length === 0) {
|
||||
this.resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
this.loadError = safeErrorMessage(error, 'Konten konnten nicht geladen werden.');
|
||||
@@ -779,11 +777,15 @@ export default {
|
||||
this.saving = true;
|
||||
try {
|
||||
const payload = { ...this.form };
|
||||
let savedAccountId = this.form.id;
|
||||
if (this.form.id) {
|
||||
await apiClient.put(`/club-accounts/${this.currentClub}/${this.form.id}`, payload);
|
||||
const response = await apiClient.put(`/club-accounts/${this.currentClub}/${this.form.id}`, payload);
|
||||
savedAccountId = response.data?.account?.id || savedAccountId;
|
||||
} else {
|
||||
await apiClient.post(`/club-accounts/${this.currentClub}`, payload);
|
||||
const response = await apiClient.post(`/club-accounts/${this.currentClub}`, payload);
|
||||
savedAccountId = response.data?.account?.id || savedAccountId;
|
||||
}
|
||||
this.selectedAccountId = savedAccountId ? String(savedAccountId) : null;
|
||||
await this.loadAccounts();
|
||||
this.showInfo('Erfolg', 'Konto gespeichert.', '', 'success');
|
||||
} catch (error) {
|
||||
@@ -809,13 +811,16 @@ export default {
|
||||
reference: this.transactionForm.reference,
|
||||
notes: this.transactionForm.notes,
|
||||
};
|
||||
let savedTransactionId = this.transactionForm.id;
|
||||
if (this.transactionForm.id) {
|
||||
await apiClient.put(`/club-accounts/${this.currentClub}/transactions/${this.transactionForm.id}`, payload);
|
||||
const response = await apiClient.put(`/club-accounts/${this.currentClub}/transactions/${this.transactionForm.id}`, payload);
|
||||
savedTransactionId = response.data?.transaction?.id || savedTransactionId;
|
||||
} else {
|
||||
await apiClient.post(`/club-accounts/${this.currentClub}/transactions`, payload);
|
||||
const response = await apiClient.post(`/club-accounts/${this.currentClub}/transactions`, payload);
|
||||
savedTransactionId = response.data?.transaction?.id || savedTransactionId;
|
||||
}
|
||||
this.selectedTransactionId = savedTransactionId ? String(savedTransactionId) : null;
|
||||
await this.loadAccounts();
|
||||
this.resetTransactionForm();
|
||||
} catch (error) {
|
||||
this.showInfo('Fehler', safeErrorMessage(error, 'Kontobewegung konnte nicht gespeichert werden.'), '', 'error');
|
||||
} finally {
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
Neue Gruppe
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="!canEdit" class="state-banner">Lesemodus aktiv. Verteilergruppen bleiben sichtbar, Änderungen sind hier gesperrt.</p>
|
||||
|
||||
<div class="groups-chip-list">
|
||||
<button
|
||||
@@ -209,6 +210,7 @@
|
||||
<div class="section-header">
|
||||
<h3>{{ threadForm.id ? 'Vorgang bearbeiten' : 'Neuer Vorgang' }}</h3>
|
||||
</div>
|
||||
<p v-if="!canEdit" class="state-banner">Lesemodus aktiv. Vorgänge lassen sich prüfen, aber nicht ändern.</p>
|
||||
|
||||
<form class="thread-form" @submit.prevent="saveThread">
|
||||
<div class="form-grid">
|
||||
@@ -297,6 +299,7 @@
|
||||
<div class="section-header">
|
||||
<h3>Nachrichten</h3>
|
||||
</div>
|
||||
<p v-if="!canEdit && selectedThread" class="state-banner">Lesemodus aktiv. Verlauf und Versandstatus bleiben sichtbar, neue Einträge sind gesperrt.</p>
|
||||
|
||||
<p v-if="!selectedThread" class="state-banner">Wähle links einen Kommunikationsvorgang aus.</p>
|
||||
<template v-else>
|
||||
@@ -405,6 +408,7 @@
|
||||
<div class="section-header">
|
||||
<h3>Vorlagen & Antworten</h3>
|
||||
</div>
|
||||
<p v-if="!canEdit" class="state-banner">Lesemodus aktiv. Vorlagen können eingesehen, aber nicht bearbeitet werden.</p>
|
||||
|
||||
<div class="groups-chip-list">
|
||||
<button
|
||||
@@ -749,19 +753,7 @@ export default {
|
||||
immediate: true,
|
||||
async handler(clubId) {
|
||||
if (!clubId) {
|
||||
this.threads = [];
|
||||
this.groups = [];
|
||||
this.members = [];
|
||||
this.templates = [];
|
||||
this.selectedThreadId = null;
|
||||
this.selectedGroupId = null;
|
||||
this.selectedTemplateId = null;
|
||||
this.threadForm = createEmptyThreadForm();
|
||||
this.groupForm = createEmptyGroupForm();
|
||||
this.templateForm = createEmptyTemplateForm();
|
||||
this.messageForm = createEmptyMessageForm();
|
||||
this.messageTemplateId = '';
|
||||
this.loadError = '';
|
||||
this.clearCommunicationState();
|
||||
return;
|
||||
}
|
||||
await this.loadCommunication();
|
||||
@@ -810,6 +802,21 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearCommunicationState() {
|
||||
this.threads = [];
|
||||
this.groups = [];
|
||||
this.members = [];
|
||||
this.templates = [];
|
||||
this.selectedThreadId = null;
|
||||
this.selectedGroupId = null;
|
||||
this.selectedTemplateId = null;
|
||||
this.threadForm = createEmptyThreadForm();
|
||||
this.groupForm = createEmptyGroupForm();
|
||||
this.templateForm = createEmptyTemplateForm();
|
||||
this.messageForm = createEmptyMessageForm();
|
||||
this.messageTemplateId = '';
|
||||
this.loadError = '';
|
||||
},
|
||||
showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
@@ -938,6 +945,14 @@ export default {
|
||||
}
|
||||
if (!this.selectedThreadId && this.threads.length > 0) {
|
||||
this.selectedThreadId = this.threads[0].id;
|
||||
} else if (this.threads.length === 0) {
|
||||
this.resetThreadForm();
|
||||
}
|
||||
if (this.groups.length === 0) {
|
||||
this.resetGroupForm();
|
||||
}
|
||||
if (this.templates.length === 0) {
|
||||
this.resetTemplateForm();
|
||||
}
|
||||
} catch (error) {
|
||||
this.loadError = safeErrorMessage(error, 'Kommunikation konnte nicht geladen werden.');
|
||||
@@ -1056,11 +1071,15 @@ export default {
|
||||
scheduledAt: this.threadForm.scheduledAt || null,
|
||||
recipientFilters: this.threadForm.threadType === 'direct' ? {} : this.threadForm.recipientFilters,
|
||||
};
|
||||
let savedThreadId = this.threadForm.id;
|
||||
if (this.threadForm.id) {
|
||||
await apiClient.put(`/club-communication/${this.currentClub}/threads/${this.threadForm.id}`, payload);
|
||||
const response = await apiClient.put(`/club-communication/${this.currentClub}/threads/${this.threadForm.id}`, payload);
|
||||
savedThreadId = response.data?.thread?.id || savedThreadId;
|
||||
} else {
|
||||
await apiClient.post(`/club-communication/${this.currentClub}/threads`, payload);
|
||||
const response = await apiClient.post(`/club-communication/${this.currentClub}/threads`, payload);
|
||||
savedThreadId = response.data?.thread?.id || savedThreadId;
|
||||
}
|
||||
this.selectedThreadId = savedThreadId ? Number(savedThreadId) : null;
|
||||
await this.loadCommunication();
|
||||
this.showInfo('Erfolg', 'Kommunikationsvorgang gespeichert.', '', 'success');
|
||||
} catch (error) {
|
||||
@@ -1119,11 +1138,15 @@ export default {
|
||||
...this.groupForm,
|
||||
filterDefinition: this.groupForm.filterDefinition,
|
||||
};
|
||||
let savedGroupId = this.groupForm.id;
|
||||
if (this.groupForm.id) {
|
||||
await apiClient.put(`/club-communication/${this.currentClub}/groups/${this.groupForm.id}`, payload);
|
||||
const response = await apiClient.put(`/club-communication/${this.currentClub}/groups/${this.groupForm.id}`, payload);
|
||||
savedGroupId = response.data?.group?.id || savedGroupId;
|
||||
} else {
|
||||
await apiClient.post(`/club-communication/${this.currentClub}/groups`, payload);
|
||||
const response = await apiClient.post(`/club-communication/${this.currentClub}/groups`, payload);
|
||||
savedGroupId = response.data?.group?.id || savedGroupId;
|
||||
}
|
||||
this.selectedGroupId = savedGroupId ? Number(savedGroupId) : null;
|
||||
await this.loadCommunication();
|
||||
this.showInfo('Erfolg', 'Verteilergruppe gespeichert.', '', 'success');
|
||||
} catch (error) {
|
||||
@@ -1154,11 +1177,15 @@ export default {
|
||||
if (!this.currentClub || !this.canEdit) return;
|
||||
this.templateSaving = true;
|
||||
try {
|
||||
let savedTemplateId = this.templateForm.id;
|
||||
if (this.templateForm.id) {
|
||||
await apiClient.put(`/club-communication/${this.currentClub}/templates/${this.templateForm.id}`, this.templateForm);
|
||||
const response = await apiClient.put(`/club-communication/${this.currentClub}/templates/${this.templateForm.id}`, this.templateForm);
|
||||
savedTemplateId = response.data?.template?.id || savedTemplateId;
|
||||
} else {
|
||||
await apiClient.post(`/club-communication/${this.currentClub}/templates`, this.templateForm);
|
||||
const response = await apiClient.post(`/club-communication/${this.currentClub}/templates`, this.templateForm);
|
||||
savedTemplateId = response.data?.template?.id || savedTemplateId;
|
||||
}
|
||||
this.selectedTemplateId = savedTemplateId ? Number(savedTemplateId) : null;
|
||||
await this.loadCommunication();
|
||||
this.showInfo('Erfolg', 'Vorlage gespeichert.', '', 'success');
|
||||
} catch (error) {
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
<h3>Rechnungsparteien</h3>
|
||||
<button type="button" class="btn-secondary" :disabled="!canEdit" @click="startCreateParty">Neue Partei</button>
|
||||
</div>
|
||||
<p v-if="!canEdit" class="state-banner">Lesemodus aktiv. Parteien können eingesehen, aber nicht bearbeitet werden.</p>
|
||||
<div class="party-list">
|
||||
<button
|
||||
v-for="party in parties"
|
||||
@@ -217,6 +218,7 @@
|
||||
<div class="section-header">
|
||||
<h3>{{ invoiceForm.id ? 'Rechnung bearbeiten' : 'Neue Rechnung' }}</h3>
|
||||
</div>
|
||||
<p v-if="!canEdit" class="state-banner">Lesemodus aktiv. Rechnungen lassen sich prüfen, aber nicht ändern.</p>
|
||||
<form class="invoice-form" @submit.prevent="submitInvoice">
|
||||
<div class="invoice-form-grid">
|
||||
<label>
|
||||
@@ -458,6 +460,45 @@ function buildInvoiceNumber(prefix, nextNumber, referenceDate = new Date()) {
|
||||
return normalizedPrefix ? `${normalizedPrefix}-${year}-${paddedCounter}` : `${year}-${paddedCounter}`;
|
||||
}
|
||||
|
||||
function createEmptyInvoiceForm() {
|
||||
const issuedOn = new Date().toISOString().slice(0, 10);
|
||||
return {
|
||||
id: null,
|
||||
invoiceDirection: 'outgoing',
|
||||
invoiceType: 'sponsoring',
|
||||
status: 'draft',
|
||||
invoiceNumber: '',
|
||||
externalReference: '',
|
||||
partyId: '',
|
||||
accountId: '',
|
||||
issuedOn,
|
||||
dueOn: addDaysIso(issuedOn, 14),
|
||||
paidOn: '',
|
||||
currencyCode: 'EUR',
|
||||
description: '',
|
||||
items: [createEmptyInvoiceItem()],
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptyPartyForm() {
|
||||
return {
|
||||
id: null,
|
||||
name: '',
|
||||
partyType: 'customer',
|
||||
status: 'active',
|
||||
contractReference: '',
|
||||
validFrom: '',
|
||||
validTo: '',
|
||||
contactName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
street: '',
|
||||
postalCode: '',
|
||||
city: '',
|
||||
notes: '',
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ClubInvoicesView',
|
||||
components: {
|
||||
@@ -486,37 +527,8 @@ export default {
|
||||
status: '',
|
||||
search: '',
|
||||
},
|
||||
invoiceForm: {
|
||||
id: null,
|
||||
invoiceDirection: 'outgoing',
|
||||
invoiceType: 'sponsoring',
|
||||
status: 'draft',
|
||||
externalReference: '',
|
||||
partyId: '',
|
||||
accountId: '',
|
||||
issuedOn: new Date().toISOString().slice(0, 10),
|
||||
dueOn: addDaysIso(new Date(), 14),
|
||||
paidOn: '',
|
||||
currencyCode: 'EUR',
|
||||
description: '',
|
||||
items: [createEmptyInvoiceItem()],
|
||||
},
|
||||
partyForm: {
|
||||
id: null,
|
||||
name: '',
|
||||
partyType: 'customer',
|
||||
status: 'active',
|
||||
contractReference: '',
|
||||
validFrom: '',
|
||||
validTo: '',
|
||||
contactName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
street: '',
|
||||
postalCode: '',
|
||||
city: '',
|
||||
notes: '',
|
||||
},
|
||||
invoiceForm: createEmptyInvoiceForm(),
|
||||
partyForm: createEmptyPartyForm(),
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
@@ -604,19 +616,7 @@ export default {
|
||||
immediate: true,
|
||||
async handler(newClub) {
|
||||
if (!newClub) {
|
||||
this.invoices = [];
|
||||
this.parties = [];
|
||||
this.accounts = [];
|
||||
this.invoiceSettings = {
|
||||
outgoingInvoicePrefix: 'RE',
|
||||
outgoingInvoiceNextNumber: 1,
|
||||
incomingInvoicePrefix: 'EI',
|
||||
incomingInvoiceNextNumber: 1,
|
||||
};
|
||||
this.selectedInvoiceId = null;
|
||||
this.resetInvoiceForm();
|
||||
this.resetPartyForm();
|
||||
this.loadError = '';
|
||||
this.clearInvoiceState();
|
||||
return;
|
||||
}
|
||||
await this.loadInvoices();
|
||||
@@ -681,6 +681,22 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearInvoiceState() {
|
||||
this.invoices = [];
|
||||
this.parties = [];
|
||||
this.accounts = [];
|
||||
this.invoiceSettings = {
|
||||
outgoingInvoicePrefix: 'RE',
|
||||
outgoingInvoiceNextNumber: 1,
|
||||
incomingInvoicePrefix: 'EI',
|
||||
incomingInvoiceNextNumber: 1,
|
||||
};
|
||||
this.selectedInvoiceId = null;
|
||||
this.selectedPartyId = null;
|
||||
this.invoiceForm = createEmptyInvoiceForm();
|
||||
this.partyForm = createEmptyPartyForm();
|
||||
this.loadError = '';
|
||||
},
|
||||
showInfo(title, message, details = '', type = 'info') {
|
||||
this.infoDialog = buildInfoConfig({ title, message, details, type });
|
||||
},
|
||||
@@ -749,9 +765,23 @@ export default {
|
||||
incomingInvoicePrefix: response.data?.settings?.incomingInvoicePrefix || 'EI',
|
||||
incomingInvoiceNextNumber: Number(response.data?.settings?.incomingInvoiceNextNumber || 1) || 1,
|
||||
};
|
||||
if (this.selectedInvoiceId && !this.selectedInvoice) {
|
||||
this.selectedInvoiceId = null;
|
||||
this.resetInvoiceForm();
|
||||
}
|
||||
if (this.selectedPartyId && !this.selectedParty) {
|
||||
this.selectedPartyId = null;
|
||||
this.resetPartyForm();
|
||||
}
|
||||
if (!this.selectedInvoiceId && this.invoices.length > 0) {
|
||||
this.selectedInvoiceId = this.invoices[0].id;
|
||||
}
|
||||
if (this.invoices.length === 0) {
|
||||
this.resetInvoiceForm();
|
||||
}
|
||||
if (this.parties.length === 0) {
|
||||
this.resetPartyForm();
|
||||
}
|
||||
this.applyWorkflowQueryPrefill();
|
||||
} catch (error) {
|
||||
this.loadError = safeErrorMessage(error, 'Rechnungen konnten nicht geladen werden.');
|
||||
@@ -767,6 +797,7 @@ export default {
|
||||
},
|
||||
applyWorkflowQueryPrefill() {
|
||||
if (!this.currentClub || !Array.isArray(this.parties)) return;
|
||||
if (this.selectedInvoiceId || this.invoiceForm.id) return;
|
||||
const partyId = String(this.$route?.query?.partyId || this.$route?.query?.selectedPartyId || '').trim();
|
||||
if (!partyId) return;
|
||||
const matchingParty = this.parties.find((party) => String(party.id) === partyId);
|
||||
@@ -798,42 +829,11 @@ export default {
|
||||
},
|
||||
resetInvoiceForm() {
|
||||
this.selectedInvoiceId = null;
|
||||
const issuedOn = new Date().toISOString().slice(0, 10);
|
||||
this.invoiceForm = {
|
||||
id: null,
|
||||
invoiceDirection: 'outgoing',
|
||||
invoiceType: 'sponsoring',
|
||||
status: 'draft',
|
||||
invoiceNumber: '',
|
||||
externalReference: '',
|
||||
partyId: '',
|
||||
accountId: '',
|
||||
issuedOn,
|
||||
dueOn: addDaysIso(issuedOn, 14),
|
||||
paidOn: '',
|
||||
currencyCode: 'EUR',
|
||||
description: '',
|
||||
items: [createEmptyInvoiceItem()],
|
||||
};
|
||||
this.invoiceForm = createEmptyInvoiceForm();
|
||||
},
|
||||
startCreateParty() {
|
||||
this.selectedPartyId = null;
|
||||
this.partyForm = {
|
||||
id: null,
|
||||
name: '',
|
||||
partyType: 'customer',
|
||||
status: 'active',
|
||||
contractReference: '',
|
||||
validFrom: '',
|
||||
validTo: '',
|
||||
contactName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
street: '',
|
||||
postalCode: '',
|
||||
city: '',
|
||||
notes: '',
|
||||
};
|
||||
this.partyForm = createEmptyPartyForm();
|
||||
},
|
||||
resetPartyForm() {
|
||||
this.startCreateParty();
|
||||
@@ -852,11 +852,15 @@ export default {
|
||||
this.partySaving = true;
|
||||
try {
|
||||
const payload = { ...this.partyForm };
|
||||
let savedPartyId = this.partyForm.id;
|
||||
if (this.partyForm.id) {
|
||||
await apiClient.put(`/club-invoices/${this.currentClub}/parties/${this.partyForm.id}`, payload);
|
||||
const response = await apiClient.put(`/club-invoices/${this.currentClub}/parties/${this.partyForm.id}`, payload);
|
||||
savedPartyId = response.data?.party?.id || savedPartyId;
|
||||
} else {
|
||||
await apiClient.post(`/club-invoices/${this.currentClub}/parties`, payload);
|
||||
const response = await apiClient.post(`/club-invoices/${this.currentClub}/parties`, payload);
|
||||
savedPartyId = response.data?.party?.id || savedPartyId;
|
||||
}
|
||||
this.selectedPartyId = savedPartyId ? String(savedPartyId) : null;
|
||||
await this.loadInvoices();
|
||||
this.showInfo('Erfolg', 'Rechnungspartei gespeichert.', '', 'success');
|
||||
} catch (error) {
|
||||
@@ -898,11 +902,15 @@ export default {
|
||||
taxRate: item.taxRate,
|
||||
})),
|
||||
};
|
||||
let savedInvoiceId = this.invoiceForm.id;
|
||||
if (this.invoiceForm.id) {
|
||||
await apiClient.put(`/club-invoices/${this.currentClub}/${this.invoiceForm.id}`, payload);
|
||||
const response = await apiClient.put(`/club-invoices/${this.currentClub}/${this.invoiceForm.id}`, payload);
|
||||
savedInvoiceId = response.data?.invoice?.id || savedInvoiceId;
|
||||
} else {
|
||||
await apiClient.post(`/club-invoices/${this.currentClub}`, payload);
|
||||
const response = await apiClient.post(`/club-invoices/${this.currentClub}`, payload);
|
||||
savedInvoiceId = response.data?.invoice?.id || savedInvoiceId;
|
||||
}
|
||||
this.selectedInvoiceId = savedInvoiceId ? String(savedInvoiceId) : null;
|
||||
await this.loadInvoices();
|
||||
this.showInfo('Erfolg', 'Rechnung gespeichert.', '', 'success');
|
||||
} catch (error) {
|
||||
|
||||
@@ -196,6 +196,7 @@
|
||||
<div class="section-header">
|
||||
<h3>{{ form.id ? 'Aufgabe bearbeiten' : 'Neue Aufgabe' }}</h3>
|
||||
</div>
|
||||
<p v-if="!canEdit" class="state-banner">Lesemodus aktiv. Aufgaben bleiben sichtbar, Änderungen am Formular sind gesperrt.</p>
|
||||
<form class="task-form" @submit.prevent="submitTask">
|
||||
<label>
|
||||
<span>Titel</span>
|
||||
@@ -420,6 +421,19 @@ function normalizeTask(payload = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptyTaskForm() {
|
||||
return {
|
||||
id: null,
|
||||
title: '',
|
||||
description: '',
|
||||
status: 'open',
|
||||
priority: 'normal',
|
||||
dueAt: '',
|
||||
remindAt: '',
|
||||
assignedUserId: '',
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ClubTasksView',
|
||||
components: {
|
||||
@@ -444,16 +458,7 @@ export default {
|
||||
priority: '',
|
||||
search: '',
|
||||
},
|
||||
form: {
|
||||
id: null,
|
||||
title: '',
|
||||
description: '',
|
||||
status: 'open',
|
||||
priority: 'normal',
|
||||
dueAt: '',
|
||||
remindAt: '',
|
||||
assignedUserId: '',
|
||||
},
|
||||
form: createEmptyTaskForm(),
|
||||
infoDialog: {
|
||||
isOpen: false,
|
||||
title: '',
|
||||
@@ -508,11 +513,7 @@ export default {
|
||||
immediate: true,
|
||||
async handler(newClub) {
|
||||
if (!newClub) {
|
||||
this.tasks = [];
|
||||
this.workflowSources = [];
|
||||
this.selectedTaskId = null;
|
||||
this.resetForm();
|
||||
this.loadError = '';
|
||||
this.clearTaskState();
|
||||
return;
|
||||
}
|
||||
await this.loadTasks();
|
||||
@@ -542,6 +543,16 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearTaskState() {
|
||||
this.tasks = [];
|
||||
this.taskDefinitions = [];
|
||||
this.taskSuggestions = [];
|
||||
this.workflowSources = [];
|
||||
this.assignableUsers = [];
|
||||
this.selectedTaskId = null;
|
||||
this.form = createEmptyTaskForm();
|
||||
this.loadError = '';
|
||||
},
|
||||
normalizeAssignableUser(user = {}) {
|
||||
const email = typeof user.email === 'string' ? user.email.trim() : '';
|
||||
return {
|
||||
@@ -632,16 +643,7 @@ export default {
|
||||
},
|
||||
resetForm() {
|
||||
this.selectedTaskId = null;
|
||||
this.form = {
|
||||
id: null,
|
||||
title: '',
|
||||
description: '',
|
||||
status: 'open',
|
||||
priority: 'normal',
|
||||
dueAt: '',
|
||||
remindAt: '',
|
||||
assignedUserId: '',
|
||||
};
|
||||
this.form = createEmptyTaskForm();
|
||||
},
|
||||
async loadTasks() {
|
||||
if (!this.currentClub) return;
|
||||
@@ -669,6 +671,8 @@ export default {
|
||||
}
|
||||
if (!this.selectedTaskId && this.tasks.length > 0) {
|
||||
this.selectedTaskId = this.tasks[0].id;
|
||||
} else if (this.tasks.length === 0) {
|
||||
this.resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
this.loadError = safeErrorMessage(error, 'Aufgaben konnten nicht geladen werden.');
|
||||
@@ -690,14 +694,17 @@ export default {
|
||||
assignedUserId: this.form.assignedUserId || null,
|
||||
};
|
||||
try {
|
||||
let savedTaskId = this.form.id;
|
||||
if (this.form.id) {
|
||||
await apiClient.put(`/club-tasks/${this.currentClub}/${this.form.id}`, payload);
|
||||
const response = await apiClient.put(`/club-tasks/${this.currentClub}/${this.form.id}`, payload);
|
||||
savedTaskId = response.data?.task?.id || savedTaskId;
|
||||
} else {
|
||||
await apiClient.post(`/club-tasks/${this.currentClub}`, payload);
|
||||
const response = await apiClient.post(`/club-tasks/${this.currentClub}`, payload);
|
||||
savedTaskId = response.data?.task?.id || savedTaskId;
|
||||
}
|
||||
this.selectedTaskId = savedTaskId ? String(savedTaskId) : null;
|
||||
await this.loadTasks();
|
||||
this.showInfo('Erfolg', 'Aufgabe gespeichert.', '', 'success');
|
||||
this.resetForm();
|
||||
} catch (error) {
|
||||
this.showInfo('Fehler', safeErrorMessage(error, 'Aufgabe konnte nicht gespeichert werden.'), '', 'error');
|
||||
} finally {
|
||||
@@ -746,6 +753,9 @@ export default {
|
||||
await apiClient.patch(`/club-tasks/${this.currentClub}/${task.id}/status`, {
|
||||
status: 'archived',
|
||||
});
|
||||
if (this.selectedTask?.id === task.id) {
|
||||
this.resetForm();
|
||||
}
|
||||
await this.loadTasks();
|
||||
this.showInfo('Erfolg', 'Aufgabe wurde archiviert.', '', 'success');
|
||||
} catch (error) {
|
||||
|
||||
@@ -2165,7 +2165,7 @@ export default {
|
||||
};
|
||||
|
||||
const configureLeagueFromUrl = async () => {
|
||||
if (!parsedMyTischtennisData.value || parsedMyTischtennisData.value.urlType !== 'table') {
|
||||
if (!parsedMyTischtennisData.value || parsedMyTischtennisData.value.urlType !== 'table' || !selectedClub.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2176,6 +2176,7 @@ export default {
|
||||
try {
|
||||
const response = await apiClient.post('/mytischtennis/configure-league', {
|
||||
url: myTischtennisUrl.value.trim(),
|
||||
clubId: selectedClub.value,
|
||||
createSeason: true
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user