Skip to content

Commit

Permalink
[feature] load and select loan manager to form (#770)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bernardo Vieira authored Jul 25, 2023
1 parent 312a9ac commit c806447
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 28 deletions.
4 changes: 2 additions & 2 deletions packages/api/src/controllers/v2/microcredit/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
};
Expand Down
11 changes: 10 additions & 1 deletion packages/api/src/controllers/v2/microcredit/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));
};
Expand Down Expand Up @@ -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 };
8 changes: 7 additions & 1 deletion packages/api/src/routes/v2/microcredit/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions packages/api/src/routes/v2/microcredit/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
4 changes: 3 additions & 1 deletion packages/api/src/validators/microcredit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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) => {},
};
9 changes: 7 additions & 2 deletions packages/core/src/database/models/microCredit/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export class MicroCreditApplicationModel extends Model<MicroCreditApplication, M
public id!: number;
public userId!: number;
public form!: object;
public selectedLoanManagerId!: number;
public prismicId!: string;
public amount!: number;
public period!: number;
Expand Down Expand Up @@ -41,11 +42,15 @@ export function initializeMicroCreditApplication(sequelize: Sequelize): typeof M
},
form: {
type: DataTypes.JSONB,
allowNull: false
allowNull: true
},
selectedLoanManagerId: {
type: DataTypes.INTEGER,
allowNull: true
},
prismicId: {
type: DataTypes.STRING(32),
allowNull: false
allowNull: true
},
amount: {
allowNull: true,
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/interfaces/app/appNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export enum NotificationType {
COMMUNITY_CREATED,
LOAN_ADDED,
LEARN_AND_EARN_DO_NEW_LESSON,
LOAN_STATUS_CHANGED
LOAN_STATUS_CHANGED,
NEW_LOAN_SUBMITTED
}

export interface AppNotification {
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/interfaces/microCredit/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface MicroCreditApplication {
id: number;
userId: number;
form: object;
selectedLoanManagerId: number;
prismicId: string;
amount: number;
period: number;
Expand All @@ -15,8 +16,9 @@ export interface MicroCreditApplication {

export interface MicroCreditApplicationCreation {
userId: number;
form: object;
prismicId: string;
form?: object;
selectedLoanManagerId?: number;
prismicId?: string;
amount?: number;
period?: number;
status?: number;
Expand Down
42 changes: 33 additions & 9 deletions packages/core/src/services/microcredit/create.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { AppUserModel } from '../../database/models/app/appUser';
import { BaseError } from '../../utils';
import { MicroCreditApplication, MicroCreditApplicationStatus } from '../../interfaces/microCredit/applications';
import {
MicroCreditApplication,
MicroCreditApplicationCreation,
MicroCreditApplicationStatus
} from '../../interfaces/microCredit/applications';
import { MicroCreditContentStorage } from '../../services/storage';
import { NotificationType } from '../../interfaces/app/appNotification';
import { Op, Transaction } from 'sequelize';
import { NullishPropertiesOf } from 'sequelize/types/utils';
import { Op, Optional, Transaction } from 'sequelize';
import { config } from '../../..';
import { models } from '../../database';
import { sendEmail } from '../../services/email';
Expand Down Expand Up @@ -147,25 +152,43 @@ export default class MicroCreditCreate {
userId: number,
form: object,
prismicId: string,
selectedLoanManagerId: number | undefined,
submitted: boolean
): Promise<MicroCreditApplication> => {
try {
const status = submitted ? MicroCreditApplicationStatus.PENDING : MicroCreditApplicationStatus.DRAFT;
let defaults: Optional<
MicroCreditApplicationCreation,
NullishPropertiesOf<MicroCreditApplicationCreation>
> = { 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
}

Expand All @@ -178,6 +201,7 @@ export default class MicroCreditCreate {

const data = await userForm.update({
form: newForm,
selectedLoanManagerId,
status
});

Expand Down
50 changes: 43 additions & 7 deletions packages/core/src/services/microcredit/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -311,7 +314,7 @@ export default class MicroCreditList {
}[];
}> => {
const [orderKey, orderDirection] = query.orderBy ? query.orderBy.split(':') : [undefined, undefined];
const where: WhereOptions<MicroCreditApplication> = {};
const where: WhereOptions<MicroCreditApplication> = { selectedLoanManagerId: userId };
if (query.status !== undefined) {
where.status = query.status;
} else {
Expand Down Expand Up @@ -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());
};
}

0 comments on commit c806447

Please sign in to comment.