diff --git a/packages/api/src/controllers/animalTypeUseRelationshipController.js b/packages/api/src/controllers/animalTypeUseRelationshipController.js
new file mode 100644
index 0000000000..533ffc5a3f
--- /dev/null
+++ b/packages/api/src/controllers/animalTypeUseRelationshipController.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 LiteFarm.org
+ * This file is part of LiteFarm.
+ *
+ * LiteFarm is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LiteFarm is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details, see .
+ */
+import AnimalTypeUseRelationship from '../models/animalTypeUseRelationshipModel.js';
+
+const animalUseController = {
+ getAnimalTypeUseRelationships() {
+ return async (_req, res) => {
+ try {
+ const useRelationships = await AnimalTypeUseRelationship.query();
+ return res.status(200).send(useRelationships);
+ } catch (error) {
+ console.error(error);
+ return res.status(500).json({
+ error,
+ });
+ }
+ };
+ },
+};
+
+export default animalUseController;
diff --git a/packages/api/src/controllers/animalUseController.js b/packages/api/src/controllers/animalUseController.js
index 4a6529c269..1028eabb7f 100644
--- a/packages/api/src/controllers/animalUseController.js
+++ b/packages/api/src/controllers/animalUseController.js
@@ -19,7 +19,7 @@ const animalUseController = {
getAnimalUses() {
return async (_req, res) => {
try {
- const uses = await AnimalUse.getAnimalUsesForTypes();
+ const uses = await AnimalUse.query();
return res.status(200).send(uses);
} catch (error) {
console.error(error);
diff --git a/packages/api/src/models/animalTypeUseRelationshipModel.js b/packages/api/src/models/animalTypeUseRelationshipModel.js
new file mode 100644
index 0000000000..d20b43158a
--- /dev/null
+++ b/packages/api/src/models/animalTypeUseRelationshipModel.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 LiteFarm.org
+ * This file is part of LiteFarm.
+ *
+ * LiteFarm is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LiteFarm is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details, see .
+ */
+
+import Model from './baseFormatModel.js';
+
+class AnimalTypeUseRelationshipModel extends Model {
+ static get tableName() {
+ return 'animal_type_use_relationship';
+ }
+
+ static get idColumn() {
+ return ['default_type_id', 'animal_use_id'];
+ }
+
+ // Optional JSON schema. This is not the database schema! Nothing is generated
+ // based on this. This is only used for validation. Whenever a model instance
+ // is created it is checked against this schema. http://json-schema.org/.
+ static get jsonSchema() {
+ return {
+ type: 'object',
+ required: ['default_type_id', 'animal_use_id'],
+ properties: {
+ default_type_id: { type: 'integer' },
+ animal_use_id: { type: 'integer' },
+ },
+ additionalProperties: false,
+ };
+ }
+}
+
+export default AnimalTypeUseRelationshipModel;
diff --git a/packages/api/src/models/animalUseModel.js b/packages/api/src/models/animalUseModel.js
index ae2ec18eba..aac49e0ac8 100644
--- a/packages/api/src/models/animalUseModel.js
+++ b/packages/api/src/models/animalUseModel.js
@@ -13,7 +13,6 @@
* GNU General Public License for more details, see .
*/
-import knex from '../util/knex.js';
import baseModel from './baseModel.js';
class AnimalUse extends baseModel {
@@ -39,32 +38,6 @@ class AnimalUse extends baseModel {
additionalProperties: false,
};
}
-
- static async getAnimalUsesForTypes() {
- const typeUseRelationships = await knex('animal_type_use_relationship')
- .select({
- default_type_id: 'default_type_id',
- useId: 'animal_use.id',
- useKey: 'animal_use.key',
- })
- .join('animal_use', 'animal_type_use_relationship.animal_use_id', '=', 'animal_use.id');
-
- const usesPerType = typeUseRelationships.reduce((map, { default_type_id, useId, useKey }) => {
- map[default_type_id] = map[default_type_id] || [];
- map[default_type_id].push({ id: useId, key: useKey });
-
- return map;
- }, {});
-
- const response = Object.entries(usesPerType).map(([defaultTypeId, uses]) => {
- return { default_type_id: +defaultTypeId, uses };
- });
-
- const allUses = await AnimalUse.query();
- response.push({ default_type_id: null, uses: allUses });
-
- return response;
- }
}
export default AnimalUse;
diff --git a/packages/api/src/routes/animalTypeUseRelationshipRoute.js b/packages/api/src/routes/animalTypeUseRelationshipRoute.js
new file mode 100644
index 0000000000..2e6b47cf80
--- /dev/null
+++ b/packages/api/src/routes/animalTypeUseRelationshipRoute.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 LiteFarm.org
+ * This file is part of LiteFarm.
+ *
+ * LiteFarm is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LiteFarm is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details, see .
+ */
+
+import express from 'express';
+
+const router = express.Router();
+import checkScope from '../middleware/acl/checkScope.js';
+import animalTypeUseRelationshipController from '../controllers/animalTypeUseRelationshipController.js';
+
+router.get(
+ '/',
+ checkScope(['get:animal_uses']),
+ animalTypeUseRelationshipController.getAnimalTypeUseRelationships(),
+);
+
+export default router;
diff --git a/packages/api/src/server.js b/packages/api/src/server.js
index 2ff266b295..cee75a6f13 100644
--- a/packages/api/src/server.js
+++ b/packages/api/src/server.js
@@ -137,6 +137,7 @@ import animalOriginRoute from './routes/animalOriginRoute.js';
import animalGroupRoute from './routes/animalGroupRoute.js';
import animalRemovalReasonRoute from './routes/animalRemovalReasonRoute.js';
import animalUseRoute from './routes/animalUseRoute.js';
+import animalTypeUseRelationshipRoute from './routes/animalTypeUseRelationshipRoute.js';
import cropRoutes from './routes/cropRoute.js';
import cropVarietyRoutes from './routes/cropVarietyRoute.js';
import fieldRoutes from './routes/fieldRoute.js';
@@ -293,6 +294,7 @@ app
.use('/animal_groups', animalGroupRoute)
.use('/animal_removal_reasons', animalRemovalReasonRoute)
.use('/animal_uses', animalUseRoute)
+ .use('/animal_type_use_relationships', animalTypeUseRelationshipRoute)
.use('/location', locationRoute)
.use('/userLog', userLogRoute)
.use('/crop', cropRoutes)
diff --git a/packages/api/tests/animal_type_use_relationship.test.js b/packages/api/tests/animal_type_use_relationship.test.js
new file mode 100644
index 0000000000..2d05f34d4f
--- /dev/null
+++ b/packages/api/tests/animal_type_use_relationship.test.js
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2024 LiteFarm.org
+ * This file is part of LiteFarm.
+ *
+ * LiteFarm is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LiteFarm is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details, see .
+ */
+
+import chai from 'chai';
+
+import chaiHttp from 'chai-http';
+chai.use(chaiHttp);
+
+import server from '../src/server.js';
+import knex from '../src/util/knex.js';
+import { tableCleanup } from './testEnvironment.js';
+
+jest.mock('jsdom');
+jest.mock('../src/middleware/acl/checkJwt.js', () =>
+ jest.fn((req, _res, next) => {
+ req.auth = {};
+ req.auth.user_id = req.get('user_id');
+ next();
+ }),
+);
+import mocks from './mock.factories.js';
+
+describe('Animal Type Use Relationship Tests', () => {
+ let farm;
+ let newOwner;
+
+ const getRequest = async ({ user_id = newOwner.user_id, farm_id = farm.farm_id }) => {
+ const response = await chai
+ .request(server)
+ .get('/animal_type_use_relationships')
+ .set('user_id', user_id)
+ .set('farm_id', farm_id);
+
+ return response;
+ };
+
+ function fakeUserFarm(role = 1) {
+ return { ...mocks.fakeUserFarm(), role_id: role };
+ }
+
+ async function returnUserFarms(role) {
+ const [mainFarm] = await mocks.farmFactory();
+ const [user] = await mocks.usersFactory();
+
+ await mocks.userFarmFactory(
+ {
+ promisedUser: [user],
+ promisedFarm: [mainFarm],
+ },
+ fakeUserFarm(role),
+ );
+ return { mainFarm, user };
+ }
+
+ async function makeAnimalUse(properties) {
+ const [animalUse] = await mocks.animal_useFactory({
+ properties,
+ });
+ return animalUse;
+ }
+
+ async function makeAnimalTypeUseRelationship(defaultType, use) {
+ const [animalTypeUseRelationship] = await mocks.animal_type_use_relationshipFactory({
+ promisedDefaultAnimalType: [defaultType],
+ promisedAnimalUse: [use],
+ });
+ return animalTypeUseRelationship;
+ }
+
+ beforeEach(async () => {
+ [farm] = await mocks.farmFactory();
+ [newOwner] = await mocks.usersFactory();
+ });
+
+ afterEach(async (done) => {
+ await tableCleanup(knex);
+ done();
+ });
+
+ afterAll(async (done) => {
+ await knex.destroy();
+ done();
+ });
+
+ describe('Get animal type use relationship tests', () => {
+ test('All users should get animal type use relationships', async () => {
+ const roles = [1, 2, 3, 5];
+ const [use1, use2, use3, use4, use5, use6, use7, use8] = await Promise.all(
+ [1, 2, 3, 4, 5, 6, 7, 8].map(async () => await makeAnimalUse()),
+ );
+
+ const [defaultType1] = await mocks.default_animal_typeFactory();
+ const [defaultType2] = await mocks.default_animal_typeFactory();
+
+ const testCase = [
+ { defaultType: defaultType1, uses: [use1, use2, use3, use6, use7, use8] },
+ { defaultType: defaultType2, uses: [use1, use5, use6, use7, use8] },
+ ];
+
+ for (let { defaultType, uses } of testCase) {
+ if (defaultType) {
+ for (let use of uses) {
+ await makeAnimalTypeUseRelationship(defaultType, use);
+ }
+ }
+ }
+
+ for (const role of roles) {
+ const { mainFarm, user } = await returnUserFarms(role);
+
+ const res = await getRequest({
+ user_id: user.user_id,
+ farm_id: mainFarm.farm_id,
+ });
+
+ expect(res.status).toBe(200);
+ expect(res.body.length).toBe(11);
+ const filteredDefaultType1 = res.body.filter(
+ (useRelationship) => useRelationship.default_type_id === defaultType1.id,
+ );
+ expect(filteredDefaultType1.length).toBe(6);
+ const filteredDefaultType2 = res.body.filter(
+ (useRelationship) => useRelationship.default_type_id === defaultType2.id,
+ );
+ expect(filteredDefaultType2.length).toBe(5);
+ }
+ });
+ });
+});
diff --git a/packages/api/tests/animal_use.test.js b/packages/api/tests/animal_use.test.js
index 777f971110..eebdefc5cc 100644
--- a/packages/api/tests/animal_use.test.js
+++ b/packages/api/tests/animal_use.test.js
@@ -71,14 +71,6 @@ describe('Animal Use Tests', () => {
return animalUse;
}
- async function makeAnimalTypeUseRelationship(defaultType, use) {
- const [animalTypeUseRelationship] = await mocks.animal_type_use_relationshipFactory({
- promisedDefaultAnimalType: [defaultType],
- promisedAnimalUse: [use],
- });
- return animalTypeUseRelationship;
- }
-
beforeEach(async () => {
[farm] = await mocks.farmFactory();
[newOwner] = await mocks.usersFactory();
@@ -101,23 +93,6 @@ describe('Animal Use Tests', () => {
[1, 2, 3, 4, 5, 6, 7, 8].map(async () => await makeAnimalUse()),
);
- const [defaultType1] = await mocks.default_animal_typeFactory();
- const [defaultType2] = await mocks.default_animal_typeFactory();
-
- const testCase = [
- { defaultType: defaultType1, uses: [use1, use2, use3, use6, use7, use8] },
- { defaultType: defaultType2, uses: [use1, use5, use6, use7, use8] },
- { defaultType: null, uses: [use1, use2, use3, use4, use5, use6, use7, use8] }, // for custom types
- ];
-
- for (let { defaultType, uses } of testCase) {
- if (defaultType) {
- for (let use of uses) {
- await makeAnimalTypeUseRelationship(defaultType, use);
- }
- }
- }
-
for (const role of roles) {
const { mainFarm, user } = await returnUserFarms(role);
@@ -127,17 +102,7 @@ describe('Animal Use Tests', () => {
});
expect(res.status).toBe(200);
- expect(res.body.length).toBe(3);
-
- res.body.forEach(({ default_type_id, uses }) => {
- const expectedTypeAndUses = testCase.find(({ defaultType }) => {
- return default_type_id ? defaultType.id === default_type_id : !defaultType;
- });
-
- expect(uses.map(({ id }) => id).sort()).toEqual(
- expectedTypeAndUses.uses.map(({ id }) => id).sort(),
- );
- });
+ expect(res.body.length).toBe(8);
}
});
});
diff --git a/packages/webapp/src/apiConfig.js b/packages/webapp/src/apiConfig.js
index e6238d1139..9eacb2fc6d 100644
--- a/packages/webapp/src/apiConfig.js
+++ b/packages/webapp/src/apiConfig.js
@@ -86,6 +86,7 @@ export const animalIdentifierTypesUrl = `${URI}/animal_identifier_types`;
export const animalIdentifierColorsUrl = `${URI}/animal_identifier_colors`;
export const animalOriginsUrl = `${URI}/animal_origins`;
export const animalUsesUrl = `${URI}/animal_uses`;
+export const animalTypeUseRelationshipsUrl = `${URI}/animal_type_use_relationships`;
export const animalRemovalReasonsUrl = `${URI}/animal_removal_reasons`;
export const soilAmendmentMethodsUrl = `${URI}/soil_amendment_methods`;
export const soilAmendmentPurposesUrl = `${URI}/soil_amendment_purposes`;
@@ -147,6 +148,7 @@ export default {
animalIdentifierColorsUrl,
animalOriginsUrl,
animalUsesUrl,
+ animalTypeUseRelationshipsUrl,
animalRemovalReasonsUrl,
soilAmendmentMethodsUrl,
soilAmendmentPurposesUrl,
diff --git a/packages/webapp/src/containers/Animals/AddAnimals/useAnimalOptions.ts b/packages/webapp/src/containers/Animals/AddAnimals/useAnimalOptions.ts
index 788ebb48e6..b3efdf052e 100644
--- a/packages/webapp/src/containers/Animals/AddAnimals/useAnimalOptions.ts
+++ b/packages/webapp/src/containers/Animals/AddAnimals/useAnimalOptions.ts
@@ -23,6 +23,7 @@ import {
useGetAnimalIdentifierColorsQuery,
useGetAnimalOriginsQuery,
useGetAnimalUsesQuery,
+ useGetAnimalTypeUseRelationshipsQuery,
} from '../../../store/api/apiSlice';
import { useTranslation } from 'react-i18next';
import { generateUniqueAnimalId } from '../../../util/animal';
@@ -52,6 +53,7 @@ export const useAnimalOptions = (...optionTypes: OptionType[]) => {
const { data: identifierColors = [] } = useGetAnimalIdentifierColorsQuery();
const { data: orgins = [] } = useGetAnimalOriginsQuery();
const { data: uses = [] } = useGetAnimalUsesQuery();
+ const { data: typeUseRelationships = [] } = useGetAnimalTypeUseRelationshipsQuery();
const options: any = {};
@@ -106,14 +108,32 @@ export const useAnimalOptions = (...optionTypes: OptionType[]) => {
}
if (optionTypes.includes('use')) {
- options.useOptions = uses.map((animalType) => ({
- default_type_id: animalType.default_type_id,
- uses: animalType.uses.map((use) => ({
- value: use.id,
- label: t(`animal:USE.${use.key}`),
- key: use.key,
- })),
- }));
+ options.useOptions = defaultTypes.map((animalType) => {
+ return {
+ default_type_id: animalType.id,
+ uses: typeUseRelationships
+ .filter((typeUse) => typeUse.default_type_id === animalType.id)
+ .map((animalUse) => {
+ const useKey = uses.find((use) => use.id === animalUse.animal_use_id);
+ return {
+ value: animalUse.animal_use_id,
+ label: t(`animal:USE.${useKey?.key}`),
+ key: useKey?.key,
+ };
+ }),
+ };
+ });
+ // Add all uses to custom type
+ options.useOptions.push({
+ default_type_id: null,
+ uses: uses.map((use) => {
+ return {
+ value: use.id,
+ label: t(`animal:USE.${use.key}`),
+ key: use.key,
+ };
+ }),
+ });
}
if (optionTypes.includes('tagType')) {
diff --git a/packages/webapp/src/store/api/apiSlice.ts b/packages/webapp/src/store/api/apiSlice.ts
index dc188d7e1e..4f6fc180e2 100644
--- a/packages/webapp/src/store/api/apiSlice.ts
+++ b/packages/webapp/src/store/api/apiSlice.ts
@@ -28,6 +28,7 @@ import {
animalIdentifierColorsUrl,
animalOriginsUrl,
animalUsesUrl,
+ animalTypeUseRelationshipsUrl,
animalRemovalReasonsUrl,
soilAmendmentMethodsUrl,
soilAmendmentPurposesUrl,
@@ -53,6 +54,7 @@ import type {
AnimalIdentifierColor,
AnimalOrigin,
AnimalUse,
+ AnimalTypeUseRelationship,
} from './types';
export const api = createApi({
@@ -83,6 +85,7 @@ export const api = createApi({
'AnimalIdentifierColors',
'AnimalOrigins',
'AnimalUses',
+ 'AnimalTypeUseRelationships',
'AnimalRemovalReasons',
'SoilAmendmentMethods',
'SoilAmendmentPurposes',
@@ -140,6 +143,10 @@ export const api = createApi({
query: () => `${animalUsesUrl}`,
providesTags: ['AnimalUses'],
}),
+ getAnimalTypeUseRelationships: build.query({
+ query: () => `${animalTypeUseRelationshipsUrl}`,
+ providesTags: ['AnimalTypeUseRelationships'],
+ }),
getAnimalRemovalReasons: build.query({
query: () => `${animalRemovalReasonsUrl}`,
providesTags: ['AnimalRemovalReasons'],
@@ -236,6 +243,7 @@ export const {
useGetAnimalIdentifierColorsQuery,
useGetAnimalOriginsQuery,
useGetAnimalUsesQuery,
+ useGetAnimalTypeUseRelationshipsQuery,
useGetAnimalRemovalReasonsQuery,
useRemoveAnimalsMutation,
useRemoveAnimalBatchesMutation,
diff --git a/packages/webapp/src/store/api/types/index.ts b/packages/webapp/src/store/api/types/index.ts
index 175ba28312..35f973ae5e 100644
--- a/packages/webapp/src/store/api/types/index.ts
+++ b/packages/webapp/src/store/api/types/index.ts
@@ -120,8 +120,13 @@ export interface AnimalOrigin {
}
export interface AnimalUse {
- default_type_id: number | null;
- uses: { id: number; key: string }[];
+ id: number;
+ key: string;
+}
+
+export interface AnimalTypeUseRelationship {
+ default_type_id: number;
+ animal_use_id: number;
}
export type AnimalRemovalReasonKeys =