Salesforce Public Sector integration setup

Salesforce is the most common case-management backbone in larger cities + state agencies. The adapter creates a Salesforce **Case** for every routed report (OAuth2 refresh-token flow), then PATCHes it to `Closed` on resolution.

What you need to set up in Salesforce first:

1. **Create a Connected App** (Setup → App Manager → New Connected App).
- Enable OAuth Settings.
- Callback URL: any URL on your side — we never visit it (refresh-token flow).
- Selected OAuth Scopes: `api` + `refresh_token`.
- Save and note the **Client ID** + **Client Secret**.
2. **Get a refresh token.** The fastest path: use Salesforce's web-server OAuth flow once via the SF CLI or sfdx (`sfdx auth:web:login -r <instance> --setalias civicReports` followed by `sfdx auth:list --verbose`). Copy the `refreshToken` value out.
3. **Add four custom fields to the Case object** (Setup → Object Manager → Case → Fields & Relationships):
- `Turtini_Report_Id__c` (Text 32)
- `Turtini_Jurisdiction__c` (Text 80)
- `Turtini_Latitude__c` (Number 9, 6)
- `Turtini_Longitude__c` (Number 9, 6)

Add `Turtini_Address__c` (Text 255) too if you want the reverse-geocoded address mirrored. Without these custom fields the adapter still creates the Case successfully but the geo + provenance is dropped.

Then in Turtini:

1. `/civicreports` → **Integrations** tab.
2. Pick the jurisdiction → **Salesforce** kind.
3. Fields:
- **Instance URL** — `https://acme.my.salesforce.com` (or your sandbox URL).
- **Client ID** + **Client Secret** — from the Connected App.
- **Refresh Token** — from step 2 above.
4. Save.

The adapter re-exchanges the refresh token for a fresh access token on *every* call (Salesforce caches at the gateway anyway). If you rotate the refresh token, just re-save the integration with the new value.

On create, the new Case ID is persisted as `externalCaseIds.salesforce` on the Turtini report. On resolution we PATCH `Status: Closed`, `Reason: Resolved`, and `Description` = your resolution note.

Common gotchas:
- **invalid_grant** on first save = refresh token is stale or was issued for a different connected app. Re-issue.
- **403 on PATCH** = the running user's profile doesn't have edit access on Case. Grant edit on `Status`, `Reason`, `Description`.
- **No custom fields populated** = you didn't create the `__c` fields. Optional but useful for reporting on Turtini-sourced volume.