diff --git a/components/studentComboBox.vue b/components/studentComboBox.vue index 09fa64a4..32de290d 100644 --- a/components/studentComboBox.vue +++ b/components/studentComboBox.vue @@ -54,12 +54,44 @@ @blur="onCrgBlur" >
- Componentes Pendentes: - + + {{ !canEdit ? 'Verificar Pendências' : 'Editar Pendências' }} + + +
+
+ +

Pendências

+
+
+
+ + + + + + + +
{{ subject.name }} + +
+ +
+
+
+

@@ -242,12 +274,14 @@ export default { ataCheck: false, laudaCheck: false, presCheck: false, + showPendencies: false, ataDocument: {}, laudaDocument: {}, presDocument: {}, uploadFile: File, crg: '', - pendencies: '', + totalSubjects: [], + studentSubjects: [], studentData: Object.assign({}, this.student), isLoading: false, defenseDate: new Date() @@ -319,6 +353,12 @@ export default { ) ) : null + this.$axios + .get(`/api/students/${this.student.id}/pendencies`) + .then(response => { + this.studentSubjects = response.data.map(pendency => pendency.subjectId) + }) + .catch(e => this.openErrorNotification(e)) }, methods: { @@ -375,6 +415,40 @@ export default { toggleEdit() { this.canEdit = !this.canEdit }, + getPendencies() { + this.$axios + .get(`/api/subjects`, { + params: { + paginate: 0 + } + }) + .then(response => { + this.totalSubjectsLength = response.headers['pagination-row-count'] + this.totalSubjects = response.data + this.showPendencies = true + }) + .catch(e => { + this.showPendencies = false + this.openErrorNotification(e) + }) + }, + updatePendencies() { + if (this.canEdit) { + this.$axios + .post( + `/api/students/${this.student.id}/pendencies/batch`, + this.studentSubjects + ) + .then(response => { + this.$toast.open({ + message: 'Pendências de aluno atualizadas com sucesso', + type: 'is-success' + }) + }) + .catch(e => this.openErrorNotification(e)) + } + this.showPendencies = false + }, mapDocuments(documents) { documents.forEach(element => { if (element.type === 1) { @@ -478,4 +552,13 @@ export default { .icon { margin-left: 1em; } + +.scrollable { + overflow-y: scroll; +} + +.bottom-sticky { + bottom: 0; + position: sticky; +} diff --git a/migrations/20190605024900_pendencies.js b/migrations/20190605024900_pendencies.js new file mode 100644 index 00000000..3b635ba8 --- /dev/null +++ b/migrations/20190605024900_pendencies.js @@ -0,0 +1,26 @@ +exports.up = function(knex, Promise) { + return knex.schema.createTable('pendencies', table => { + table.increments('id').primary() + table + .integer('studentId') + .unsigned() + .notNullable() + table + .foreign('studentId') + .references('id') + .inTable('students') + table + .integer('subjectId') + .unsigned() + .notNullable() + table + .foreign('subjectId') + .references('id') + .inTable('subjects') + table.unique(['studentId', 'subjectId']) + }) +} + +exports.down = function(knex, Promise) { + return knex.schema.dropTable('pendencies') +} diff --git a/seeds/pendencies.js b/seeds/pendencies.js new file mode 100644 index 00000000..49d44cef --- /dev/null +++ b/seeds/pendencies.js @@ -0,0 +1,39 @@ +const Pendency = require('../server/models/Pendency') + +exports.seed = async function(knex, Promise) { + // Deletes ALL existing entries + await knex('pendencies').del() + + const data = [ + { + studentId: 1, + subjectId: 1 + }, + { + studentId: 1, + subjectId: 2 + }, + { + studentId: 1, + subjectId: 3 + }, + { + studentId: 2, + subjectId: 1 + }, + { + studentId: 3, + subjectId: 1 + }, + { + studentId: 3, + subjectId: 3 + }, + { + studentId: 5, + subjectId: 6 + } + ] + + return Promise.all(data.map(pendency => Pendency.forge(pendency).save())) +} diff --git a/server/controllers/pendencies/FromBatch.js b/server/controllers/pendencies/FromBatch.js new file mode 100644 index 00000000..20184a40 --- /dev/null +++ b/server/controllers/pendencies/FromBatch.js @@ -0,0 +1,41 @@ +const errors = require('../../../shared/errors') +const Students = require('../../models/Student') +const Pendencies = require('../../models/Pendency') +const { knex } = require('../../db') + +module.exports = async function pendenciesFromBatch(ctx) { + const { studentId } = ctx.params + const subjectsIdsReceived = ctx.request.body + + if ((await Students.where('id', studentId).count()) === 0) { + ctx.status = 404 + ctx.body = { code: errors.NOT_FOUND } + return + } + + await knex.transaction(async trx => { + const pendenciesExisting = await Pendencies.where({ studentId }).fetchAll({ + transacting: trx + }) + + const subjectsIdsExisting = pendenciesExisting.map(pend => + pend.get('subjectId') + ) + + const addPendencies = subjectsIdsReceived.filter( + id => !subjectsIdsExisting.includes(id) + ) + const delPendencies = pendenciesExisting.filter( + pendency => !subjectsIdsReceived.includes(pendency.get('subjectId')) + ) + const addPromises = addPendencies.map(subjectId => + new Pendencies({ subjectId, studentId }).save(null, { transacting: trx }) + ) + const delPromises = delPendencies.map(({ id }) => + Pendencies.where({ id }).destroy({ transacting: trx }) + ) + return Promise.all(addPromises.concat(delPromises)) + }) + + ctx.body = await Pendencies.where({ studentId }).fetchAll() +} diff --git a/server/controllers/pendencies/List.js b/server/controllers/pendencies/List.js new file mode 100644 index 00000000..77527c19 --- /dev/null +++ b/server/controllers/pendencies/List.js @@ -0,0 +1,16 @@ +const errors = require('../../../shared/errors') + +const Students = require('../../models/Student') +const Pendencies = require('../../models/Pendency') + +module.exports = async function listPendencies(ctx) { + const { studentId } = ctx.params + + if ((await Students.where('id', studentId).count()) === 0) { + ctx.status = 404 + ctx.body = { code: errors.NOT_FOUND } + return + } + + ctx.body = await Pendencies.where({ studentId }).fetchAll() +} diff --git a/server/controllers/pendencies/Show.js b/server/controllers/pendencies/Show.js new file mode 100644 index 00000000..2f48e645 --- /dev/null +++ b/server/controllers/pendencies/Show.js @@ -0,0 +1,24 @@ +const errors = require('../../../shared/errors') + +const Students = require('../../models/Student') +const Pendencies = require('../../models/Pendency') + +module.exports = async function showPendency(ctx) { + const { studentId, id } = ctx.params + + if ((await Students.where('id', studentId).count()) === 0) { + ctx.status = 404 + ctx.body = { code: errors.NOT_FOUND } + return + } + + const pendencyFind = await Pendencies.where({ id }).fetch() + + if (pendencyFind === null) { + ctx.status = 404 + ctx.body = { code: errors.NOT_FOUND } + return + } + + ctx.body = pendencyFind +} diff --git a/server/controllers/pendencies/index.js b/server/controllers/pendencies/index.js new file mode 100644 index 00000000..21b99a3c --- /dev/null +++ b/server/controllers/pendencies/index.js @@ -0,0 +1,5 @@ +const Show = require('./Show') +const List = require('./List') +const FromBatch = require('./FromBatch') + +module.exports = { Show, List, FromBatch } diff --git a/server/controllers/subjects/List.js b/server/controllers/subjects/List.js index 2bd2b185..9f796f95 100644 --- a/server/controllers/subjects/List.js +++ b/server/controllers/subjects/List.js @@ -2,6 +2,10 @@ const Subject = require('../../models/Subject') const utils = require('../../utils') module.exports = async function listSubjects(ctx) { - const { page = 1 } = ctx.request.query + const { page = 1, paginate } = ctx.request.query + if (paginate !== undefined && paginate === '0') { + ctx.body = await Subject.fetchAll() + return + } utils.paginateContext(ctx, await Subject.fetchPage({ page })) } diff --git a/server/models/Pendency.js b/server/models/Pendency.js new file mode 100644 index 00000000..3dda31e4 --- /dev/null +++ b/server/models/Pendency.js @@ -0,0 +1,7 @@ +const { bookshelf } = require('../db') + +const Pendency = bookshelf.model('Pendency', { + tableName: 'pendencies' +}) + +module.exports = Pendency diff --git a/server/router.js b/server/router.js index 9440af96..a3ce3f37 100644 --- a/server/router.js +++ b/server/router.js @@ -10,6 +10,7 @@ const students = require('./controllers/students') const subjects = require('./controllers/subjects') const auth = require('./controllers/auth') const solicitations = require('./controllers/solicitations') +const pendencies = require('./controllers/pendencies') const router = new Router() const api = new Router({ prefix: '/api' }) @@ -39,12 +40,23 @@ api.put( students.UpdateAcademicHighlight ) api.put('/students/:id', bodyJson, students.Update) + // Documents Routes api.get('/students/:studentId/documents', documents.List) api.get('/students/:studentId/documents/:id', documents.Show) api.get('/students/:studentId/documents/:id/view', documents.View) api.post('/students/:studentId/documents', bodyMultipart, documents.Upload) api.post('/students/from-csv', bodyMultipart, students.FromCsv) + +// Pendencies Routes +api.get('/students/:studentId/pendencies/:id', pendencies.Show) +api.get('/students/:studentId/pendencies/', pendencies.List) +api.post( + '/students/:studentId/pendencies/batch', + bodyJson, + pendencies.FromBatch +) + // Subjects Routes api.get('/subjects/', subjects.List) api.get('/subjects/:id', subjects.Show) diff --git a/test/server/controllers/pendencies.spec.js b/test/server/controllers/pendencies.spec.js new file mode 100644 index 00000000..2f2a55ac --- /dev/null +++ b/test/server/controllers/pendencies.spec.js @@ -0,0 +1,113 @@ +/** + * @jest-environment node + */ + +const chai = require('chai') +const chaiHttp = require('chai-http') +chai.use(chaiHttp) + +const testUtils = require('../test-utils') +const server = require('../../../server') +const db = require('../../../server/db') +const errors = require('../../../shared/errors') + +jest.useFakeTimers() +describe('/api/students/:id/pendencies/', () => { + beforeEach(async () => { + await db.knex.migrate.rollback() + await db.knex.migrate.latest() + await db.knex.seed.run() + }, 100000) + + test('GET /students/1/pendencies/', async done => { + const { token } = await testUtils.user('admin') + const res = await chai + .request(server.listen()) + .get('/api/students/1/pendencies/') + .set('Authorization', `Bearer ${token}`) + expect(res.body).toBeDefined() + expect(res.type).toEqual('application/json') + expect(res.status).toEqual(200) + expect(res.body.length).toEqual(3) + expect(res.body[0].studentId).toEqual(1) + expect(res.body[0].subjectId).toEqual(1) + expect(res.body[1].studentId).toEqual(1) + expect(res.body[1].subjectId).toEqual(2) + expect(res.body[2].studentId).toEqual(1) + expect(res.body[2].subjectId).toEqual(3) + done() + }) + + test('GET /students/1/pendencies/1', async done => { + const { token } = await testUtils.user('admin') + const res = await chai + .request(server.listen()) + .get('/api/students/1/pendencies/1') + .set('Authorization', `Bearer ${token}`) + expect(res.body).toBeDefined() + expect(res.type).toEqual('application/json') + expect(res.status).toEqual(200) + expect(res.body.subjectId).toEqual(1) + expect(res.body.studentId).toEqual(1) + done() + }) + + test('GET /students/1000/pendencies/1 NOT FOUND STUDENT', async done => { + const { token } = await testUtils.user('admin') + const res = await chai + .request(server.listen()) + .get('/api/students/1000/pendencies/1') + .set('Authorization', `Bearer ${token}`) + expect(res.body).toBeDefined() + expect(res.type).toEqual('application/json') + expect(res.status).toEqual(404) + expect(res.body.code).toEqual(errors.NOT_FOUND) + done() + }) + + test('GET /students/1/pendencies/1000 NOT FOUND PENDENCY', async done => { + const { token } = await testUtils.user('admin') + const res = await chai + .request(server.listen()) + .get('/api/students/1/pendencies/1000') + .set('Authorization', `Bearer ${token}`) + expect(res.body).toBeDefined() + expect(res.type).toEqual('application/json') + expect(res.status).toEqual(404) + expect(res.body.code).toEqual(errors.NOT_FOUND) + done() + }) + + test('GET /students/4/pendencies/ NO PENDENCIES', async done => { + const { token } = await testUtils.user('admin') + const res = await chai + .request(server.listen()) + .get('/api/students/4/pendencies/') + .set('Authorization', `Bearer ${token}`) + expect(res.body).toBeDefined() + expect(res.type).toEqual('application/json') + expect(res.status).toEqual(200) + expect(res.body.length).toEqual(0) + done() + }) + + test('POST /students/1/pendencies/batch PENDENCIES FROM BATCH', async done => { + const { token } = await testUtils.user('admin') + const res = await chai + .request(server.listen()) + .post('/api/students/1/pendencies/batch') + .set('Authorization', `Bearer ${token}`) + .send([2, 3, 4]) + expect(res.body).toBeDefined() + expect(res.type).toEqual('application/json') + expect(res.status).toEqual(200) + expect(res.body.length).toEqual(3) + expect(res.body[0].studentId).toEqual(1) + expect(res.body[0].subjectId).toEqual(2) + expect(res.body[1].studentId).toEqual(1) + expect(res.body[1].subjectId).toEqual(3) + expect(res.body[2].studentId).toEqual(1) + expect(res.body[2].subjectId).toEqual(4) + done() + }) +}) diff --git a/test/server/controllers/subjects.spec.js b/test/server/controllers/subjects.spec.js index a43a8439..2426d164 100644 --- a/test/server/controllers/subjects.spec.js +++ b/test/server/controllers/subjects.spec.js @@ -121,4 +121,21 @@ describe('/api/subjects', () => { expect(res.status).toEqual(422) done() }) + + test('GET /subjects?paginate=0 ALL SUBJECTS', async done => { + const { token } = await testUtils.user('admin') + const res = await chai + .request(server.listen()) + .get('/api/subjects?paginate=0') + .set('Authorization', `Bearer ${token}`) + expect(res.body).toBeDefined() + expect(res.type).toEqual('application/json') + expect(res.status).toEqual(200) + expect(res.body.length).toEqual(96) + expect(res.body[0].name).toEqual('ALGEBRA LINEAR PARA COMPUTACAO') + expect(res.body[95].name).toEqual( + '(ELETIVA) DISCIPLINA ELETIVA - SISTEMAS DE INFORMACAO' + ) + done() + }) }) diff --git a/test/server/models/pendencies.spec.js b/test/server/models/pendencies.spec.js new file mode 100644 index 00000000..6b22510f --- /dev/null +++ b/test/server/models/pendencies.spec.js @@ -0,0 +1,36 @@ +/** + * @jest-environment node + */ + +const chai = require('chai') +const chaiHttp = require('chai-http') +chai.use(chaiHttp) + +const db = require('../../../server/db') +const Pendency = require('../../../server/models/Pendency') + +jest.useFakeTimers() + +describe('/api/students/:idStudent/pendencies', () => { + beforeEach(async done => { + await db.knex.migrate.rollback() + await db.knex.migrate.latest() + await db.knex.seed.run() + done() + }, 100000) + + afterEach(() => { + return db.knex.migrate.rollback() + }) + + test('Create a Pendency', async done => { + const pendency = await Pendency.forge({ + studentId: 1, + subjectId: 10 + }).save() + expect(pendency.id).toBeDefined() + expect(pendency.attributes.studentId).toEqual(1) + expect(pendency.attributes.subjectId).toEqual(10) + done() + }) +})