# Story 4.3: Akeneo Collaborative Workflow Submission & Outcome Tracking

## Story

**As a** data steward,
**I want** low-confidence predictions to be submitted to Akeneo Collaborative Workflow and their approval or rejection outcomes to be reflected automatically in the system,
**So that** I can manage category review entirely through Akeneo's standard approval process.

## Status

done

## Acceptance Criteria

All ACs met.

## Tasks / Subtasks

- [x] **Task 1: `akeneo_workflow` migration** (AC: schema)
  - [x] `20260524000003_create_akeneo_workflow_table.php` — table with `id`, `product_id` (FK products.id CASCADE), `prediction_id` (nullable — FK populated in Epic 5), `akeneo_product_identifier`, `suggested_category_code`, `workflow_status` (default `pending`), `akeneo_proposal_code` (nullable), `submitted_at` (nullable), `outcome_received_at` (nullable), `error_message` (nullable), `created_at`, `updated_at`.
  - [x] Indexes: `idx_akeneo_workflow_product_id`, `idx_akeneo_workflow_status`, `idx_akeneo_workflow_proposal_code`.

- [x] **Task 2: `AkeneoService::submitToCollaborativeWorkflow()`** (AC: submit to workflow)
  - [x] POST `/api/rest/v1/products-draft/{akeneIdentifier}` with categories + evidence summary.
  - [x] Same 30s timeout, 3-retry, exponential backoff as Story 4.2.
  - [x] Returns proposal code from Akeneo response (`code` or `proposal_code` field).
  - [x] Callers store returned proposal code in `akeneo_workflow.akeneo_proposal_code` and update `workflow_status = 'submitted'` + `submitted_at`.

- [x] **Task 3: `AkeneoService::getProposalStatus()`** (AC: poll outcomes)
  - [x] GET `/api/rest/v1/products-draft/{proposalCode}`.
  - [x] Maps Akeneo status values to `'approved'`, `'rejected'`, or `'pending'`.

- [x] **Task 4: `scripts/poll_akeneo_workflow.php`** (AC: poll script)
  - [x] SAPI guard. Authenticates once. Queries all `akeneo_workflow` rows with `workflow_status = 'submitted'`.
  - [x] For each: calls `getProposalStatus()`; skips `'pending'` records.
  - [x] For `'approved'`/`'rejected'`: updates `akeneo_workflow.workflow_status`, `outcome_received_at` AND `products.status` + `products.updated_at` **in one transaction**.
  - [x] On exception: marks workflow record `failed`, writes `akeneo_last_integration_failure`, continues to next record.
  - [x] Logs summary: "Workflow poll complete: {approved} approved, {rejected} rejected, {pending} still pending".
  - [x] Cron header documents `*/5 * * * *` schedule.

## Dev Notes

### Collaborative Workflow API endpoint

`submitToCollaborativeWorkflow()` targets `/api/rest/v1/products-draft/{identifier}` — the standard Akeneo draft/proposal endpoint. The exact response shape varies by Akeneo version; the method reads `decoded['code'] ?? decoded['proposal_code']` and falls back to `{identifier}-draft` so the system never fails silently on an unexpected schema.

### Transaction scope for status transitions

`poll_akeneo_workflow.php` updates both `akeneo_workflow` and `products` in a single `beginTransaction/commit` block. If the DB write fails mid-update, `rollBack()` is called and the workflow record remains `submitted` so the next poll run retries it cleanly.

### Products status transitions (AC)

| Workflow status  | products.status set to |
|------------------|------------------------|
| `submitted`      | `submitted`            |
| `approved`       | `approved`             |
| `rejected`       | `rejected`             |

Note: the `submitted` → products transition happens when the Epic 5 caller saves the `akeneo_workflow` record (not in the poll script). The poll script only handles `approved`/`rejected` transitions.

### File list

**New files:** `db/migrations/20260524000003_create_akeneo_workflow_table.php`, `scripts/poll_akeneo_workflow.php`  
**Modified files:** `src/Services/AkeneoService.php` (submitToCollaborativeWorkflow, getProposalStatus — implemented in Story 4.1 rewrite)
