Factor out the duplicated sync logic from syncAddresses(), syncPhones(),
and syncEmails() into a single generic syncSubEntities() method that
accepts callables for fetch/update/create/delete operations. Each
specialized sync method is now a thin ~10-line wrapper.
(Closes#204)
Add IDBConnection dependency to MemberService and wrap create(),
update(), and softDelete() in transactions (beginTransaction/commit/
rollback). This ensures atomicity when inserting/updating members
alongside sub-entities (addresses, phones, emails) — a failure at
any step now rolls back the entire operation instead of leaving
orphaned records.
(Closes#203)
Replace in-memory array_filter with a dedicated SQL query
WHERE deleted_at IS NOT NULL in MemberMapper::findArchived().
This fixes broken pagination (limit/offset now apply to archived
members only), eliminates unnecessary data transfer, and follows
the same pattern as the countArchived() fix from #201.
- MemberMapper: 8 new *WithRelations() methods that fetch members with
addresses, phones, and emails in a single query using LEFT JOINs
- MemberMapper: addJoinClauses() and fetchWithRelations() private helpers
that handle JOIN duplication (one member × multiple sub-entities)
- MemberService: refactored findAll, findByFamily, findByStatus, search,
findByBirthdayThisMonth, findWithUnpaidFees, findFiltered, fullTextSearch
to delegate to joined mapper methods
- MemberService: added arrayToMember() and arrayToAddress() helpers so
buildMatchContext() works with flat-array results from fullTextSearch
- MemberServiceTest: updated all existing tests to mock new method names
and return flat-array format with nested sub-entities
- MemberServiceTest: added 10 new tests covering joined methods, backward
compatibility, and correct shape of returned data
- Moved issue-200 plan from open/ to done/
- Move completed plan files to .plans/done/
- Move 18 open plan files to .plans/open/
- Update .gitignore to exclude .verified_plans temp file
- Verified all 18 open plans still describe unimplemented issues
Inventory:
- General material, stock items, and sales CRUD
- Category management with CategoryPicker component
- Inventory reports service
- Database migrations and mappers
Frontend:
- Inventory view with tabs (Allgemeinmaterial, Verkaufsmaterial, Verkäufe)
- Forms for general material, stock items, and sales
- Search bar fix: flexbox wrapper with inline icon replaces broken NcTextField icon slot
Tests:
- All 1,491 tests pass (zero errors, warnings, deprecations)
- BundleImportServiceTest: fixed ZipArchive empty-file deprecation
- BundleImportService: null-coalescing for targetFields
- PHPUnit test infra: make test target via container
CLAUDE.md:
- Added Testing section as PR gating criterion
- createZipContent: check file_exists before file_get_contents, handle
ZipArchive close on empty archives
- extractCsvFiles: check filesize before ZipArchive::open to avoid
'Using empty file is deprecated' warning
- previewBundle: use null coalescing for $schema['targetFields']
Supports:
make test # all suites (Unit, Integration, DatabasePortability)
make test suite=Unit
make test suite=Integration
make test suite=DatabasePortability
Also switches composer to install dev dependencies so PHPUnit is available.
The NcTextField :show-icon prop with #icon slot doesn't work correctly
in @nextcloud/vue v9 — it renders a default icon that overlaps the text.
Replaced with a wrapper div that uses an absolutely positioned Magnify
icon alongside a clean NcTextField input.
The container inherits the host's resolver by default. If the host is
behind a DNS64 gateway (common for IPv6-transitioned home networks,
some mobile carriers, and certain NAT64 setups), the resolver returns
IPv6-only `64:ff9b::/96`-synthesized addresses for IPv4-only services.
A default Docker bridge network is IPv4-only, so those synthesized
addresses are unreachable and every outbound HTTPS call (self-update
check, Nextcloud calendar/contacts sync) fails with "No DNS record
found". Pinning to public resolvers sidesteps the synthesis entirely.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add shared PaginationControls component and usePageSize/useClientPagination
composables so every list view has a configurable "Zeilen pro Seite" dropdown
(10/20/50/100) persisted per table to localStorage. Also fix MemberController
returning paginated count instead of true total when no filters are active.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The original seed migration (Version000014) runs inside a transaction that
silently fails on PostgreSQL and SQLite during batch migration execution.
Add a post-migration IRepairStep that runs outside the migration transaction
and idempotently inserts the 3 default Stufen if the table is empty. MySQL
installs with existing data are unaffected (skips automatically).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make Mitglieder, Aktive Kinder, and Ansprechpartner columns sortable in
FamilyList. Add sorting to all FeeOverview columns (Mitglied, Betrag,
Bezahlt, Zahlungsdatum). Minor backup store and view fixes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add proper error handling in BackupController update endpoints instead of
generic handleAction. SelfUpdateService now auto-runs occ upgrade after
update, handles .tar.gz file extensions for PharData, accepts configurable
min download size for signatures, and fixes apps_paths config parsing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ensureLagerFolder endpoint and getLagerFilesById to browse Lager files
by ID rather than manual path. Rework LagerDetail file browser UI and add
folder links in LagerList.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add optional uhrzeit column to mv_injuries via migration 17. Update the
Injury entity, service, form component, and list view to capture and
display the time an injury occurred.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Version bump across info.xml, webpack.config.js, and main.js. Webpack now
outputs content-hashed filenames and templates/index.php dynamically resolves
them. Makefile cleans stale JS bundles before rebuilding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make quickfilters combinable (status + rolle + birthday + unpaid fees with AND logic)
- Add Mitglieder/Erziehungsberechtigte role quickfilters
- Add PLZ and Wohnort columns from primary address for KJR reports
- Add saved filter system (localStorage-persisted, create/apply/delete)
- Backend: unified findFiltered() in MemberMapper/MemberService/MemberController
- Fix all 75 pre-existing test failures (constructor mismatches, optional geburtsdatum,
DsgvoController body reading, FileController lagerFiles API, CalDavBackend stub)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a "Kalender-Synchronisation" section to Settings that lets users
trigger a full sync of member birthdays and Juleica reminders to the
Nextcloud CalDAV backend. The calendar is created automatically if it
doesn't exist. Users can also check/enable the Calendar UI app.
- CalendarSyncService: add CalDavBackend integration (ensureCalendarExists,
fullSyncCalDav, getCalendarStatus, enableCalendarApp)
- CalendarController: new REST endpoints (status, sync, enable-app)
- calendar.js Pinia store: frontend state management
- Settings.vue: calendar sync UI with status display and sync button
- CLAUDE.md: add workflow rule for commit+push after each feature
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>