22
33import { asyncMiddleware , errorWithCode } from '@bcgov/nodejs-common-utils' ;
44import { Router } from 'express' ;
5+ import moment from 'moment' ;
6+ import csv from 'csv' ;
57import config from '../../config' ;
68import DataManager from '../../libs/db2' ;
79import UserDistricts from '../../libs/db2/model/userDistricts' ;
810import ExemptionController from '../controllers_v1/ExemptionController' ;
911import ExemptionStatusController from '../controllers_v1/ExemptionStatusController' ;
12+ import { AGREEMENT_EXPORT_COLUMNS , AGREEMENT_EXPORT } from '../../constants' ;
13+
1014const router = new Router ( ) ;
1115
1216const dm2 = new DataManager ( config ) ;
@@ -38,57 +42,64 @@ const getAgreeementsForAH = async (user, filterSettings) => {
3842 } ) ;
3943} ;
4044
41- // Search agreements by RAN, contact name, and client name. This is only used by Web
42- router . get (
43- '/search' ,
44- asyncMiddleware ( async ( req , res ) => {
45- const { user, query } = req ;
46- const filterSettings = JSON . parse ( query . filterSettings ) ;
47- const page = Number ( filterSettings . page || 0 ) ;
48- const limit = Number ( filterSettings . limit || 10 ) ;
49- let agreements = [ ] ;
50- let totalItems = 0 ;
51- if ( user . isAgreementHolder ( ) ) {
52- agreements = await getAgreeementsForAH ( user , {
53- ...filterSettings ,
54- limit : null ,
55- page : null ,
56- } ) ;
57- } else if ( user . isAdministrator ( ) || user . canReadAll ( ) || user . isRangeOfficer ( ) ) {
58- if ( ! ( filterSettings ?. zoneInfo ?. selectedZones ?. length === 0 ) ) {
59- agreements = await Agreement . findWithAllRelations (
60- db ,
61- { 'ref_zone.id' : filterSettings . zoneInfo ?. selectedZones } ,
62- {
63- ...filterSettings ,
64- limit : null ,
65- page : null ,
66- } ,
67- true ,
68- ) ;
69- }
70- } else if ( user . isDecisionMaker ( ) || user . canReadDistrict ( ) ) {
71- const districts = await UserDistricts . find ( db , { user_id : user . id } ) ;
72- const zones = await Zone . find ( db , {
73- district_id : districts . map ( ( d ) => d . id ) ,
74- } ) ;
45+ const getAgreements = async ( user , filterSettings ) => {
46+ let agreements = [ ] ;
47+ if ( user . isAgreementHolder ( ) ) {
48+ agreements = await getAgreeementsForAH ( user , {
49+ ...filterSettings ,
50+ limit : null ,
51+ page : null ,
52+ } ) ;
53+ } else if ( user . isAdministrator ( ) || user . canReadAll ( ) || user . isRangeOfficer ( ) ) {
54+ if ( ! ( filterSettings ?. zoneInfo ?. selectedZones ?. length === 0 ) ) {
7555 agreements = await Agreement . findWithAllRelations (
7656 db ,
77- { zone_id : zones . map ( ( z ) => z . id ) } ,
57+ { 'ref_zone.id' : filterSettings . zoneInfo ?. selectedZones } ,
7858 {
7959 ...filterSettings ,
8060 limit : null ,
8161 page : null ,
8262 } ,
83- false ,
63+ true ,
8464 ) ;
85- } else {
86- throw errorWithCode ( 'Unable to determine user role' , 500 ) ;
8765 }
88- totalItems = agreements . length ;
66+ } else if ( user . isDecisionMaker ( ) || user . canReadDistrict ( ) ) {
67+ const districts = await UserDistricts . find ( db , { user_id : user . id } ) ;
68+ const zones = await Zone . find ( db , {
69+ district_id : districts . map ( ( d ) => d . id ) ,
70+ } ) ;
71+ agreements = await Agreement . findWithAllRelations (
72+ db ,
73+ { zone_id : zones . map ( ( z ) => z . id ) } ,
74+ {
75+ ...filterSettings ,
76+ limit : null ,
77+ page : null ,
78+ } ,
79+ false ,
80+ ) ;
81+ } else {
82+ throw errorWithCode ( 'Unable to determine user role' , 500 ) ;
83+ }
84+
85+ return agreements ;
86+ } ;
87+
88+ // Search agreements by RAN, contact name, and client name. This is only used by Web
89+ router . get (
90+ '/search' ,
91+ asyncMiddleware ( async ( req , res ) => {
92+ const { user, query } = req ;
93+ const filterSettings = query . filterSettings ? JSON . parse ( query . filterSettings ) : { } ;
94+ const page = Number ( filterSettings . page || 0 ) ;
95+ const limit = Number ( filterSettings . limit || 10 ) ;
96+
97+ const agreementsAll = await getAgreements ( user , filterSettings ) ;
98+ const totalItems = agreementsAll . length ;
8999 const startIndex = ( page - 1 ) * limit ;
90100 const endIndex = startIndex + limit ;
91- agreements = agreements . slice ( startIndex , endIndex ) ;
101+ const agreements = agreementsAll . slice ( startIndex , endIndex ) ;
102+
92103 agreements . map ( ( agreement ) => agreement . transformToV1 ( ) ) ;
93104 const totalPages = Math . ceil ( totalItems / limit ) ;
94105 const currentPage = page > totalPages ? totalPages : page ;
@@ -103,6 +114,97 @@ router.get(
103114 } ) ,
104115) ;
105116
117+ // Export agreements as CSV
118+ router . get (
119+ '/export' ,
120+ asyncMiddleware ( async ( req , res ) => {
121+ const { user, query } = req ;
122+
123+ if ( ! user . isAdministrator ( ) && ! user . isRangeOfficer ( ) && ! user . isDecisionMaker ( ) && ! user . isReadOnly ( ) ) {
124+ throw errorWithCode ( 'Unauthorized' , 403 ) ;
125+ }
126+
127+ const filterSettings = query . filterSettings ? JSON . parse ( query . filterSettings ) : { } ;
128+
129+ let agreements = await getAgreements ( user , filterSettings ) ;
130+ // Filter out test agreements
131+ agreements = agreements . filter ( ( agreement ) => ! agreement . forestFileId . startsWith ( AGREEMENT_EXPORT . TEST_RAN_PREFIX ) ) ;
132+ agreements . map ( ( agreement ) => agreement . transformToV1 ( ) ) ;
133+
134+ const csvData = agreements . map ( ( agreement ) => {
135+ // Sort clients: primary (A) first, then others
136+ const sortedClients = agreement . clients . sort ( ( a , b ) => {
137+ if ( a . clientTypeCode === 'A' ) return - 1 ;
138+ if ( b . clientTypeCode === 'A' ) return 1 ;
139+ return 0 ;
140+ } ) ;
141+
142+ const primaryClient = sortedClients [ 0 ] ;
143+ const clientName = primaryClient ?. name || '' ;
144+
145+ // Get up to 5 agreement holders (from user account names) and emails, fill remaining with empty strings
146+ const holders = Array ( 5 )
147+ . fill ( '' )
148+ . map ( ( _ , index ) => {
149+ const client = sortedClients [ index ] ;
150+ if ( ! client ) return '' ;
151+ // Use user name if available, otherwise blank
152+ if ( client . userGivenName || client . userFamilyName ) {
153+ return `${ client . userGivenName } ${ client . userFamilyName } ` . trim ( ) ;
154+ }
155+ return '' ;
156+ } ) ;
157+ const emails = Array ( 5 )
158+ . fill ( '' )
159+ . map ( ( _ , index ) => sortedClients [ index ] ?. email || '' ) ;
160+
161+ return {
162+ [ AGREEMENT_EXPORT_COLUMNS . RAN ] : agreement . forestFileId ,
163+ [ AGREEMENT_EXPORT_COLUMNS . LICENSE_TYPE ] : Agreement . getLicenseTypeText ( agreement ) ,
164+ [ AGREEMENT_EXPORT_COLUMNS . RANGE_NAME ] : agreement . plan ?. rangeName || '' ,
165+ [ AGREEMENT_EXPORT_COLUMNS . LICENSEE ] : clientName ,
166+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_1 ] : holders [ 0 ] ,
167+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_1_EMAIL ] : emails [ 0 ] ,
168+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_2 ] : holders [ 1 ] ,
169+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_2_EMAIL ] : emails [ 1 ] ,
170+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_3 ] : holders [ 2 ] ,
171+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_3_EMAIL ] : emails [ 2 ] ,
172+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_4 ] : holders [ 3 ] ,
173+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_4_EMAIL ] : emails [ 3 ] ,
174+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_5 ] : holders [ 4 ] ,
175+ [ AGREEMENT_EXPORT_COLUMNS . AGREEMENT_HOLDER_5_EMAIL ] : emails [ 4 ] ,
176+ [ AGREEMENT_EXPORT_COLUMNS . ZONE_AGROLOGIST ] : agreement . zone ?. user
177+ ? `${ agreement . zone . user . givenName } ${ agreement . zone . user . familyName } `
178+ : '' ,
179+ [ AGREEMENT_EXPORT_COLUMNS . ZONE ] : agreement . zone ?. code || '' ,
180+ [ AGREEMENT_EXPORT_COLUMNS . DISTRICT ] : agreement . zone ?. district ?. code || '' ,
181+ [ AGREEMENT_EXPORT_COLUMNS . PLAN_STATUS ] : agreement . plan ?. status ?. name || AGREEMENT_EXPORT . NO_PLAN_DEFAULT ,
182+ [ AGREEMENT_EXPORT_COLUMNS . PLAN_START_DATE ] : agreement . plan ?. planStartDate
183+ ? moment ( agreement . plan . planStartDate ) . format ( AGREEMENT_EXPORT . DATE_FORMAT )
184+ : '' ,
185+ [ AGREEMENT_EXPORT_COLUMNS . PLAN_END_DATE ] : agreement . plan ?. planEndDate
186+ ? moment ( agreement . plan . planEndDate ) . format ( AGREEMENT_EXPORT . DATE_FORMAT )
187+ : '' ,
188+ [ AGREEMENT_EXPORT_COLUMNS . USAGE_STATUS ] : Agreement . getUsageStatusText ( agreement . usageStatus ) ,
189+ [ AGREEMENT_EXPORT_COLUMNS . PERCENTAGE_USE ] : agreement . percentageUse || 0 ,
190+ [ AGREEMENT_EXPORT_COLUMNS . HAS_CURRENT_SCHEDULE ] : agreement . hasCurrentSchedule
191+ ? AGREEMENT_EXPORT . YES
192+ : AGREEMENT_EXPORT . NO ,
193+ [ AGREEMENT_EXPORT_COLUMNS . EXEMPTION_STATUS ] : Agreement . getExemptionStatusText ( agreement . exemptionStatus ) ,
194+ } ;
195+ } ) ;
196+
197+ csv . stringify ( csvData , { header : true } , ( err , output ) => {
198+ if ( err ) {
199+ throw err ;
200+ }
201+ res . setHeader ( 'Content-Type' , AGREEMENT_EXPORT . CONTENT_TYPE ) ;
202+ res . setHeader ( 'Content-Disposition' , `attachment; filename="${ AGREEMENT_EXPORT . FILENAME } "` ) ;
203+ res . status ( 200 ) . send ( output ) ;
204+ } ) ;
205+ } ) ,
206+ ) ;
207+
106208// Exemption routes nested under /agreement/:agreementId/exemption
107209const exemptionBase = '/:agreementId/exemption' ;
108210// Exemption history
0 commit comments