From c8064474d5a2bc2e3b86bb6934d6c5a31b2fdec3 Mon Sep 17 00:00:00 2001 From: Bernardo Vieira Date: Tue, 25 Jul 2023 15:54:51 +0100 Subject: [PATCH] [feature] load and select loan manager to form (#770) --- .../src/controllers/v2/microcredit/create.ts | 4 +- .../src/controllers/v2/microcredit/list.ts | 11 +++- .../api/src/routes/v2/microcredit/create.ts | 8 ++- .../api/src/routes/v2/microcredit/list.ts | 22 ++++++++ packages/api/src/validators/microcredit.ts | 4 +- ...115751-create-micro-credit-applications.js | 8 ++- ...725123444-update-microcreditApplication.js | 24 +++++++++ .../models/microCredit/applications.ts | 9 +++- .../src/interfaces/app/appNotification.ts | 3 +- .../interfaces/microCredit/applications.ts | 6 ++- .../core/src/services/microcredit/create.ts | 42 ++++++++++++---- .../core/src/services/microcredit/list.ts | 50 ++++++++++++++++--- 12 files changed, 163 insertions(+), 28 deletions(-) create mode 100644 packages/core/src/database/migrations/z20230725123444-update-microcreditApplication.js diff --git a/packages/api/src/controllers/v2/microcredit/create.ts b/packages/api/src/controllers/v2/microcredit/create.ts index 50cc427f6..e9381e362 100644 --- a/packages/api/src/controllers/v2/microcredit/create.ts +++ b/packages/api/src/controllers/v2/microcredit/create.ts @@ -97,10 +97,10 @@ class MicroCreditController { } const form = req.body.form; - const { submit, prismicId } = req.body; + const { submit, prismicId, selectedLoanManagerId } = req.body; this.microCreditService - .saveForm(req.user.userId, form, prismicId, !!submit) + .saveForm(req.user.userId, form, prismicId, selectedLoanManagerId, !!submit) .then(community => standardResponse(res, 201, true, community)) .catch(e => standardResponse(res, 400, false, '', { error: e })); }; diff --git a/packages/api/src/controllers/v2/microcredit/list.ts b/packages/api/src/controllers/v2/microcredit/list.ts index 226875990..daf61f377 100644 --- a/packages/api/src/controllers/v2/microcredit/list.ts +++ b/packages/api/src/controllers/v2/microcredit/list.ts @@ -46,7 +46,7 @@ class MicroCreditController { return; } this.microCreditService - .listApplications(req.query) + .listApplications(req.user.userId, req.query) .then(r => standardResponse(res, 200, true, r)) .catch(e => standardResponse(res, 400, false, '', { error: e })); }; @@ -110,6 +110,15 @@ class MicroCreditController { .then(r => standardResponse(res, 200, true, r)) .catch(e => standardResponse(res, 400, false, '', { error: e })); }; + + getLoanManagersByCountry = (req: RequestWithUser, res: Response) => { + const country = req.params.country as string; + + this.microCreditService + .getLoanManagersByCountry(country) + .then(r => standardResponse(res, 200, true, r)) + .catch(e => standardResponse(res, 400, false, '', { error: e })); + }; } export { MicroCreditController }; diff --git a/packages/api/src/routes/v2/microcredit/create.ts b/packages/api/src/routes/v2/microcredit/create.ts index bb8cd69dd..977752ad8 100644 --- a/packages/api/src/routes/v2/microcredit/create.ts +++ b/packages/api/src/routes/v2/microcredit/create.ts @@ -141,7 +141,13 @@ export default (route: Router): void => { * properties: * form: * type: object - * required: true + * required: false + * prismicId: + * type: string + * required: false + * selectedLoanManagerId: + * type: number + * required: false * submit: * type: boolean * required: false diff --git a/packages/api/src/routes/v2/microcredit/list.ts b/packages/api/src/routes/v2/microcredit/list.ts index 4237cee04..a37ce382d 100644 --- a/packages/api/src/routes/v2/microcredit/list.ts +++ b/packages/api/src/routes/v2/microcredit/list.ts @@ -263,4 +263,26 @@ export default (route: Router): void => { * - BearerToken: [] */ route.get('/form/:id', authenticateToken, controller.getUserForm); + + /** + * @swagger + * + * /microcredit/managers/{country}: + * get: + * tags: + * - "microcredit" + * summary: "Get Microcredit managers by country" + * description: "Get Microcredit managers by country" + * parameters: + * - in: path + * name: country + * schema: + * type: string + * required: true + * description: country tag (eg. NG, KE, GH, etc.) + * responses: + * "200": + * description: OK + */ + route.get('/managers/:country', controller.getLoanManagersByCountry); }; diff --git a/packages/api/src/validators/microcredit.ts b/packages/api/src/validators/microcredit.ts index a2d63f646..5f6e51143 100644 --- a/packages/api/src/validators/microcredit.ts +++ b/packages/api/src/validators/microcredit.ts @@ -159,9 +159,11 @@ const saveForm = celebrate({ body: defaultSchema .object({ prismicId: Joi.string().required(), - form: Joi.object().required(), + form: Joi.object().optional(), + selectedLoanManagerId: Joi.number().optional(), submit: Joi.bool().optional() }) + .or('selectedLoanManagerId', 'form') .required() }); const addNote = celebrate({ diff --git a/packages/core/src/database/migrations/z20230609115751-create-micro-credit-applications.js b/packages/core/src/database/migrations/z20230609115751-create-micro-credit-applications.js index 365344542..070f9c2a3 100644 --- a/packages/core/src/database/migrations/z20230609115751-create-micro-credit-applications.js +++ b/packages/core/src/database/migrations/z20230609115751-create-micro-credit-applications.js @@ -20,11 +20,15 @@ module.exports = { }, form: { type: Sequelize.JSONB, - allowNull: false + allowNull: true + }, + selectedLoanManagerId: { + type: Sequelize.INTEGER, + allowNull: true }, prismicId: { type: Sequelize.STRING(32), - allowNull: false + allowNull: true }, amount: { allowNull: true, diff --git a/packages/core/src/database/migrations/z20230725123444-update-microcreditApplication.js b/packages/core/src/database/migrations/z20230725123444-update-microcreditApplication.js new file mode 100644 index 000000000..4fb334e7f --- /dev/null +++ b/packages/core/src/database/migrations/z20230725123444-update-microcreditApplication.js @@ -0,0 +1,24 @@ +'use strict'; + +// eslint-disable-next-line no-undef +module.exports = { + async up(queryInterface, Sequelize) { + if (process.env.NODE_ENV === 'test') { + return; + } + + await queryInterface.addColumn('microcredit_applications', 'selectedLoanManagerId', { + type: Sequelize.INTEGER, + allowNull: true + }); + await queryInterface.changeColumn('microcredit_applications', 'form', { + type: Sequelize.JSONB, + allowNull: true + }); + await queryInterface.changeColumn('microcredit_applications', 'prismicId', { + type: Sequelize.STRING(32), + allowNull: true + }); + }, + down: (queryInterface) => {}, +}; diff --git a/packages/core/src/database/models/microCredit/applications.ts b/packages/core/src/database/models/microCredit/applications.ts index 9d4bf6ed7..b9dee714d 100644 --- a/packages/core/src/database/models/microCredit/applications.ts +++ b/packages/core/src/database/models/microCredit/applications.ts @@ -8,6 +8,7 @@ export class MicroCreditApplicationModel extends Model => { try { const status = submitted ? MicroCreditApplicationStatus.PENDING : MicroCreditApplicationStatus.DRAFT; + let defaults: Optional< + MicroCreditApplicationCreation, + NullishPropertiesOf + > = { userId, status }; + // add fields only if they are not undefined + // since it fails to have undefined as default + if (form) { + defaults = { ...defaults, form }; + } + if (selectedLoanManagerId) { + defaults = { ...defaults, selectedLoanManagerId }; + } + if (prismicId) { + defaults = { ...defaults, prismicId }; + } const [userForm, created] = await models.microCreditApplications.findOrCreate({ where: { userId, prismicId }, - defaults: { - form, - userId, - prismicId, - status - } + defaults }); if (status === MicroCreditApplicationStatus.PENDING) { - // TODO: notify loan manager! + // notify loan manager! + models.appUser + .findOne({ + where: { + id: userForm.selectedLoanManagerId + } + }) + .then(user => user && sendNotification([user], NotificationType.NEW_LOAN_SUBMITTED, true, true)); // TODO: send email to user with form } @@ -178,6 +201,7 @@ export default class MicroCreditCreate { const data = await userForm.update({ form: newForm, + selectedLoanManagerId, status }); diff --git a/packages/core/src/services/microcredit/list.ts b/packages/core/src/services/microcredit/list.ts index 002d2ec20..27abff979 100644 --- a/packages/core/src/services/microcredit/list.ts +++ b/packages/core/src/services/microcredit/list.ts @@ -287,12 +287,15 @@ export default class MicroCreditList { }; // read application from models.microCreditApplications, including appUser to include profile - public listApplications = async (query: { - offset?: number; - limit?: number; - status?: number; - orderBy?: 'appliedOn' | 'appliedOn:asc' | 'appliedOn:desc'; - }): Promise<{ + public listApplications = async ( + userId: number, + query: { + offset?: number; + limit?: number; + status?: number; + orderBy?: 'appliedOn' | 'appliedOn:asc' | 'appliedOn:desc'; + } + ): Promise<{ count: number; rows: { // from models.appUser @@ -311,7 +314,7 @@ export default class MicroCreditList { }[]; }> => { const [orderKey, orderDirection] = query.orderBy ? query.orderBy.split(':') : [undefined, undefined]; - const where: WhereOptions = {}; + const where: WhereOptions = { selectedLoanManagerId: userId }; if (query.status !== undefined) { where.status = query.status; } else { @@ -705,4 +708,37 @@ export default class MicroCreditList { throw new utils.BaseError('NOT_ALLOWED', 'should be a loanManager, councilMember, ambassador or form owner'); }; + + public getLoanManagersByCountry = async (country: string) => { + let loanManagers: number[] = []; + + // TODO: this is hardcoded for now, but we should have a better way to do this + if (config.jsonRpcUrl.indexOf('alfajores') !== -1) { + loanManagers = [5700, 5801]; + } else { + switch (country.toLowerCase()) { + case 'br': + loanManagers = [12928, 106251]; + break; + case 'ug': + loanManagers = [30880, 106251]; + break; + case 'ng': + case 've': + loanManagers = [106251]; + break; + } + } + + const users = await models.appUser.findAll({ + attributes: ['id', 'address', 'firstName', 'lastName', 'avatarMediaPath'], + where: { + id: { + [Op.in]: loanManagers + } + } + }); + + return users.map(u => u.toJSON()); + }; }