feat(form-builder): add ApplyStatus, DismissalReasonType, BindingTargetType enums (WS-6)
DismissalReasonType has six values; manually_resolved is intentionally absent because Resolve and Dismiss are separate workflows (RFC V2). Refs: RFC-WS-6.md §3 (Q4 partial-status separation), §4 (V2 dismiss enum) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
38
api/app/Enums/FormBuilder/ApplyStatus.php
Normal file
38
api/app/Enums/FormBuilder/ApplyStatus.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\FormBuilder;
|
||||
|
||||
/**
|
||||
* RFC-WS-6 §3 (Q4) — apply-state of a FormSubmission's binding pipeline.
|
||||
* Distinct from {@see \App\Models\FormBuilder\FormSubmission::$identity_match_status},
|
||||
* which tracks identity resolution (RFC O1).
|
||||
*/
|
||||
enum ApplyStatus: string
|
||||
{
|
||||
case PENDING = 'pending';
|
||||
case COMPLETED = 'completed';
|
||||
case PARTIAL = 'partial';
|
||||
case FAILED = 'failed';
|
||||
|
||||
public function isTerminal(): bool
|
||||
{
|
||||
return $this !== self::PENDING;
|
||||
}
|
||||
|
||||
public function isOpen(): bool
|
||||
{
|
||||
return $this !== self::COMPLETED;
|
||||
}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PENDING => 'Wachtrij',
|
||||
self::COMPLETED => 'Voltooid',
|
||||
self::PARTIAL => 'Gedeeltelijk mislukt',
|
||||
self::FAILED => 'Mislukt',
|
||||
};
|
||||
}
|
||||
}
|
||||
19
api/app/Enums/FormBuilder/BindingTargetType.php
Normal file
19
api/app/Enums/FormBuilder/BindingTargetType.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\FormBuilder;
|
||||
|
||||
/**
|
||||
* RFC-WS-6 §4 (V1) — storage shape of a binding-target attribute, as
|
||||
* declared by {@see \App\FormBuilder\Bindings\BindingTypeRegistry}.
|
||||
*
|
||||
* Source of truth for whether a target accepts the `Append` merge
|
||||
* strategy (only COLLECTION does — V1 rejects scalar-append).
|
||||
*/
|
||||
enum BindingTargetType: string
|
||||
{
|
||||
case SCALAR = 'scalar';
|
||||
case COLLECTION = 'collection';
|
||||
case RELATION = 'relation';
|
||||
}
|
||||
40
api/app/Enums/FormBuilder/DismissalReasonType.php
Normal file
40
api/app/Enums/FormBuilder/DismissalReasonType.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\FormBuilder;
|
||||
|
||||
/**
|
||||
* RFC-WS-6 §4 (V2) — typed reason for dismissing a
|
||||
* {@see \App\Models\FormBuilder\FormSubmissionActionFailure}.
|
||||
*
|
||||
* `manually_resolved` is intentionally absent — Resolve and Dismiss are
|
||||
* separate workflows (resolved_at + resolved_note vs. dismissed_at +
|
||||
* dismissed_reason_type/note).
|
||||
*/
|
||||
enum DismissalReasonType: string
|
||||
{
|
||||
case SCHEMA_DELETED = 'schema_deleted';
|
||||
case TARGET_ENTITY_DELETED = 'target_entity_deleted';
|
||||
case BINDING_REMOVED = 'binding_removed';
|
||||
case DUPLICATE_SUBMISSION = 'duplicate_submission';
|
||||
case DATA_QUALITY_ISSUE = 'data_quality_issue';
|
||||
case OTHER = 'other';
|
||||
|
||||
public function requiresNote(): bool
|
||||
{
|
||||
return $this === self::OTHER;
|
||||
}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::SCHEMA_DELETED => 'Formulier verwijderd',
|
||||
self::TARGET_ENTITY_DELETED => 'Doel-entiteit verwijderd',
|
||||
self::BINDING_REMOVED => 'Binding verwijderd',
|
||||
self::DUPLICATE_SUBMISSION => 'Dubbele inzending',
|
||||
self::DATA_QUALITY_ISSUE => 'Datakwaliteit-probleem',
|
||||
self::OTHER => 'Anders',
|
||||
};
|
||||
}
|
||||
}
|
||||
41
api/tests/Unit/Enums/FormBuilder/ApplyStatusTest.php
Normal file
41
api/tests/Unit/Enums/FormBuilder/ApplyStatusTest.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Enums\FormBuilder;
|
||||
|
||||
use App\Enums\FormBuilder\ApplyStatus;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ApplyStatusTest extends TestCase
|
||||
{
|
||||
public function test_has_four_cases_with_stable_values(): void
|
||||
{
|
||||
$values = array_map(fn (ApplyStatus $case) => $case->value, ApplyStatus::cases());
|
||||
sort($values);
|
||||
$this->assertSame(['completed', 'failed', 'partial', 'pending'], $values);
|
||||
}
|
||||
|
||||
public function test_is_terminal_truth_table(): void
|
||||
{
|
||||
$this->assertFalse(ApplyStatus::PENDING->isTerminal());
|
||||
$this->assertTrue(ApplyStatus::COMPLETED->isTerminal());
|
||||
$this->assertTrue(ApplyStatus::PARTIAL->isTerminal());
|
||||
$this->assertTrue(ApplyStatus::FAILED->isTerminal());
|
||||
}
|
||||
|
||||
public function test_is_open_truth_table(): void
|
||||
{
|
||||
$this->assertTrue(ApplyStatus::PENDING->isOpen());
|
||||
$this->assertFalse(ApplyStatus::COMPLETED->isOpen());
|
||||
$this->assertTrue(ApplyStatus::PARTIAL->isOpen());
|
||||
$this->assertTrue(ApplyStatus::FAILED->isOpen());
|
||||
}
|
||||
|
||||
public function test_label_returns_non_empty_dutch_string_for_each_case(): void
|
||||
{
|
||||
foreach (ApplyStatus::cases() as $case) {
|
||||
$this->assertNotSame('', $case->label());
|
||||
}
|
||||
}
|
||||
}
|
||||
25
api/tests/Unit/Enums/FormBuilder/BindingTargetTypeTest.php
Normal file
25
api/tests/Unit/Enums/FormBuilder/BindingTargetTypeTest.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Enums\FormBuilder;
|
||||
|
||||
use App\Enums\FormBuilder\BindingTargetType;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class BindingTargetTypeTest extends TestCase
|
||||
{
|
||||
public function test_has_three_cases_with_stable_values(): void
|
||||
{
|
||||
$values = array_map(fn (BindingTargetType $case) => $case->value, BindingTargetType::cases());
|
||||
sort($values);
|
||||
$this->assertSame(['collection', 'relation', 'scalar'], $values);
|
||||
}
|
||||
|
||||
public function test_from_string_round_trip(): void
|
||||
{
|
||||
$this->assertSame(BindingTargetType::SCALAR, BindingTargetType::from('scalar'));
|
||||
$this->assertSame(BindingTargetType::COLLECTION, BindingTargetType::from('collection'));
|
||||
$this->assertSame(BindingTargetType::RELATION, BindingTargetType::from('relation'));
|
||||
}
|
||||
}
|
||||
50
api/tests/Unit/Enums/FormBuilder/DismissalReasonTypeTest.php
Normal file
50
api/tests/Unit/Enums/FormBuilder/DismissalReasonTypeTest.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Enums\FormBuilder;
|
||||
|
||||
use App\Enums\FormBuilder\DismissalReasonType;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class DismissalReasonTypeTest extends TestCase
|
||||
{
|
||||
public function test_has_six_cases_with_stable_values(): void
|
||||
{
|
||||
$values = array_map(fn (DismissalReasonType $case) => $case->value, DismissalReasonType::cases());
|
||||
sort($values);
|
||||
$this->assertSame([
|
||||
'binding_removed',
|
||||
'data_quality_issue',
|
||||
'duplicate_submission',
|
||||
'other',
|
||||
'schema_deleted',
|
||||
'target_entity_deleted',
|
||||
], $values);
|
||||
}
|
||||
|
||||
public function test_manually_resolved_is_intentionally_absent(): void
|
||||
{
|
||||
$values = array_map(fn (DismissalReasonType $case) => $case->value, DismissalReasonType::cases());
|
||||
$this->assertNotContains('manually_resolved', $values);
|
||||
}
|
||||
|
||||
public function test_requires_note_only_for_other(): void
|
||||
{
|
||||
$this->assertTrue(DismissalReasonType::OTHER->requiresNote());
|
||||
|
||||
foreach (DismissalReasonType::cases() as $case) {
|
||||
if ($case === DismissalReasonType::OTHER) {
|
||||
continue;
|
||||
}
|
||||
$this->assertFalse($case->requiresNote(), "{$case->value} must not require a note");
|
||||
}
|
||||
}
|
||||
|
||||
public function test_label_returns_non_empty_dutch_string_for_each_case(): void
|
||||
{
|
||||
foreach (DismissalReasonType::cases() as $case) {
|
||||
$this->assertNotSame('', $case->label());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user