twenty-migrate-close 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,284 @@
1
+ import { CloseData, CloseLead, CloseOpportunity, CloseNote, CloseTask } from './extract';
2
+
3
+ export interface TwentyPerson {
4
+ name?: {
5
+ firstName?: string;
6
+ lastName?: string;
7
+ };
8
+ email?: string;
9
+ phone?: string;
10
+ jobTitle?: string;
11
+ company?: string;
12
+ city?: string;
13
+ country?: string;
14
+ createdAt?: string;
15
+ updatedAt?: string;
16
+ source?: 'close';
17
+ closeId?: string;
18
+ }
19
+
20
+ export interface TwentyCompany {
21
+ name?: string;
22
+ domainName?: string;
23
+ industry?: string;
24
+ description?: string;
25
+ phone?: string;
26
+ website?: string;
27
+ createdAt?: string;
28
+ updatedAt?: string;
29
+ source?: 'close';
30
+ closeId?: string;
31
+ }
32
+
33
+ export interface TwentyOpportunity {
34
+ name?: string;
35
+ amount?: number;
36
+ closeDate?: string;
37
+ pipeline?: string;
38
+ stage?: string;
39
+ createdAt?: string;
40
+ updatedAt?: string;
41
+ source?: 'close';
42
+ closeId?: string;
43
+ personId?: string;
44
+ companyId?: string;
45
+ }
46
+
47
+ export interface TwentyNote {
48
+ body?: string;
49
+ createdAt?: string;
50
+ updatedAt?: string;
51
+ source?: 'close';
52
+ closeId?: string;
53
+ personId?: string;
54
+ companyId?: string;
55
+ opportunityId?: string;
56
+ }
57
+
58
+ export interface TwentyActivity {
59
+ subject?: string;
60
+ note?: string;
61
+ type?: string;
62
+ createdAt?: string;
63
+ updatedAt?: string;
64
+ source?: 'close';
65
+ closeId?: string;
66
+ personId?: string;
67
+ companyId?: string;
68
+ opportunityId?: string;
69
+ }
70
+
71
+ export interface TransformedData {
72
+ people: TwentyPerson[];
73
+ companies: TwentyCompany[];
74
+ opportunities: TwentyOpportunity[];
75
+ notes: TwentyNote[];
76
+ activities: TwentyActivity[];
77
+ }
78
+
79
+ export async function transformData(closeData: Partial<CloseData>): Promise<TransformedData> {
80
+ const transformed: TransformedData = {
81
+ people: [],
82
+ companies: [],
83
+ opportunities: [],
84
+ notes: [],
85
+ activities: []
86
+ };
87
+
88
+ console.log('🔄 Transforming Close data to Twenty CRM format...');
89
+
90
+ // Transform leads to people and companies
91
+ if (closeData.leads) {
92
+ console.log(`👥 Transforming ${closeData.leads.length} leads...`);
93
+ const { people, companies } = transformLeadsToPeopleAndCompanies(closeData.leads);
94
+ transformed.people = people;
95
+ transformed.companies = companies;
96
+ }
97
+
98
+ // Transform opportunities
99
+ if (closeData.opportunities) {
100
+ console.log(`💼 Transforming ${closeData.opportunities.length} opportunities...`);
101
+ transformed.opportunities = transformOpportunities(closeData.opportunities);
102
+ }
103
+
104
+ // Transform notes
105
+ if (closeData.notes) {
106
+ console.log(`📝 Transforming ${closeData.notes.length} notes...`);
107
+ transformed.notes = transformNotes(closeData.notes);
108
+ }
109
+
110
+ // Transform tasks to activities
111
+ if (closeData.tasks) {
112
+ console.log(`📋 Transforming ${closeData.tasks.length} tasks...`);
113
+ transformed.activities = transformTasks(closeData.tasks);
114
+ }
115
+
116
+ console.log('✅ Data transformation completed');
117
+ return transformed;
118
+ }
119
+
120
+ function transformLeadsToPeopleAndCompanies(leads: CloseLead[]): { people: TwentyPerson[], companies: TwentyCompany[] } {
121
+ const people: TwentyPerson[] = [];
122
+ const companies: TwentyCompany[] = [];
123
+ const companyMap = new Map<string, TwentyCompany>();
124
+
125
+ for (const lead of leads) {
126
+ // Transform lead to person
127
+ const person: TwentyPerson = {
128
+ source: 'close',
129
+ closeId: lead.id,
130
+ createdAt: lead.date_created,
131
+ updatedAt: lead.date_updated
132
+ };
133
+
134
+ // Transform name
135
+ if (lead.first_name || lead.last_name) {
136
+ person.name = {
137
+ firstName: lead.first_name || '',
138
+ lastName: lead.last_name || ''
139
+ };
140
+ } else if (lead.display_name) {
141
+ // Split full name into first and last name
142
+ const nameParts = lead.display_name.trim().split(/\s+/);
143
+ person.name = {
144
+ firstName: nameParts[0] || '',
145
+ lastName: nameParts.slice(1).join(' ') || ''
146
+ };
147
+ }
148
+
149
+ // Transform email
150
+ if (lead.email) {
151
+ person.email = lead.email;
152
+ }
153
+
154
+ // Transform phone
155
+ if (lead.phone) {
156
+ person.phone = lead.phone;
157
+ }
158
+
159
+ // Transform job title
160
+ if (lead.title) {
161
+ person.jobTitle = lead.title;
162
+ }
163
+
164
+ // Transform company name
165
+ if (lead.company_name) {
166
+ person.company = lead.company_name;
167
+
168
+ // Create company if not exists
169
+ if (!companyMap.has(lead.company_name)) {
170
+ const company: TwentyCompany = {
171
+ name: lead.company_name,
172
+ source: 'close',
173
+ closeId: lead.id, // Use lead ID as reference
174
+ createdAt: lead.date_created,
175
+ updatedAt: lead.date_updated
176
+ };
177
+ companyMap.set(lead.company_name, company);
178
+ }
179
+ }
180
+
181
+ people.push(person);
182
+ }
183
+
184
+ companies.push(...Array.from(companyMap.values()));
185
+
186
+ return { people, companies };
187
+ }
188
+
189
+ function transformOpportunities(opportunities: CloseOpportunity[]): TwentyOpportunity[] {
190
+ return opportunities.map(opportunity => {
191
+ const twentyOpportunity: TwentyOpportunity = {
192
+ source: 'close',
193
+ closeId: opportunity.id,
194
+ createdAt: opportunity.date_created,
195
+ updatedAt: opportunity.date_updated
196
+ };
197
+
198
+ // Transform name (use status as name if no name provided)
199
+ twentyOpportunity.name = opportunity.status_label || 'Opportunity';
200
+
201
+ // Transform amount
202
+ if (opportunity.value !== undefined && opportunity.value !== null) {
203
+ twentyOpportunity.amount = opportunity.value;
204
+ }
205
+
206
+ // Transform currency (store as pipeline for now)
207
+ if (opportunity.currency) {
208
+ twentyOpportunity.pipeline = opportunity.currency;
209
+ }
210
+
211
+ // Transform status as stage
212
+ if (opportunity.status_label) {
213
+ twentyOpportunity.stage = opportunity.status_label;
214
+ }
215
+
216
+ // Transform expected close date
217
+ if (opportunity.expected_close_date) {
218
+ twentyOpportunity.closeDate = opportunity.expected_close_date;
219
+ }
220
+
221
+ // Link to lead
222
+ if (opportunity.lead_id) {
223
+ twentyOpportunity.personId = opportunity.lead_id;
224
+ }
225
+
226
+ return twentyOpportunity;
227
+ });
228
+ }
229
+
230
+ function transformNotes(notes: CloseNote[]): TwentyNote[] {
231
+ return notes.map(note => {
232
+ const twentyNote: TwentyNote = {
233
+ source: 'close',
234
+ closeId: note.id,
235
+ createdAt: note.date_created,
236
+ updatedAt: note.date_updated
237
+ };
238
+
239
+ // Transform note
240
+ if (note.note) {
241
+ twentyNote.body = note.note;
242
+ }
243
+
244
+ // Link to lead
245
+ if (note.lead_id) {
246
+ twentyNote.personId = note.lead_id;
247
+ }
248
+
249
+ return twentyNote;
250
+ });
251
+ }
252
+
253
+ function transformTasks(tasks: CloseTask[]): TwentyActivity[] {
254
+ return tasks.map(task => {
255
+ const twentyActivity: TwentyActivity = {
256
+ source: 'close',
257
+ closeId: task.id,
258
+ createdAt: task.date_created,
259
+ updatedAt: task.date_updated
260
+ };
261
+
262
+ // Transform text as subject
263
+ if (task.text) {
264
+ twentyActivity.subject = task.text;
265
+ }
266
+
267
+ // Transform status as type
268
+ if (task.status) {
269
+ twentyActivity.type = task.status;
270
+ }
271
+
272
+ // Transform due date
273
+ if (task.due_date) {
274
+ twentyActivity.note = `Due: ${task.due_date}`;
275
+ }
276
+
277
+ // Link to lead
278
+ if (task.lead_id) {
279
+ twentyActivity.personId = task.lead_id;
280
+ }
281
+
282
+ return twentyActivity;
283
+ });
284
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "removeComments": false,
16
+ "noImplicitAny": true,
17
+ "strictNullChecks": true,
18
+ "strictFunctionTypes": true,
19
+ "noImplicitReturns": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "moduleResolution": "node",
22
+ "baseUrl": "./",
23
+ "paths": {
24
+ "@/*": ["src/*"]
25
+ },
26
+ "allowSyntheticDefaultImports": true,
27
+ "experimentalDecorators": true,
28
+ "emitDecoratorMetadata": true
29
+ },
30
+ "include": [
31
+ "src/**/*"
32
+ ],
33
+ "exclude": [
34
+ "node_modules",
35
+ "dist",
36
+ "**/*.test.ts",
37
+ "**/*.spec.ts"
38
+ ]
39
+ }