Files
twenty/packages/twenty-apps
Rashad Karanouh 50c4659cae feat(twenty-partners): import an opportunity from TFT via manual workflow (#21979)
## What

Adds a one-way, manual copy of a single Opportunity from the
**twentyfortwenty (TFT)** workspace into **partners**. No automatic/echo
sync — one record per button press.

A TFT-side **manual Workflow** (a "Run workflow" button on the
Opportunity record) → **HTTP Request** action → `POST /s/opportunities`
on the partners app. The new `import-opportunity-from-tft` logic
function:

- **Shared-secret guard** on the `x-application-secret` header vs the
existing `PARTNER_APPLICATION_SECRET` app variable (the SDK's
`isAuthRequired` only accepts user JWTs, not API keys — same pattern as
`submit-partner-application`).
- **Idempotent on `tftOpportunityId`** (a field the partners Opportunity
object already has); falls back to `name` for manual calls. Re-press →
no duplicate.
- **Find-or-create** the Company (by name) and the point-of-contact
Person (by primary email), then `createOpportunity` with `name / amount
/ closeDate / stage / companyId / pointOfContactId`.

## Out of scope

- The **TFT-side workflow is a UI step** (built once in the TFT
workspace) — it can't live in this repo. The exact HTTP action config +
body mapping lives in the workflow itself.
- Owner/workspace-member copy and stage-enum remapping are intentionally
skipped (YAGNI).

## Files

- `src/logic-functions/import-opportunity-from-tft.logic-function.ts` —
the handler + manifest.
- `src/logic-functions/__tests__/import-opportunity-from-tft.test.ts` —
unit tests (auth reject / idempotent / mapped create).
- `package.json` — version bump **0.5.5 → 0.6.0** (minor; new feature).

## Verification (local bundle)

- `yarn test:unit` 3/3 · `yarn lint` 0/0 · `yarn twenty dev --once` →
`created logicFunction import-opportunity-from-tft`, no manifest
warnings.
- Live `POST /s/opportunities` with the secret → `201
{ok:true,created:true,id}` (confirms the app role can create Opportunity
+ Company + Person). Re-POST same `tftOpportunityId` → `created:false`.
Wrong secret → `unauthorized`.

## Deploy note

Additive (new logic function + HTTP route) — upgrades cleanly with
`deploy` + `install`. `PARTNER_APPLICATION_SECRET` is already set on
prod, so no new application variable to configure.
2026-06-23 08:47:16 +02:00
..