Skip to content

Commit

Permalink
Merge pull request #3373 from LiteFarmOrg/LF-4168-add-necessary-colum…
Browse files Browse the repository at this point in the history
…ns-for-animal-creation-to-animal-batch-tables-and-update-ap-is

Lf 4168 add necessary columns for animal creation to animal batch tables and update ap is
  • Loading branch information
Duncan-Brain authored Aug 15, 2024
2 parents 701adac + b2cd75b commit f10044c
Show file tree
Hide file tree
Showing 22 changed files with 445 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 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 <https://www.gnu.org/licenses/>.
*/

export const up = async function (knex) {
const otherUse = await knex('animal_use').select('*').where({ key: 'OTHER' }).first();
const otherIdentifierType = await knex('animal_identifier_type')
.select('*')
.where({ key: 'OTHER' })
.first();

/*----------------------------------------
Update animal with new details
----------------------------------------*/
await knex.schema.alterTable('animal', (table) => {
table
.integer('identifier_type_id')
.references('id')
.inTable('animal_identifier_type')
.nullable();
table.string('identifier_type_other').nullable();
table.check(
`(identifier_type_other IS NOT NULL AND identifier_type_id = ${otherIdentifierType.id}) OR (identifier_type_other IS NULL)`,
[],
'identifier_type_other_id_check',
);
table
.enu('organic_status', ['Non-Organic', 'Transitional', 'Organic'])
.notNullable()
.defaultTo('Non-Organic');
table.string('supplier').nullable();
table.float('price').unsigned().nullable();
});

await knex.schema.createTable('animal_use_relationship', (table) => {
table.integer('animal_id').references('id').inTable('animal').notNullable();
table.integer('use_id').references('id').inTable('animal_use').notNullable();
table.primary(['animal_id', 'use_id']);
table.string('other_use');
table.check(
`(other_use IS NOT NULL AND use_id = ${otherUse.id}) OR (other_use IS NULL)`,
[],
'other_use_id_check',
);
});

/*----------------------------------------
Update animal batch with new details
----------------------------------------*/
await knex.schema.alterTable('animal_batch', (table) => {
table
.enu('organic_status', ['Non-Organic', 'Transitional', 'Organic'])
.notNullable()
.defaultTo('Non-Organic');
table.string('supplier').nullable();
table.float('price').unsigned().nullable();
table.string('dam').nullable();
table.string('sire').nullable();
});

await knex.schema.createTable('animal_batch_use_relationship', (table) => {
table.integer('animal_batch_id').references('id').inTable('animal_batch').notNullable();
table.integer('use_id').references('id').inTable('animal_use').notNullable();
table.primary(['animal_batch_id', 'use_id']);
table.string('other_use');
table.check(
`(other_use IS NOT NULL AND use_id = ${otherUse.id}) OR (other_use IS NULL)`,
[],
'other_use_id_check',
);
});
};

export const down = async function (knex) {
/*----------------------------------------
Undo: Update animal with new details
----------------------------------------*/
await knex.schema.alterTable('animal', (table) => {
table.dropChecks(['identifier_type_other_id_check']);
table.dropColumns([
'identifier_type_id',
'identifier_type_other',
'organic_status',
'supplier',
'price',
]);
});

await knex.schema.dropTable('animal_use_relationship');

/*----------------------------------------
Undo: Update animal batch with new details
----------------------------------------*/
await knex.schema.alterTable('animal_batch', (table) => {
table.dropColumns(['organic_status', 'supplier', 'price', 'dam', 'sire']);
});

await knex.schema.dropTable('animal_batch_use_relationship');
};
13 changes: 12 additions & 1 deletion packages/api/src/controllers/animalBatchController.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import CustomAnimalBreedModel from '../models/customAnimalBreedModel.js';
import CustomAnimalTypeModel from '../models/customAnimalTypeModel.js';
import { handleObjectionError } from '../util/errorCodes.js';
import { assignInternalIdentifiers } from '../util/animal.js';
import { uploadPublicImage } from '../util/imageUpload.js';

