Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a5710f6
fix(settings): resolve a schema slug inside dossiq's own register
rubenvdlinde Sep 2, 2026
3ace901
feat(demo): seed a demo caseload so the dashboard has something to show
rubenvdlinde Sep 2, 2026
26c274c
Merge remote-tracking branch 'origin/development' into feat/demo-case…
rubenvdlinde Sep 2, 2026
c4d66a7
fix(settings): stop merging dossiq's schema annotations onto another …
rubenvdlinde Sep 2, 2026
67bee0b
fix(dashboard): let the alert widgets show at-risk cases, not only ov…
rubenvdlinde Sep 2, 2026
ea7a917
fix(widgets): raise the alert caps, and cover the caseload regression…
rubenvdlinde Sep 2, 2026
4dfde28
refactor(demo): satisfy gate-17 and gate-66 on the demo caseload gateway
rubenvdlinde Sep 2, 2026
6d8b48d
style(e2e): apply prettier to the demo caseload spec
rubenvdlinde Sep 2, 2026
904d3cb
docs(dev): record how to bring up the demo caseload and demo email
rubenvdlinde Sep 2, 2026
4304443
fix(e2e): stop the caseload spec leaving a case behind on every run
rubenvdlinde Sep 2, 2026
b95a6fc
test(settings): declare SchemaSlugResolver as a used class
rubenvdlinde Sep 2, 2026
d5523f2
fix(dashboard): stop two case panels counting closed cases
rubenvdlinde Sep 2, 2026
62f87be
fix(dashboard): make the KPI cards answer the questions the spec asks
rubenvdlinde Sep 2, 2026
8fda7ce
test(settings): declare the resolver in the third SettingsService tes…
rubenvdlinde Sep 2, 2026
a859796
fix(widgets): the `_filters[x]` query param is inert, so three widget…
rubenvdlinde Sep 2, 2026
15b3bb5
fix(store): the inert `_filters[x]` param, everywhere it was used
rubenvdlinde Sep 3, 2026
638c44e
docs(spec): tag the three methods the filter rewrite touched
rubenvdlinde Sep 3, 2026
bb11782
Merge remote-tracking branch 'origin/development' into feat/demo-case…
rubenvdlinde Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,57 @@ npm run dev # Development build (watch mode)
npm run build # Production build
```

## Demo data

The dashboard widgets, the Tasks page and My Work all filter on the current
user, so a fresh instance shows empty panels even when the register imported
cleanly. Seed a working caseload:

```bash
occ dossiq:demo:seed # 18 cases across the four case types, 32 tasks
occ dossiq:demo:seed --verify-only # report the buckets without creating anything
```

Safe to re-run. A case whose title is already present is skipped, and its tasks
with it.

The dates in `lib/Settings/demo_caseload_seed_data.json` are relative day
offsets, resolved when the seed runs, so the overdue and at-risk buckets stay
correct however long the file sits in the repo. Give a case either
`deadlineInDays` or `startInDays`, never both: `deadline` is a materialised
calculation over `startDate` plus the case type's `processingDeadline`, so the
seed reaches a deadline by backdating the start date.

`--verify-only` counts what the dashboard will read by querying the register
back, not by counting the seed file. A count taken from the input agrees with
the input whatever the materialiser did.

## Demo email

The Nextcloud dashboard's mail widget needs a mail server and an account. The
shared dev environment ships both.

```bash
docker compose -f .github/docker-compose.yml --profile mail up -d greenmail
bash .github/docker/mail/seed-mail.sh localhost 3025

occ app:enable mail
occ mail:account:create admin "Gemeente Demo" admin@test.local \
conduction-greenmail 3143 none admin@test.local admin@test.local \
conduction-greenmail 3025 none admin@test.local admin@test.local
occ mail:account:sync 1
```

Then put the widgets on the dashboard:

```bash
occ user:setting admin dashboard layout \
"procest_my_tasks_widget,procest_task_reminders_widget,mail-unread,procest_overdue_cases_widget,procest_deadline_alerts_widget,procest_cases_overview_widget"
```

The widget ids still carry the `procest_` prefix. Renaming them would drop the
widget out of every layout that already names it, so they stay.

## Code Quality

```bash
Expand Down
1 change: 1 addition & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ Vrij en open source onder de EUPL-1.2-licentie.
<command>OCA\Dossiq\Command\MigrateTenantsCommand</command>
<command>OCA\Dossiq\Command\MigratePartnersCommand</command>
<command>OCA\Dossiq\Command\SeedBezwaarBeroepCommand</command>
<command>OCA\Dossiq\Command\SeedDemoCaseloadCommand</command>
</commands>

<settings>
Expand Down
163 changes: 163 additions & 0 deletions lib/Command/SeedDemoCaseloadCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php

