twenty-migrate-espocrm 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.
- package/.env.example +15 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +97 -0
- package/dist/cli.js.map +1 -0
- package/dist/extract.d.ts +50 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +221 -0
- package/dist/extract.js.map +1 -0
- package/dist/load.d.ts +46 -0
- package/dist/load.d.ts.map +1 -0
- package/dist/load.js +268 -0
- package/dist/load.js.map +1 -0
- package/dist/reporter.d.ts +6 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +205 -0
- package/dist/reporter.js.map +1 -0
- package/dist/transform.d.ts +60 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/transform.js +168 -0
- package/dist/transform.js.map +1 -0
- package/package.json +49 -0
- package/src/cli.ts +139 -0
- package/src/extract.ts +309 -0
- package/src/load.ts +280 -0
- package/src/reporter.ts +196 -0
- package/src/transform.ts +261 -0
- package/tsconfig.json +39 -0
package/src/transform.ts
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { EspoCRMData, EspoCRMContact, EspoCRMAccount, EspoCRMOpportunity, EspoCRMNote } 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?: 'espocrm';
|
|
17
|
+
espocrmId?: 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?: 'espocrm';
|
|
30
|
+
espocrmId?: 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?: 'espocrm';
|
|
42
|
+
espocrmId?: string;
|
|
43
|
+
personId?: string;
|
|
44
|
+
companyId?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface TwentyNote {
|
|
48
|
+
body?: string;
|
|
49
|
+
createdAt?: string;
|
|
50
|
+
updatedAt?: string;
|
|
51
|
+
source?: 'espocrm';
|
|
52
|
+
espocrmId?: string;
|
|
53
|
+
personId?: string;
|
|
54
|
+
companyId?: string;
|
|
55
|
+
opportunityId?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface TransformedData {
|
|
59
|
+
people: TwentyPerson[];
|
|
60
|
+
companies: TwentyCompany[];
|
|
61
|
+
opportunities: TwentyOpportunity[];
|
|
62
|
+
notes: TwentyNote[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function transformData(espocrmData: Partial<EspoCRMData>): Promise<TransformedData> {
|
|
66
|
+
const transformed: TransformedData = {
|
|
67
|
+
people: [],
|
|
68
|
+
companies: [],
|
|
69
|
+
opportunities: [],
|
|
70
|
+
notes: []
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
console.log('🔄 Transforming EspoCRM data to Twenty CRM format...');
|
|
74
|
+
|
|
75
|
+
// Transform contacts to people
|
|
76
|
+
if (espocrmData.contacts) {
|
|
77
|
+
console.log(`👥 Transforming ${espocrmData.contacts.length} contacts...`);
|
|
78
|
+
transformed.people = transformContactsToPeople(espocrmData.contacts);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Transform accounts
|
|
82
|
+
if (espocrmData.accounts) {
|
|
83
|
+
console.log(`🏢 Transforming ${espocrmData.accounts.length} accounts...`);
|
|
84
|
+
transformed.companies = transformAccounts(espocrmData.accounts);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Transform opportunities
|
|
88
|
+
if (espocrmData.opportunities) {
|
|
89
|
+
console.log(`💼 Transforming ${espocrmData.opportunities.length} opportunities...`);
|
|
90
|
+
transformed.opportunities = transformOpportunities(espocrmData.opportunities);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Transform notes
|
|
94
|
+
if (espocrmData.notes) {
|
|
95
|
+
console.log(`📝 Transforming ${espocrmData.notes.length} notes...`);
|
|
96
|
+
transformed.notes = transformNotes(espocrmData.notes);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('✅ Data transformation completed');
|
|
100
|
+
return transformed;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function transformContactsToPeople(contacts: EspoCRMContact[]): TwentyPerson[] {
|
|
104
|
+
return contacts.map(contact => {
|
|
105
|
+
const twentyPerson: TwentyPerson = {
|
|
106
|
+
source: 'espocrm',
|
|
107
|
+
espocrmId: contact.id,
|
|
108
|
+
createdAt: contact.createdAt,
|
|
109
|
+
updatedAt: contact.modifiedAt
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Transform name
|
|
113
|
+
if (contact.firstName || contact.lastName) {
|
|
114
|
+
twentyPerson.name = {
|
|
115
|
+
firstName: contact.firstName || '',
|
|
116
|
+
lastName: contact.lastName || ''
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Transform email
|
|
121
|
+
if (contact.email) {
|
|
122
|
+
twentyPerson.email = contact.email;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Transform phone
|
|
126
|
+
if (contact.phone) {
|
|
127
|
+
twentyPerson.phone = contact.phone;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Transform job title
|
|
131
|
+
if (contact.title) {
|
|
132
|
+
twentyPerson.jobTitle = contact.title;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Link to account (stored as company reference)
|
|
136
|
+
if (contact.accountId) {
|
|
137
|
+
twentyPerson.company = contact.accountId;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return twentyPerson;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function transformAccounts(accounts: EspoCRMAccount[]): TwentyCompany[] {
|
|
145
|
+
return accounts.map(account => {
|
|
146
|
+
const twentyCompany: TwentyCompany = {
|
|
147
|
+
source: 'espocrm',
|
|
148
|
+
espocrmId: account.id,
|
|
149
|
+
createdAt: account.createdAt,
|
|
150
|
+
updatedAt: account.modifiedAt
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Transform name
|
|
154
|
+
if (account.name) {
|
|
155
|
+
twentyCompany.name = account.name;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Transform website
|
|
159
|
+
if (account.website) {
|
|
160
|
+
twentyCompany.website = account.website;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Transform industry
|
|
164
|
+
if (account.industry) {
|
|
165
|
+
twentyCompany.industry = account.industry;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Transform description
|
|
169
|
+
if (account.description) {
|
|
170
|
+
twentyCompany.description = account.description;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Transform phone
|
|
174
|
+
if (account.phone) {
|
|
175
|
+
twentyCompany.phone = account.phone;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return twentyCompany;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function transformOpportunities(opportunities: EspoCRMOpportunity[]): TwentyOpportunity[] {
|
|
183
|
+
return opportunities.map(opportunity => {
|
|
184
|
+
const twentyOpportunity: TwentyOpportunity = {
|
|
185
|
+
source: 'espocrm',
|
|
186
|
+
espocrmId: opportunity.id,
|
|
187
|
+
createdAt: opportunity.createdAt,
|
|
188
|
+
updatedAt: opportunity.modifiedAt
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Transform name
|
|
192
|
+
if (opportunity.name) {
|
|
193
|
+
twentyOpportunity.name = opportunity.name;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Transform amount
|
|
197
|
+
if (opportunity.amount !== undefined && opportunity.amount !== null) {
|
|
198
|
+
twentyOpportunity.amount = opportunity.amount;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Transform currency (store as pipeline for now)
|
|
202
|
+
if (opportunity.currency) {
|
|
203
|
+
twentyOpportunity.pipeline = opportunity.currency;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Transform stage
|
|
207
|
+
if (opportunity.stage) {
|
|
208
|
+
twentyOpportunity.stage = opportunity.stage;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Transform close date
|
|
212
|
+
if (opportunity.closeDate) {
|
|
213
|
+
twentyOpportunity.closeDate = opportunity.closeDate;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Link to account
|
|
217
|
+
if (opportunity.accountId) {
|
|
218
|
+
twentyOpportunity.companyId = opportunity.accountId;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Link to contact
|
|
222
|
+
if (opportunity.contactId) {
|
|
223
|
+
twentyOpportunity.personId = opportunity.contactId;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return twentyOpportunity;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function transformNotes(notes: EspoCRMNote[]): TwentyNote[] {
|
|
231
|
+
return notes.map(note => {
|
|
232
|
+
const twentyNote: TwentyNote = {
|
|
233
|
+
source: 'espocrm',
|
|
234
|
+
espocrmId: note.id,
|
|
235
|
+
createdAt: note.createdAt,
|
|
236
|
+
updatedAt: note.modifiedAt
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Transform note
|
|
240
|
+
if (note.note) {
|
|
241
|
+
twentyNote.body = note.note;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Link to parent based on parentType
|
|
245
|
+
if (note.parentId && note.parentType) {
|
|
246
|
+
switch (note.parentType) {
|
|
247
|
+
case 'Contact':
|
|
248
|
+
twentyNote.personId = note.parentId;
|
|
249
|
+
break;
|
|
250
|
+
case 'Account':
|
|
251
|
+
twentyNote.companyId = note.parentId;
|
|
252
|
+
break;
|
|
253
|
+
case 'Opportunity':
|
|
254
|
+
twentyNote.opportunityId = note.parentId;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return twentyNote;
|
|
260
|
+
});
|
|
261
|
+
}
|
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
|
+
}
|