# Story 4.2: Outbound Category Submission & Rate Limiting

## Story

**As a** system administrator,
**I want** the system to submit approved categorization results back to Akeneo reliably,
**So that** approved category assignments appear in Akeneo without manual data entry, even when the Akeneo API is slow or rate-limiting.

## Status

done

## Acceptance Criteria

All ACs met.

## Tasks / Subtasks

- [x] **Task 1: `AkeneoService::submitCategoryUpdate()`** (AC: PATCH, 2xx success, retry on 429/5xx, no retry on 404, re-auth on 401)
  - [x] PATCH `/api/rest/v1/products/{akeneIdentifier}` with `{"categories": [categoryCode]}` body and Bearer token.
  - [x] 30-second timeout (via `requestWithRetry` default).
  - [x] Returns `true` on 2xx.
  - [x] Throws `"Akeneo API rate limit exceeded after 3 retries."` after 3 × 429 attempts (1s/2s/4s backoff).
  - [x] Throws `"Akeneo server error after 3 retries (HTTP {code})."` after 3 × 5xx attempts.
  - [x] Throws `"Akeneo product not found: {identifier}"` on 404, no retry.
  - [x] Re-authenticates once on 401, then retries.
  - [x] All failures logged to `logs/app.log` with context (url, identifier, category).

- [x] **Task 2: Integration failure tracking** (AC: caller persists `akeneo_last_integration_failure`)
  - [x] Callers (`sync_akeneo.php`, `poll_akeneo_workflow.php`) catch exceptions and write `akeneo_last_integration_failure` to `system_settings` with timestamp + reason.
  - [x] `getHealthStats()` returns `akeneo.integration_fail` from settings; health dashboard displays it in the Akeneo Connection card.

## Dev Notes

### submitCategoryUpdate is thin

`submitCategoryUpdate()` delegates entirely to `requestWithRetry()`. The `$logIdentifier` and `$logCategory` parameters ensure all log entries include product context. The method returns `bool` (always `true` — exceptions surface all non-success paths), keeping the caller interface clean.

### Integration failure is at the call site

Per AC, `system_settings.akeneo_last_integration_failure` is set by the **calling code** (scripts), not inside AkeneoService. AkeneoService only logs to `app.log`. This keeps AkeneoService free of settings side-effects in test scenarios.

### File list

**Modified files:** `src/Services/AkeneoService.php` (submitCategoryUpdate implemented in the Story 4.1 rewrite), `scripts/sync_akeneo.php` (integration_fail tracking), `scripts/poll_akeneo_workflow.php` (integration_fail tracking), `src/Controllers/AdminController.php` (getHealthStats includes integration_fail), `src/Views/admin/health.php` (integration fail display), `public/assets/js/admin-health.js` (integration fail in Akeneo card)
