# Upstream FE PR draft (D2) — "Email Alert" approval action that scaffolds a Flow

**Target:** `pingcap-inc/NexusCRM` (via fork `chendu-dotcom/NexusCRM`) — **front-end only**.
**Layer:** L0 front-end convenience (generic, app-neutral). Emits **`ActionType='Flow'`**;
never writes `ActionType='Alert'` (that case fails closed at runtime). No backend change.
**Standard basis:** `l0-l1-standard.md` T3 — the email-alert behaviour is registered
content (a Flow); this is just an authoring affordance that produces it.
**Companion:** L2 catalog seed (D1, already in `deploy-baseline.seed.yaml`) populates the picker.

> Status: **draft — needs `FlowMetadata` finalization against the live flow types +
> `npm run build` + staging verification.** I verified the integration points and API
> signatures against the built ref `4937bb74`; I could not build-test the upstream
> front-end in this environment.

## UX (the goal)
In each approval lifecycle section (Initial Submission / Final Approval / Final
Rejection / …) the **Action Type** picker offers **"Email Alert"**. Selecting it shows a
template picker + recipients + sender (reuse `EmailAlertForm.tsx`). On **Save**, it
scaffolds a Flow (a `sendEmail` node with the chosen template + recipients) and creates a
`Flow` ProcessAction bound to that section's `ActionEvent`.

## Verified integration points (built ref 4937bb74)
- `frontend/src/platform/features/approvals/components/ProcessActionsSection.tsx`
  - `ALLOWED_ACTION_TYPES = ['FieldUpdate','Flow']` (~L75) — add an `'Alert'` **UI** option; `getActionTypeLabel('Alert') = 'Email Alert'` already exists (~L81).
  - form state (~L178) + `resetForm` (~L221): add `formEmailTemplateId`, `formRecipients`, `formSenderType`, `formCcEmails`.
  - render form (~L446–562): when `formActionType === 'Alert'`, render `<EmailAlertForm …/>` instead of the Field/Flow inputs.
  - `handleAddAction` (~L304) + `handleUpdateAction` (~L280): branch on `formActionType === 'Alert'` → call `scaffoldEmailAlertFlow(...)` (below) instead of a direct `createProcessAction`.
  - `startEditing` (~L258) already blocks legacy `ActionType==='Alert'` rows (read-only) — keep; edit of the new Flow-backed alert edits the Flow.
- APIs (verified signatures):
  - `flowsApi.saveFlow(flowData: Partial<Flow>, metadata: FlowMetadata): Promise<{ id, VersionNumber }>` (`infrastructure/api/flows/definitions.ts:444`)
  - `flowsApi.listApprovalNotificationFlows(): Promise<Flow[]>` (`…/flows/execution.ts:54`)
  - `approvalsAPI.createProcessAction(action: Omit<APIProcessAction,'Id'|'CreatedDate'|'LastModifiedDate'>): Promise<{ id }>` (`infrastructure/api/approvals.ts:844`)
  - `EmailAlertForm.tsx` props already provide template/recipients/sender/cc inputs.
  - sendEmail node config keys (from `SendEmailPanel.tsx`): `templateId`, `to`, `cc`, `variables` (each `{ stringValue } | { elementReference }`).

