diff --git a/apps/entity-service/src/trees/dto/establishment-trees.dto.ts b/apps/entity-service/src/trees/dto/establishment-trees.dto.ts index 036e5de..29ccfb6 100644 --- a/apps/entity-service/src/trees/dto/establishment-trees.dto.ts +++ b/apps/entity-service/src/trees/dto/establishment-trees.dto.ts @@ -1,6 +1,7 @@ import { JsonApiAttributes } from "@terramatch-microservices/common/dto/json-api-attributes"; import { JsonApiDto } from "@terramatch-microservices/common/decorators"; import { ApiProperty } from "@nestjs/swagger"; +import { Dictionary } from "lodash"; export class PreviousPlantingCountDto { @ApiProperty({ nullable: true, description: "Taxonomic ID for this tree species row" }) @@ -17,17 +18,31 @@ export class PreviousPlantingCountDto { @JsonApiDto({ type: "establishmentTrees", id: "string" }) export class EstablishmentsTreesDto extends JsonApiAttributes { @ApiProperty({ - type: [String], - description: "The species that were specified at the establishment of the parent entity." + type: "object", + additionalProperties: { type: "array", items: { type: "string" } }, + description: "The species that were specified at the establishment of the parent entity keyed by collection", + example: { "tree-planted": ["Aster Peraliens", "Circium carniolicum"], "non-tree": ["Coffee"] } }) - establishmentTrees: string[]; + establishmentTrees: Dictionary; @ApiProperty({ type: "object", - additionalProperties: { $ref: "#/components/schemas/PreviousPlantingCountDto" }, + additionalProperties: { + type: "object", + additionalProperties: { $ref: "#/components/schemas/PreviousPlantingCountDto" } + }, nullable: true, - description: "If the entity in this request is a report, the sum totals of previous planting by species.", - example: { "Aster persaliens": { amount: 256 }, "Cirsium carniolicum": { taxonId: "wfo-0000130112", amount: 1024 } } + description: + "If the entity in this request is a report, the sum totals of previous planting by species by collection.", + example: { + "tree-planted": { + "Aster persaliens": { amount: 256 }, + "Cirsium carniolicum": { taxonId: "wfo-0000130112", amount: 1024 } + }, + "non-tree": { + Coffee: { amount: 2048 } + } + } }) - previousPlantingCounts?: Record; + previousPlantingCounts?: Dictionary>; } diff --git a/apps/entity-service/src/trees/tree.service.spec.ts b/apps/entity-service/src/trees/tree.service.spec.ts index faf9cdf..2de68ec 100644 --- a/apps/entity-service/src/trees/tree.service.spec.ts +++ b/apps/entity-service/src/trees/tree.service.spec.ts @@ -74,27 +74,42 @@ describe("TreeService", () => { const nursery = await NurseryFactory.create({ projectId: project.id }); const nurseryReport = await NurseryReportFactory.create({ nurseryId: nursery.id }); - const projectTrees = (await TreeSpeciesFactory.forProject.createMany(3, { speciesableId: project.id })) + const projectTreesPlanted = ( + await TreeSpeciesFactory.forProjectTreePlanted.createMany(3, { speciesableId: project.id }) + ) .map(({ name }) => name) .sort(); - const siteTrees = (await TreeSpeciesFactory.forSite.createMany(2, { speciesableId: site.id })) + const siteTreesPlanted = (await TreeSpeciesFactory.forSiteTreePlanted.createMany(2, { speciesableId: site.id })) .map(({ name }) => name) .sort(); - const nurseryTrees = (await TreeSpeciesFactory.forNursery.createMany(4, { speciesableId: nursery.id })) + const siteNonTrees = (await TreeSpeciesFactory.forSiteNonTree.createMany(3, { speciesableId: site.id })) + .map(({ name }) => name) + .sort(); + const nurserySeedlings = ( + await TreeSpeciesFactory.forNurserySeedling.createMany(4, { speciesableId: nursery.id }) + ) .map(({ name }) => name) .sort(); let result = await service.getEstablishmentTrees("project-reports", projectReport.uuid); - expect(result.sort()).toEqual(projectTrees); + console.log("result", result); + expect(Object.keys(result).length).toBe(1); + expect(result["tree-planted"].sort()).toEqual(projectTreesPlanted); result = await service.getEstablishmentTrees("sites", site.uuid); - expect(result.sort()).toEqual(projectTrees); + expect(Object.keys(result).length).toBe(1); + expect(result["tree-planted"].sort()).toEqual(projectTreesPlanted); result = await service.getEstablishmentTrees("nurseries", nursery.uuid); - expect(result.sort()).toEqual(projectTrees); + expect(Object.keys(result).length).toBe(1); + expect(result["tree-planted"].sort()).toEqual(projectTreesPlanted); result = await service.getEstablishmentTrees("site-reports", siteReport.uuid); - expect(result.sort()).toEqual(uniq([...siteTrees, ...projectTrees]).sort()); + expect(Object.keys(result).length).toBe(2); + expect(result["tree-planted"].sort()).toEqual(uniq([...siteTreesPlanted, ...projectTreesPlanted]).sort()); + expect(result["non-tree"].sort()).toEqual(siteNonTrees); result = await service.getEstablishmentTrees("nursery-reports", nurseryReport.uuid); - expect(result.sort()).toEqual(uniq([...nurseryTrees, ...projectTrees]).sort()); + expect(Object.keys(result).length).toBe(2); + expect(result["tree-planted"].sort()).toEqual(projectTreesPlanted); + expect(result["nursery-seedling"].sort()).toEqual(nurserySeedlings); }); it("throws with bad inputs to establishment trees", async () => { @@ -137,41 +152,55 @@ describe("TreeService", () => { amount: (counts[tree.name]?.amount ?? 0) + (tree.amount ?? 0) } }); - const projectReportTrees = await TreeSpeciesFactory.forProjectReport.createMany(3, { + const projectReportTreesPlanted = await TreeSpeciesFactory.forProjectReportTreePlanted.createMany(3, { speciesableId: projectReport1.id }); - projectReportTrees.push( - await TreeSpeciesFactory.forProjectReport.create({ + projectReportTreesPlanted.push( + await TreeSpeciesFactory.forProjectReportTreePlanted.create({ speciesableId: projectReport1.id, taxonId: "wfo-projectreporttree" }) ); let result = await service.getPreviousPlanting("project-reports", projectReport2.uuid); - expect(result).toMatchObject(projectReportTrees.reduce(reduceTreeCounts, {})); + expect(Object.keys(result)).toMatchObject(["tree-planted"]); + expect(result).toMatchObject({ "tree-planted": projectReportTreesPlanted.reduce(reduceTreeCounts, {}) }); - const siteReport1Trees = await TreeSpeciesFactory.forSiteReport.createMany(3, { speciesableId: siteReport1.id }); - siteReport1Trees.push( - await TreeSpeciesFactory.forSiteReport.create({ + const siteReport1TreesPlanted = await TreeSpeciesFactory.forSiteReportTreePlanted.createMany(3, { + speciesableId: siteReport1.id + }); + siteReport1TreesPlanted.push( + await TreeSpeciesFactory.forSiteReportTreePlanted.create({ speciesableId: siteReport1.id, taxonId: "wfo-sitereporttree" }) ); - const siteReport2Trees = await TreeSpeciesFactory.forSiteReport.createMany(3, { speciesableId: siteReport2.id }); + const siteReport2TreesPlanted = await TreeSpeciesFactory.forSiteReportTreePlanted.createMany(3, { + speciesableId: siteReport2.id + }); + const siteReport2NonTrees = await TreeSpeciesFactory.forSiteReportNonTree.createMany(2, { + speciesableId: siteReport2.id + }); result = await service.getPreviousPlanting("site-reports", siteReport1.uuid); expect(result).toMatchObject({}); result = await service.getPreviousPlanting("site-reports", siteReport2.uuid); - const siteReport1TreesReduced = siteReport1Trees.reduce(reduceTreeCounts, {}); - expect(result).toMatchObject(siteReport1TreesReduced); + const siteReport1TreesPlantedReduced = siteReport1TreesPlanted.reduce(reduceTreeCounts, {}); + expect(Object.keys(result)).toMatchObject(["tree-planted"]); + expect(result).toMatchObject({ "tree-planted": siteReport1TreesPlantedReduced }); result = await service.getPreviousPlanting("site-reports", siteReport3.uuid); - expect(result).toMatchObject(siteReport2Trees.reduce(reduceTreeCounts, siteReport1TreesReduced)); + expect(Object.keys(result).sort()).toMatchObject(["non-tree", "tree-planted"]); + expect(result).toMatchObject({ + "tree-planted": siteReport2TreesPlanted.reduce(reduceTreeCounts, siteReport1TreesPlantedReduced), + "non-tree": siteReport2NonTrees.reduce(reduceTreeCounts, {}) + }); result = await service.getPreviousPlanting("nursery-reports", nurseryReport2.uuid); expect(result).toMatchObject({}); - const nurseryReportTrees = await TreeSpeciesFactory.forNurseryReport.createMany(5, { + const nurseryReportSeedlings = await TreeSpeciesFactory.forNurseryReportSeedling.createMany(5, { speciesableId: nurseryReport1.id }); result = await service.getPreviousPlanting("nursery-reports", nurseryReport2.uuid); - expect(result).toMatchObject(nurseryReportTrees.reduce(reduceTreeCounts, {})); + expect(Object.keys(result)).toMatchObject(["nursery-seedling"]); + expect(result).toMatchObject({ "nursery-seedling": nurseryReportSeedlings.reduce(reduceTreeCounts, {}) }); }); it("handles bad input to get previous planting with undefined or an exception", async () => { diff --git a/apps/entity-service/src/trees/tree.service.ts b/apps/entity-service/src/trees/tree.service.ts index 55ebc06..9e2a1f3 100644 --- a/apps/entity-service/src/trees/tree.service.ts +++ b/apps/entity-service/src/trees/tree.service.ts @@ -5,11 +5,12 @@ import { ProjectReport, Site, SiteReport, + TreeSpecies, TreeSpeciesResearch } from "@terramatch-microservices/database/entities"; -import { Op } from "sequelize"; +import { Op, WhereOptions } from "sequelize"; import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; -import { filter, flatten, uniq } from "lodash"; +import { Dictionary, filter, flatten, flattenDeep, groupBy, uniq } from "lodash"; import { PreviousPlantingCountDto } from "./dto/establishment-trees.dto"; export const ESTABLISHMENT_REPORTS = ["project-reports", "site-reports", "nursery-reports"] as const; @@ -18,8 +19,23 @@ export type EstablishmentReport = (typeof ESTABLISHMENT_REPORTS)[number]; export const ESTABLISHMENT_ENTITIES = ["sites", "nurseries", ...ESTABLISHMENT_REPORTS] as const; export type EstablishmentEntity = (typeof ESTABLISHMENT_ENTITIES)[number]; +type TreeReportModelType = typeof ProjectReport | typeof SiteReport | typeof NurseryReport; +type TreeModelType = TreeReportModelType | typeof Project | typeof Site | typeof Nursery; + const isReport = (type: EstablishmentEntity): type is EstablishmentReport => type.endsWith("-reports"); +const treeAssociations = (model: TreeModelType, attributes: string[], where?: WhereOptions) => + model.TREE_ASSOCIATIONS.map(association => ({ required: false, association, attributes, where })); + +const uniqueTreeNames = (trees: Dictionary) => + Object.keys(trees).reduce( + (dict, collection) => ({ + ...dict, + [collection]: uniq(filter(trees[collection].map(({ name }) => name))) + }), + {} as Dictionary + ); + @Injectable() export class TreeService { async searchScientificNames(search: string) { @@ -39,27 +55,28 @@ export class TreeService { ).map(({ taxonId, scientificName }) => ({ taxonId, scientificName })); } - async getEstablishmentTrees(entity: EstablishmentEntity, uuid: string): Promise { + async getEstablishmentTrees(entity: EstablishmentEntity, uuid: string): Promise> { if (entity === "site-reports" || entity === "nursery-reports") { // For site and nursery reports, we fetch both the establishment species on the parent entity // and on the Project + const parentModel = entity === "site-reports" ? Site : Nursery; const whereOptions = { where: { uuid }, attributes: [], include: [ { - model: entity === "site-reports" ? Site : Nursery, + model: parentModel, // This id isn't necessary for the data we want to fetch, but sequelize requires it for // the nested includes attributes: ["id"], include: [ - { association: "treeSpecies", attributes: ["name"] }, + ...treeAssociations(parentModel, ["name", "collection"]), { model: Project, // This id isn't necessary for the data we want to fetch, but sequelize requires it for // the nested includes attributes: ["id"], - include: [{ association: "treeSpecies", attributes: ["name"] }] + include: treeAssociations(Project, ["name", "collection"]) } ] } @@ -72,9 +89,15 @@ export class TreeService { if (report == null) throw new NotFoundException(); const parent = report instanceof SiteReport ? report.site : report.nursery; - const parentTrees = parent.treeSpecies.map(({ name }) => name); - const projectTrees = parent.project.treeSpecies.map(({ name }) => name); - return uniq(filter([...parentTrees, ...projectTrees])); + const trees = groupBy( + flattenDeep([ + parentModel.TREE_ASSOCIATIONS.map(association => parent[association] as TreeSpecies[]), + Project.TREE_ASSOCIATIONS.map(association => parent.project[association] as TreeSpecies[]) + ]), + "collection" + ); + + return uniqueTreeNames(trees); } else if (["sites", "nurseries", "project-reports"].includes(entity)) { // for these we simply pull the project's trees const whereOptions = { @@ -86,7 +109,7 @@ export class TreeService { // This id isn't necessary for the data we want to fetch, but sequelize requires it for // the nested includes attributes: ["id"], - include: [{ association: "treeSpecies", attributes: ["name"] }] + include: treeAssociations(Project, ["name", "collection"]) } ] }; @@ -98,7 +121,9 @@ export class TreeService { : ProjectReport.findOne(whereOptions)); if (entityModel == null) throw new NotFoundException(); - return filter(entityModel.project.treeSpecies.map(({ name }) => name)); + return uniqueTreeNames( + groupBy(flatten(Project.TREE_ASSOCIATIONS.map(association => entityModel.project[association])), "collection") + ); } else { throw new BadRequestException(`Entity type not supported: [${entity}]`); } @@ -107,73 +132,66 @@ export class TreeService { async getPreviousPlanting( entity: EstablishmentEntity, uuid: string - ): Promise> { + ): Promise>> { if (!isReport(entity)) return undefined; - const treeReportWhere = (parentAttribute: string, report: ProjectReport | SiteReport | NurseryReport) => ({ - attributes: [], - where: { - [parentAttribute]: report[parentAttribute], - dueAt: { [Op.lt]: report.dueAt } - }, - include: [ - { - association: "treeSpecies", - attributes: ["taxonId", "name", "amount"], - where: { amount: { [Op.gt]: 0 } } - } - ] - }); - - let records: (SiteReport | ProjectReport | NurseryReport)[]; + let model: TreeReportModelType; switch (entity) { - case "project-reports": { - const report = await ProjectReport.findOne({ - where: { uuid }, - attributes: ["dueAt", "projectId"] - }); - if (report == null) throw new NotFoundException(); - - records = await ProjectReport.findAll(treeReportWhere("projectId", report)); + case "project-reports": + model = ProjectReport; break; - } - case "site-reports": { - const report = await SiteReport.findOne({ - where: { uuid }, - attributes: ["dueAt", "siteId"] - }); - if (report == null) throw new NotFoundException(); - - records = await SiteReport.findAll(treeReportWhere("siteId", report)); + case "site-reports": + model = SiteReport; break; - } - - case "nursery-reports": { - const report = await NurseryReport.findOne({ - where: { uuid }, - attributes: ["dueAt", "nurseryId"] - }); - if (report == null) throw new NotFoundException(); - records = await NurseryReport.findAll(treeReportWhere("nurseryId", report)); + case "nursery-reports": + model = NurseryReport; break; - } default: throw new BadRequestException(); } - const trees = flatten(records.map(({ treeSpecies }) => treeSpecies)); - return trees.reduce>( - (counts, tree) => ({ - ...counts, - [tree.name]: { - taxonId: counts[tree.name]?.taxonId ?? tree.taxonId, - amount: (counts[tree.name]?.amount ?? 0) + (tree.amount ?? 0) - } + // @ts-expect-error Can't narrow the union TreeReportModelType automatically + const report: InstanceType = await model.findOne({ + where: { uuid }, + attributes: ["dueAt", model.PARENT_ID] + }); + if (report == null) throw new NotFoundException(); + + // @ts-expect-error Can't narrow the union TreeReportModelType automatically + const records: InstanceType[] = await model.findAll({ + attributes: [], + where: { + [model.PARENT_ID]: report[model.PARENT_ID], + dueAt: { [Op.lt]: report.dueAt } + }, + include: treeAssociations(model, ["taxonId", "name", "collection", "amount"], { amount: { [Op.gt]: 0 } }) + }); + + const trees = groupBy( + flattenDeep( + records.map(record => model.TREE_ASSOCIATIONS.map(association => record[association] as TreeSpecies[])) + ), + "collection" + ); + + return Object.keys(trees).reduce( + (dict, collection) => ({ + ...dict, + [collection]: trees[collection].reduce( + (counts, tree) => ({ + ...counts, + [tree.name]: { + taxonId: counts[tree.name]?.taxonId ?? tree.taxonId, + amount: (counts[tree.name]?.amount ?? 0) + (tree.amount ?? 0) + } + }), + {} as Dictionary + ) }), - {} + {} as Dictionary> ); } } diff --git a/apps/entity-service/src/trees/trees.conroller.spec.ts b/apps/entity-service/src/trees/trees.conroller.spec.ts index 4f4c169..ad1e327 100644 --- a/apps/entity-service/src/trees/trees.conroller.spec.ts +++ b/apps/entity-service/src/trees/trees.conroller.spec.ts @@ -47,10 +47,12 @@ describe("TreesController", () => { describe("getEstablishmentData", () => { it("should return establishment data", async () => { const stubData = { - Coffee: { amount: 123 }, - Banana: { amount: 75, taxonId: "wfo-faketaxonid" } + "non-tree": { + Coffee: { amount: 123 }, + Banana: { amount: 75, taxonId: "wfo-faketaxonid" } + } }; - treeService.getEstablishmentTrees.mockResolvedValue(Object.keys(stubData)); + treeService.getEstablishmentTrees.mockResolvedValue({ "non-tree": ["Coffee", "Banana"] }); treeService.getPreviousPlanting.mockResolvedValue(stubData); const result = await controller.getEstablishmentData({ entity: "site-reports", @@ -60,7 +62,7 @@ describe("TreesController", () => { expect(treeService.getPreviousPlanting).toHaveBeenCalledWith("site-reports", "fakeuuid"); const resource = result.data as Resource; expect(resource.id).toBe("site-reports|fakeuuid"); - expect(resource.attributes.establishmentTrees).toMatchObject(Object.keys(stubData)); + expect(resource.attributes.establishmentTrees).toMatchObject({ "non-tree": ["Coffee", "Banana"] }); expect(resource.attributes.previousPlantingCounts).toMatchObject(stubData); }); }); diff --git a/apps/research-service/src/site-polygons/site-polygon-query.builder.ts b/apps/research-service/src/site-polygons/site-polygon-query.builder.ts index f0b7c76..5b10c72 100644 --- a/apps/research-service/src/site-polygons/site-polygon-query.builder.ts +++ b/apps/research-service/src/site-polygons/site-polygon-query.builder.ts @@ -43,10 +43,10 @@ export class SitePolygonQueryBuilder { private siteJoin: IncludeOptions = { model: Site, include: [ - { association: "treeSpecies", attributes: ["name", "amount"] }, + { association: "treesPlanted", attributes: ["name", "amount"] }, { model: SiteReport, - include: [{ association: "treeSpecies", attributes: ["name", "amount"] }], + include: [{ association: "treesPlanted", attributes: ["name", "amount"] }], attributes: ["dueAt", "submittedAt"] } ], diff --git a/apps/research-service/src/site-polygons/site-polygons.service.spec.ts b/apps/research-service/src/site-polygons/site-polygons.service.spec.ts index aabd5e0..0b9b5c2 100644 --- a/apps/research-service/src/site-polygons/site-polygons.service.spec.ts +++ b/apps/research-service/src/site-polygons/site-polygons.service.spec.ts @@ -63,9 +63,9 @@ describe("SitePolygonsService", () => { it("should return all establishment tree species", async () => { const sitePolygon = await SitePolygonFactory.create(); const site = await sitePolygon.loadSite(); - await TreeSpeciesFactory.forSite.createMany(3, { speciesableId: site.id }); + await TreeSpeciesFactory.forSiteTreePlanted.createMany(3, { speciesableId: site.id }); - const treeSpecies = await site.loadTreeSpecies(); + const treeSpecies = await site.loadTreesPlanted(); const treeSpeciesDto = await service.getEstablishmentTreeSpecies(sitePolygon); expect(treeSpeciesDto.length).toBe(treeSpecies.length); @@ -82,15 +82,23 @@ describe("SitePolygonsService", () => { const site = await sitePolygon.loadSite(); await SiteReportFactory.createMany(2, { siteId: site.id }); const siteReports = await site.loadReports(); - await TreeSpeciesFactory.forSiteReport.createMany(3, { speciesableId: siteReports[0].id }); - await TreeSpeciesFactory.forSiteReport.createMany(5, { speciesableId: siteReports[1].id }); + await TreeSpeciesFactory.forSiteReportTreePlanted.createMany(3, { speciesableId: siteReports[0].id }); + await TreeSpeciesFactory.forSiteReportTreePlanted.createMany(5, { speciesableId: siteReports[1].id }); - await siteReports[0].loadTreeSpecies(); - await siteReports[1].loadTreeSpecies(); + await siteReports[0].loadTreesPlanted(); + await siteReports[1].loadTreesPlanted(); const reportingPeriodsDto = await service.getReportingPeriods(sitePolygon); expect(reportingPeriodsDto.length).toBe(siteReports.length); - expect(siteReports[0]).toMatchObject(reportingPeriodsDto[0]); - expect(siteReports[1]).toMatchObject(reportingPeriodsDto[1]); + expect({ + dueAt: siteReports[0].dueAt, + submittedAt: siteReports[0].submittedAt, + treeSpecies: siteReports[0].treesPlanted + }).toMatchObject(reportingPeriodsDto[0]); + expect({ + dueAt: siteReports[1].dueAt, + submittedAt: siteReports[1].submittedAt, + treeSpecies: siteReports[1].treesPlanted + }).toMatchObject(reportingPeriodsDto[1]); }); it("should return all polygons when there are fewer than the page size", async () => { diff --git a/apps/research-service/src/site-polygons/site-polygons.service.ts b/apps/research-service/src/site-polygons/site-polygons.service.ts index 28752e3..6f45ca4 100644 --- a/apps/research-service/src/site-polygons/site-polygons.service.ts +++ b/apps/research-service/src/site-polygons/site-polygons.service.ts @@ -33,7 +33,7 @@ export class SitePolygonsService { const site = await sitePolygon.loadSite(); if (site == null) return []; - return (await site.loadTreeSpecies()).map(({ name, amount }) => ({ name, amount })); + return (await site.loadTreesPlanted()).map(({ name, amount }) => ({ name, amount })); } async getReportingPeriods(sitePolygon: SitePolygon): Promise { @@ -47,7 +47,7 @@ export class SitePolygonsService { reportingPeriods.push({ dueAt: report.dueAt, submittedAt: report.submittedAt, - treeSpecies: (await report.loadTreeSpecies()).map(({ name, amount }) => ({ name, amount })) + treeSpecies: (await report.loadTreesPlanted()).map(({ name, amount }) => ({ name, amount })) }); } diff --git a/libs/database/src/lib/entities/nursery-report.entity.ts b/libs/database/src/lib/entities/nursery-report.entity.ts index 00daf4b..c621079 100644 --- a/libs/database/src/lib/entities/nursery-report.entity.ts +++ b/libs/database/src/lib/entities/nursery-report.entity.ts @@ -17,6 +17,9 @@ import { TreeSpecies } from "./tree-species.entity"; // A quick stub for tree endpoints @Table({ tableName: "v2_nursery_reports", underscored: true, paranoid: true }) export class NurseryReport extends Model { + static readonly TREE_ASSOCIATIONS = ["seedlings"]; + static readonly PARENT_ID = "nurseryId"; + @PrimaryKey @AutoIncrement @Column(BIGINT.UNSIGNED) @@ -42,5 +45,5 @@ export class NurseryReport extends Model { constraints: false, scope: { speciesableType: "App\\Models\\V2\\Nurseries\\NurseryReport", collection: "nursery-seedling" } }) - treeSpecies: TreeSpecies[] | null; + seedlings: TreeSpecies[] | null; } diff --git a/libs/database/src/lib/entities/nursery.entity.ts b/libs/database/src/lib/entities/nursery.entity.ts index 1cf606e..3988d4a 100644 --- a/libs/database/src/lib/entities/nursery.entity.ts +++ b/libs/database/src/lib/entities/nursery.entity.ts @@ -17,6 +17,8 @@ import { NurseryReport } from "./nursery-report.entity"; // A quick stub for the tree service endpoints. @Table({ tableName: "v2_nurseries", underscored: true, paranoid: true }) export class Nursery extends Model { + static readonly TREE_ASSOCIATIONS = ["seedlings"]; + @PrimaryKey @AutoIncrement @Column(BIGINT.UNSIGNED) @@ -38,14 +40,7 @@ export class Nursery extends Model { constraints: false, scope: { speciesableType: "App\\Models\\V2\\Nurseries\\Nursery", collection: "nursery-seedling" } }) - treeSpecies: TreeSpecies[] | null; - - async loadTreeSpecies() { - if (this.treeSpecies == null) { - this.treeSpecies = await this.$get("treeSpecies"); - } - return this.treeSpecies; - } + seedlings: TreeSpecies[] | null; @HasMany(() => NurseryReport) reports: NurseryReport[] | null; diff --git a/libs/database/src/lib/entities/project-report.entity.ts b/libs/database/src/lib/entities/project-report.entity.ts index 8379876..c580bb1 100644 --- a/libs/database/src/lib/entities/project-report.entity.ts +++ b/libs/database/src/lib/entities/project-report.entity.ts @@ -17,6 +17,9 @@ import { Project } from "./project.entity"; // A quick stub for the tree endpoints @Table({ tableName: "v2_project_reports", underscored: true, paranoid: true }) export class ProjectReport extends Model { + static readonly TREE_ASSOCIATIONS = ["treesPlanted"]; + static readonly PARENT_ID = "projectId"; + @PrimaryKey @AutoIncrement @Column(BIGINT.UNSIGNED) @@ -42,5 +45,5 @@ export class ProjectReport extends Model { constraints: false, scope: { speciesableType: "App\\Models\\V2\\Projects\\ProjectReport", collection: "tree-planted" } }) - treeSpecies: TreeSpecies[] | null; + treesPlanted: TreeSpecies[] | null; } diff --git a/libs/database/src/lib/entities/project.entity.ts b/libs/database/src/lib/entities/project.entity.ts index 488dc71..e819f7b 100644 --- a/libs/database/src/lib/entities/project.entity.ts +++ b/libs/database/src/lib/entities/project.entity.ts @@ -18,6 +18,8 @@ import { ProjectReport } from "./project-report.entity"; @Table({ tableName: "v2_projects", underscored: true, paranoid: true }) export class Project extends Model { + static readonly TREE_ASSOCIATIONS = ["treesPlanted"]; + @PrimaryKey @AutoIncrement @Column(BIGINT.UNSIGNED) @@ -337,7 +339,7 @@ export class Project extends Model { constraints: false, scope: { speciesableType: "App\\Models\\V2\\Projects\\Project", collection: "tree-planted" } }) - treeSpecies: TreeSpecies[] | null; + treesPlanted: TreeSpecies[] | null; @HasMany(() => ProjectReport) reports: ProjectReport[] | null; diff --git a/libs/database/src/lib/entities/site-report.entity.ts b/libs/database/src/lib/entities/site-report.entity.ts index b8d1c25..e1faf49 100644 --- a/libs/database/src/lib/entities/site-report.entity.ts +++ b/libs/database/src/lib/entities/site-report.entity.ts @@ -17,6 +17,9 @@ import { Site } from "./site.entity"; // A quick stub for the research endpoints @Table({ tableName: "v2_site_reports", underscored: true, paranoid: true }) export class SiteReport extends Model { + static readonly TREE_ASSOCIATIONS = ["treesPlanted", "nonTrees"]; + static readonly PARENT_ID = "siteId"; + @PrimaryKey @AutoIncrement @Column(BIGINT.UNSIGNED) @@ -46,13 +49,13 @@ export class SiteReport extends Model { constraints: false, scope: { speciesableType: "App\\Models\\V2\\Sites\\SiteReport", collection: "tree-planted" } }) - treeSpecies: TreeSpecies[] | null; + treesPlanted: TreeSpecies[] | null; - async loadTreeSpecies() { - if (this.treeSpecies == null) { - this.treeSpecies = await this.$get("treeSpecies"); + async loadTreesPlanted() { + if (this.treesPlanted == null) { + this.treesPlanted = await this.$get("treesPlanted"); } - return this.treeSpecies; + return this.treesPlanted; } @HasMany(() => TreeSpecies, { @@ -60,12 +63,12 @@ export class SiteReport extends Model { constraints: false, scope: { speciesableType: "App\\Models\\V2\\Sites\\SiteReport", collection: "non-tree" } }) - nonTreeSpecies: TreeSpecies[] | null; + nonTrees: TreeSpecies[] | null; - async loadNonTreeSpecies() { - if (this.nonTreeSpecies == null) { - this.nonTreeSpecies = await this.$get("nonTreeSpecies"); + async loadNonTrees() { + if (this.nonTrees == null) { + this.nonTrees = await this.$get("nonTrees"); } - return this.nonTreeSpecies; + return this.nonTrees; } } diff --git a/libs/database/src/lib/entities/site.entity.ts b/libs/database/src/lib/entities/site.entity.ts index 8835bbd..3827ec1 100644 --- a/libs/database/src/lib/entities/site.entity.ts +++ b/libs/database/src/lib/entities/site.entity.ts @@ -17,6 +17,8 @@ import { Project } from "./project.entity"; // A quick stub for the research endpoints @Table({ tableName: "v2_sites", underscored: true, paranoid: true }) export class Site extends Model { + static readonly TREE_ASSOCIATIONS = ["treesPlanted", "nonTrees"]; + @PrimaryKey @AutoIncrement @Column(BIGINT.UNSIGNED) @@ -38,13 +40,13 @@ export class Site extends Model { constraints: false, scope: { speciesableType: "App\\Models\\V2\\Sites\\Site", collection: "tree-planted" } }) - treeSpecies: TreeSpecies[] | null; + treesPlanted: TreeSpecies[] | null; - async loadTreeSpecies() { - if (this.treeSpecies == null) { - this.treeSpecies = await this.$get("treeSpecies"); + async loadTreesPlanted() { + if (this.treesPlanted == null) { + this.treesPlanted = await this.$get("treesPlanted"); } - return this.treeSpecies; + return this.treesPlanted; } @HasMany(() => TreeSpecies, { @@ -52,13 +54,13 @@ export class Site extends Model { constraints: false, scope: { speciesableType: "App\\Models\\V2\\Sites\\Site", collection: "non-tree" } }) - nonTreeSpecies: TreeSpecies[] | null; + nonTrees: TreeSpecies[] | null; - async loadNonTreeSpecies() { - if (this.nonTreeSpecies == null) { - this.nonTreeSpecies = await this.$get("nonTreeSpecies"); + async loadNonTrees() { + if (this.nonTrees == null) { + this.nonTrees = await this.$get("nonTrees"); } - return this.nonTreeSpecies; + return this.nonTrees; } @HasMany(() => SiteReport) diff --git a/libs/database/src/lib/factories/tree-species.factory.ts b/libs/database/src/lib/factories/tree-species.factory.ts index c630a62..d6f93b2 100644 --- a/libs/database/src/lib/factories/tree-species.factory.ts +++ b/libs/database/src/lib/factories/tree-species.factory.ts @@ -17,45 +17,59 @@ const defaultAttributesFactory = async () => ({ }); export const TreeSpeciesFactory = { - forNursery: FactoryGirl.define(TreeSpecies, async () => ({ + forNurserySeedling: FactoryGirl.define(TreeSpecies, async () => ({ ...(await defaultAttributesFactory()), speciesableType: "App\\Models\\V2\\Nurseries\\Nursery", speciesableId: NurseryFactory.associate("id"), collection: "nursery-seedling" })), - forNurseryReport: FactoryGirl.define(TreeSpecies, async () => ({ + forNurseryReportSeedling: FactoryGirl.define(TreeSpecies, async () => ({ ...(await defaultAttributesFactory()), speciesableType: "App\\Models\\V2\\Nurseries\\NurseryReport", speciesableId: NurseryReportFactory.associate("id"), collection: "nursery-seedling" })), - forProject: FactoryGirl.define(TreeSpecies, async () => ({ + forProjectTreePlanted: FactoryGirl.define(TreeSpecies, async () => ({ ...(await defaultAttributesFactory()), speciesableType: "App\\Models\\V2\\Projects\\Project", speciesableId: ProjectFactory.associate("id"), collection: "tree-planted" })), - forProjectReport: FactoryGirl.define(TreeSpecies, async () => ({ + forProjectReportTreePlanted: FactoryGirl.define(TreeSpecies, async () => ({ ...(await defaultAttributesFactory()), speciesableType: "App\\Models\\V2\\Projects\\ProjectReport", speciesableId: ProjectReportFactory.associate("id"), collection: "tree-planted" })), - forSite: FactoryGirl.define(TreeSpecies, async () => ({ + forSiteTreePlanted: FactoryGirl.define(TreeSpecies, async () => ({ ...(await defaultAttributesFactory()), speciesableType: "App\\Models\\V2\\Sites\\Site", speciesableId: SiteFactory.associate("id"), collection: "tree-planted" })), - forSiteReport: FactoryGirl.define(TreeSpecies, async () => ({ + forSiteNonTree: FactoryGirl.define(TreeSpecies, async () => ({ + ...(await defaultAttributesFactory()), + speciesableType: "App\\Models\\V2\\Sites\\Site", + speciesableId: SiteFactory.associate("id"), + collection: "non-tree" + })), + + forSiteReportTreePlanted: FactoryGirl.define(TreeSpecies, async () => ({ ...(await defaultAttributesFactory()), speciesableType: "App\\Models\\V2\\Sites\\SiteReport", speciesableId: SiteReportFactory.associate("id"), collection: "tree-planted" + })), + + forSiteReportNonTree: FactoryGirl.define(TreeSpecies, async () => ({ + ...(await defaultAttributesFactory()), + speciesableType: "App\\Models\\V2\\Sites\\SiteReport", + speciesableId: SiteReportFactory.associate("id"), + collection: "non-tree" })) };