more club funtions, fix for team edit

This commit is contained in:
Torsten Schulz (local)
2026-06-21 17:15:04 +02:00
parent 542fae089c
commit 302ed20a9b
18 changed files with 2032 additions and 16 deletions

View File

@@ -299,6 +299,7 @@ export const CLUB_DATA_MODELS = {
'invoice_number',
'external_reference',
'party_id',
'account_id',
'issued_on',
'due_on',
'paid_on',

View File

@@ -42,6 +42,7 @@ const ClubHistoryView = () => import('./views/ClubHistoryView.vue');
const ClubStatisticsView = () => import('./views/ClubStatisticsView.vue');
const ClubArchiveView = () => import('./views/ClubArchiveView.vue');
const ClubAccountsView = () => import('./views/ClubAccountsView.vue');
const ClubInvoicesView = () => import('./views/ClubInvoicesView.vue');
const ClubConceptModuleView = () => import('./views/ClubConceptModuleView.vue');
const Impressum = () => import('./views/Impressum.vue');
const Datenschutz = () => import('./views/Datenschutz.vue');
@@ -140,6 +141,7 @@ const conceptRoutes = CLUB_CONCEPT_ROUTES
.filter((route) => route.path !== '/club-statistics')
.filter((route) => route.path !== '/club-archive')
.filter((route) => route.path !== '/club-accounts')
.filter((route) => route.path !== '/club-invoices')
.map((route) => ({
path: route.path,
name: route.name,
@@ -189,6 +191,7 @@ const routes = [
{ path: '/club-statistics', name: 'club-statistics', component: ClubStatisticsView, meta: withMeta({ products: clubOnly, permission: ['statistics', 'read'] }) },
{ path: '/club-archive', name: 'club-archive', component: ClubArchiveView, meta: withMeta({ products: clubOnly, permission: ['settings', 'read'] }) },
{ path: '/club-accounts', name: 'club-accounts', component: ClubAccountsView, meta: withMeta({ products: clubOnly, permission: ['members', 'read'] }) },
{ path: '/club-invoices', name: 'club-invoices', component: ClubInvoicesView, meta: withMeta({ products: clubOnly, permission: ['members', 'read'] }) },
...conceptRoutes,
{ path: '/impressum', name: 'impressum', component: Impressum, meta: withMeta({ public: true, products: allProducts }) },
{ path: '/datenschutz', name: 'datenschutz', component: Datenschutz, meta: withMeta({ public: true, products: allProducts }) },

File diff suppressed because it is too large Load Diff

View File

@@ -124,6 +124,30 @@
</div>
</section>
<section v-if="currentClub && !loading" class="card">
<h2>Rechnungsnummern</h2>
<p class="hint">Automatische Nummernvergabe für Ausgangs- und Eingangsrechnungen.</p>
<div class="field-grid">
<div class="field-group">
<label>Präfix Ausgangsrechnungen</label>
<input v-model="outgoingInvoicePrefix" class="text-input" maxlength="24" placeholder="RE" />
</div>
<div class="field-group">
<label>Nächste Nummer Ausgang</label>
<input v-model.number="outgoingInvoiceNextNumber" class="text-input" type="number" min="1" step="1" />
</div>
<div class="field-group">
<label>Präfix Eingangsrechnungen</label>
<input v-model="incomingInvoicePrefix" class="text-input" maxlength="24" placeholder="EI" />
</div>
<div class="field-group">
<label>Nächste Nummer Eingang</label>
<input v-model.number="incomingInvoiceNextNumber" class="text-input" type="number" min="1" step="1" />
</div>
</div>
<p class="hint">Beispiel heute: {{ invoiceNumberPreview('outgoing') }} / {{ invoiceNumberPreview('incoming') }}</p>
</section>
<section v-if="currentClub && !loading" class="card actions-card">
<div class="actions">
<button class="btn btn-primary" @click="save">{{ $t('clubSettings.save') }}</button>
@@ -249,6 +273,10 @@ export default {
myTischtennisFedNickname: '',
autoFetchRankings: false,
memberDataQualityRequirements: defaultMemberDataQualityRequirements(),
outgoingInvoicePrefix: 'RE',
outgoingInvoiceNextNumber: 1,
incomingInvoicePrefix: 'EI',
incomingInvoiceNextNumber: 1,
saved: false,
loading: false,
loadError: null,
@@ -315,6 +343,10 @@ export default {
this.myTischtennisFedNickname = '';
this.autoFetchRankings = false;
this.memberDataQualityRequirements = defaultMemberDataQualityRequirements();
this.outgoingInvoicePrefix = 'RE';
this.outgoingInvoiceNextNumber = 1;
this.incomingInvoicePrefix = 'EI';
this.incomingInvoiceNextNumber = 1;
this.loadError = null;
return;
}
@@ -330,6 +362,10 @@ export default {
this.myTischtennisFedNickname = club?.myTischtennisFedNickname ?? '';
this.autoFetchRankings = !!club?.autoFetchRankings;
this.memberDataQualityRequirements = this.normalizeMemberDataQualityRequirements(club?.memberDataQualityRequirements);
this.outgoingInvoicePrefix = club?.outgoingInvoicePrefix ?? 'RE';
this.outgoingInvoiceNextNumber = Number(club?.outgoingInvoiceNextNumber ?? 1) || 1;
this.incomingInvoicePrefix = club?.incomingInvoicePrefix ?? 'EI';
this.incomingInvoiceNextNumber = Number(club?.incomingInvoiceNextNumber ?? 1) || 1;
} catch (e) {
this.loadError = this.$t('clubSettings.loadFailed');
this.greeting = '';
@@ -339,6 +375,10 @@ export default {
this.myTischtennisFedNickname = '';
this.autoFetchRankings = false;
this.memberDataQualityRequirements = defaultMemberDataQualityRequirements();
this.outgoingInvoicePrefix = 'RE';
this.outgoingInvoiceNextNumber = 1;
this.incomingInvoicePrefix = 'EI';
this.incomingInvoiceNextNumber = 1;
} finally {
this.loading = false;
}
@@ -363,6 +403,26 @@ export default {
return null;
}
},
normalizeInvoicePrefix(value, fallback) {
const normalized = String(value || fallback || '').trim().toUpperCase().slice(0, 24);
return normalized || fallback;
},
normalizeInvoiceNextNumber(value) {
const numeric = Number.parseInt(value, 10);
return Number.isInteger(numeric) && numeric > 0 ? numeric : 1;
},
invoiceNumberPreview(direction) {
const year = new Date().getFullYear();
const isIncoming = direction === 'incoming';
const prefix = isIncoming
? this.normalizeInvoicePrefix(this.incomingInvoicePrefix, 'EI')
: this.normalizeInvoicePrefix(this.outgoingInvoicePrefix, 'RE');
const nextNumber = isIncoming
? this.normalizeInvoiceNextNumber(this.incomingInvoiceNextNumber)
: this.normalizeInvoiceNextNumber(this.outgoingInvoiceNextNumber);
const padded = String(nextNumber).padStart(4, '0');
return `${prefix}-${year}-${padded}`;
},
emptyVenueForm() {
return { id: null, name: '', address: '', zip: '', city: '' };
},
@@ -444,6 +504,10 @@ export default {
myTischtennisFedNickname: this.myTischtennisFedNickname || null,
autoFetchRankings: this.autoFetchRankings,
memberDataQualityRequirements: this.normalizeMemberDataQualityRequirements(this.memberDataQualityRequirements),
outgoingInvoicePrefix: this.normalizeInvoicePrefix(this.outgoingInvoicePrefix, 'RE'),
outgoingInvoiceNextNumber: this.normalizeInvoiceNextNumber(this.outgoingInvoiceNextNumber),
incomingInvoicePrefix: this.normalizeInvoicePrefix(this.incomingInvoicePrefix, 'EI'),
incomingInvoiceNextNumber: this.normalizeInvoiceNextNumber(this.incomingInvoiceNextNumber),
});
this.saved = true;
setTimeout(() => (this.saved = false), 1500);