diff --git a/.gitignore b/.gitignore index cde51608..8c47bd0e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ schemaspy import public/doc sonar-runner/build +sonar-runner/.gradle # Autogenerated document location. See npm scripts. apidoc diff --git a/planTemplate.docx b/planTemplate.docx new file mode 100644 index 00000000..38a2d3ae Binary files /dev/null and b/planTemplate.docx differ diff --git a/src/libs/cdogs.js b/src/libs/cdogs.js new file mode 100644 index 00000000..b0530e25 --- /dev/null +++ b/src/libs/cdogs.js @@ -0,0 +1,86 @@ +import { errorWithCode, logger } from '@bcgov/nodejs-common-utils'; +import axios from "axios"; +import fs from 'fs'; + +export class Cdogs { + + constructor(authenticaitonURL = process.env.CDOGS_AUTHENTICATION_URL, + serviceURL = process.env.CDOGS_SERVICE_URL, + clientId = process.env.CDOGS_CLIENT_ID, + clientSecret = process.env.CDOGS_CLIENT_SECRET, + enabled = process.env.CDOGS_ENABLED) { + this.authenticationURL = authenticaitonURL; + this.serviceURL = serviceURL; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.enabled = enabled; + } + + async init() { + this.template = { + encodingType: 'base64', + fileType: 'docx', + content: await this.readTemplate() + } + this.options = { + cacheReport: false, + convertTo: "pdf", + overwrite: true, + } + } + + async getBearerToken() { + const tokenEndpoint = `${this.authenticationURL}/auth/realms/comsvcauth/protocol/openid-connect/token` + const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64') + try { + const response = await axios.post(tokenEndpoint, 'grant_type=client_credentials', { + headers: { + 'Authorization': `Basic ${credentials}`, + 'Content-Type': 'application/x-www-form-urlencoded', + } + }); + logger.debug("Bearer token retrieved") + return response.data.access_token + } + catch (error) { + logger.error(`Failed to retrieve bearer token: ${JSON.stringify(error)}`) + throw errorWithCode('Failed to retrieve bearer token' + JSON.stringify(error), 500) + } + } + + async generatePDF(planData) { + if (!eval(this.enabled)) + return; + const serviceURL = `${this.serviceURL}/api/v2/template/render` + try { + const token = await this.getBearerToken() + const payload = { + data: planData, + options: { ...this.options, 'reportName': planData.agreementId + '.pdf' }, + template: this.template + } + const response = await axios.post(serviceURL, JSON.stringify(payload), { + timeout: 20000, + responseType: 'arraybuffer', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + return response.data + } catch (error) { + logger.error(`Error generating PDF file: ${JSON.stringify(error)}`) + throw errorWithCode(`Error generating PDF file: ${JSON.stringify(error)} ${JSON.stringify(error)}`, 500) + } + } + + async readTemplate(template = './planTemplate.docx') { + try { + const data = fs.readFileSync(template); + return Buffer.from(data).toString('base64'); + } catch (error) { + logger.error(`Error reading template file ${template}: ${JSON.stringify(error)}`) + throw errorWithCode(`Error reading template file ${template}: ${JSON.stringify(error)}`, 500) + } + } +} \ No newline at end of file diff --git a/src/libs/mailer.js b/src/libs/mailer.js index da591282..172c7d04 100644 --- a/src/libs/mailer.js +++ b/src/libs/mailer.js @@ -37,7 +37,7 @@ export class Mailer { try { const token = await this.getBearerToken() const emailPayload = { to, from, subject, body, bodyType, } - logger.debug("email payload: " + JSON.stringify(emailPayload)) + logger.debug("email payload: " + JSON.stringify(emailPayload)) await axios.post(emailEndpoint, JSON.stringify(emailPayload), { headers: { 'Content-Type': 'application/json', diff --git a/src/libs/template.js b/src/libs/template.js deleted file mode 100644 index ab4d7369..00000000 --- a/src/libs/template.js +++ /dev/null @@ -1,327 +0,0 @@ -// -// SecureImage -// -// Copyright © 2018 Province of British Columbia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Created by Jason Leach on 2018-01-18. -// - -/* eslint-env es6 */ - -'use strict'; - -import fs from 'fs'; -import handlebars from 'handlebars'; -import moment from 'moment'; -import path from 'path'; -import wkhtmltopdf from 'wkhtmltopdf'; -import { logger } from '@bcgov/nodejs-common-utils'; -import { AGREEMENT_HOLDER_ROLE, NOT_PROVIDED, DAYS_ON_THE_AVERAGE, DATE_FORMAT } from '../constants'; - -if (process.platform === 'linux') { - // On Linux (OpenShift) we need to run our own copy of the binary with any related - // libs. - const lpath = path.join(__dirname, '../', 'wkhtmltopdf-amd64-0.12.4', 'lib'); - const bpath = path.join(__dirname, '../', 'wkhtmltopdf-amd64-0.12.4', 'bin', 'wkhtmltopdf'); - wkhtmltopdf.command = `LD_LIBRARY_PATH=${process.env.LD_LIBRARY_PATH}:${lpath} ${bpath}`; -} - -// -// Format Helpers -// - -/** - * Convert a boolean value to its human readable equivolent - * - * @param {Boolean} boolValue The value to be operated on - * @returns A string with the reformated date - */ -// eslint-disable-next-line no-confusing-arrow -const asYesOrNoValue = boolValue => boolValue ? 'YES' : 'NO'; - -/** - * Present the date time in a more readable way - * - * @param {string | Date} isoFormatDate The stringified date time - * @param {boolean} isYearIncluded The boolean to specify whether the year is needed - * @returns {string} a formatted string or 'Not provided' - */ -const formatDateFromServer = (isoFormatDate, isYearIncluded = true, notProvided = NOT_PROVIDED) => { - if (isoFormatDate) { - const m = moment(isoFormatDate, DATE_FORMAT.SERVER_SIDE); - - if (isYearIncluded) { - return m.format(DATE_FORMAT.CLIENT_SIDE); - } - return m.format(DATE_FORMAT.CLIENT_SIDE_WITHOUT_YEAR); - } - return notProvided; -}; - -/** - * Convert the contact type / role to its string equivolent - * - * @param {Contact} contact The contact to be operated on - * @returns The `String` representing the contacts role - */ -const contactRole = (contact) => { - if (!contact) { - return NOT_PROVIDED; - } - if (contact.clientTypeCode === AGREEMENT_HOLDER_ROLE.PRIMARY) { - return 'Primary'; - } - - return 'Secondary'; -}; - -const handleNullValue = (value, notProvided) => { - if (!value) { - return typeof notProvided === 'string' ? notProvided : NOT_PROVIDED; - } - return value; -}; - -/** - * Convert the zone code / descriiption to its string equivolent - * - * @param {Zone} zone The zone to be operated on - * @returns The `String` representing the district - */ -const getDistrict = (zone) => { - if (!zone) { - return NOT_PROVIDED; - } - if (zone.district && zone.district.description) { - return `${zone.district.code} - ${zone.district.description}`; - } - return zone.district.code; -}; - -/** - * Convert the agreement type code / descriiption to its string equivolent - * - * @param {AgreementType} agreementType The zone to be operated on - * @returns The `String` representing the agreement type - */ -const getAgreementType = (agreementType) => { - if (!agreementType) { - return NOT_PROVIDED; - } - if (agreementType.description) { - return `${agreementType.code} - ${agreementType.description}`; - } - return agreementType.code; -}; - -/** - * Capitalize the first letter for a string - * - * @param {String} string The string to be operated on - * @returns A string with the first letter capitalized - */ -export const capitalizeFirstLetter = (string) => { - if (!string) { - return ''; - } - return string.charAt(0).toUpperCase() + string.slice(1); -}; - -/** - * Reformat the contact name - * - * @param {Contact} contact The contact to be operated on - * @returns A `String` represeting the contact name in the format `First Last` - */ -const contactFullName = (contact) => { - const [lastName, firstName] = contact.name - .split(',') - .map(string => string.toLowerCase()) - .map(string => string.trim()); - return `${capitalizeFirstLetter(firstName)} ${capitalizeFirstLetter(lastName)}`; -}; - -/** - * Handlebars helper to build the full name of the primary agreement holder - * - * @param {[Contact]} contacts The `Agreement` contacts - * @returns A string representing the full name of the promary contact - */ -export const primaryContactFullName = (contacts) => { - const [pcontact] = contacts - .filter(contact => contact.clientTypeCode === AGREEMENT_HOLDER_ROLE.PRIMARY); - - return contactFullName(pcontact); -}; - -const getYesOrNo = boolean => ( - boolean ? 'Yes' : 'No' -); - - -const shift = (number, precision) => { - const numArray = (`${number}`).split('e'); - return +(`${numArray[0]}e${(numArray[1] ? (+numArray[1] + precision) : precision)}`); -}; - -const round = (number, precision) => ( - shift(Math.round(shift(number, +precision)), -precision) -); - -/** - * Round the float to 1 decimal - * - * @param {float} number - * @returns the rounded float number - */ -export const roundToSingleDecimalPlace = number => ( - round(number, 1) -); - -/** - * - * @param {number} numberOfAnimals - * @param {number} totalDays - * @param {number} auFactor parameter provided from the livestock type - * @returns {float} the total AUMs - */ -export const calcTotalAUMs = (numberOfAnimals = 0, totalDays, auFactor = 0) => ( - ((numberOfAnimals * totalDays * auFactor) / DAYS_ON_THE_AVERAGE) -); - -/** - * Present user friendly string when getting null or undefined value - * - * @param {string | Date} first the string in the class Date form - * @param {string | Date} second the string in the class Date form - * @param {bool} isUserFriendly - * @returns {number | string} the number of days or 'N/P' - */ -export const calcDateDiff = (first, second, isUserFriendly) => { - if (first && second) { - return moment(first).diff(moment(second), 'days'); - } - return isUserFriendly ? 'N/P' : 0; -}; - -/** - * Calculate Private Land Deduction Animal Unit Month - * - * @param {number} totalAUMs - * @param {float} pasturePldPercent - * @returns {float} the pld AUMs - */ -export const calcPldAUMs = (totalAUMs, pasturePldPercent = 0) => ( - totalAUMs * pasturePldPercent -); - -/** - * Calculate Crown Animal Unit Month - * - * @param {number} totalAUMs - * @param {number} pldAUMs - * @returns {float} the crown AUMs - */ -export const calcCrownAUMs = (totalAUMs, pldAUMs) => ( - (totalAUMs - pldAUMs) -); - -/** - * Calculate the total Crown Animal Unit Month - * - * @param {Array} entries grazing schedule entries - * @returns {float} the total crown AUMs - */ -export const calcCrownTotalAUMs = (entries = []) => { - const reducer = (accumulator, currentValue) => accumulator + currentValue; - if (entries.length === 0) { - return 0; - } - return entries - .map(entry => entry.crownAUMs) - .reduce(reducer); -}; - -export const getPastureNames = (pastureIds = [], pastures = {}) => { - const pastureNames = pastureIds.map((pId) => { - const pasture = pastures.find(p => p.id === pId); - return pasture && pasture.name; - }); - const { length } = pastureNames; - switch (length) { - case 0: - return NOT_PROVIDED; - case 1: - case 2: - return pastureNames.join(' and '); - default: - return `${pastureNames.slice(0, length - 1).join(', ')}, and ${pastureNames[length - 1]}`; - } -}; - -// -// Document Rendering -// - -/** - * Compile the handelbars template and run it with the given context - * to produce html. - * - * @param {ReadStream} source A stream asociated to the handelbars markup template - * @param {JSON} context The object with appropriate data for the template - * @returns A resolved `Promise` with the HTML data. - */ -export const compile = (source, context) => { - handlebars.registerHelper('getContactRole', contactRole); - handlebars.registerHelper('getDistrict', getDistrict); - handlebars.registerHelper('getContactFullName', contactFullName); - handlebars.registerHelper('getPrimaryContactName', primaryContactFullName); - handlebars.registerHelper('getStandardDateFormat', formatDateFromServer); - handlebars.registerHelper('getBoolAsYesNoValue', asYesOrNoValue); - handlebars.registerHelper('handleNullValue', handleNullValue); - handlebars.registerHelper('getAgreementType', getAgreementType); - handlebars.registerHelper('getYesOrNo', getYesOrNo); - - const html = handlebars.compile(source.toString('utf-8'))(context); - return Promise.resolve(html); -}; - -/** - * Load the html template from the local file system and return it as a Buffer. - * - * @param {String} fileName The path and name of the file to be loaded - * @returns A resolved `Promise` with a stream of the loaded file; rejected otherwise. - */ -export const loadTemplate = (fileName) => { - const docpath = path.join(__dirname, '../../', 'templates', fileName); - - return new Promise((resolve, reject) => { - fs.access(docpath, fs.constants.R_OK, (accessErr) => { - if (accessErr) { - logger.warn('Unable to find templates'); - reject(accessErr); - } - - fs.readFile(docpath, (readFileErr, data) => { - if (readFileErr) { - logger.warn('Unable to load template'); - reject(readFileErr); - } - - resolve(data); - }); - }); - }); -}; diff --git a/src/router/controllers_v1/PDFGeneratoin.js b/src/router/controllers_v1/PDFGeneratoin.js new file mode 100644 index 00000000..260a396a --- /dev/null +++ b/src/router/controllers_v1/PDFGeneratoin.js @@ -0,0 +1,146 @@ +import moment from 'moment'; +import { Cdogs } from '../../libs/cdogs' +import PlanController from '../controllers_v1/PlanController' +import { calcCrownAUMs, calcDateDiff, calcPldAUMs, calcTotalAUMs, roundToSingleDecimalPlace } from '../helpers/PDFHelper'; +import { NOT_PROVIDED } from '../../constants'; + +export default class PDFGeneration { + static async generatePDF(req, res) { + const { + user, + params, + } = req; + const { planId } = params; + const dogs = new Cdogs(); + dogs.init() + const plan = await PlanController.fetchPlan(planId, user) + const adg = new AdditionalDetailsGenerator() + adg.setClientConfirmationStatus(plan) + adg.setInvasivePlantCheckListIsEmpty(plan) + adg.setPlantCommunityDetails(plan) + adg.setIndicatorPlantDetails(plan) + adg.setScheduleDetails(plan) + plan.currentDate = moment() + const response = await dogs.generatePDF(plan) + res.json(response).end() + } +} + +class AdditionalDetailsGenerator { + setClientConfirmationStatus(plan) { + for (const client of plan.agreement.clients) { + const confirmation = plan.confirmations.find((item) => { + return item.clientId === client.id + }) + client.confirmationStatus = 'Not Confirmed' + if (confirmation && confirmation.confirmed === true) { + client.confirmationStatus = 'Confirmed' + } + } + } + + setPlantCommunityDetails(plan) { + for (const pasture of plan.pastures) { + if (pasture) { + for (const pasture of plan.pastures) { + if (isNaN(pasture.pldPercentage)) { + pasture.pldPercentage = 0 + } + } + if (!pasture.allowableAum) + pasture.allowableAum = NOT_PROVIDED + if (!pasture.pldPercent) + pasture.pldPercent = NOT_PROVIDED + if (!pasture.notes) + pasture.notes = NOT_PROVIDED + for (const plantCommunity of pasture.plantCommunities) { + if (plantCommunity) { + if (!plantCommunity.rangeReadinessMonth && plantCommunity.rangeReadinessDay) { + plantCommunity.rangeReadinessDate = moment() + .set('month', plantCommunity.rangeReadinessMonth - 1) + .set('date', plantCommunity.rangeReadinessDay) + .format('MMMM D') + } + if (plantCommunity.plantCommunityActions) { + for (const action of plantCommunity.plantCommunityActions) { + if (action) { + action.name = action.actionType.name === 'Other' + ? `${action.name} (Other)` + : action.actionType.name + if (action.actionType.name === 'Timing') { + action.NoGrazeStartDate = action.noGrazeStartMonth && action.noGrazeStartDay + ? moment() + .set('month', action.noGrazeStartMonth - 1) + .set('date', action.noGrazeStartDay) + .format('MMMM Do') + : 'Not Provided' + action.NoGrazeEndDate = action.noGrazeEndMonth && action.noGrazeEndDay + ? moment() + .set('month', action.noGrazeEndMonth - 1) + .set('date', action.noGrazeEndDay) + .format('MMMM Do') + : 'Not Provided' + } + } + } + } + } + } + } + } + } + + setIndicatorPlantDetails(plan) { + for (const pasture of plan.pastures) { + if (pasture) { + for (const plantCommunity of pasture.plantCommunities) { + if (plantCommunity) { + for (const indicatorPlant of plantCommunity.indicatorPlants) { + indicatorPlant.name = indicatorPlant.plantSpecies.name === 'Other' + ? `${indicatorPlant.name} (Other)` + : indicatorPlant.plantSpecies.name + } + } + } + } + } + } + + setScheduleDetails(plan) { + for (const schedule of plan.grazingSchedules) { + if (schedule) { + schedule.crownTotalAUM = 0 + for (const entry of schedule.grazingScheduleEntries) { + if (entry) { + const pasture = plan.pastures.find((item) => { + return item.id === entry.pastureId + }) + entry.pasture = 'N/A' + if (pasture) { + entry.pasture = pasture.name + entry.graceDays = entry.graceDays || pasture.graceDays + } + entry.days = calcDateDiff(entry.dateOut, entry.dateIn, false) + entry.auFactor = entry.livestockType?.auFactor + entry.totalAUM = calcTotalAUMs(entry.livestockCount, entry.days, entry.auFactor) + entry.pldAUM = roundToSingleDecimalPlace(calcPldAUMs(entry.totalAUM, pasture.pldPercentage)) + entry.crownAUM = roundToSingleDecimalPlace(calcCrownAUMs(entry.totalAUM, entry.pldAUM)) + schedule.crownTotalAUM = schedule.crownTotalAUM + entry.crownAUM + } + } + if (plan.agreement.usage) { + const usage = plan.agreement.usage.find(usage => { return usage.year === schedule.year }) + schedule.authorizedAUM = usage.authorizedAum + schedule.percentUse = ((schedule.crownTotalAUM / schedule.authorizedAUM) * 100).toFixed(2) + } + } + } + } + + setInvasivePlantCheckListIsEmpty(plan) { + plan.invasivePlantChecklist.isEmpty = !(plan.invasivePlantChecklist.beginInUninfestedArea || + plan.invasivePlantChecklist.equipmentAndVehiclesParking || + plan.invasivePlantChecklist.revegetate || + plan.invasivePlantChecklist.undercarrigesInspected) + } +} \ No newline at end of file diff --git a/src/router/controllers_v1/PlanController.js b/src/router/controllers_v1/PlanController.js index e1a1ed83..4a632d22 100644 --- a/src/router/controllers_v1/PlanController.js +++ b/src/router/controllers_v1/PlanController.js @@ -48,7 +48,13 @@ export default class PlanController { checkRequiredFields( ['planId'], 'params', req, ); + const response = await PlanController.fetchPlan(planId, user) + return res.status(200) + .json(response) + .end(); + } + static async fetchPlan(planId, user,) { try { const [plan] = await Plan.findWithStatusExtension(db, { 'plan.id': planId }, ['id', 'desc']); if (!plan) { @@ -95,14 +101,11 @@ export default class PlanController { }; }), ); - - return res.status(200) - .json({ - ...plan, - grazingSchedules: mappedGrazingSchedules, - files: filteredFiles, - }) - .end(); + return { + ...plan, + grazingSchedules: mappedGrazingSchedules, + files: filteredFiles, + } } logger.info('loading last version'); @@ -122,8 +125,8 @@ export default class PlanController { logger.error(`Unable to fetch plan, error: ${error.message}`); throw errorWithCode(`There was a problem fetching the record. Error: ${error.message}`, error.code || 500); } - } + } /** * Create Plan * @param {*} req : express req object @@ -487,4 +490,6 @@ export default class PlanController { res.status(204) .end(); } -} + + +} \ No newline at end of file diff --git a/src/router/helpers/PDFHelper.js b/src/router/helpers/PDFHelper.js new file mode 100644 index 00000000..2cd934e1 --- /dev/null +++ b/src/router/helpers/PDFHelper.js @@ -0,0 +1,77 @@ + +import moment from 'moment' +import { DAYS_ON_THE_AVERAGE } from '../../constants' + +const shift = (number, precision) => { + const numArray = `${number}`.split('e') + return +`${numArray[0]}e${numArray[1] ? +numArray[1] + precision : precision}` +} + +const round = (number, precision) => + shift(Math.round(shift(number, +precision)), -precision) + +/** + * Round the float to 1 decimal + * + * @param {float} number + * @returns the rounded float number + */ +export const roundToSingleDecimalPlace = number => round(number, 1) + +/** + * + * @param {number} numberOfAnimals + * @param {number} totalDays + * @param {number} auFactor parameter provided from the livestock type + * @returns {float} the total AUMs + */ +export const calcTotalAUMs = (numberOfAnimals = 0, totalDays, auFactor = 0) => + (numberOfAnimals * totalDays * auFactor) / DAYS_ON_THE_AVERAGE + +/** + * Present user friendly string when getting null or undefined value + * + * @param {string | Date} first the string in the class Date form + * @param {string | Date} second the string in the class Date form + * @param {bool} isUserFriendly + * @returns {number | string} the number of days or 'N/P' + */ +export const calcDateDiff = (first, second, isUserFriendly) => { + if (first && second) { + return moment(first).diff(moment(second), 'days') + 1 + } + return isUserFriendly ? 'N/P' : 0 +} + +/** + * Calculate Private Land Deduction Animal Unit Month + * + * @param {number} totalAUMs + * @param {float} pasturePldPercent + * @returns {float} the pld AUMs + */ +export const calcPldAUMs = (totalAUMs, pasturePldPercent = 0) => + totalAUMs * pasturePldPercent + +/** + * Calculate Crown Animal Unit Month + * + * @param {number} totalAUMs + * @param {number} pldAUMs + * @returns {float} the crown AUMs + */ +export const calcCrownAUMs = (totalAUMs, pldAUMs) => totalAUMs - pldAUMs + +/** + * Calculate the total Crown Animal Unit Month + * + * @param {Array} entries grazing schedule entries + * @returns {float} the total crown AUMs + */ +export const calcCrownTotalAUMs = (entries = []) => { + const reducer = (accumulator, currentValue) => accumulator + currentValue + if (entries.length === 0) { + return 0 + } + return entries.map(entry => entry.crownAUMs).reduce(reducer) +} diff --git a/src/router/index.js b/src/router/index.js index 59ae6c65..5ddd6f78 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -35,7 +35,6 @@ import feedback from './routes_v1/feedback'; import files from './routes_v1/files'; import plan from './routes_v1/plan'; import reference from './routes_v1/reference'; -import report from './routes_v1/report'; import user from './routes_v1/user'; import version from './routes_v1/version'; import zone from './routes_v1/zone'; @@ -59,9 +58,8 @@ module.exports = (app) => { app.use('/api/v1/plan', plan); app.use('/api/v1/reference', reference); app.use('/api/v1/zone', zone); - app.use('/api/v1/report', report); app.use('/api/v1/user', user); app.use('/api/v1/feedback', feedback); app.use('/api/v1/files', files); app.use('/api/v1/emailTemplate', emailTemplate) -}; +}; \ No newline at end of file diff --git a/src/router/routes_v1/plan.js b/src/router/routes_v1/plan.js index d6eee117..2d154ac4 100644 --- a/src/router/routes_v1/plan.js +++ b/src/router/routes_v1/plan.js @@ -29,6 +29,7 @@ import { Router } from 'express'; * Controller for individual routes related to plan. */ import PlanController from '../controllers_v1/PlanController'; +import PDFGeneration from '../controllers_v1/PDFGeneratoin.js'; import PlanVersionController from '../controllers_v1/PlanVersionController'; import PlanStatusController from '../controllers_v1/PlanStatusController'; import PlanPastureController from '../controllers_v1/PlanPastureController'; @@ -213,4 +214,6 @@ router.put('/:planId?/management-consideration/:considerationId?', asyncMiddlewa // Delete a management consideration router.delete('/:planId?/management-consideration/:considerationId?', asyncMiddleware(PlanManagementConsiderationController.destroy)); +// Create a management consideration +router.get('/:planId?/PDF', asyncMiddleware(PDFGeneration.generatePDF)); module.exports = router; \ No newline at end of file diff --git a/src/router/routes_v1/report.js b/src/router/routes_v1/report.js deleted file mode 100644 index 7b6bbf02..00000000 --- a/src/router/routes_v1/report.js +++ /dev/null @@ -1,144 +0,0 @@ -// -// SecureImage -// -// Copyright © 2018 Province of British Columbia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Created by Jason Leach on 2018-01-18. -// - -/* eslint-env es6 */ - -'use strict'; - -import { asyncMiddleware, errorWithCode } from '@bcgov/nodejs-common-utils'; -import { Router } from 'express'; -import wkhtmltopdf from 'wkhtmltopdf'; -import config from '../../config'; -import { TEMPLATES } from '../../constants'; -import DataManager from '../../libs/db2'; -import { compile, loadTemplate, calcDateDiff, calcTotalAUMs, calcPldAUMs, calcCrownAUMs, roundToSingleDecimalPlace, calcCrownTotalAUMs, getPastureNames } from '../../libs/template'; - -const router = new Router(); -const dm2 = new DataManager(config); -const { - db, - Agreement, - Plan, -} = dm2; - -const userCanAccessAgreement = async (user, agreementId) => { - const [agreement] = await Agreement.find(db, { forest_file_id: agreementId }); - if (!agreement) { - throw errorWithCode('Unable to find the related agreement', 500); - } - - const can = await user.canAccessAgreement(db, agreement); - if (!can) { - throw errorWithCode('You do not access to this agreement', 403); - } -}; - -// -// PDF -// - -router.get('/:planId/', asyncMiddleware(async (req, res) => { - const { user, params } = req; - const { planId } = params; - - try { - const agreementId = await Plan.agreementForPlanId(db, planId); - if (!userCanAccessAgreement(user, agreementId)) { - throw errorWithCode('You do not access to this agreement', 403); - } - - const [agreement] = await Agreement.findWithAllRelations(db, { forest_file_id: agreementId }); - agreement.transformToV1(); - - const [plan] = await Plan.findWithStatusExtension(db, { 'plan.id': planId }, ['id', 'desc']); - await plan.eagerloadAllOneToMany(); - - const { pastures, grazingSchedules: gss, ministerIssues: mis } = plan || []; - const { zone } = agreement || {}; - const { user: staff } = zone || {}; - const { givenName, familyName } = staff; - user.name = givenName && familyName && `${givenName} ${familyName}`; - - const grazingSchedules = gss.map((schedule) => { - const { grazingScheduleEntries: gse, year } = schedule; - const grazingScheduleEntries = gse && gse.map((entry) => { - const { - pastureId, - livestockType, - livestockCount, - dateIn, - dateOut, - } = entry; - const days = calcDateDiff(dateOut, dateIn, false); - const pasture = pastures.find(p => p.id === pastureId); - const graceDays = pasture && pasture.graceDays; - const pldPercent = pasture && pasture.pldPercent; - const auFactor = livestockType && livestockType.auFactor; - - const totalAUMs = calcTotalAUMs(livestockCount, days, auFactor); - const pldAUMs = roundToSingleDecimalPlace(calcPldAUMs(totalAUMs, pldPercent)); - const crownAUMs = roundToSingleDecimalPlace(calcCrownAUMs(totalAUMs, pldAUMs)); - return { - ...entry, - pasture, - graceDays, - days, - pldAUMs, - crownAUMs, - }; - }); - const crownTotalAUMs = roundToSingleDecimalPlace(calcCrownTotalAUMs(grazingScheduleEntries)); - const yearUsage = agreement.usage.find(u => u.year === year); - const authorizedAUMs = yearUsage && yearUsage.authorizedAum; - return { - ...schedule, - grazingScheduleEntries, - crownTotalAUMs, - authorizedAUMs, - }; - }); - - const ministerIssues = mis.map((mi) => { - const ministerIssue = { ...mi }; - ministerIssue.pastureNames = getPastureNames(ministerIssue.pastures, pastures); - ministerIssue.actionsExist = ministerIssue.ministerIssueActions - && (ministerIssue.ministerIssueActions.length > 0); - - return ministerIssue; - }); - - const template = await loadTemplate(TEMPLATES.RANGE_USE_PLAN); - const html = await compile(template, { - user, - agreement, - plan, - zone, - pastures, - grazingSchedules, - ministerIssues, - }); - - return wkhtmltopdf(html).pipe(res); - } catch (err) { - throw err; - } -})); - -module.exports = router; \ No newline at end of file