# Juris-PMS Configurable Workflow Foundation Plan

## A. Current status architecture

Work Item status is currently stored as a plain string in `work_items.status`:

```php
$table->string('status', 30)->default('to_do');
```

There is no Work Item status enum, status model, workflow model, foreign key, category, or transition model.

Current status values in the development database:

| Status | Count | Projects |
|---|---:|---|
| `to_do` | 24 | PMS, PORTAL, AI |
| `in_progress` | 9 | PMS, PORTAL |
| `done` | 4 | PMS, PORTAL |

Status usage:

- Creation always writes `to_do` in `CreateWorkItem`.
- Creation requests do not accept a status.
- Updates validate status against only `['to_do']`.
- The Work Item panel receives only `['to_do']` as its editor options.
- Worklog filtering accepts any 30-character string, then filters directly on `work_items.status`.
- Worklog filter options are derived only from statuses currently used by that project's Work Items.
- API/Inertia payloads serialize status as a bare string.
- The badge knows only `to_do`; unknown values get a generated label and neutral styling.
- TypeScript represents Work Item status as an unrestricted `string`.
- Status ordering is alphabetical in Worklog filters, not workflow order.
- There are no explicit status transition restrictions.
- Project lifecycle status (`planning`, `active`, etc.) is separate and should remain separate.

Exact current files involved:

- `database/migrations/2026_09_08_000001_create_work_items_table.php`
- `app/Models/WorkItem.php`
- `app/Domain/WorkItems/CreateWorkItem.php`
- `app/Domain/WorkItems/UpdateWorkItem.php`
- `app/Domain/WorkItems/WorkItemCreationContext.php`
- `app/Http/Requests/CreateWorkItemRequest.php`
- `app/Http/Requests/UpdateWorkItemRequest.php`
- `app/Http/Controllers/ProjectWorklogController.php`
- `app/Http/Controllers/WorkItemCollaborationController.php`
- `resources/js/types/index.ts`
- `resources/js/components/work-items/CreateWorkItemModal.vue`
- `resources/js/components/work-items/WorkItemPanel.vue`
- `resources/js/components/work-items/WorkItemStatusBadge.vue`
- `resources/js/pages/Projects/Worklog.vue`
- Relevant PHP and Vue tests listed under section J

## B. Problems and limitations

1. `to_do` is hardcoded independently in the database migration, creation service, update validation, panel payload, badges, and tests.
2. Existing data contains `in_progress` and `done`, but `UpdateWorkItemRequest` rejects both. An existing item in either status cannot retain that value through a normal edit unless status is omitted.
3. Status has no semantic category. Reporting cannot reliably distinguish completed versus active work after status display names become configurable.
4. Worklog options omit valid statuses with zero current items. A configured `QA` status would be absent until at least one item used it.
5. Status labels are inferred by converting underscores to spaces. Renaming a display label independently of its stable key is impossible.
6. Badge styling is tied to one hardcoded key. Custom status names cannot receive consistent semantic styling.
7. Status order is alphabetical or incidental rather than configured.
8. Validation does not establish that a status belongs to the Work Item's project workflow.
9. There is no deterministic workflow-aware initial status for newly created Work Items.
10. Direct `Project::create()` is widely used in tests and seeders. Assignment cannot rely solely on the HTTP controller.

## C. Recommended workflow architecture

Use organization-owned workflows that projects reference directly.

This is smaller than a `project_workflows` pivot because the initial requirement is exactly one workflow per project. It also permits projects in the same organization to reuse a workflow without duplicating definitions.

```text
Organization
  └── has many Workflows
        └── has many WorkflowStatuses

Project
  └── belongs to one Workflow

WorkItem
  └── retains a stable status key
      resolved against Project → Workflow → WorkflowStatuses
```

Important design decisions:

