@@ -93,6 +93,90 @@ const loadImportContext = async (companyId, projectId) => {
9393 return { project, statusArray } ;
9494} ;
9595
96+ /* Map the parser's rich card data onto each task into the shapes the task
97+ * create path persists (S3-01): resolve member emails → assignees, build
98+ * checklistArray + attachment link-references, and fold Trello labels into the
99+ * description (there is no programmatic tag-create path). Mutates `tasks`; a
100+ * no-op for CSV/Jira tasks, which carry none of these fields. */
101+ const enrichImportTasks = async ( companyId , tasks ) => {
102+ const emails = Array . from ( new Set (
103+ tasks . flatMap ( ( t ) => ( Array . isArray ( t . memberEmails ) ? t . memberEmails : [ ] ) ) . filter ( Boolean ) ,
104+ ) ) ;
105+ const emailToId = { } ;
106+ if ( emails . length ) {
107+ try {
108+ const users = await MongoDbCrudOpration ( SCHEMA_TYPE . GOLBAL , {
109+ type : SCHEMA_TYPE . USERS ,
110+ data : [ { Employee_Email : { $in : emails } } , { _id : 1 , Employee_Email : 1 } ] ,
111+ } , 'find' ) ;
112+ ( users || [ ] ) . forEach ( ( u ) => {
113+ if ( u && u . Employee_Email ) emailToId [ String ( u . Employee_Email ) . toLowerCase ( ) ] = String ( u . _id ) ;
114+ } ) ;
115+ } catch ( error ) {
116+ logger . error ( `[importers] member email resolve failed: ${ error . message } ` ) ;
117+ }
118+ }
119+
120+ tasks . forEach ( ( task ) => {
121+ if ( Array . isArray ( task . memberEmails ) && task . memberEmails . length ) {
122+ const ids = task . memberEmails . map ( ( e ) => emailToId [ String ( e ) . toLowerCase ( ) ] ) . filter ( Boolean ) ;
123+ if ( ids . length ) task . AssigneeUserId = Array . from ( new Set ( [ ...( task . AssigneeUserId || [ ] ) , ...ids ] ) ) ;
124+ }
125+ if ( Array . isArray ( task . checklists ) && task . checklists . length ) {
126+ task . checklistArray = task . checklists . map ( ( cl ) => ( {
127+ id : new mongoose . Types . ObjectId ( ) . toString ( ) ,
128+ name : cl . name || 'Checklist' ,
129+ items : ( Array . isArray ( cl . items ) ? cl . items : [ ] ) . map ( ( it ) => ( { name : it . name , isChecked : ! ! it . isChecked } ) ) ,
130+ } ) ) ;
131+ }
132+ if ( Array . isArray ( task . attachments ) && task . attachments . length ) {
133+ task . attachments = task . attachments . map ( ( att ) => ( {
134+ id : new mongoose . Types . ObjectId ( ) . toString ( ) ,
135+ filename : att . name || att . url ,
136+ mediaURL : att . url ,
137+ size : att . bytes || 0 ,
138+ type : 'link' ,
139+ } ) ) ;
140+ }
141+ if ( Array . isArray ( task . labels ) && task . labels . length ) {
142+ const names = task . labels . map ( ( l ) => l && l . name ) . filter ( Boolean ) ;
143+ if ( names . length ) {
144+ task . rawDescription = `${ task . rawDescription || '' } ${ task . rawDescription ? '\n\n' : '' } Labels: ${ names . join ( ', ' ) } ` . slice ( 0 , 10000 ) ;
145+ }
146+ }
147+ } ) ;
148+ } ;
149+
150+ /* Create the parsed Trello comments on the freshly-created tasks. Each input
151+ * task was stamped with `createdTaskId` by createMultipleTasks. Best-effort:
152+ * a failed comment never fails the import. */
153+ const createImportComments = async ( companyId , projectData , sprintId , folderId , tasks , userId ) => {
154+ for ( const task of tasks ) {
155+ if ( ! task . createdTaskId || ! Array . isArray ( task . comments ) || ! task . comments . length ) continue ;
156+ for ( const c of task . comments ) {
157+ if ( ! c || ! c . text ) continue ;
158+ const body = `${ c . author ? c . author + ': ' : '' } ${ c . text } ` . slice ( 0 , 10000 ) ;
159+ try {
160+ await MongoDbCrudOpration ( companyId , {
161+ type : SCHEMA_TYPE . COMMENTS ,
162+ data : {
163+ message : body ,
164+ userId,
165+ type : 'text' ,
166+ projectId : new mongoose . Types . ObjectId ( projectData . _id ) ,
167+ taskId : new mongoose . Types . ObjectId ( task . createdTaskId ) ,
168+ sprintId : new mongoose . Types . ObjectId ( sprintId ) ,
169+ project : false ,
170+ ...( folderId ? { folderId : new mongoose . Types . ObjectId ( folderId ) } : { } ) ,
171+ } ,
172+ } , 'save' ) ;
173+ } catch ( error ) {
174+ logger . error ( `[importers] comment import failed for task ${ task . createdTaskId } : ${ error . message } ` ) ;
175+ }
176+ }
177+ }
178+ } ;
179+
96180/* Record the job, feed the bulk-create pipeline, update the job. Returns the
97181 * response envelope. Identical create path to the Jira importer. */
98182const finishImport = async ( companyId , { source, project, sprintId, sprintName, folderId, folderName, userData, statusArray, tasks, skipped } ) => {
@@ -124,6 +208,9 @@ const finishImport = async (companyId, { source, project, sprintId, sprintName,
124208 sprint . folderId = folderId ;
125209 sprint . folderName = folderName || '' ;
126210 }
211+ // S3-01: fold Trello rich data (checklists, attachments, members, labels)
212+ // onto each task before creation; comments are added after (they need ids).
213+ await enrichImportTasks ( companyId , tasks ) ;
127214 const tasksWithSprint = tasks . map ( ( task ) => ( { ...task , sprintId, sprintArray : sprint } ) ) ;
128215
129216 try {
@@ -135,6 +222,8 @@ const finishImport = async (companyId, { source, project, sprintId, sprintName,
135222 statusArray,
136223 sprint,
137224 } ) ;
225+ await createImportComments ( companyId , projectData , sprintId , folderId , tasksWithSprint , userId )
226+ . catch ( ( commentErr ) => logger . error ( `[importers] comment import error: ${ commentErr . message } ` ) ) ;
138227 const createdCount = Array . isArray ( result ?. data ) ? result . data . length : tasks . length ;
139228 await MongoDbCrudOpration ( companyId , {
140229 type : SCHEMA_TYPE . IMPORT_JOBS ,
0 commit comments