diff --git a/src/client/pages/Section/DataTable/Table/RowData/Cell/Calculated/Calculated.tsx b/src/client/pages/Section/DataTable/Table/RowData/Cell/Calculated/Calculated.tsx index b3e3298845..81ed900da5 100644 --- a/src/client/pages/Section/DataTable/Table/RowData/Cell/Calculated/Calculated.tsx +++ b/src/client/pages/Section/DataTable/Table/RowData/Cell/Calculated/Calculated.tsx @@ -8,7 +8,9 @@ import { PropsCell } from '../props' const Calculated: React.FC = (props) => { const { nodeValue, row } = props - const value = !Objects.isEmpty(nodeValue.raw) ? Numbers.format(nodeValue.raw, row.props?.format?.integer ? 0 : 2) : '' + const value = !Objects.isEmpty(nodeValue?.raw) + ? Numbers.format(nodeValue.raw, row.props?.format?.integer ? 0 : 2) + : '' return
{value}
} diff --git a/src/i18n/resources/en/panEuropean/panEuropean.json b/src/i18n/resources/en/panEuropean/panEuropean.json index 8d55afa08c..f894054634 100644 --- a/src/i18n/resources/en/panEuropean/panEuropean.json +++ b/src/i18n/resources/en/panEuropean/panEuropean.json @@ -1013,10 +1013,11 @@ "growingStockPerHa": "Growing stock per hectar (1.2a/1.1a) - {{year}}", "netAnnualIncrementPerHa": "Net annual increment per ha (3.1/1.1a) - {{year}}", "reasonabilityCheck": "Reasonableness check", - "roundwoodRemovalAsPctOfGrowingStockSupply": "Roundwood removals as percent of growing stock on Forest available for wood supply (3.2 / 1.2a) - {{year}}", - "roundwoodRemovalAsPctOfGrowingStockTotal": "Roundwood removals as percent of growing stock on Forest and Other wooded land (3.2 / 1.2a) - {{year}}", + "roundwoodRemovalAsPctOfGrowingStockSupply": "Roundwood removals as percent of Growing stock on Forest available for wood supply (3.2 / 1.2a)", + "roundwoodRemovalAsPctOfGrowingStockTotal": "Roundwood removals as percent of Growing stock on Forest and Other wooded land (3.2 / 1.2a)", "soilCarbon": "Soil carbon (1.4a/1.1a) - {{year}}", "totalWithDamageOverTotal": "Area with damage as a share of Total forest area (2.4/1.1a) - {{year}}", + "unit": "Unit", "variable": "Variable - Year" }, "recreationFacilities": { diff --git a/src/test/migrations/steps/20250222015809-step-panEuropean-2025-reasonability-check-3-2-fix.ts b/src/test/migrations/steps/20250222015809-step-panEuropean-2025-reasonability-check-3-2-fix.ts new file mode 100644 index 0000000000..9d878b17ca --- /dev/null +++ b/src/test/migrations/steps/20250222015809-step-panEuropean-2025-reasonability-check-3-2-fix.ts @@ -0,0 +1,363 @@ +import { AssessmentNames, ColProps, ColType, RowProps, RowType } from 'meta/assessment' + +import { AssessmentController } from 'server/controller/assessment' +import { BaseProtocol, Schemas } from 'server/db' +import { ColRepository } from 'server/repository/assessment/col' +import { RowRepository } from 'server/repository/assessment/row' +import { TableRepository } from 'server/repository/assessment/table' + +const years = [2020, 2015, 2010, 2005, 2000, 1990] + +const getRoundwoodSupplyYearsCols = (year: number, cycleUuid: string): Array => { + let index = years.indexOf(year) + if (year === 2020) index += 2 // Offset var name and unit '%' + return [ + { + // year col + colType: ColType.placeholder, + index, + labels: { + [cycleUuid]: { + label: year.toString(), + }, + }, + style: { + [cycleUuid]: { + justifyContent: 'center', + }, + }, + }, + { + // Forest col + colType: ColType.placeholder, + index: index + 1, + }, + { + // FAWS col + colName: 'FAWS', + colType: ColType.calculated, + calculateFn: { + [cycleUuid]: `table_3_2._of_which_forest_available_for_wood_supply_${year}.net_annual_increment / (table_1_2a._of_which_available_for_wood_supply_${year}.total * 1000)`, + }, + index: index + 2, + }, + { + // OWL col + colType: ColType.placeholder, + index: index + 3, + }, + { + // FOWL col + colType: ColType.placeholder, + index: index + 4, + }, + ] +} + +const getRoundwoodTotalYearsCols = (year: number, cycleUuid: string): Array => { + let index = years.indexOf(year) + if (year === 2020) index += 2 // Offset var name and unit '%' + return [ + { + // year col + colType: ColType.placeholder, + index, + labels: { + [cycleUuid]: { + label: year.toString(), + }, + }, + style: { + [cycleUuid]: { + justifyContent: 'center', + }, + }, + }, + { + // Forest col + colType: ColType.placeholder, + index: index + 1, + }, + { + // FAWS col + colType: ColType.placeholder, + index: index + 2, + }, + { + // OWL col + colType: ColType.placeholder, + index: index + 3, + }, + { + // FOWL col + colType: ColType.calculated, + // TODO: add calculateFn + // colName: '', + // calculateFn: { + // [cycleUuid]: '', + // }, + index: index + 4, + }, + ] +} + +export default async (client: BaseProtocol) => { + const { assessment, cycle } = await AssessmentController.getOneWithCycle( + { assessmentName: AssessmentNames.panEuropean, cycleName: '2025' }, + client + ) + + const cycleUuid = cycle.uuid + const schemaAssessment = Schemas.getName(assessment) + const tableName = 'reasonability_check_3_2' + const table = await TableRepository.getOne({ assessment, cycle, tableName }) + + // 1. Add new grid template columns & update column names + const gridTemplateColumns = '250px repeat(2, min-content) repeat(4, minmax(min-content, 1fr))' + + await client.query( + `update ${schemaAssessment}.table + set props = jsonb_set( + jsonb_set( + jsonb_set( + props, + '{style}', + coalesce(props->'style', '{}'::jsonb), + true + ), + '{style,${cycleUuid}}', + $1::jsonb, + true + ), + '{columnNames,${cycleUuid}}', + $2::jsonb + ) + where props->>'name' = '${tableName}' + `, + [JSON.stringify({ gridTemplateColumns }), JSON.stringify(['unit', 'year', 'forest', 'FAWS', 'OWL', 'FOWL'])] + ) + + // 2. Delete current cols + await client.query( + `delete from ${schemaAssessment}.col + where id in ( + select c.id + from ${schemaAssessment}.col c + join ${schemaAssessment}.row r on c.row_id = r.id + join ${schemaAssessment}.table t on r.table_id = t.id + where t.props->>'name' = '${tableName}' + );`, + [] + ) + // 3. Delete current rows + await client.query( + `delete from ${schemaAssessment}.row + where id in ( + select r.id + from ${schemaAssessment}.row r + join ${schemaAssessment}.table t on r.table_id = t.id + where t.props->>'name' = '${tableName}' + );`, + [] + ) + + // 4. Insert new rows and cols for Roundwood Supply and Roundwood Total + const headerRowProps: RowProps = { + index: 'header_0', + type: RowType.header, + } + + const headerCols: Array = [ + { + colType: ColType.header, + index: 0, + labels: { + [cycle.uuid]: { + key: 'panEuropean.variable', + }, + }, + }, + { + colType: ColType.header, + index: 1, + labels: { + [cycle.uuid]: { + key: 'panEuropean.reasonabilityChecks.unit', + }, + }, + }, + { + colType: ColType.header, + index: 2, + labels: { + [cycle.uuid]: { + key: 'common.year', + }, + }, + }, + { + colType: ColType.header, + index: 3, + labels: { + [cycle.uuid]: { + key: 'panEuropean.reasonabilityChecks.forest', + }, + }, + }, + + { + colType: ColType.header, + index: 4, + labels: { + [cycle.uuid]: { + key: 'panEuropean.reasonabilityChecks.FAWS', + }, + }, + }, + + { + colType: ColType.header, + index: 5, + labels: { + [cycle.uuid]: { + key: 'panEuropean.reasonabilityChecks.OWL', + }, + }, + }, + { + colType: ColType.header, + index: 6, + labels: { + [cycle.uuid]: { + key: 'panEuropean.reasonabilityChecks.FOWL', + }, + }, + }, + ] + + const headerRow = await RowRepository.create({ assessment, cycles: [cycle], rowProps: headerRowProps, table }, client) + headerCols.forEach(async (colProps) => { + await ColRepository.create({ assessment, cycles: [cycle], row: headerRow, colProps }, client) + }) + + const roundwoodSupplyRows: Array = years.map((year, index) => { + return { + index, + label: { + key: 'panEuropean.reasonabilityChecks.roundwoodRemovalAsPctOfGrowingStockSupply', + }, + type: RowType.data, + variableName: `roundwoodRemovalAsPctOfGrowingStockSupply_${year}`, + } + }) + + const roundwoodSupplyVarAndUnitCols: Array = [ + { + colType: ColType.header, + index: 0, + labels: { + [cycle.uuid]: { + key: 'panEuropean.reasonabilityChecks.roundwoodRemovalAsPctOfGrowingStockSupply', + }, + }, + style: { + [cycle.uuid]: { + colSpan: 1, + rowSpan: 6, + }, + }, + }, + { + colType: ColType.placeholder, + index: 1, + labels: { + [cycle.uuid]: { + label: '%', + }, + }, + style: { + [cycle.uuid]: { + colSpan: 1, + justifyContent: 'center', + rowSpan: 6, + }, + }, + }, + ] + + const roundwoodTotalRows: Array = years.map((year, index) => { + return { + index, + label: { + key: 'panEuropean.reasonabilityChecks.roundwoodRemovalAsPctOfGrowingStockTotal', + }, + type: RowType.data, + variableName: `roundwoodRemovalAsPctOfGrowingStockTotal_${year}`, + } + }) + + const roundwoodTotalVarAndUnitCols: Array = [ + { + colType: ColType.header, + index: 0, + labels: { + [cycle.uuid]: { + key: 'panEuropean.reasonabilityChecks.roundwoodRemovalAsPctOfGrowingStockTotal', + }, + }, + style: { + [cycle.uuid]: { + colSpan: 1, + rowSpan: 6, + }, + }, + }, + { + colType: ColType.placeholder, + index: 1, + labels: { + [cycle.uuid]: { + label: '%', + }, + }, + style: { + [cycle.uuid]: { + colSpan: 1, + justifyContent: 'center', + rowSpan: 6, + }, + }, + }, + ] + + roundwoodSupplyRows.forEach(async (rowProps) => { + const row = await RowRepository.create({ assessment, cycles: [cycle], rowProps, table }, client) + if (rowProps.index === 0) { + roundwoodSupplyVarAndUnitCols.forEach(async (colProps) => { + await ColRepository.create({ assessment, cycles: [cycle], row, colProps }, client) + }) + } + const year = years[Number(rowProps.index)] + const yearCols = getRoundwoodSupplyYearsCols(year, cycle.uuid) + yearCols.forEach(async (colProps) => { + await ColRepository.create({ assessment, cycles: [cycle], row, colProps }, client) + }) + }) + + roundwoodTotalRows.forEach(async (rowProps) => { + const row = await RowRepository.create({ assessment, cycles: [cycle], rowProps, table }, client) + if (rowProps.index === 0) { + roundwoodTotalVarAndUnitCols.forEach(async (colProps) => { + await ColRepository.create({ assessment, cycles: [cycle], row, colProps }, client) + }) + } + const year = years[Number(rowProps.index)] + const yearCols = getRoundwoodTotalYearsCols(year, cycle.uuid) + yearCols.forEach(async (colProps) => { + await ColRepository.create({ assessment, cycles: [cycle], row, colProps }, client) + }) + }) + + // 5. Generate cache + await AssessmentController.generateMetaCache(client) +}