- Keep `work_items.status` as the canonical stable status key in this stage.
- Do not replace it with a database ID yet.
- Resolve status meaning through the project's assigned workflow and the status key.
- Make status keys unique within each workflow, not globally.
- Never allow a status key to be edited after it has been used. Change only its display name, category, position, or active state.
- Treat inactive statuses as historical: existing items may retain them, but users cannot newly select them.
- Use a PHP backed enum for categories: `todo`, `in_progress`, and `done`.
- Do not use a database enum, because expanding categories later would require schema alteration.
- Add an explicit `is_initial` field rather than assuming the first status is always the creation default.
- Initially allow movement between any active statuses in the assigned workflow.

Keeping the status key avoids rewriting 37 existing Work Items and avoids a risky dual-write period. A normalized `workflow_status_id` could be introduced later if database-level referential enforcement becomes necessary, but it is not required for the current foundation.

## D. Proposed database schema

### `workflows`

| Column | Definition |
|---|---|
| `id` | bigint primary key |
| `organization_id` | foreign key to organizations |
| `name` | varchar(100) |
| `key` | varchar(64), stable machine key |
| `is_active` | boolean, default true |
| `is_default` | boolean, default false |
| timestamps | standard Laravel timestamps |

Constraints and indexes:

- Unique: `(organization_id, key)`
- Index: `(organization_id, is_active)`
- Only one default workflow per organization, enforced transactionally in the domain service and tested.

Use `key`, rather than a globally unique slug, because separate organizations should remain isolated.

### `workflow_statuses`

| Column | Definition |
|---|---|
| `id` | bigint primary key |
| `workflow_id` | foreign key to workflows |
| `key` | varchar(30), stable machine key |
| `name` | varchar(100), editable display name |
| `category` | varchar(20) |
| `position` | unsigned integer |
| `is_initial` | boolean, default false |
| `is_active` | boolean, default true |
| timestamps | standard Laravel timestamps |

Constraints and indexes:

- Unique: `(workflow_id, key)`
- Unique: `(workflow_id, position)`
- Index: `(workflow_id, is_active, position)`
- Category checked in PHP using `WorkflowStatusCategory`
- Exactly one active initial status per workflow, enforced transactionally

### `projects`

Add a nullable `workflow_id` foreign key initially, then backfill it and make it non-null.

Foreign-key behavior should be restrictive. An assigned workflow must not be deletable while projects reference it. Deactivation is safer than deletion.

### `work_items`

Retain `status varchar(30)`. Remove its database default in the eventual contract migration, because the initial status must come from the assigned workflow rather than a global `to_do` default.

No `project_workflows` table is needed yet. If workflow assignment history or scheduled workflow changes are later required, that can be added without changing the workflow/status definitions.

## E. Migration and backfill strategy

Use an expand/backfill/contract sequence.

### Migration 1: workflow tables and nullable assignment

- Create `workflows`.
- Create `workflow_statuses`.
- Add nullable `projects.workflow_id`.
- Do not change `work_items.status`.

### Migration 2: deterministic backfill

For each organization:

1. Create or locate a workflow using stable key `default`.
2. Create the following statuses by key.

| Key | Display name | Category | Position | Initial |
|---|---|---|---:|---|
| `to_do` | To Do | `todo` | 10 | Yes |
| `in_progress` | In Progress | `in_progress` | 20 | No |
| `done` | Done | `done` | 30 | No |

3. Assign every existing project in that organization to the workflow using its resolved database ID.
4. Verify every distinct `work_items.status` has a matching status key in the project's assigned workflow.
5. Abort the migration with a descriptive exception if any unknown value is found.

The backfill must discover distinct database values rather than assuming only `to_do`. The current database specifically requires all three mappings.

### Migration 3: assignment enforcement

- Make `projects.workflow_id` non-null.
- Add the foreign key and index if not already added.
- Remove the global `to_do` default from `work_items.status`.
- Do not rewrite any Work Item status values.

### Safety properties

