Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .debt-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"eslint.inlineDisables": 14,
"eslint.suppressedViolations": 308,
"phpcs.inlineIgnores": 3,
"phpmd.suppressWarnings": 182,
"phpmd.suppressWarnings": 180,
"phpstan.ignorePatterns": 11,
"phpstan.inlineIgnores": 4,
"phpunit.skips": 5,
Expand Down
6 changes: 3 additions & 3 deletions lib/AppInfo/Registrar/DerivedStatusListenerRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
* @link https://conduction.nl
*
* @spec openspec/changes/what-a-status-declares/specs/status-transition-engine/spec.md
* @spec openspec/specs/status-transition-engine/spec.md
*/

declare(strict_types=1);
Expand All @@ -36,7 +36,7 @@
*
* @psalm-suppress UnusedClass
*
* @spec openspec/changes/what-a-status-declares/specs/status-transition-engine/spec.md
* @spec openspec/specs/status-transition-engine/spec.md
*/
class DerivedStatusListenerRegistrar {

Expand Down Expand Up @@ -65,7 +65,7 @@ class DerivedStatusListenerRegistrar {
*
* @return void
*
* @spec openspec/changes/what-a-status-declares/specs/status-transition-engine/spec.md
* @spec openspec/specs/status-transition-engine/spec.md
*/
public function register(IRegistrationContext $context): void {
// ONE journal for the two listeners. An autowired class is built again
Expand Down
34 changes: 15 additions & 19 deletions lib/Controller/BrcController.php
Original file line number Diff line number Diff line change
Expand Up @@ -455,13 +455,22 @@ private function destroyDecision(string $uuid): JSONResponse {
return $this->zgwService->mappingNotFoundResponse(self::ZGW_API, $resource);
}

$object = $resource . ' ' . $uuid;
try {
// Validate the besluit exists (will throw if not found).
$existingObj = $objectService->find(
$uuid,
register: $mappingConfig['sourceRegister'],
schema: $mappingConfig['sourceSchema']
);
} catch (\Throwable $e) {
return $this->zgwService->storeFailureResponse(error: $e, operation: 'BRC delete besluit', object: $object);
}

if ($existingObj === null) {
return $this->zgwService->notFoundResponse();
}

try {
$existingData = $this->objectToArray(row: $existingObj);

// Run destroy business rules.
Expand Down Expand Up @@ -493,25 +502,12 @@ private function destroyDecision(string $uuid): JSONResponse {

// Delete the besluit itself.
$objectService->deleteObject(uuid: $uuid);

$baseUrl = $this->zgwService->buildBaseUrl($this->request, self::ZGW_API, $resource);
$this->zgwService->publishNotification(
self::ZGW_API,
$resource,
$baseUrl . '/' . $uuid,
'destroy'
);

return new JSONResponse(data: [], statusCode: Http::STATUS_NO_CONTENT);
} catch (\Throwable $e) {
$this->zgwService->getLogger()->error(
'BRC delete besluit error: ' . $e->getMessage(),
['exception' => $e]
);
return new JSONResponse(
data: ['detail' => 'Not found'],
statusCode: Http::STATUS_NOT_FOUND
);
return $this->zgwService->storeFailureResponse(error: $e, operation: 'BRC delete besluit', object: $object);
}//end try

$this->zgwService->notifyDeleted(request: $this->request, zgwApi: self::ZGW_API, resource: $resource, uuid: $uuid);

return new JSONResponse(data: [], statusCode: Http::STATUS_NO_CONTENT);
}//end destroyBesluit()
}//end class
95 changes: 60 additions & 35 deletions lib/Controller/DrcController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
namespace OCA\Dossiq\Controller;

use OCA\Dossiq\Service\ZgwService;
use OCA\Dossiq\Service\Zgw\ZgwRuleResults;
use OCA\Dossiq\Service\Zgw\ZgwSearchScope;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\AnonRateLimit;
Expand Down Expand Up @@ -649,7 +650,8 @@ private function destroyGebruiksrecht(string $uuid): JSONResponse {
/**
* Delete an enkelvoudiginformatieobject.
*
* Refused while ObjectInformatieObjecten point at it (drc-008a). After a successful delete its
* Refused while ObjectInformatieObjecten point at it (drc-008a), and refused when that could
* not be looked up. After a successful delete its
* gebruiksrechten go (drc-008), and its stored files go when the document could be read
* before the delete.
*
Expand All @@ -661,9 +663,14 @@ private function destroyEio(string $uuid): JSONResponse {
$hasStoredFiles = $this->isReadableEio(uuid: $uuid);

// Drc-008a (VNG): Block EIO deletion when OIO relations exist.
if ($this->zgwService->getObjectService() !== null
&& empty($this->findOioRelationsForEio(eioUuid: $uuid)) === false
) {
try {
$relations = $this->findOioRelationsForEio(eioUuid: $uuid);
} catch (\Throwable $e) {
// The search did not run, so nobody knows whether relations point here. Refuse.
return $this->relationCheckNotCompleted(uuid: $uuid, cause: $e->getMessage());
}

if (empty($relations) === false) {
return new JSONResponse(
[
'detail' => $this->l10n->t('The document cannot be deleted: there are related ObjectInformatieObjecten.'),
Expand Down Expand Up @@ -701,6 +708,26 @@ private function destroyEio(string $uuid): JSONResponse {
return $response;
}//end destroyEio()

/**
* The answer when the relation check of a document delete could not run (drc-008a).
*
* The same refusal the rules give, logged with the rule and the document. Nothing is deleted.
*
* @param string $uuid The document uuid.
* @param string $cause Why the check could not run.
*
* @return JSONResponse
*/
private function relationCheckNotCompleted(string $uuid, string $cause): JSONResponse {
$refusal = (new ZgwRuleResults(logger: $this->zgwService->getLogger()))
->notVerified(rule: 'drc-008a', object: 'document ' . $uuid, cause: $cause);

return new JSONResponse(
data: $this->zgwService->buildValidationError(ruleResult: $refusal),
statusCode: $refusal['status']
);
}//end relationCheckNotCompleted()

/**
* Whether the document can be read, which is what decides whether its files are cleaned up.
*
Expand Down Expand Up @@ -1177,6 +1204,8 @@ public function audittrailShow(string $resource, string $uuid, string $auditUuid
* @param string $eioUuid The EIO UUID
*
* @return array List of related object IDs linked to this EIO
*
* @throws \Throwable When a schema cannot be searched.
*/
private function findOioRelationsForEio(string $eioUuid): array {
$objectService = $this->zgwService->getObjectService();
Expand Down Expand Up @@ -1237,44 +1266,40 @@ private function findOioRelationsForEio(string $eioUuid): array {
* @param string $register The register ID
* @param string $schema The schema ID
*
* @return array List of related object IDs
* @return array List of related object IDs. Empty means the search ran and found none.
*
* @throws \Throwable When the schema cannot be searched.
*/
private function searchRelationsInSchema(
object $objectService,
string $eioUuid,
string $register,
string $schema,
): array {
try {
// Try exact UUID match (OIO may store just the UUID).
$query = $objectService->buildSearchQuery(
requestParams: ['document' => $eioUuid, '_limit' => 1],
register: $register,
schema: $schema
);
$result = $objectService->searchObjectsPaginated(query: $query);
$ids = $this->extractIdsFromResults(result: $result);
if (empty($ids) === false) {
return $ids;
}

// Fallback: full-text search by UUID (document field stores
// the full URL, and field-specific LIKE is not supported).
$query = $objectService->buildSearchQuery(
requestParams: ['_search' => $eioUuid, '_limit' => 1],
register: $register,
schema: $schema
);
$result = $objectService->searchObjectsPaginated(query: $query);
$ids = $this->extractIdsFromResults(result: $result);
if (empty($ids) === false) {
return $ids;
}
} catch (\Throwable $e) {
$this->zgwService->getLogger()->warning(
'drc-008a: Relation search failed for schema ' . $schema . ': ' . $e->getMessage()
);
}//end try
// Try exact UUID match (OIO may store just the UUID).
$query = $objectService->buildSearchQuery(
requestParams: ['document' => $eioUuid, '_limit' => 1],
register: $register,
schema: $schema
);
$result = $objectService->searchObjectsPaginated(query: $query);
$ids = $this->extractIdsFromResults(result: $result);
if (empty($ids) === false) {
return $ids;
}

// Fallback: full-text search by UUID (document field stores
// the full URL, and field-specific LIKE is not supported).
$query = $objectService->buildSearchQuery(
requestParams: ['_search' => $eioUuid, '_limit' => 1],
register: $register,
schema: $schema
);
$result = $objectService->searchObjectsPaginated(query: $query);
$ids = $this->extractIdsFromResults(result: $result);
if (empty($ids) === false) {
return $ids;
}

return [];
}//end searchRelationsInSchema()
Expand Down
10 changes: 5 additions & 5 deletions lib/Listener/DerivedStatusListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
*
* @link https://conduction.nl
*
* @spec openspec/changes/what-a-status-declares/specs/status-transition-engine/spec.md
* @spec openspec/specs/status-transition-engine/spec.md
*/

declare(strict_types=1);
Expand All @@ -63,7 +63,7 @@
*
* @implements IEventListener<Event>
*
* @spec openspec/changes/what-a-status-declares/specs/status-transition-engine/spec.md
* @spec openspec/specs/status-transition-engine/spec.md
*/
class DerivedStatusListener implements IEventListener {

Expand Down Expand Up @@ -115,7 +115,7 @@ public function __construct(
*
* @return void
*
* @spec openspec/changes/what-a-status-declares/specs/status-transition-engine/spec.md
* @spec openspec/specs/status-transition-engine/spec.md
*/
public function handle(Event $event): void {
if ($event instanceof ObjectUpdatingEvent === false) {
Expand Down Expand Up @@ -171,7 +171,7 @@ public function handle(Event $event): void {
*
* @return array<string, mixed>|null The payload, or null.
*
* @spec openspec/changes/what-a-status-declares/specs/status-transition-engine/spec.md
* @spec openspec/specs/status-transition-engine/spec.md
*/
private function payload(ObjectEntity $entity): ?array {
try {
Expand All @@ -191,7 +191,7 @@ private function payload(ObjectEntity $entity): ?array {
*
* @return bool True when this is a case.
*
* @spec openspec/changes/what-a-status-declares/specs/status-transition-engine/spec.md
* @spec openspec/specs/status-transition-engine/spec.md
*/
private function isCaseSchema(array $object): bool {
$expected = $this->settingsService->getConfigValue('case_schema');
Expand Down
2 changes: 1 addition & 1 deletion lib/Service/ProcessMining/DwellTimeAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public function clock(): string {
*
* @return array<string, int> Working days, keyed by statusType id.
*
* @spec openspec/changes/what-a-status-declares/specs/doorlooptijd-dashboard/spec.md
* @spec openspec/specs/doorlooptijd-dashboard/spec.md
*/
public function heldTotalsByStatus(array $casesById, ?DateTimeImmutable $now = null): array {
if ($this->held === null) {
Expand Down
2 changes: 1 addition & 1 deletion lib/Service/ProcessMiningService.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public function __construct(
* @return array<string, mixed> The structured response body.
*
* @spec openspec/changes/process-mining-bottlenecks/tasks.md#T01
* @spec openspec/changes/what-a-status-declares/specs/doorlooptijd-dashboard/spec.md
* @spec openspec/specs/doorlooptijd-dashboard/spec.md
*/
public function getReport(array $params): array {
$to = ($this->dates->tryParse($params['to'] ?? null) ?? $this->dates->today());
Expand Down
1 change: 0 additions & 1 deletion lib/Service/Settings/SchemaAnnotationReconciler.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ private function reconcileSchemaAnnotationBlocks(object $schemaMapper, int|strin
* on a case-type store it never reads.
*
* @spec openspec/specs/status-transition-engine/spec.md
* @spec openspec/changes/what-a-status-declares/specs/status-transition-engine/spec.md
*/
private function mergeOntoLiveSchema(object $schemaMapper, string $slug, array $annotations): int {
// 🔴 RESOLVE INSIDE OUR OWN REGISTER. An unscoped slug lookup here merged
Expand Down
Loading
Loading