## New helper — `features/approvals/utils/emailAlertScaffold.ts`
```ts
import { flowsApi } from '../../../infrastructure/api/flows';
import { approvalsAPI } from '../../../infrastructure/api/approvals';
import type { RecipientRowData } from '../approvalTypes';

// Convert the EmailAlertForm recipients to a Flow sendEmail `to` scalar.
// v1: literal emails only (type 'email'); role/user/field paths are a follow-up
// (need approval-context / schema resolution). Returns comma-joined addresses.
function recipientsToTo(recipients: RecipientRowData[]): { stringValue: string } {
  const emails = recipients
    .filter(r => r.type === 'email' && r.recipient && r.recipient.includes('@'))
    .map(r => r.recipient as string);
  return { stringValue: emails.join(', ') };
}

/** Scaffold a Flow (single sendEmail node) + a Flow ProcessAction bound to the event.
 *  Emits ActionType='Flow' — NOT 'Alert'. Rolls the Flow back if the action create fails. */
export async function scaffoldEmailAlertFlow(args: {
  processDefinitionId: string;
  processNodeId?: string;
  actionEvent: string;          // Submit | FinalApproval | FinalRejection | …
  name: string;
  emailTemplateId: string;
  recipients: RecipientRowData[];
  ccEmails?: string;
  order: number;
}): Promise<void> {
  const dev = `Approval_EmailAlert_${args.actionEvent}_${slug(args.name)}`; // deterministic; dedupe if exists
  // NOTE for DEV: fill FlowMetadata per the live flow types (mirror the Flow builder's
  // save payload for a single sendEmail node). Required arrays default to [].
  const metadata /*: FlowMetadata */ = {
    label: args.name,
    description: `Approval email alert (${args.actionEvent})`,
    processType: 'AutoLaunchedFlow',       // confirm the ProcessType enum value
    status: 'Draft',
    actionCalls: [{
      name: `sendAlert_${slug(args.name)}`,
      label: args.name,
      actionType: 'sendEmail',
      actionName: 'sendEmail',
      locationX: 100, locationY: 100,
      inputParameters: [
        { name: 'templateId', value: { stringValue: args.emailTemplateId } },
        { name: 'to', value: recipientsToTo(args.recipients) },
        ...(args.ccEmails ? [{ name: 'cc', value: { stringValue: args.ccEmails } }] : []),
      ],
    }],
    variables: [], formulas: [], constants: [], recordCreates: [], recordUpdates: [],
    recordDeletes: [], recordLookups: [], assignments: [], decisions: [], loops: [],
  };

  const flow = await flowsApi.saveFlow({ DeveloperName: dev }, metadata as any);
  try {
    await approvalsAPI.createProcessAction({
      Name: args.name,
      ProcessDefinitionId: args.processDefinitionId,
      ProcessNodeId: args.processNodeId,
      ActionType: 'Flow',                  // ← the live path, never 'Alert'
      ActionEvent: args.actionEvent as any,
      ApiName: dev,                        // Flow DeveloperName
      Order: args.order,
    });
  } catch (e) {
    // best-effort rollback of the orphaned Flow
    try { await flowsApi.deleteFlowDefinition?.(dev); } catch { /* log */ }
    throw e;
  }
}

const slug = (s: string) => s.trim().replace(/[^A-Za-z0-9]+/g, '_').slice(0, 40);
```

## Open items for DEV (finish + verify)
1. **`FlowMetadata` exact shape** — mirror the Flow builder's `saveFlow` payload for a
   single `sendEmail` node (types in `infrastructure/api/flows/*`); confirm `processType`
   enum + required arrays. This is the only part I could not pin to live types.
2. **Recipient fidelity** — v1 supports literal `email` recipients; map role/user/field to
   approval-context / `{!…}` field refs as a follow-up (needs the record context the
   approval Flow passes to the notification Flow).
3. **Approval/record context → template render** — confirm `executeApprovalFlowNotification`
   passes the target record so `{{Record.*}}` resolves in the scaffolded Flow's sendEmail.
4. **Flow naming / dedupe** — deterministic `DeveloperName`; on edit, update the existing Flow.
5. **Build + verify:** `cd frontend && npm run build`; then on staging add an Email Alert on
   Initial Submission / Final Approval / Final Rejection, pick templates, confirm a `Flow`
   ProcessAction + a scaffolded sendEmail Flow are created and email fires.

## Open the PR
Fork `chendu-dotcom/NexusCRM`, branch `feat/approval-email-alert-scaffolds-flow`, commit the
two files above, open a cross-repo PR to `pingcap-inc/NexusCRM` titled
*"feat(approval): Email Alert action option that scaffolds a Flow (no backend arm)"*,
linking pingcap-inc/nexuscrm-pingcap-pack#3530.