- No Work Item row is deleted or reset.
- IDs are resolved dynamically after creating or finding records by organization and stable key.
- Migration is compatible with the existing database.
- It does not require `migrate:fresh`.
- It can be made idempotent internally with `updateOrInsert` or `firstOrCreate` semantics.
- All backfill steps should run in transactions where supported.

### Rollback caution

The workflow assignment can be removed safely because Work Item keys remain unchanged. However, rollback should first verify that every Work Item still uses a legacy-supported key. A configured custom key introduced after deployment could lose its definition if workflow tables were dropped.

## F. Backend changes

### Models

Add:

- `Workflow`
- `WorkflowStatus`
- `WorkflowStatusCategory` backed enum

Relationships:

- `Organization::workflows()`
- `Project::workflow()`
- `Workflow::organization()`
- `Workflow::projects()`
- `Workflow::statuses()`
- `WorkflowStatus::workflow()`

Useful workflow behavior:

- `activeStatuses()` ordered by `position`
- `initialStatus()`
- `statusByKey(string $key)`
- `allowsStatusKey(string $key, bool $allowExistingInactive = false)`

### Project creation

`CreateProject` should resolve or provision the organization's default workflow inside its existing transaction and assign `workflow_id` before completion.

Because tests and seeders frequently call `Project::create()` directly, introduce a focused `EnsureDefaultWorkflow` service and use it in migrations/backfill, `CreateProject`, local seeding, and test workspace builders.

Avoid a model-created event that silently writes several related records. Explicit service use is easier to reason about and test.

### Work Item creation

`CreateWorkItem` should:

1. Load the project's assigned workflow.
2. Resolve its active initial status.
3. Write that status's stable key.
4. Fail clearly if the project has no valid workflow or initial status.

The browser does not need to submit status initially. If creation later permits selection, it must validate against the same active workflow statuses.

### Work Item updating

Replace `Rule::in(['to_do'])` with project-aware validation.

A dedicated rule such as `ActiveWorkflowStatus` should verify:

- The key exists in the project's assigned workflow.
- The status definition is active; or
- It equals the Work Item's current key, allowing unrelated edits to an item whose historical status has since been deactivated.

The domain service should repeat the invariant. Request validation alone does not protect non-HTTP callers.

### Serialization

Return status as a structured definition while retaining the current `status` key for compatibility:

```json
{
  "status": "in_progress",
  "status_definition": {
    "key": "in_progress",
    "name": "In Progress",
    "category": "in_progress",
    "position": 20,
    "active": true
  }
}
```

Editor and filter options should be structured objects ordered by configured position, not strings derived from currently used values.

### Filtering

Validate the Worklog status filter against the project workflow. An inactive status may remain filterable if Work Items currently use it, so historical items do not disappear.

The query may continue filtering `work_items.status` directly.

### Status transitions

For this stage, updating from any current status to any active status in the assigned workflow is valid.

Later transition support can be added with a `workflow_transitions` table containing workflow, source status, destination status, optional permission or condition metadata, and active state. The current status resolution service becomes the insertion point for transition checks, so no foundational redesign is required.

## G. Frontend changes

### Create Work Item

The create modal currently has no status field. Preserve that behavior and let the backend select the workflow's initial status.

Extend each project in `workItemCreation` with workflow identity, initial status, and ordered active status definitions if useful for future UI. Do not add a new selector unless requested.

### Work Item panel

Change `editorOptions.statuses` from `string[]` to structured status definitions.

The select should use:

- Option value: stable `key`
- Option label: configured `name`
- Order: configured `position`

Include the current inactive status when editing an item that already uses it, but mark or disable it appropriately so users can move away without selecting it again.

### Worklog

Provide every configured workflow status, even when its current Work Item count is zero.

Filter options should use key/name pairs ordered by workflow position. The query string can continue using the key.

### Badges

`WorkItemStatusBadge` should receive the status definition rather than infer meaning from the key.

Map category to existing visual variants:

- `todo` → neutral
- `in_progress` → info
- `done` → success

Display the configured name.

### TypeScript

Introduce types similar to:

```ts
type WorkflowStatusCategory = 'todo' | 'in_progress' | 'done';

type WorkflowStatus = {
    key: string;
    name: string;
    category: WorkflowStatusCategory;
    position: number;
    active: boolean;
};
```

Do not reuse `TaskStatus` from the generic UI reference. Its values describe an unrelated presentation component and do not match Work Item workflow keys.

## H. Authorization approach

Do not add a new permission in this foundation stage.

Future workflow administration should use existing permissions:

- Organization-wide reusable workflow creation and editing: `organization.manage`
- Assigning a workflow to a project: `project.manage`

If workflows remain organization-owned and reusable, a project manager should not be allowed to edit a shared definition unless they also have organization management authority. They may eventually be allowed to select among workflows approved for their organization.

No administration UI or routes are required now.

## I. Test plan

### Workflow foundation

- Creates the default workflow for an organization.
- Default workflow has stable key `default`.
- Creates exactly the `to_do`, `in_progress`, and `done` definitions.
- Category values are accepted only from the enum.
- Status keys are unique within a workflow.
- The same status key can exist safely in separate workflows.
- Exactly one initial active status is resolved.
- Status ordering follows `position`.
- Inactive workflows cannot be assigned to new projects.
- Inactive statuses cannot be selected for a new transition.
- An existing Work Item may retain its current inactive status during unrelated edits.

### Project assignment

- Existing projects receive their organization's default workflow.
- New projects created through `CreateProject` receive a workflow.
- Assignments use resolved records, never fixed IDs.
- A project cannot reference another organization's workflow.
- Project creation rolls back workflow-dependent work together with existing membership and role setup.

### Migration and backfill

- `to_do` remains `to_do`.
- `in_progress` remains `in_progress`.
- `done` remains `done`.
- Work Item counts and IDs remain unchanged.
- Unknown legacy status causes a safe, descriptive migration failure.
- Every project receives a non-null valid workflow.
- Repeated organization keys do not cross organization boundaries.

### Work Item creation and update

- New Work Items use the assigned workflow's initial status.
- Creation does not depend on `to_do`.
- Updates accept any active status in the assigned workflow.
- Updates reject status keys from another workflow or project.
- Updates reject unknown status keys.
- Updates reject inactive statuses except retaining the item's current value.
- Domain services enforce the same rules when invoked without HTTP validation.

### API and UI payloads

- Detail payload includes the configured status name and category.
- Panel options contain all active statuses in configured order.
- Worklog filters contain unused active statuses.
- Worklog filtering remains project-scoped.
- Badges map categories to semantic variants.
- Custom display names render without changing stored keys.

### Regression coverage

- Work Item hierarchy and type tests
- Creation and editing tests
- Project-scoped relationships and search
- Authorization and membership tests
- Activities and notes
- Activity media attachments
- Original Work Item attachments
- Worklog filtering and reordering
- WorkItemPanel load, edit, and error handling
- CreateWorkItemModal behavior

## J. Exact files likely to change

### New files

- `app/Enums/WorkflowStatusCategory.php`
- `app/Models/Workflow.php`
- `app/Models/WorkflowStatus.php`
- `app/Domain/Workflows/EnsureDefaultWorkflow.php`
- `app/Domain/Workflows/ResolveProjectWorkflowStatus.php`
- `app/Rules/ActiveWorkflowStatus.php`
- One or more new migrations for workflow tables, assignment, backfill, and enforcement
- `tests/Feature/WorkflowFoundationTest.php`
- Potentially `tests/Unit/WorkflowStatusCategoryTest.php`

### Existing backend files