/**
* Dossiq dossiq:demo:seed command.
*
* Seeds a demonstrable caseload from lib/Settings/demo_caseload_seed_data.json:
* cases across the shipped case types plus their tasks, positioned so every
* dashboard widget has rows to show.
*
* Safe to re-run. A case whose title is already present is skipped, and its
* tasks with it.
*
* @category Command
* @package OCA\Dossiq\Command
*
* @author Conduction Development Team <info@conduction.nl>
* @copyright 2026 Conduction B.V.
* @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* SPDX-License-Identifier: EUPL-1.2
* SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
*
* @version GIT: <git-id>
*
* @link https://conduction.nl
*/

declare(strict_types=1);

namespace OCA\Dossiq\Command;

use OCA\Dossiq\Service\DemoCaseloadReport;
use OCA\Dossiq\Service\DemoCaseloadSeedDataService;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Seed the demo caseload.
*
* @spec openspec/specs/dossiq-app-scaffold/spec.md
*/
class SeedDemoCaseloadCommand extends Command {
/**
* Wire the command against the seeder and the user/group managers.
*
* @param DemoCaseloadSeedDataService $seeder The demo caseload seeder.
* @param DemoCaseloadReport $report Reads the caseload buckets back.
* @param IUserSession $userSession Session used to impersonate an admin.
* @param IGroupManager $groupManager Resolves an admin to impersonate.
*/
public function __construct(
private readonly DemoCaseloadSeedDataService $seeder,
private readonly DemoCaseloadReport $report,
private readonly IUserSession $userSession,
private readonly IGroupManager $groupManager,
) {
parent::__construct();
}//end __construct()

/**
* Define the command name, description and options.
*
* @return void
*
* @spec openspec/specs/dossiq-app-scaffold/spec.md
*/
protected function configure(): void {
$this->setName(name: 'dossiq:demo:seed')
->setDescription('Seed a demo caseload of cases and tasks, so the dashboard has something to show.')
->addOption(
name: 'verify-only',
shortcut: null,
mode: InputOption::VALUE_NONE,
description: 'Report what is already in the register and create nothing.'
);
}//end configure()

/**
* Run the seed and report what landed.
*
* @param InputInterface $input Console input.
* @param OutputInterface $output Console output.
*
* @return int Symfony command exit code.
*
* @spec openspec/specs/dossiq-app-scaffold/spec.md
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
// OpenRegister enforces RBAC on saveObject against the current user.
// occ runs with no session ("Anonymous"), which lacks create rights on
// the Case schema, so impersonate an admin for the seed.
if ($this->userSession->getUser() === null) {
$admin = $this->resolveAdmin();
if ($admin === null) {
$output->writeln('<error>No admin user found to run the seed under.</error>');
return Command::FAILURE;
}

$this->userSession->setUser($admin);
$output->writeln('<comment>Seeding as admin user "' . $admin->getUID() . '".</comment>');
}

$verifyOnly = (bool)$input->getOption('verify-only');

try {
if ($verifyOnly === false) {
$result = $this->seeder->seed();
$output->writeln('<info>dossiq:demo:seed done</info>');
$output->writeln(' cases created = ' . $result['cases']);
$output->writeln(' tasks created = ' . $result['tasks']);
$output->writeln(' cases skipped = ' . $result['skipped'] . ' (already present)');
}

$buckets = $this->report->buckets();
} catch (\Throwable $e) {
$output->writeln('<error>Demo caseload seed failed: ' . $e->getMessage() . '</error>');
return Command::FAILURE;
}

// Read back from the register, because the deadline a case ends up with
// is materialised by OpenRegister, not written by the seed.
$output->writeln('');
$output->writeln('<info>What the dashboard will read</info>');
$output->writeln(' open cases = ' . $buckets['open']);
$output->writeln(' overdue = ' . $buckets['overdue']);
$output->writeln(' deadline within 3d = ' . $buckets['dueSoon']);
$output->writeln(' closed = ' . $buckets['closed']);
$output->writeln(' open tasks = ' . $buckets['tasksOpen']);
$output->writeln(' tasks due within 3d = ' . $buckets['tasksDue']);

if ($buckets['tasksOpen'] === 0) {
$output->writeln('');
$output->writeln('<error>No open tasks landed. Check that task_schema points inside the dossiq register.</error>');
return Command::FAILURE;
}

return Command::SUCCESS;
}//end execute()

/**
* Resolve the first member of the admin group, if any.
*
* @return IUser|null The admin user to impersonate, or null when none exists.
*/
private function resolveAdmin(): ?IUser {
$adminGroup = $this->groupManager->get('admin');
if ($adminGroup === null) {
return null;
}

$users = $adminGroup->getUsers();
if (count($users) === 0) {
return null;
}

return reset($users);
}//end resolveAdmin()
}//end class
Loading
Loading