const animalBatchController = {
getFarmAnimalBatches() {
Expand All @@ -29,7 +30,12 @@ const animalBatchController = {
const rows = await AnimalBatchModel.query()
.where({ farm_id })
.whereNotDeleted()
.withGraphFetched({ internal_identifier: true, group_ids: true, sex_detail: true });
.withGraphFetched({
internal_identifier: true,
group_ids: true,
sex_detail: true,
animal_batch_use_relationships: true,
});
return res.status(200).send(
rows.map(({ internal_identifier, group_ids, ...rest }) => ({
...rest,
Expand Down Expand Up @@ -206,6 +212,11 @@ const animalBatchController = {
}
};
},
uploadAnimalBatchImage() {
return async (req, res, next) => {
await uploadPublicImage('animal_batch')(req, res, next);
};
},
};

export default animalBatchController;
30 changes: 29 additions & 1 deletion packages/api/src/controllers/animalController.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import AnimalGroupRelationshipModel from '../models/animalGroupRelationshipModel
import { assignInternalIdentifiers } from '../util/animal.js';
import { handleObjectionError } from '../util/errorCodes.js';
import { checkAndTrimString } from '../util/util.js';
import AnimalUseRelationshipModel from '../models/animalUseRelationshipModel.js';
import { uploadPublicImage } from '../util/imageUpload.js';

const animalController = {
getFarmAnimals() {
Expand All @@ -32,7 +34,11 @@ const animalController = {
const rows = await AnimalModel.query()
.where({ farm_id })
.whereNotDeleted()
.withGraphFetched({ internal_identifier: true, group_ids: true });
.withGraphFetched({
internal_identifier: true,
group_ids: true,
animal_use_relationships: true,
});
return res.status(200).send(
rows.map(({ internal_identifier, group_ids, ...rest }) => ({
...rest,
Expand Down Expand Up @@ -142,6 +148,23 @@ const animalController = {
}

individualAnimalResult.group_ids = groupIds;

const animalUseRelationships = [];
if (animal.animal_use_relationships?.length) {
for (const relationship of animal.animal_use_relationships) {
animalUseRelationships.push(
await baseController.postWithResponse(
AnimalUseRelationshipModel,
{ ...relationship, animal_id: individualAnimalResult.id },
req,
{ trx },
),
);
}
}

individualAnimalResult.animal_use_relationships = animalUseRelationships;

result.push(individualAnimalResult);
}

Expand Down Expand Up @@ -239,6 +262,11 @@ const animalController = {
}
};
},
uploadAnimalImage() {
return async (req, res, next) => {
await uploadPublicImage('animal')(req, res, next);
};
},
};

export default animalController;
20 changes: 20 additions & 0 deletions packages/api/src/middleware/checkAnimalEntities.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { handleObjectionError } from '../util/errorCodes.js';
import CustomAnimalTypeModel from '../models/customAnimalTypeModel.js';
import DefaultAnimalBreedModel from '../models/defaultAnimalBreedModel.js';
import CustomAnimalBreedModel from '../models/customAnimalBreedModel.js';
import AnimalUseModel from '../models/animalUseModel.js';

/**
* Middleware function to check if the provided animal entities exist and belong to the farm. The IDs must be passed as a comma-separated query string.
Expand Down Expand Up @@ -189,6 +190,25 @@ export function validateAnimalBatchCreationBody(animalBatchKey) {
}
}

const relationshipsKey =
animalBatchKey === 'batch'
? 'animal_batch_use_relationships'
: 'animal_use_relationships';

if (animalOrBatch[relationshipsKey]) {
if (!Array.isArray(animalOrBatch[relationshipsKey])) {
return res.status(400).send(`${relationshipsKey} must be an array`);
}

const otherUse = await AnimalUseModel.query().where({ key: 'OTHER' }).first();

for (const relationship of animalOrBatch[relationshipsKey]) {
if (relationship.use_id != otherUse.id && relationship.other_use) {
return res.status(400).send('other_use notes is for other use type');
}
}
}

// Skip the process if type_name and breed_name are not passed
if (!type_name && !breed_name) {
continue;
Expand Down
12 changes: 12 additions & 0 deletions packages/api/src/models/animalBatchModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import AnimalBatchSexDetailModel from './animalBatchSexDetailModel.js';
import AnimalBatchGroupRelationshipModel from './animalBatchGroupRelationshipModel.js';
import AnimalUnionBatchIdViewModel from './animalUnionBatchIdViewModel.js';
import { checkAndTrimString } from '../util/util.js';
import AnimalBatchUseRelationshipModel from './animalBatchUseRelationshipModel.js';

class AnimalBatchModel extends baseModel {
static get tableName() {
Expand Down Expand Up @@ -86,6 +87,9 @@ class AnimalBatchModel extends baseModel {
animal_removal_reason_id: { type: ['integer', 'null'] },
removal_explanation: { type: ['string', 'null'] },
removal_date: { type: ['string', 'null'], format: 'date-time' },
organic_status: { type: 'string', enum: ['Non-Organic', 'Transitional', 'Organic'] },
supplier: { type: ['string', 'null'], maxLength: 255 },
price: { type: ['number', 'null'] },
...this.baseProperties,
},
additionalProperties: false,
Expand Down Expand Up @@ -124,6 +128,14 @@ class AnimalBatchModel extends baseModel {
this.select('id').from('animal_group').where('deleted', false);
}),
},
animal_batch_use_relationships: {
relation: Model.HasManyRelation,
modelClass: AnimalBatchUseRelationshipModel,
join: {
from: 'animal_batch.id',
to: 'animal_batch_use_relationship.animal_batch_id',
},
},
};
}
}
Expand Down
44 changes: 44 additions & 0 deletions packages/api/src/models/animalBatchUseRelationshipModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

import Model from './baseFormatModel.js';

class AnimalBatchUseRelationshipModel extends Model {
static get tableName() {
return 'animal_batch_use_relationship';
}

static get idColumn() {
return ['animal_batch_id', '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: ['animal_batch_id', 'use_id'],
properties: {
animal_batch_id: { type: 'integer' },
use_id: { type: 'integer' },
other_use: { type: ['string', 'null'] },
},
additionalProperties: false,
};
}
}

export default AnimalBatchUseRelationshipModel;
14 changes: 14 additions & 0 deletions packages/api/src/models/animalModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import AnimalUnionBatchIdViewModel from './animalUnionBatchIdViewModel.js';
import AnimalGroupRelationshipModel from './animalGroupRelationshipModel.js';
import Model from './baseFormatModel.js';
import { checkAndTrimString } from '../util/util.js';
import AnimalUseRelationshipModel from './animalUseRelationshipModel.js';

class Animal extends baseModel {
static get tableName() {
Expand Down Expand Up @@ -94,6 +95,11 @@ class Animal extends baseModel {
animal_removal_reason_id: { type: ['integer', 'null'] },
removal_explanation: { type: ['string', 'null'] },
removal_date: { type: ['string', 'null'], format: 'date-time' },
identifier_type_id: { type: ['integer', 'null'] },
identifier_type_other: { type: ['string', 'null'] },
organic_status: { type: 'string', enum: ['Non-Organic', 'Transitional', 'Organic'] },
supplier: { type: ['string', 'null'], maxLength: 255 },
price: { type: ['number', 'null'] },
...this.baseProperties,
},
additionalProperties: false,
Expand Down Expand Up @@ -123,6 +129,14 @@ class Animal extends baseModel {
this.select('id').from('animal_group').where('deleted', false);
}),
},
animal_use_relationships: {
relation: Model.HasManyRelation,
modelClass: AnimalUseRelationshipModel,
join: {
from: 'animal.id',
to: 'animal_use_relationship.animal_id',
},
},
};
}
}
Expand Down
44 changes: 44 additions & 0 deletions packages/api/src/models/animalUseRelationshipModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

import Model from './baseFormatModel.js';

class AnimalUseRelationshipModel extends Model {
static get tableName() {
return 'animal_use_relationship';
}

static get idColumn() {
return ['animal_id', '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: ['animal_id', 'use_id'],
properties: {
animal_id: { type: 'integer' },
use_id: { type: 'integer' },
other_use: { type: ['string', 'null'] },
},
additionalProperties: false,
};
}
}

export default AnimalUseRelationshipModel;
Loading

0 comments on commit f10044c

Please sign in to comment.