- `app/Models/Organization.php`
- `app/Models/Project.php`
- `app/Models/WorkItem.php`
- `app/Domain/Projects/CreateProject.php`
- `app/Domain/WorkItems/CreateWorkItem.php`
- `app/Domain/WorkItems/UpdateWorkItem.php`
- `app/Domain/WorkItems/WorkItemCreationContext.php`
- `app/Http/Requests/UpdateWorkItemRequest.php`
- Potentially `app/Http/Requests/CreateWorkItemRequest.php`
- `app/Http/Controllers/ProjectWorklogController.php`
- `app/Http/Controllers/WorkItemCollaborationController.php`
- `database/seeders/LocalDevelopmentSeeder.php`

The original Work Item migration should not be edited after it has been applied. All schema changes belong in new migrations.

### Existing frontend files

- `resources/js/types/index.ts`
- `resources/js/components/work-items/CreateWorkItemModal.vue`
- `resources/js/components/work-items/WorkItemPanel.vue`
- `resources/js/components/work-items/WorkItemStatusBadge.vue`
- `resources/js/pages/Projects/Worklog.vue`

### Existing tests

- `tests/Feature/ProjectAdministrationTest.php`
- `tests/Feature/WorkItemFoundationTest.php`
- `tests/Feature/ProjectWorklogTest.php`
- `tests/Feature/WorkItemPanelTest.php`
- `tests/Feature/WorkItemCollaborationTest.php`
- `tests/Feature/WorkItemAttachmentTest.php`
- `resources/js/components/work-items/CreateWorkItemModal.test.ts`
- `resources/js/components/work-items/WorkItemPanel.test.ts`
- `resources/js/components/work-items/WorkItemPresentation.test.ts`
- `resources/js/pages/Projects/WorklogPage.test.ts`

The generic `TaskStatusBadge.vue`, project-status components, and project lifecycle request validation should not be converted to workflow statuses.

## K. Risks

1. The biggest immediate risk is assuming all data is `to_do`. The database proves otherwise.
2. Making `projects.workflow_id` non-null before completing the backfill would break existing projects and many tests.
3. Changing old applied migrations would not update the existing database and would create differences between fresh and upgraded installations.
4. Using one globally shared workflow would leak configuration across organizations.
5. Using mutable names as keys would corrupt filters, reporting, and existing Work Item references after a rename.
6. Deleting statuses that are still referenced would orphan semantic meaning. Statuses should be deactivated.
7. Using only request validation would let service-level callers create invalid state.
8. Deriving filter options from existing Work Items would continue hiding valid unused workflow statuses.
9. A database default of `to_do` could create invalid Work Items for projects whose workflow uses another initial key.
10. Direct `Project::create()` calls in tests and seeders will need explicit workflow provisioning or a carefully designed test helper.
11. Shared organization workflows require clear future authorization: editing one may affect multiple projects.
12. Adding transition tables now would add unnecessary complexity and prematurely constrain the design.

## L. Recommended implementation sequence

1. Add the category enum and workflow/status models.
2. Add workflow tables and nullable `projects.workflow_id`.
3. Implement `EnsureDefaultWorkflow` with the three current keys.
4. Backfill one default workflow per organization and assign every existing project.
5. Validate that every current Work Item key resolves in its project workflow.
6. Enforce non-null project workflow assignment and remove the global Work Item status default.
7. Add project/workflow relationships.
8. Integrate default workflow assignment into `CreateProject` and local seeding.
9. Add the centralized status resolver and validation rule.
10. Update Work Item creation to use the configured initial status.
11. Update Work Item editing and Worklog filtering to validate against the assigned workflow.
12. Extend backend payloads with structured status definitions while retaining the status key.
13. Update WorkItemPanel, Worklog, badge presentation, and TypeScript types.
14. Add workflow-specific migration, isolation, inactive-status, and creation tests.
15. Update existing regression fixtures and run the complete PHP and frontend test suites.
16. Do not add workflow administration UI or transition rules in this stage.

**READY FOR WORKFLOW FOUNDATION IMPLEMENTATION: YES**
