Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lf 4380 api route for edit update route for edit animal and batch details #3484

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
02567b0
LF-4380 Add edit route on animals and batches
Duncan-Brain Aug 23, 2024
f0907a2
LF-4380 Make edit endpoints for controller
Duncan-Brain Aug 26, 2024
909ec4b
LF-4380 Allow specifiying enum key for tests
Duncan-Brain Sep 27, 2024
d7ec5c0
LF-4380 Add use relationships to tableCleanup
Duncan-Brain Sep 27, 2024
a9005fd
LF-4380 Add edit test and use async await on requests
Duncan-Brain Sep 27, 2024
6ecee80
LF-4380 Add tests for animal batches and add async await syntax to re…
Duncan-Brain Sep 27, 2024
6b879c9
LF-4380 Allow origin_id to be edited
Duncan-Brain Sep 27, 2024
12fd4b1
LF-4380 Add comment for delineating Remove tests
Duncan-Brain Oct 2, 2024
41e2a67
LF-4380 Move add animal functions to be reused in edit to utility fun…
Duncan-Brain Oct 2, 2024
8581fee
LF-4380 Replace code with utility function to be reused in edit, and …
Duncan-Brain Oct 2, 2024
f994fa6
LF-4380 Add utility functions to add batch and comments about groups too
Duncan-Brain Oct 2, 2024
4338df8
LF-4380 Update animals and batch edit endpoint to use types breeds an…
Duncan-Brain Oct 2, 2024
304d1ee
LF-4380 Consolidate middleware for reuse in edit endpoint
Duncan-Brain Oct 2, 2024
3e4543b
LF-4380 Extract logical checks for reuse
Duncan-Brain Oct 4, 2024
2d398ee
LF-4380 WIP - Add type and breed checks to edit and refine functions
Duncan-Brain Oct 4, 2024
7e2dfa8
LF-4380 Genericize type function
Duncan-Brain Oct 4, 2024
9690443
LF-4380 Reword check breed matched type to account for editing
Duncan-Brain Oct 4, 2024
3eb111a
LF-4380 Finish consolidating breed checks to mirror type checks
Duncan-Brain Oct 4, 2024
dddfdc2
LF-4380 Pass animalOrBatchRecord as prop, rename function, add comments
Duncan-Brain Oct 4, 2024
a4310da
LF-4380 Add sex detail check to edit and adapt logical check for exis…
Duncan-Brain Oct 4, 2024
f0b5bf2
LF-4380 Add use rleationship check to edit
Duncan-Brain Oct 7, 2024
3be3d50
LF-4380 Add null values to type and breed editing
Duncan-Brain Oct 8, 2024
be18671
LF-4380 Add animal origin check and fix logical checks
Duncan-Brain Oct 8, 2024
9b52193
LF-4380 Update comments
Duncan-Brain Oct 8, 2024
49656b7
LF-4380 Move out generic utilities to their own files
Duncan-Brain Oct 9, 2024
3b18ebc
LF-4380 Absolute freaking mystery to me why this happens...
Duncan-Brain Oct 10, 2024
77fa4a7
LF-4380 WIP test coverage up till sex and count - sex and count failing
Duncan-Brain Oct 11, 2024
9954d5f
LF-4380 Cleanup comments, small fixes based on testing, augment type_…
Duncan-Brain Oct 16, 2024
e17cbd0
LF-4380 Fix enum factories with specific db id constraints
Duncan-Brain Oct 16, 2024
87fcbea
LF-4380 Complete test coverage on animal_batches endpoint
Duncan-Brain Oct 16, 2024
ac4c320
LF-4380 Move middleware tests out of EDIT tests and comment functions
Duncan-Brain Oct 16, 2024
dcfb01c
LF-4380 Add mystery fix to animal controller
Duncan-Brain Oct 16, 2024
8d633d9
LF-4380 Copy middleware tests to animal test
Duncan-Brain Oct 16, 2024
c32864a
LF-4380 Remove unreachable test
Duncan-Brain Oct 16, 2024
2a1c525
LF-4380 Fix relation for findById function -- add comments to unreach…
Duncan-Brain Oct 16, 2024
ece3996
LF-4380 Add animal identifier middleware and test
Duncan-Brain Oct 16, 2024
b79fc72
LF-4380 Cleanup groupIds comments
Duncan-Brain Oct 17, 2024
533c8c6
LF-4380 Add licence
Duncan-Brain Oct 17, 2024
a0de1e4
LF-4380 Add fix to new breed creation logic -- add back in previously…
Duncan-Brain Oct 17, 2024
948d956
LF-4380 Simplify core existence logic remove extra functions
Duncan-Brain Oct 17, 2024
f8f8c05
LF-4380 Fix case where nulling existing breed, fix case where adding …
Duncan-Brain Oct 17, 2024
d660b06
LF-4380 Add tests for case where nulling existing breed, fix case whe…
Duncan-Brain Oct 17, 2024
d196bec
LF-4380 Revert utility object on req
Duncan-Brain Oct 18, 2024
c76bc27
LF-4380 Address PR feedback about desired keys, groupIdMap, oneTruthy…
Duncan-Brain Oct 19, 2024
595596d
LF-4380 Rename upsertGraph, checkAnimalSexDetail, preexistingAnimalOr…
Duncan-Brain Oct 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 67 additions & 43 deletions packages/api/src/controllers/animalBatchController.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
import { Model, transaction } from 'objection';
import AnimalBatchModel from '../models/animalBatchModel.js';
import baseController from './baseController.js';
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 { assignInternalIdentifiers, checkAndAddCustomTypeAndBreed } from '../util/animal.js';
import { uploadPublicImage } from '../util/imageUpload.js';
import _pick from 'lodash/pick.js';

