@@ -5,6 +5,8 @@ const mongoose = require("mongoose");
55const logger = require ( "../../Config/loggerConfig" ) ;
66const { taskMongo } = require ( '../Tasks/helpers/task_class_Mongo' ) ;
77const { validateImportInput, transformJiraRows } = require ( './helpers/jiraRules' ) ;
8+ const { validateCsvInput, transformCsvRows } = require ( './helpers/csvRules' ) ;
9+ const { validateTrelloInput, parseTrelloBoard } = require ( './helpers/trelloRules' ) ;
810
911// Jira importer. The client parses the Jira CSV export (the xlsx lib reads
1012// CSV) and posts plain rows; the server maps statuses/priorities and feeds
@@ -20,91 +22,16 @@ exports.importFromJira = async (req, res) => {
2022 const { rows, projectId, sprintId, sprintName, folderId, folderName, userData } = req . body || { } ;
2123 const userId = userData && ( userData . id || userData . _id ) ? String ( userData . id || userData . _id ) : '' ;
2224 const check = validateImportInput ( { companyId, projectId, sprintId, rows, userId } ) ;
23- if ( ! check . valid ) {
24- return res . send ( { status : false , statusText : check . reason } ) ;
25- }
25+ if ( ! check . valid ) return res . send ( { status : false , statusText : check . reason } ) ;
2626
27- const [ project , statusDocs ] = await Promise . all ( [
28- MongoDbCrudOpration ( companyId , {
29- type : SCHEMA_TYPE . PROJECTS ,
30- data : [ { _id : new mongoose . Types . ObjectId ( projectId ) } ] ,
31- } , 'findOne' ) ,
32- MongoDbCrudOpration ( companyId , {
33- type : dbCollections . SETTINGS ,
34- data : [ { name : settingsCollectionDocs . TASK_STATUS } ] ,
35- } , 'find' ) ,
36- ] ) ;
37- if ( ! project ) {
38- return res . send ( { status : false , statusText : 'Project not found.' } ) ;
39- }
40- const statusArray = ( ( statusDocs && statusDocs [ 0 ] && statusDocs [ 0 ] . settings ) || [ ] )
41- . map ( ( status ) => ( { name : status . name , key : status . key , type : status . type } ) )
42- . filter ( ( status ) => status . name !== undefined ) ;
43- if ( ! statusArray . length ) {
44- return res . send ( { status : false , statusText : 'No task statuses configured for this company.' } ) ;
45- }
27+ const ctx = await loadImportContext ( companyId , projectId ) ;
28+ if ( ctx . error ) return res . send ( { status : false , statusText : ctx . error } ) ;
4629
47- const { tasks, skipped } = transformJiraRows ( {
48- rows,
49- statusNames : statusArray . map ( ( status ) => status . name ) ,
50- leaderId : userId ,
51- } ) ;
52- if ( ! tasks . length ) {
53- return res . send ( { status : false , statusText : 'No importable rows found (a Summary column is required).' } ) ;
54- }
30+ const { tasks, skipped } = transformJiraRows ( { rows, statusNames : ctx . statusArray . map ( ( status ) => status . name ) , leaderId : userId } ) ;
31+ if ( ! tasks . length ) return res . send ( { status : false , statusText : 'No importable rows found (a Summary column is required).' } ) ;
5532
56- const job = await MongoDbCrudOpration ( companyId , {
57- type : SCHEMA_TYPE . IMPORT_JOBS ,
58- data : {
59- userId,
60- source : 'jira' ,
61- projectId : new mongoose . Types . ObjectId ( projectId ) ,
62- sprintId : new mongoose . Types . ObjectId ( sprintId ) ,
63- status : 'processing' ,
64- total : tasks . length ,
65- processed : 0 ,
66- created : 0 ,
67- errorList : [ ] ,
68- } ,
69- } , 'save' ) ;
70-
71- const projectData = {
72- _id : project . _id ,
73- CompanyId : companyId ,
74- ProjectName : project . ProjectName ,
75- ProjectCode : project . ProjectCode ,
76- lastTaskId : project . lastTaskId ,
77- } ;
78- const sprint = { id : sprintId , name : sprintName || '' } ;
79- if ( folderId ) {
80- sprint . folderId = folderId ;
81- sprint . folderName = folderName || '' ;
82- }
83- const tasksWithSprint = tasks . map ( ( task ) => ( { ...task , sprintId, sprintArray : sprint } ) ) ;
84-
85- try {
86- const result = await taskMongo . createMultipleTasks ( {
87- tasks : tasksWithSprint ,
88- userData : { id : userId , Employee_Name : userData . Employee_Name || '' , companyOwnerId : userData . companyOwnerId || '' } ,
89- projectData,
90- indexObj : { } ,
91- statusArray,
92- sprint,
93- } ) ;
94- const createdCount = Array . isArray ( result ?. data ) ? result . data . length : tasks . length ;
95- await MongoDbCrudOpration ( companyId , {
96- type : SCHEMA_TYPE . IMPORT_JOBS ,
97- data : [ { _id : job . _id } , { $set : { status : 'done' , processed : tasks . length , created : createdCount } } ] ,
98- } , 'updateOne' ) ;
99- return res . send ( { status : true , statusText : `Imported ${ createdCount } tasks from Jira (${ skipped } rows skipped).` , data : { jobId : job . _id , created : createdCount , skipped } } ) ;
100- } catch ( creationError ) {
101- logger . error ( `[importers] jira job ${ job . _id } failed: ${ creationError . message } ` ) ;
102- await MongoDbCrudOpration ( companyId , {
103- type : SCHEMA_TYPE . IMPORT_JOBS ,
104- data : [ { _id : job . _id } , { $set : { status : 'failed' , errorList : [ String ( creationError . message || creationError ) . slice ( 0 , 300 ) ] } } ] ,
105- } , 'updateOne' ) . catch ( ( ) => { } ) ;
106- return res . send ( { status : false , statusText : `Import failed: ${ creationError . message } ` } ) ;
107- }
33+ const out = await finishImport ( companyId , { source : 'jira' , project : ctx . project , sprintId, sprintName, folderId, folderName, userData, statusArray : ctx . statusArray , tasks, skipped } ) ;
34+ return res . send ( out ) ;
10835 } catch ( error ) {
10936 logger . error ( `ERROR in jira import: ${ error . message } ` ) ;
11037 return res . send ( { status : false , statusText : error . message } ) ;
@@ -129,3 +56,145 @@ exports.listImports = async (req, res) => {
12956 return res . send ( { status : false , statusText : error . message } ) ;
13057 }
13158} ;
59+
60+ // ── Shared pipeline for the CSV + Trello importers (mirrors importFromJira) ──
61+
62+ const STATUS_FALLBACK_TYPE = 'default_active' ;
63+
64+ /* Load the project + a usable task-status list. Prefer the PROJECT's own
65+ * taskStatusData (it matches the board, including any custom statuses); fall back
66+ * to the company task-status template only if the project has none. Every entry
67+ * is normalized so it always carries a type/key — otherwise createMultipleTasks
68+ * builds a task with an empty statusType and the task schema (statusType is
69+ * required) rejects the whole import. Returns { project, statusArray } or { error }. */
70+ const loadImportContext = async ( companyId , projectId ) => {
71+ const project = await MongoDbCrudOpration ( companyId , {
72+ type : SCHEMA_TYPE . PROJECTS ,
73+ data : [ { _id : new mongoose . Types . ObjectId ( projectId ) } ] ,
74+ } , 'findOne' ) ;
75+ if ( ! project ) return { error : 'Project not found.' } ;
76+
77+ let source = Array . isArray ( project . taskStatusData ) ? project . taskStatusData : [ ] ;
78+ if ( ! source . length ) {
79+ const statusDocs = await MongoDbCrudOpration ( companyId , {
80+ type : dbCollections . SETTINGS ,
81+ data : [ { name : settingsCollectionDocs . TASK_STATUS } ] ,
82+ } , 'find' ) ;
83+ source = ( statusDocs && statusDocs [ 0 ] && statusDocs [ 0 ] . settings ) || [ ] ;
84+ }
85+ const statusArray = source
86+ . filter ( ( status ) => status && status . name !== undefined && status . name !== null && String ( status . name ) . trim ( ) !== '' )
87+ . map ( ( status , idx ) => ( {
88+ name : status . name ,
89+ key : ( status . key !== undefined && status . key !== null ) ? status . key : idx + 1 ,
90+ type : status . type || STATUS_FALLBACK_TYPE ,
91+ } ) ) ;
92+ if ( ! statusArray . length ) return { error : 'No task statuses configured for this project.' } ;
93+ return { project, statusArray } ;
94+ } ;
95+
96+ /* Record the job, feed the bulk-create pipeline, update the job. Returns the
97+ * response envelope. Identical create path to the Jira importer. */
98+ const finishImport = async ( companyId , { source, project, sprintId, sprintName, folderId, folderName, userData, statusArray, tasks, skipped } ) => {
99+ const userId = String ( ( userData && ( userData . id || userData . _id ) ) || '' ) ;
100+ const job = await MongoDbCrudOpration ( companyId , {
101+ type : SCHEMA_TYPE . IMPORT_JOBS ,
102+ data : {
103+ userId,
104+ source,
105+ projectId : project . _id ,
106+ sprintId : new mongoose . Types . ObjectId ( sprintId ) ,
107+ status : 'processing' ,
108+ total : tasks . length ,
109+ processed : 0 ,
110+ created : 0 ,
111+ errorList : [ ] ,
112+ } ,
113+ } , 'save' ) ;
114+
115+ const projectData = {
116+ _id : project . _id ,
117+ CompanyId : companyId ,
118+ ProjectName : project . ProjectName ,
119+ ProjectCode : project . ProjectCode ,
120+ lastTaskId : project . lastTaskId ,
121+ } ;
122+ const sprint = { id : sprintId , name : sprintName || '' } ;
123+ if ( folderId ) {
124+ sprint . folderId = folderId ;
125+ sprint . folderName = folderName || '' ;
126+ }
127+ const tasksWithSprint = tasks . map ( ( task ) => ( { ...task , sprintId, sprintArray : sprint } ) ) ;
128+
129+ try {
130+ const result = await taskMongo . createMultipleTasks ( {
131+ tasks : tasksWithSprint ,
132+ userData : { id : userId , Employee_Name : ( userData && userData . Employee_Name ) || '' , companyOwnerId : ( userData && userData . companyOwnerId ) || '' } ,
133+ projectData,
134+ indexObj : { } ,
135+ statusArray,
136+ sprint,
137+ } ) ;
138+ const createdCount = Array . isArray ( result ?. data ) ? result . data . length : tasks . length ;
139+ await MongoDbCrudOpration ( companyId , {
140+ type : SCHEMA_TYPE . IMPORT_JOBS ,
141+ data : [ { _id : job . _id } , { $set : { status : 'done' , processed : tasks . length , created : createdCount } } ] ,
142+ } , 'updateOne' ) ;
143+ return { status : true , statusText : `Imported ${ createdCount } tasks from ${ source } (${ skipped } skipped).` , data : { jobId : job . _id , created : createdCount , skipped } } ;
144+ } catch ( creationError ) {
145+ logger . error ( `[importers] ${ source } job ${ job . _id } failed: ${ creationError . message } ` ) ;
146+ await MongoDbCrudOpration ( companyId , {
147+ type : SCHEMA_TYPE . IMPORT_JOBS ,
148+ data : [ { _id : job . _id } , { $set : { status : 'failed' , errorList : [ String ( creationError . message || creationError ) . slice ( 0 , 300 ) ] } } ] ,
149+ } , 'updateOne' ) . catch ( ( ) => { } ) ;
150+ return { status : false , statusText : `Import failed: ${ creationError . message } ` } ;
151+ }
152+ } ;
153+
154+ /* POST /api/v2/imports/csv
155+ * body: { rows, mapping?, projectId, sprintId, sprintName?, folderId?, folderName?, userData } */
156+ exports . importFromCsv = async ( req , res ) => {
157+ try {
158+ const companyId = req . headers [ 'companyid' ] || '' ;
159+ const { rows, mapping, projectId, sprintId, sprintName, folderId, folderName, userData } = req . body || { } ;
160+ const userId = userData && ( userData . id || userData . _id ) ? String ( userData . id || userData . _id ) : '' ;
161+ const check = validateCsvInput ( { companyId, projectId, sprintId, rows, userId } ) ;
162+ if ( ! check . valid ) return res . send ( { status : false , statusText : check . reason } ) ;
163+
164+ const ctx = await loadImportContext ( companyId , projectId ) ;
165+ if ( ctx . error ) return res . send ( { status : false , statusText : ctx . error } ) ;
166+
167+ const { tasks, skipped } = transformCsvRows ( { rows, mapping, statusNames : ctx . statusArray . map ( ( status ) => status . name ) , leaderId : userId } ) ;
168+ if ( ! tasks . length ) return res . send ( { status : false , statusText : 'No importable rows found (a task-name column is required).' } ) ;
169+
170+ const out = await finishImport ( companyId , { source : 'csv' , project : ctx . project , sprintId, sprintName, folderId, folderName, userData, statusArray : ctx . statusArray , tasks, skipped } ) ;
171+ return res . send ( out ) ;
172+ } catch ( error ) {
173+ logger . error ( `ERROR in csv import: ${ error . message } ` ) ;
174+ return res . send ( { status : false , statusText : error . message } ) ;
175+ }
176+ } ;
177+
178+ /* POST /api/v2/imports/trello
179+ * body: { board, projectId, sprintId, sprintName?, folderId?, folderName?, userData } */
180+ exports . importFromTrello = async ( req , res ) => {
181+ try {
182+ const companyId = req . headers [ 'companyid' ] || '' ;
183+ const { board, projectId, sprintId, sprintName, folderId, folderName, userData } = req . body || { } ;
184+ const userId = userData && ( userData . id || userData . _id ) ? String ( userData . id || userData . _id ) : '' ;
185+ const check = validateTrelloInput ( { companyId, projectId, sprintId, board, userId } ) ;
186+ if ( ! check . valid ) return res . send ( { status : false , statusText : check . reason } ) ;
187+
188+ const ctx = await loadImportContext ( companyId , projectId ) ;
189+ if ( ctx . error ) return res . send ( { status : false , statusText : ctx . error } ) ;
190+
191+ const { tasks, skipped } = parseTrelloBoard ( { board, statusNames : ctx . statusArray . map ( ( status ) => status . name ) , leaderId : userId } ) ;
192+ if ( ! tasks . length ) return res . send ( { status : false , statusText : 'No importable cards found.' } ) ;
193+
194+ const out = await finishImport ( companyId , { source : 'trello' , project : ctx . project , sprintId, sprintName, folderId, folderName, userData, statusArray : ctx . statusArray , tasks, skipped } ) ;
195+ return res . send ( out ) ;
196+ } catch ( error ) {
197+ logger . error ( `ERROR in trello import: ${ error . message } ` ) ;
198+ return res . send ( { status : false , statusText : error . message } ) ;
199+ }
200+ } ;
0 commit comments