# AF3 — attachments: apply privacy, link-only rows, kind values

Read `_COMMON.md` first. No migration needed (columns exist from P0 `2026_11_03_100002`).

## Why
P0 added `clinic_patient_attachments.attachable_type/id, kind, external_url, is_private` and `ClinicPatientAttachment::scopeVisibleTo()`, but no controller applies them; download/destroy assume a stored file so a link-only row 500s; `kind` vocabulary differs from design.md (image|pdf|photo|video|link …).

## Do (files)
- `Modules/Clinic/app/Http/Controllers/ClinicPatientAttachmentController.php`:
  - index: apply `visibleTo($user->id)` unless the user `can('clinic.fields.private')`; accept optional `attachable_type`+`attachable_id` filter (allow-list the morph types: encounter, encounter_diagnosis, rad_study, clinical_procedure (string alias), clinical_document — map short aliases to model classes, reject unknown → 422); response includes kind, is_private, external_url, attachable_type (alias), attachable_id, is_external.
  - store: accept either a file OR `external_url` (https/http only, max 1024) — exactly one; accept `is_private`, `kind`, `attachable_type/attachable_id` (same allow-list; verify the target belongs to the same company AND same patient where it has a patient_id); derive `kind` from mime when not given: image/* → image, application/pdf → pdf, video/* → video, external → link, else file.
  - download: 404 for a private row the user can't see; for a link-only row return 422 `{message, external_url}` (or a redirect-free JSON) — never touch Storage with a null path.
  - destroy: only delete the blob when `path` is set; private rows only by uploader or `clinic.fields.private`.
  - update: allow toggling `is_private` (uploader or `clinic.fields.private`).
- Find the attachment Form Request(s) used by the controller and update rules accordingly (edit them; they are yours).
- `Modules/Clinic/app/Models/ClinicPatientAttachment.php`: add `KINDS` const = ['image','pdf','photo','video','link','file','report','consent'], `kindFromMime()` static helper, alias map for attachable types.
- `Modules/Nutrition/app/Http/Controllers/PatientPhotoController.php` (read it first): if it lists ClinicPatientAttachment rows, apply the same visibleTo rule; if it uses a different model, leave it and say so.
- Lang keys (both ar/en) in `Modules/Clinic/lang/{ar,en}/errors.php` for new messages.

## Tests
Extend nothing existing; create `Modules/Clinic/tests/Feature/Af3AttachmentPrivacyTest.php`: private hidden from another user / visible to uploader / visible with clinic.fields.private; link-only store + download returns external_url without error; destroy link-only row works; attachable to an encounter of another patient → 422; kind derived from mime. Also run the existing `Modules/Clinic/tests/Feature/ClinicPatientAttachmentTest.php` and keep it green (adjust only if a test asserted the old exact response shape — report it).

## Out of scope
routes file (existing routes suffice — if you need a new route, report it), seeders, HistoryService, prescriptions, FE.
