@@ -9,6 +9,10 @@ import xlsx from 'xlsx'
99
1010import { config } from '~/src/config/index.js'
1111import { createLogger } from '~/src/helpers/logging/logger.js'
12+ import {
13+ formatPaymentAmount ,
14+ formatPaymentDate
15+ } from '~/src/helpers/payment-helper.js'
1216import { getSubmissionRecords } from '~/src/repositories/submission-repository.js'
1317import {
1418 getFormDefinition ,
@@ -66,6 +70,18 @@ const SUBMISSION_DATE_HEADER_TEXT = 'Submission date'
6670const SUBMISSION_FORM_NAME = 'SubmissionFormName'
6771const SUBMISSION_FORM_NAME_TEXT = 'Form name'
6872
73+ const PAYMENT_DESCRIPTION_HEADER = 'PaymentDescription'
74+ const PAYMENT_DESCRIPTION_HEADER_TEXT = 'Payment description'
75+
76+ const PAYMENT_AMOUNT_HEADER = 'PaymentAmount'
77+ const PAYMENT_AMOUNT_HEADER_TEXT = 'Payment amount'
78+
79+ const PAYMENT_REFERENCE_HEADER = 'PaymentReference'
80+ const PAYMENT_REFERENCE_HEADER_TEXT = 'Payment reference'
81+
82+ const PAYMENT_DATE_HEADER = 'PaymentDate'
83+ const PAYMENT_DATE_HEADER_TEXT = 'Payment date'
84+
6985const CSAT_FORM_ID = '691db72966b1bdc98fa3e72a'
7086
7187/**
@@ -338,6 +354,116 @@ export async function addFirstCellsToRow(
338354 }
339355}
340356
357+ /**
358+ * Add form component cells to a row
359+ * @param {FormModel | undefined } formModel - the form model
360+ * @param {Map<string, CellValue> } row - the row to add cells to
361+ * @param {SpreadsheetContext } context - the spreadsheet context
362+ * @param {WithId<FormSubmissionDocument> } record - the submission record
363+ * @param {SpreadsheetOptions | undefined } [options] - spreadsheet options
364+ */
365+ function addFormComponentCellsToRow ( formModel , row , context , record , options ) {
366+ formModel ?. componentMap . forEach ( ( component , key ) => {
367+ if ( ! component . isFormComponent ) {
368+ return
369+ }
370+
371+ if ( hasRepeater ( component . page . pageDef ) ) {
372+ const repeaterName = component . page . pageDef . repeat . options . name
373+ const hasRepeaterData = repeaterName in record . data . repeaters
374+ const items = hasRepeaterData ? record . data . repeaters [ repeaterName ] : [ ]
375+
376+ for ( let index = 0 ; index < items . length ; index ++ ) {
377+ const value = getValue ( items [ index ] , key , component )
378+ const componentKey = `${ component . name } ${ index + 1 } `
379+ const componentValue = `${ component . label } ${ index + 1 } `
380+
381+ addCellToRow ( row , componentKey , value , options )
382+ addHeader ( context , component , componentKey , componentValue )
383+ }
384+ } else if ( component . type === ComponentType . FileUploadField ) {
385+ const files = record . data . files [ component . name ]
386+ const fileLinks = Array . isArray ( files )
387+ ? files . map ( ( f ) => f . userDownloadLink ) . join ( ' \r\n' )
388+ : ''
389+
390+ addCellToRow ( row , component . name , fileLinks , options )
391+ addHeader ( context , component )
392+ } else {
393+ const value = getValue ( record . data . main , key , component )
394+
395+ addCellToRow ( row , component . name , value , options )
396+ addHeader ( context , component )
397+ }
398+ } )
399+ }
400+
401+ /**
402+ * Adds a header if not already present
403+ * @param {Map<string, string> } headers - the headers map
404+ * @param {string } key - the header key
405+ * @param {string } value - the header display text
406+ */
407+ function addHeaderIfMissing ( headers , key , value ) {
408+ if ( ! headers . has ( key ) ) {
409+ headers . set ( key , value )
410+ }
411+ }
412+
413+ /**
414+ * Add payment cells to a row if payment data exists
415+ * @param {Map<string, CellValue> } row - the row to add cells to
416+ * @param {Caches } caches - the spreadsheet caches
417+ * @param {WithId<FormSubmissionDocument> } record - the submission record
418+ * @param {SpreadsheetOptions | undefined } [options] - spreadsheet options
419+ */
420+ function addPaymentCellsToRow ( row , caches , record , options ) {
421+ const payment = record . data . payment
422+ if ( ! payment ) {
423+ return
424+ }
425+
426+ addCellToRow ( row , PAYMENT_DESCRIPTION_HEADER , payment . description , options )
427+ addHeaderIfMissing (
428+ caches . headers ,
429+ PAYMENT_DESCRIPTION_HEADER ,
430+ PAYMENT_DESCRIPTION_HEADER_TEXT
431+ )
432+
433+ addCellToRow (
434+ row ,
435+ PAYMENT_AMOUNT_HEADER ,
436+ formatPaymentAmount ( payment . amount ) ,
437+ options
438+ )
439+ addHeaderIfMissing (
440+ caches . headers ,
441+ PAYMENT_AMOUNT_HEADER ,
442+ PAYMENT_AMOUNT_HEADER_TEXT
443+ )
444+
445+ addCellToRow ( row , PAYMENT_REFERENCE_HEADER , payment . reference , options )
446+ addHeaderIfMissing (
447+ caches . headers ,
448+ PAYMENT_REFERENCE_HEADER ,
449+ PAYMENT_REFERENCE_HEADER_TEXT
450+ )
451+
452+ if ( payment . createdAt ) {
453+ addCellToRow (
454+ row ,
455+ PAYMENT_DATE_HEADER ,
456+ formatPaymentDate ( payment . createdAt ) ,
457+ options
458+ )
459+ addHeaderIfMissing (
460+ caches . headers ,
461+ PAYMENT_DATE_HEADER ,
462+ PAYMENT_DATE_HEADER_TEXT
463+ )
464+ }
465+ }
466+
341467/**
342468 * Generate a submission file for a form id
343469 * @param {string } formId - the form id
@@ -379,40 +505,8 @@ export async function generateSubmissionsFile(
379505 )
380506
381507 addCellToRow ( row , SUBMISSION_FORM_NAME , formNameFromId , options )
382-
383- formModel ?. componentMap . forEach ( ( component , key ) => {
384- if ( ! component . isFormComponent ) {
385- return
386- }
387-
388- if ( hasRepeater ( component . page . pageDef ) ) {
389- const repeaterName = component . page . pageDef . repeat . options . name
390- const hasRepeaterData = repeaterName in record . data . repeaters
391- const items = hasRepeaterData ? record . data . repeaters [ repeaterName ] : [ ]
392-
393- for ( let index = 0 ; index < items . length ; index ++ ) {
394- const value = getValue ( items [ index ] , key , component )
395- const componentKey = `${ component . name } ${ index + 1 } `
396- const componentValue = `${ component . label } ${ index + 1 } `
397-
398- addCellToRow ( row , componentKey , value , options )
399- addHeader ( context , component , componentKey , componentValue )
400- }
401- } else if ( component . type === ComponentType . FileUploadField ) {
402- const files = record . data . files [ component . name ]
403- const fileLinks = Array . isArray ( files )
404- ? files . map ( ( f ) => f . userDownloadLink ) . join ( ' \r\n' )
405- : ''
406-
407- addCellToRow ( row , component . name , fileLinks , options )
408- addHeader ( context , component )
409- } else {
410- const value = getValue ( record . data . main , key , component )
411-
412- addCellToRow ( row , component . name , value , options )
413- addHeader ( context , component )
414- }
415- } )
508+ addFormComponentCellsToRow ( formModel , row , context , record , options )
509+ addPaymentCellsToRow ( row , caches , record , options )
416510
417511 rows . push ( row )
418512 }
@@ -523,6 +617,21 @@ function sortHeaders(components, headers) {
523617 const idxA = componentNames . indexOf ( nameA )
524618 const idxB = componentNames . indexOf ( nameB )
525619
620+ // Both not found -> keep original order
621+ if ( idxA === - 1 && idxB === - 1 ) {
622+ return 0
623+ }
624+
625+ // A not found, B found -> A goes after B
626+ if ( idxA === - 1 ) {
627+ return 1
628+ }
629+
630+ // A found, B not found -> A goes before B
631+ if ( idxB === - 1 ) {
632+ return - 1
633+ }
634+
526635 return idxA - idxB
527636 } )
528637}
@@ -556,7 +665,7 @@ export function buildPreHeaders(options) {
556665/**
557666 * Build an xlsx workbook from the headers and rows
558667 * @param {string } formId - the form id
559- * @param {[string, string][] } headers - the file header
668+ * @param {[string, string][] } headers - the file headers (including payment headers)
560669 * @param {Map<string, CellValue >[] } rows - the data rows
561670 * @param {SpreadsheetOptions } [options]
562671 */
0 commit comments