-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathconvert-lead.flow.ts
More file actions
171 lines (158 loc) · 7.58 KB
/
Copy pathconvert-lead.flow.ts
File metadata and controls
171 lines (158 loc) · 7.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { defineFlow } from '@objectstack/spec';
/**
* Convert Lead → Customer + Opportunity — Master-Detail Screen Flow
*
* A user-triggered wizard that walks a sales rep through converting a qualified
* lead into a full Account + Opportunity. Unlike a flat field-list wizard, each
* step renders the target object's COMPLETE create form via an `object-form`
* screen node (`config.objectName`): the client renders the real ObjectForm —
* including inline master-detail child grids — persists the record (and its
* children, atomically), and resumes the run with the new record's id bound to
* `config.idVariable`.
*
* start → get_lead → decision (already converted?)
* → screen_already_converted (abort path)
* → screen_account (Step 1 — full Customer form → account_id)
* → screen_opportunity (Step 2 — full Opportunity form WITH product
* line-items grid, prefilled account → opportunity_id)
* → update_lead (mark converted + link account & opportunity)
* → end
*/
export const ConvertLeadScreenFlow = defineFlow({
name: 'crm_convert_lead_wizard',
label: 'Convert Lead to Customer + Opportunity',
description:
'Screen-flow wizard that walks the rep through a full Customer form then a full Opportunity form (with product line items), creates both, and marks the lead converted.',
type: 'screen',
status: 'active',
runAs: 'user',
variables: [
// ── input (from the action trigger) ───────────────────────────────────
{ name: 'recordId', type: 'text', isInput: true, isOutput: false },
// ── intermediate (populated at runtime) ───────────────────────────────
{ name: 'lead_record', type: 'object', isInput: false, isOutput: false },
// ── produced by the object-form steps (the saved record ids) ──────────
// `isInput: true` lets the trigger API pre-populate them for automated
// testing; at runtime each is bound by its step's `idVariable` on resume.
{ name: 'account_id', type: 'text', isInput: true, isOutput: true },
{ name: 'opportunity_id', type: 'text', isInput: true, isOutput: true },
],
nodes: [
// ── 1. Start ──────────────────────────────────────────────────────────
{ id: 'start', type: 'start', label: 'Start' },
// ── 2. Load the lead record ───────────────────────────────────────────
{
id: 'get_lead',
type: 'get_record',
label: 'Load Lead',
config: {
objectName: 'crm_lead',
// get_record filters via `filter` (it ignores a bare `recordId`), so
// load THIS lead by id — otherwise findOne with an empty filter returns
// the first lead in the table.
filter: { id: '{recordId}' },
outputVariable: 'lead_record',
},
},
// ── 3. Guard: already converted? ──────────────────────────────────────
{
id: 'check_converted',
type: 'decision',
label: 'Already Converted?',
config: {
conditions: [
{ label: 'Yes — already converted', expression: "{lead_record.status} == 'converted'" },
{ label: 'No — proceed', expression: 'true' },
],
},
},
// ── 3a. Already-converted abort screen ────────────────────────────────
{
id: 'screen_already_converted',
type: 'screen',
label: 'Already Converted',
config: {
waitForInput: true,
title: 'Already Converted',
description: 'This lead has already been converted to an opportunity.',
},
},
// ── 4. Step 1 — full Customer (Account) form ──────────────────────────
// `objectName` ⇒ object-form screen: the client renders the real
// crm_account create form, persists it, and resumes with the new id under
// `account_id`.
{
id: 'screen_account',
type: 'screen',
label: 'Customer',
config: {
objectName: 'crm_account',
idVariable: 'account_id',
title: 'Step 1 of 2 · Customer',
description: 'Review and complete the customer record carried over from the lead.',
defaults: {
name: '{lead_record.company}',
},
},
},
// ── 5. Step 2 — full Opportunity form WITH product line items ──────────
// crm_opportunity_line_item.opportunity declares inlineEdit: 'grid', so the
// standard Opportunity form (and therefore this step) renders an editable
// product line-item grid below the header — the master-detail entry the
// user asked for. The account FK is prefilled with Step 1's new id.
{
id: 'screen_opportunity',
type: 'screen',
label: 'Opportunity',
config: {
objectName: 'crm_opportunity',
idVariable: 'opportunity_id',
title: 'Step 2 of 2 · Opportunity',
description: 'Add the opportunity and its product line items. Saved together in one transaction.',
defaults: {
name: '{lead_record.name}',
account: '{account_id}',
stage: 'qualification',
},
},
},
// ── 6. Mark the lead converted + link both new records ────────────────
{
id: 'update_lead',
type: 'update_record',
label: 'Mark Lead Converted',
config: {
objectName: 'crm_lead',
filter: { id: '{recordId}' },
fields: {
status: 'converted',
account: '{account_id}',
converted_opportunity: '{opportunity_id}',
},
},
},
// ── 7. End ────────────────────────────────────────────────────────────
{ id: 'end', type: 'end', label: 'End' },
],
edges: [
{ id: 'e1', source: 'start', target: 'get_lead', type: 'default' },
{ id: 'e2', source: 'get_lead', target: 'check_converted', type: 'default' },
// guard branches
{ id: 'e3a', source: 'check_converted', target: 'screen_already_converted', type: 'default', condition: "lead_record.status == 'converted'", label: 'Yes' },
{ id: 'e3b', source: 'check_converted', target: 'screen_account', type: 'default', label: 'No' },
{ id: 'e3c', source: 'screen_already_converted', target: 'end', type: 'default' },
// main path — full Customer form → full Opportunity form → link
{ id: 'e4', source: 'screen_account', target: 'screen_opportunity', type: 'default' },
{ id: 'e5', source: 'screen_opportunity', target: 'update_lead', type: 'default' },
{ id: 'e6', source: 'update_lead', target: 'end', type: 'default' },
],
errorHandling: {
strategy: 'fail',
maxRetries: 0,
retryDelayMs: 0,
backoffMultiplier: 1,
maxRetryDelayMs: 0,
jitter: false,
},
});