closer-cli 0.0.4 → 0.4.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,42 @@
1
+ const { ALLOWED_CSV_FILES } = require('../helpers')
2
+
3
+ exports.command = 'import:data <command>'
4
+ exports.desc = 'Import CSV data to Closer'
5
+
6
+ exports.builder = yargs => {
7
+ yargs
8
+ .commandDir('importData_cmds')
9
+ .option('type', {
10
+ alias: 't',
11
+ describe: 'CSV type to consider during import',
12
+ choices: Object.keys(ALLOWED_CSV_FILES),
13
+ array: true
14
+ })
15
+ .option('mode', {
16
+ alias: 'm',
17
+ describe: 'Import mode, if "full" all database records will be logically deleted',
18
+ choices: ['full', 'incremental']
19
+ })
20
+ .option('non-interactive', {
21
+ describe: '',
22
+ type: 'bool'
23
+ })
24
+ .option('out', {
25
+ alias: 'o',
26
+ describe: 'Directory path that will contain errored CSV files',
27
+ type: 'string',
28
+ default: '.out'
29
+ })
30
+ .option('skipTasksBeforeImport', {
31
+ describe: 'Skip tasks before start import process',
32
+ type: 'bool'
33
+ })
34
+ .option('csvDelimiter', {
35
+ describe: 'CSV Delimiter',
36
+ type: 'string',
37
+ default: ','
38
+ })
39
+ .implies('non-interactive', ['type', 'mode'])
40
+ }
41
+
42
+ exports.handler = () => {}
@@ -0,0 +1,277 @@
1
+ /* eslint-disable no-console */
2
+ /* eslint-disable no-await-in-loop */
3
+ /* eslint-disable no-restricted-syntax */
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+ const chalk = require('chalk')
7
+ const prompts = require('prompts')
8
+ const Ora = require('ora')
9
+ const {
10
+ getCsvFilesFromDir,
11
+ ALLOWED_CSV_FILES,
12
+ getGoogleSheetCsvUrl,
13
+ importCsvFromFile,
14
+ validateCsvFromFile,
15
+ refreshOrders,
16
+ truncateTablesByCsvTypes,
17
+ download,
18
+ callBeforeDataImportProcess,
19
+ callAfterDataImportProcess,
20
+ getExecutionTime
21
+ } = require('../../helpers')
22
+
23
+ exports.command = 'multiple [options]'
24
+
25
+ exports.desc = 'Import different CSV files'
26
+
27
+ exports.builder = yargs => {
28
+ yargs
29
+ .usage(`Usage: ${chalk.cyan('$0 import multiple')} [options]`)
30
+ .option('fromDir', {
31
+ alias: 'd',
32
+ describe: 'Directory path that contains files to import',
33
+ type: 'string'
34
+ })
35
+ .option('fromGoogleDocId', {
36
+ alias: 'g',
37
+ describe: 'Google Doc ID that contains sheets to import',
38
+ type: 'string'
39
+ })
40
+ .conflicts('fromDir', 'fromGoogleDocId')
41
+ .check(argv => {
42
+ if (!argv.fromDir && !argv.fromGoogleDocId) {
43
+ throw new Error('Missing required argument: fromDir OR fromGoogleDocId')
44
+ }
45
+
46
+ if (argv.fromDir) {
47
+ fs.accessSync(argv.fromDir, fs.constants.F_OK)
48
+ }
49
+
50
+ return true
51
+ })
52
+ }
53
+
54
+ exports.handler = async argv => {
55
+ const hrstart = process.hrtime()
56
+ const report = {
57
+ start: new Date(),
58
+ end: null,
59
+ time: null,
60
+ endpoint: argv.endpoint,
61
+ mode: null,
62
+ type: [],
63
+ result: []
64
+ }
65
+
66
+ const interactive = !argv.nonInteractive
67
+
68
+ let csvChoices = []
69
+
70
+ if (argv.fromDir) {
71
+ const csvFilesFromDir = getCsvFilesFromDir(argv.fromDir)
72
+
73
+ csvChoices = Object.keys(ALLOWED_CSV_FILES).map(csvType => ({
74
+ title: ALLOWED_CSV_FILES[csvType],
75
+ csvType,
76
+ value: {
77
+ file: path.resolve(argv.fromDir, ALLOWED_CSV_FILES[csvType]),
78
+ csvType
79
+ },
80
+ disabled: !csvFilesFromDir.includes(ALLOWED_CSV_FILES[csvType])
81
+ }))
82
+ } else {
83
+ csvChoices = Object.keys(ALLOWED_CSV_FILES).map(csvType => ({
84
+ title: csvType,
85
+ csvType,
86
+ value: {
87
+ url: getGoogleSheetCsvUrl(argv.fromGoogleDocId, csvType),
88
+ csvType
89
+ }
90
+ }))
91
+ }
92
+
93
+ let files
94
+ if (interactive && !argv.type) {
95
+ const answers = await prompts(
96
+ [
97
+ {
98
+ instructions: false,
99
+ name: 'files',
100
+ type: 'multiselect',
101
+ message: argv.fromDir ? 'Select Files' : 'Select Sheets',
102
+ warn: argv.fromDir && 'File not found',
103
+ choices: csvChoices,
104
+ min: 1
105
+ }
106
+ ],
107
+ { onCancel: () => process.exit() }
108
+ )
109
+
110
+ files = answers.files
111
+ } else {
112
+ files = argv.type.map(type => {
113
+ const choice = csvChoices.find(csvChoice => csvChoice.csvType === type)
114
+ return choice.value
115
+ })
116
+ }
117
+
118
+ let mode
119
+ if (interactive && !argv.mode) {
120
+ const answers = await prompts(
121
+ [
122
+ {
123
+ type: 'select',
124
+ name: 'mode',
125
+ message: 'Import Mode',
126
+ choices: [
127
+ {
128
+ title: 'Incremental',
129
+ value: 'incremental'
130
+ },
131
+ {
132
+ title: 'Full',
133
+ description: 'Before starting, all database records will be logically deleted',
134
+ value: 'full'
135
+ }
136
+ ],
137
+ hint: ' '
138
+ }
139
+ ],
140
+ { onCancel: () => process.exit() }
141
+ )
142
+
143
+ mode = answers.mode
144
+ } else {
145
+ mode = argv.mode
146
+ }
147
+
148
+ const spinner = new Ora()
149
+
150
+ const validations = []
151
+ const csvReports = []
152
+
153
+ const ws = {
154
+ endpoint: argv.endpoint,
155
+ adminSecret: argv['admin-secret']
156
+ }
157
+
158
+ let parsedFiles = [...files]
159
+
160
+ if (argv.fromGoogleDocId) {
161
+ parsedFiles = []
162
+ console.log('')
163
+ spinner.start('Downloading Google sheets...')
164
+ for (const item of files) {
165
+ const filepath = await download(item.url, argv.out, `${item.csvType}.csv`)
166
+ parsedFiles.push({ file: filepath, csvType: item.csvType })
167
+ }
168
+ spinner.succeed('Google sheets downloaded')
169
+ }
170
+
171
+ //
172
+ // Validate CSV headers sequentially
173
+ //
174
+ for (const item of parsedFiles) {
175
+ const validation = await validateCsvFromFile(item.file, item.csvType, ws, argv.csvDelimiter)
176
+ validations.push(validation)
177
+ }
178
+
179
+ const invalidFiles = validations.filter(v => v.error)
180
+ if (invalidFiles.length > 0) {
181
+ console.log('')
182
+ console.log(invalidFiles.map(f => `ERROR => [${chalk.red(f.inputFile)}] ${f.error}`).join('\n'))
183
+ process.exit()
184
+ }
185
+
186
+ if (interactive) {
187
+ console.log('')
188
+ const { confirmed } = await prompts(
189
+ [
190
+ {
191
+ type: 'confirm',
192
+ name: 'confirmed',
193
+ message: 'Continue?',
194
+ initial: false
195
+ }
196
+ ],
197
+ { onCancel: () => process.exit() }
198
+ )
199
+
200
+ if (!confirmed) {
201
+ process.exit()
202
+ }
203
+ }
204
+
205
+ console.log('')
206
+
207
+ //
208
+ // Call ImportService.beforeImportProcess (import flag)
209
+ //
210
+ if (!argv.skipTasksBeforeImport) {
211
+ try {
212
+ await callBeforeDataImportProcess(spinner, ws)
213
+ } catch (e) {
214
+ process.exit()
215
+ }
216
+ }
217
+
218
+ //
219
+ // Import FULL?
220
+ //
221
+ if (mode === 'full') {
222
+ const csvTypes = parsedFiles.map(i => i.csvType)
223
+ await truncateTablesByCsvTypes(csvTypes, spinner, ws)
224
+ }
225
+
226
+ //
227
+ // Import CSV files sequentially
228
+ //
229
+ for (const item of parsedFiles) {
230
+ const csvReport = await importCsvFromFile(
231
+ item.file,
232
+ item.csvType,
233
+ argv.out,
234
+ spinner,
235
+ ws,
236
+ argv.csvDelimiter
237
+ )
238
+ csvReports.push(csvReport)
239
+ }
240
+
241
+ //
242
+ // Refresh Orders of type Draft
243
+ //
244
+ // eslint-disable-next-line no-unused-vars
245
+ const refreshedOrders = await refreshOrders(spinner, ws)
246
+
247
+ //
248
+ // Report
249
+ //
250
+
251
+ report.end = new Date()
252
+ report.time = getExecutionTime(hrstart)
253
+ report.mode = mode
254
+ report.type = files.map(f => f.csvType)
255
+
256
+ const reportRows = []
257
+
258
+ csvReports.forEach(csvReport => {
259
+ reportRows.push({
260
+ type: 'import.csv',
261
+ payload: csvReport
262
+ })
263
+ })
264
+
265
+ reportRows.push({
266
+ type: 'refreshed.orders',
267
+ payload: refreshedOrders
268
+ })
269
+
270
+ report.result = reportRows
271
+
272
+ // const message = getDataImportMessageReport(report)
273
+
274
+ await callAfterDataImportProcess(spinner, ws, report)
275
+
276
+ process.exit()
277
+ }
@@ -0,0 +1,213 @@
1
+ /* eslint-disable no-restricted-syntax */
2
+ /* eslint-disable no-console */
3
+ const fs = require('fs')
4
+ const chalk = require('chalk')
5
+
6
+ const Ora = require('ora')
7
+ const prompts = require('prompts')
8
+ const {
9
+ importCsvFromFile,
10
+ getExecutionTime,
11
+ refreshOrders,
12
+ callAfterDataImportProcess,
13
+ callBeforeDataImportProcess,
14
+ ALLOWED_CSV_FILES,
15
+ download,
16
+ validateCsvFromFile
17
+ } = require('../../helpers')
18
+
19
+ exports.command = 'single [options]'
20
+
21
+ exports.desc = 'Import only one CSV file'
22
+
23
+ exports.builder = yargs => {
24
+ yargs
25
+ .usage(`Usage: ${chalk.cyan('$0 import single')} [options]`)
26
+ .option('fromFile', {
27
+ alias: 'f',
28
+ describe: 'File to import',
29
+ type: 'string'
30
+ })
31
+ .option('fromUrl', {
32
+ alias: 'u',
33
+ describe: 'Remote URL file to import',
34
+ type: 'string'
35
+ })
36
+ .conflicts('fromFile', 'fromUrl')
37
+ .check(argv => {
38
+ if (!argv.fromFile && !argv.fromUrl) {
39
+ throw new Error('Missing required argument: fromFile OR fromUrl')
40
+ }
41
+
42
+ if (argv.fromFile) {
43
+ fs.accessSync(argv.fromFile, fs.constants.F_OK)
44
+ }
45
+
46
+ return true
47
+ })
48
+ }
49
+
50
+ exports.handler = async argv => {
51
+ const spinner = new Ora()
52
+ const hrstart = process.hrtime()
53
+
54
+ const report = {
55
+ start: new Date(),
56
+ end: null,
57
+ time: null,
58
+ endpoint: argv.endpoint,
59
+ mode: null,
60
+ type: [],
61
+ result: []
62
+ }
63
+
64
+ const ws = {
65
+ endpoint: argv.endpoint,
66
+ adminSecret: argv['admin-secret']
67
+ }
68
+
69
+ const interactive = !argv.nonInteractive
70
+
71
+ let type
72
+ if (interactive && !argv.type) {
73
+ const answers = await prompts(
74
+ [
75
+ {
76
+ instructions: false,
77
+ name: 'type',
78
+ type: 'select',
79
+ message: 'Select Type',
80
+ choices: Object.keys(ALLOWED_CSV_FILES).map(csvType => ({
81
+ title: csvType,
82
+ value: csvType
83
+ }))
84
+ }
85
+ ],
86
+ { onCancel: () => process.exit() }
87
+ )
88
+
89
+ type = answers.type
90
+ } else {
91
+ // eslint-disable-next-line prefer-destructuring
92
+ type = argv.type[0]
93
+ }
94
+
95
+ let mode
96
+ if (interactive && !argv.mode) {
97
+ const answers = await prompts(
98
+ [
99
+ {
100
+ type: 'select',
101
+ name: 'mode',
102
+ message: 'Import Mode',
103
+ choices: [
104
+ {
105
+ title: 'Incremental',
106
+ value: 'incremental'
107
+ },
108
+ {
109
+ title: 'Full',
110
+ description: 'Before starting, all database records will be logically deleted',
111
+ value: 'full'
112
+ }
113
+ ],
114
+ hint: ' '
115
+ }
116
+ ],
117
+ { onCancel: () => process.exit() }
118
+ )
119
+
120
+ mode = answers.mode
121
+ } else {
122
+ mode = argv.mode
123
+ }
124
+
125
+ if (interactive) {
126
+ console.log('')
127
+ const { confirmed } = await prompts(
128
+ [
129
+ {
130
+ type: 'confirm',
131
+ name: 'confirmed',
132
+ message: 'Continue?',
133
+ initial: false
134
+ }
135
+ ],
136
+ { onCancel: () => process.exit() }
137
+ )
138
+
139
+ if (!confirmed) {
140
+ process.exit()
141
+ }
142
+
143
+ console.log('')
144
+ }
145
+
146
+ let csvReport
147
+
148
+ let filepath = argv.fromFile
149
+
150
+ if (argv.fromUrl) {
151
+ spinner.start('Downloading file...')
152
+ filepath = await download(argv.fromUrl, argv.out, `${type}.csv`)
153
+ spinner.succeed('Downloaded file')
154
+ }
155
+
156
+ const validation = await validateCsvFromFile(filepath, type, ws, argv.csvDelimiter)
157
+
158
+ if (validation.error) {
159
+ console.log(`ERROR => [${chalk.red(validation.inputFile)}] ${validation.error}`)
160
+ process.exit()
161
+ }
162
+
163
+ //
164
+ // Call ImportService.beforeImportProcess (import flag)
165
+ //
166
+ if (!argv.skipTasksBeforeImport) {
167
+ try {
168
+ await callBeforeDataImportProcess(spinner, ws)
169
+ } catch (e) {
170
+ process.exit()
171
+ }
172
+ }
173
+
174
+ if (argv.fromFile) {
175
+ csvReport = await importCsvFromFile(
176
+ argv.fromFile,
177
+ type,
178
+ argv.out,
179
+ spinner,
180
+ ws,
181
+ argv.csvDelimiter
182
+ )
183
+ }
184
+
185
+ const refreshedOrders = await refreshOrders(spinner, ws)
186
+
187
+ //
188
+ // Report
189
+ //
190
+
191
+ report.end = new Date()
192
+ report.time = getExecutionTime(hrstart)
193
+ report.mode = mode
194
+ report.type = [type]
195
+
196
+ const reportRows = []
197
+
198
+ reportRows.push({
199
+ type: 'import.csv',
200
+ payload: csvReport
201
+ })
202
+
203
+ reportRows.push({
204
+ type: 'refreshed.orders',
205
+ payload: refreshedOrders
206
+ })
207
+
208
+ report.result = reportRows
209
+
210
+ await callAfterDataImportProcess(spinner, ws, report)
211
+
212
+ process.exit()
213
+ }
@@ -0,0 +1,63 @@
1
+ const chalk = require('chalk')
2
+ const Ora = require('ora')
3
+ const {
4
+ importMedia,
5
+ flushMedia,
6
+ getExecutionTime,
7
+ callAfterMediaImportProcess
8
+ } = require('../helpers')
9
+
10
+ exports.command = 'import:media'
11
+ exports.desc = 'Import media (images, 360, videos) to Closer'
12
+
13
+ exports.builder = yargs => {
14
+ yargs
15
+ .usage(`Usage: ${chalk.cyan('$0 import:media')} [options]`)
16
+
17
+ .option('flush', {
18
+ describe: 'Delete all before the import process (DB records and files from disk).',
19
+ type: 'bool'
20
+ })
21
+
22
+ .option('reprocess', {
23
+ describe: 'Reprocess media that were already processed by previous import.',
24
+ type: 'bool'
25
+ })
26
+ }
27
+
28
+ exports.handler = async argv => {
29
+ const spinner = new Ora()
30
+ const hrstart = process.hrtime()
31
+
32
+ const report = {
33
+ start: new Date(),
34
+ end: null,
35
+ time: null,
36
+ endpoint: argv.endpoint,
37
+ flush: argv.flush,
38
+ reprocess: argv.reprocess,
39
+ result: null
40
+ }
41
+
42
+ const ws = {
43
+ endpoint: argv.endpoint,
44
+ adminSecret: argv['admin-secret']
45
+ }
46
+
47
+ if (argv.flush) {
48
+ await flushMedia(spinner, ws)
49
+ }
50
+
51
+ const result = await importMedia(argv.reprocess, spinner, ws)
52
+
53
+ //
54
+ // Report
55
+ //
56
+ report.end = new Date()
57
+ report.time = getExecutionTime(hrstart)
58
+ report.result = result
59
+
60
+ await callAfterMediaImportProcess(spinner, ws, report)
61
+
62
+ process.exit()
63
+ }
@@ -0,0 +1,6 @@
1
+ CustomerCode,CustomerName,PricebookCode,CustomerEmail,AddressStreet,AddressCity,AddressState,AddressZip,AddressCountry,GroupCodes,Markup
2
+ CUST01,Customer 01,SPB,cust01@example.com,Via Garibaldi,Vicenza,VI,36100,Italia,AI|WA,1.2
3
+ CUST02,Customer 02,SPB,cust02@example.com,Via De Gasperi,Roma,RM,00165,Italia,AI|WA,1.3
4
+ CUST03,Customer 03,SPB,cust03@example.com,Prato della Valle,Padova,PD,35100,Italia,AI|WA,1.0
5
+ CUST04,Customer 04,SPB,cust04@example.com,Via Etnea,Catania,CT,95121,Italia,AI,1.0
6
+ CUST05,Customer 05,SPB,cust05@example.com,Via Manzoni,Milano,MI,20121,Italia,WA,1.2
@@ -0,0 +1,33 @@
1
+ Entity,Key,Field,Value
2
+ Order,label,inputField1,Partita IVA
3
+ Order,schema,inputField1,"{""type"": ""string"", ""minLength"": 11, ""maxLength"": 12}"
4
+ Order,enabled,inputField1,true
5
+ Order,required,inputField1,true
6
+ Order,component,inputField1,text
7
+ Order,position,inputField1,5
8
+ Order,label,inputField2,Shipping Notes
9
+ Order,component,inputField2,text
10
+ Order,enabled,inputField2,true
11
+ Order,position,inputField2,2
12
+ Order,schema,inputField2,"{""type"": ""string""}"
13
+ Order,required,inputField2,false
14
+ Order,label,inputField3,Payment days
15
+ Order,component,inputField3,number
16
+ Order,enabled,inputField3,true
17
+ Order,position,inputField3,3
18
+ Order,required,inputField3,true
19
+ Order,schema,inputField3,"{""type"": ""integer"", ""minimum"": 0, ""maximum"": 120}"
20
+ Order,label,inputField4,Payment Method
21
+ Order,component,inputField4,dropdown
22
+ Order,required,inputField4,true
23
+ Order,enabled,inputField4,true
24
+ Order,schema,inputField4,"{""type"": ""string""}"
25
+ Order,dataSource,inputField4,"[{""value"":""BT"", ""label"":""Bank Transfer""},{""value"":""CC"", ""label"":""Credit Card""},{""value"":""PP"", ""label"":""PayPal""}]"
26
+ Order,position,inputField4,1
27
+ Order,component,customerLookup1,dropdown
28
+ Order,required,customerLookup1,true
29
+ Order,enabled,customerLookup1,true
30
+ Order,schema,customerLookup1,"{""type"": ""string""}"
31
+ Order,position,customerLookup1,4
32
+ Order,label,customerLookup1,Courier
33
+ Order,dataSource,customerLookup1,lookup
package/csv/groups.csv ADDED
@@ -0,0 +1,3 @@
1
+ GroupCode,GroupName
2
+ AI,Agenzia Italiana
3
+ WA,Worldwide Agency
package/csv/labels.csv ADDED
@@ -0,0 +1,49 @@
1
+ Locale,Key,Value
2
+ en,filter1,Product Type
3
+ en,filter2,Product Group
4
+ en,filter3,Color Group
5
+ en,filter4,Delivery
6
+ en,filter5,Made In
7
+ en,filter6,Fit
8
+ en,filter7,Line
9
+ en,filter8,Price Class
10
+ it,filter1,Tipologia Prodotto
11
+ it,filter2,Gruppo Prodotto
12
+ it,filter3,Gruppo Colore
13
+ it,filter4,Consegna
14
+ it,filter5,Made In
15
+ it,filter6,Vestibilità
16
+ it,filter7,Linea
17
+ it,filter8,Fascia di Prezzo
18
+ it,label1,Composizione
19
+ en,label1,Composition
20
+ en,label2,Notes
21
+ it,label2,Note
22
+ it,label3,Barcode
23
+ en,label3,Barcode
24
+ it,label4,Note Espositore
25
+ en,label4,Merchandiser Notes
26
+ en,label5,Cancelled Notes
27
+ it,label5,Note di Cancellazione
28
+ it,macrofilter1,Stagione
29
+ en,macrofilter1,Season
30
+ it,variant1,Tessuto
31
+ en,variant1,Fabric
32
+ it,variant2,Colore
33
+ en,variant2,Color
34
+ en,request-forgot-password_notification-title,Forgot your password?
35
+ it,request-forgot-password_notification-title,Hai dimenticato la password?
36
+ en,request-forgot-password_notification-message,"To create a new password, just copy the following code to the app: ${forgotPasswordToken}"
37
+ it,request-forgot-password_notification-message,"Per creare una nuova password, copia il seguente codice nell'app: ${forgotPasswordToken}"
38
+ en,forgot-password_notification-title,Your password has been updated
39
+ it,forgot-password_notification-title,La tua password è stata aggiornata
40
+ en,forgot-password_notification-message,Welcome back ${user.name}! Your Closer account password has been updated.
41
+ it,forgot-password_notification-message,Bentornato ${user.name}! La password del tuo account Closer è stata aggiornata.
42
+ en,change-order-owner-source-user_notification-title,Order shared
43
+ it,change-order-owner-source-user_notification-title,Ordine condiviso
44
+ en,change-order-owner-source-user_notification-message,You shared the order ${orderId} with ${targetUser.name}.
45
+ it,change-order-owner-source-user_notification-message,Hai condiviso l'ordine ${orderId} con ${targetUser.name}.
46
+ en,change-order-owner-target-user_notification-title,You have received an order
47
+ it,change-order-owner-target-user_notification-title,Hai ricevuto un ordine
48
+ en,change-order-owner-target-user_notification-message,${targetUser.name} sent you an order ${orderId}.
49
+ it,change-order-owner-target-user_notification-message,${targetUser.name} ti ha inviato un ordine ${orderId}.
@@ -0,0 +1,11 @@
1
+ CustomerCode,Field,Code,Description,Entity,Position
2
+ CUST01,customerLookup1,BT,BRT Bartolini,Order,1
3
+ CUST01,customerLookup1,CC,UPS,Order,2
4
+ CUST02,customerLookup1,BC,SDA Poste Italiane,Order,1
5
+ CUST02,customerLookup1,CC,UPS,Order,2
6
+ CUST03,customerLookup1,CC,UPS,Order,1
7
+ CUST03,customerLookup1,PP,DHL,Order,2
8
+ CUST04,customerLookup1,BT,BRT Bartolini,Order,1
9
+ CUST04,customerLookup1,PP,DHL,Order,2
10
+ CUST05,customerLookup1,CC,UPS,Order,1
11
+ CUST05,customerLookup1,BC,SDA Poste Italiane,Order,2