const animalBatchController = {
getFarmAnimalBatches() {
Expand Down Expand Up @@ -60,50 +59,19 @@ const animalBatchController = {
const { farm_id } = req.headers;
const result = [];

// avoid attempts to add an already created type or breed to the DB
// where multiple batches have the same type_name or breed_name
// Create utility object used in type and breed
const typeIdsMap = {};
const typeBreedIdsMap = {};

for (const animalBatch of req.body) {
if (animalBatch.type_name) {
let typeId = typeIdsMap[animalBatch.type_name];

if (!typeId) {
const newType = await baseController.postWithResponse(
CustomAnimalTypeModel,
{ type: animalBatch.type_name, farm_id },
req,
{ trx },
);
typeId = newType.id;
typeIdsMap[animalBatch.type_name] = typeId;
}
animalBatch.custom_type_id = typeId;
delete animalBatch.type_name;
}

if (animalBatch.breed_name) {
const typeColumn = animalBatch.default_type_id ? 'default_type_id' : 'custom_type_id';
const typeId = animalBatch.type_name
? typeIdsMap[animalBatch.type_name]
: animalBatch.default_type_id || animalBatch.custom_type_id;
const typeBreedKey = `${typeColumn}_${typeId}_${animalBatch.breed_name}`;
let breedId = typeBreedIdsMap[typeBreedKey];

if (!breedId) {
const newBreed = await baseController.postWithResponse(
CustomAnimalBreedModel,
{ farm_id, [typeColumn]: typeId, breed: animalBatch.breed_name },
req,
{ trx },
);
breedId = newBreed.id;
typeBreedIdsMap[typeBreedKey] = breedId;
}
animalBatch.custom_breed_id = breedId;
delete animalBatch.breed_name;
}
await checkAndAddCustomTypeAndBreed(
req,
typeIdsMap,
typeBreedIdsMap,
animalBatch,
farm_id,
trx,
);

// Remove farm_id if it happens to be set in animal object since it should be obtained from header
delete animalBatch.farm_id;
Expand All @@ -128,6 +96,62 @@ const animalBatchController = {
};
},

editAnimalBatches() {
return async (req, res) => {
const trx = await transaction.start(Model.knex());

try {
const { farm_id } = req.headers;

// Create utility object used in type and breed
const typeIdsMap = {};
const typeBreedIdsMap = {};

const desiredKeys = [
'id',
'count',
'custom_breed_id',
'custom_type_id',
'default_breed_id',
'default_type_id',
'name',
'notes',
'photo_url',
'organic_status',
'supplier',
'price',
'sex_detail',
'origin_id',
'group_ids',
'animal_batch_use_relationships',
];

// select only allowed properties to edit
for (const animalBatch of req.body) {
await checkAndAddCustomTypeAndBreed(
req,
typeIdsMap,
typeBreedIdsMap,
animalBatch,
farm_id,
trx,
);

const keysExisting = desiredKeys.filter((key) => key in animalBatch);
const data = _pick(animalBatch, keysExisting);
SayakaOno marked this conversation as resolved.
Show resolved Hide resolved

await baseController.upsertGraph(AnimalBatchModel, data, req, { trx });
}

await trx.commit();
// Do not send result revalidate using tags on frontend
return res.status(204).send();
} catch (error) {
handleObjectionError(error, res, trx);
}
};
},

removeAnimalBatches() {
return async (req, res) => {
const trx = await transaction.start(Model.knex());
Expand Down
183 changes: 88 additions & 95 deletions packages/api/src/controllers/animalController.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@
import { Model, transaction } from 'objection';
import AnimalModel from '../models/animalModel.js';
import baseController from './baseController.js';
import CustomAnimalBreedModel from '../models/customAnimalBreedModel.js';
import CustomAnimalTypeModel from '../models/customAnimalTypeModel.js';
import AnimalGroupModel from '../models/animalGroupModel.js';
import AnimalGroupRelationshipModel from '../models/animalGroupRelationshipModel.js';
import { assignInternalIdentifiers } from '../util/animal.js';
import {
assignInternalIdentifiers,
upsertGroup,
checkAndAddCustomTypeAndBreed,
} 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';
import _pick from 'lodash/pick.js';

const animalController = {
getFarmAnimals() {
Expand Down Expand Up @@ -58,126 +57,120 @@ const animalController = {
addAnimals() {
return async (req, res) => {
const trx = await transaction.start(Model.knex());

try {
const { farm_id } = req.headers;
const result = [];

// avoid attempts to add an already created type or breed to the DB
// where multiple animals have the same type_name or breed_name
// Create utility object used in type and breed
const typeIdsMap = {};
const typeBreedIdsMap = {};

for (const animal of req.body) {
if (animal.type_name) {
let typeId = typeIdsMap[animal.type_name];

if (!typeId) {
const newType = await baseController.postWithResponse(
CustomAnimalTypeModel,
{ type: animal.type_name, farm_id },
req,
{ trx },
);
typeId = newType.id;
typeIdsMap[animal.type_name] = typeId;
}
animal.custom_type_id = typeId;
delete animal.type_name;
}

if (animal.breed_name) {
const typeColumn = animal.default_type_id ? 'default_type_id' : 'custom_type_id';
const typeId = animal.type_name
? typeIdsMap[animal.type_name]
: animal.default_type_id || animal.custom_type_id;
const typeBreedKey = `${typeColumn}_${typeId}_${animal.breed_name}`;
let breedId = typeBreedIdsMap[typeBreedKey];

if (!breedId) {
const newBreed = await baseController.postWithResponse(
CustomAnimalBreedModel,
{ farm_id, [typeColumn]: typeId, breed: animal.breed_name },
req,
{ trx },
);
breedId = newBreed.id;
typeBreedIdsMap[typeBreedKey] = breedId;
}
animal.custom_breed_id = breedId;
delete animal.breed_name;
}
await checkAndAddCustomTypeAndBreed(
req,
typeIdsMap,
typeBreedIdsMap,
animal,
farm_id,
trx,
);

await upsertGroup(req, animal, farm_id, trx);

// Remove farm_id if it happens to be set in animal object since it should be obtained from header
delete animal.farm_id;

const groupName = checkAndTrimString(animal.group_name);
delete animal.group_name;

const individualAnimalResult = await baseController.postWithResponse(
const individualAnimalResult = await baseController.insertGraphWithResponse(
AnimalModel,
{ ...animal, farm_id },
req,
{ trx },
);

const groupIds = [];
if (groupName) {
let group = await baseController.existsInTable(trx, AnimalGroupModel, {
name: groupName,
farm_id,
deleted: false,
});

if (!group) {
group = await baseController.postWithResponse(
AnimalGroupModel,
{ name: groupName, farm_id },
req,
{ trx },
);
}

groupIds.push(group.id);

// Insert into join table
await AnimalGroupRelationshipModel.query(trx).insert({
animal_id: individualAnimalResult.id,
animal_group_id: group.id,
});
}

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;
// Format group_ids
individualAnimalResult.group_ids =
individualAnimalResult.group_ids?.map((group) => group.animal_group_id) || [];

result.push(individualAnimalResult);
}

await trx.commit();

await assignInternalIdentifiers(result, 'animal');

return res.status(201).send(result);
} catch (error) {
await handleObjectionError(error, res, trx);
}
};
},
editAnimals() {
return async (req, res) => {
const trx = await transaction.start(Model.knex());

try {
const { farm_id } = req.headers;
// Create utility object used in type and breed
const typeIdsMap = {};
const typeBreedIdsMap = {};

const desiredKeys = [
'id',
'custom_breed_id',
'custom_type_id',
'default_breed_id',
'default_type_id',
'sex_id',
'name',
'birth_date',
'identifier',
'identifier_color_id',
'identifier_placement_id',
'identifier_type_id',
'identifier_type_other',
'origin_id',
'dam',
'sire',
'brought_in_date',
'weaning_date',
'notes',
'photo_url',
'organic_status',
'supplier',
'price',
'sex_detail',
'origin_id',
'group_ids',
'animal_use_relationships',
];

// select only allowed properties to edit
for (const animal of req.body) {
await checkAndAddCustomTypeAndBreed(
req,
typeIdsMap,
typeBreedIdsMap,
animal,
farm_id,
trx,
);

await upsertGroup(req, animal, farm_id, trx);

const keysExisting = desiredKeys.filter((key) => key in animal);
const data = _pick(animal, keysExisting);

await baseController.upsertGraph(AnimalModel, data, req, { trx });
}

await trx.commit();
// Do not send result revalidate using tags on frontend
return res.status(204).send();
} catch (error) {
handleObjectionError(error, res, trx);
}
};
},
removeAnimals() {
return async (req, res) => {
const trx = await transaction.start(Model.knex());
Expand Down
Loading
Loading