feat: MyEasyCMS v2 — Full SaaS rebuild
Some checks failed
Workflow / ⚫️ Test (push) Has been cancelled
Workflow / ʦ TypeScript (push) Has been cancelled

Complete rebuild of 22-year-old PHP CMS as modern SaaS:

Database (15 migrations, 42+ tables):
- Foundation: account_settings, audit_log, GDPR register, cms_files
- Module Engine: modules, fields, records, permissions, relations + RPC
- Members: 45+ field member profiles, departments, roles, honors, SEPA mandates
- Courses: courses, sessions, categories, instructors, locations, attendance
- Bookings: rooms, guests, bookings with availability
- Events: events, registrations, holiday passes
- Finance: SEPA batches/items (pain.008/001 XML), invoices
- Newsletter: campaigns, templates, recipients, subscriptions
- Site Builder: site_pages (Puck JSON), site_settings, cms_posts
- Portal Auth: member_portal_invitations, user linking

Feature Packages (9):
- @kit/module-builder — dynamic low-code CRUD engine
- @kit/member-management — 31 API methods, 21 actions, 8 components
- @kit/course-management, @kit/booking-management, @kit/event-management
- @kit/finance — SEPA XML generator + IBAN validator
- @kit/newsletter — campaigns + dispatch
- @kit/document-generator — PDF/Excel/Word
- @kit/site-builder — Puck visual editor, 15 blocks, public rendering

Pages (60+):
- Dashboard with real stats from all APIs
- Full CRUD for all 8 domains with react-hook-form + Zod
- Recharts statistics
- German i18n throughout
- Member portal with auth + invitation system
- Public club websites via Puck at /club/[slug]

Infrastructure:
- Dockerfile (multi-stage, standalone output)
- docker-compose.yml (Supabase self-hosted + Next.js)
- Kong API gateway config
- .env.production.example
This commit is contained in:
Zaid Marzguioui
2026-03-29 23:17:38 +02:00
parent 61ff48cb73
commit 1294caa7fa
120 changed files with 11013 additions and 1858 deletions

View File

@@ -148,6 +148,59 @@ export function createFinanceApi(client: SupabaseClient<Database>) {
return { ...data, items: items ?? [] };
},
// --- SEPA auto-populate from members (Gap 3) ---
async populateBatchFromMembers(batchId: string, accountId: string) {
// Get all active members with active SEPA mandates + dues categories
const { data: members, error: memberError } = await client
.from('members')
.select('id, first_name, last_name, dues_category_id')
.eq('account_id', accountId)
.eq('status', 'active');
if (memberError) throw memberError;
const { data: mandates, error: mandateError } = await client
.from('sepa_mandates')
.select('*')
.eq('account_id', accountId)
.eq('status', 'active')
.eq('is_primary', true);
if (mandateError) throw mandateError;
const { data: categories, error: catError } = await client
.from('dues_categories')
.select('id, amount')
.eq('account_id', accountId);
if (catError) throw catError;
const mandateMap = new Map((mandates ?? []).map((m: any) => [m.member_id, m]));
const categoryMap = new Map((categories ?? []).map((c: any) => [c.id, Number(c.amount)]));
let addedCount = 0;
for (const member of (members ?? []) as any[]) {
const mandate = mandateMap.get(member.id);
if (!mandate) continue;
const amount = member.dues_category_id ? categoryMap.get(member.dues_category_id) ?? 0 : 0;
if (amount <= 0) continue;
const { error } = await client.from('sepa_items').insert({
batch_id: batchId,
member_id: member.id,
debtor_name: `${member.first_name} ${member.last_name}`,
debtor_iban: mandate.iban,
debtor_bic: mandate.bic,
amount,
mandate_id: mandate.mandate_reference,
mandate_date: mandate.mandate_date,
remittance_info: `Mitgliedsbeitrag ${new Date().getFullYear()}`,
});
if (!error) addedCount++;
}
await this.recalculateBatchTotals(batchId);
return { addedCount };
},
// --- Utilities ---
validateIban,
};