Created
December 7, 2022 12:15
-
-
Save heroic/53e92859b490bf9ce20133be536bd64d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { | |
request, | |
rabbitmq: { publish }, | |
sequelize, | |
GcError, | |
} = require('@galaxycard/utils'); | |
const { Sequelize } = sequelize; | |
const Op = Sequelize.Op; | |
const { | |
hasUserEnoughContacts, | |
approvedReferenceContacts, | |
} = require('./contact'); | |
const PERSONAL_DATA_FIELDS = [ | |
'income_type', | |
'company_name', | |
'income_range', | |
'marital_status', | |
'kids', | |
'education', | |
'work_experience', | |
'residence', | |
'fathers_name', | |
'company_type', | |
]; | |
// waitlisted is a status that for automatic approval, is same as processed | |
const PROCESSED_APPLICATION_STATUSES = [ | |
'approved', | |
'rejected', | |
'archived', | |
'amit_user', | |
'usha_user', | |
'banned', | |
'apollo_user', | |
]; | |
const Application = sequelize.define( | |
'applications', | |
{ | |
user_id: { | |
type: Sequelize.BIGINT, | |
}, | |
status: { | |
type: Sequelize.STRING, | |
}, | |
from_where: { | |
type: Sequelize.STRING, | |
}, | |
active: { | |
type: Sequelize.BOOLEAN, | |
}, | |
income_type: { | |
type: Sequelize.STRING, | |
}, | |
income_range: { | |
type: Sequelize.BIGINT, | |
}, | |
marital_status: { | |
type: Sequelize.STRING, | |
}, | |
kids: { | |
type: Sequelize.STRING, | |
}, | |
experiment_id: { | |
type: Sequelize.BIGINT, | |
}, | |
picture_id: { | |
type: Sequelize.STRING, | |
}, | |
residence: { | |
type: Sequelize.STRING, | |
}, | |
education: { | |
type: Sequelize.STRING, | |
}, | |
company_type: { | |
type: Sequelize.STRING, | |
}, | |
company_name: { | |
type: Sequelize.STRING, | |
}, | |
work_experience: { | |
type: Sequelize.INTEGER, | |
}, | |
fathers_name: { | |
type: Sequelize.STRING, | |
}, | |
fee_paid: { | |
type: Sequelize.INTEGER, | |
}, | |
product: { | |
type: Sequelize.STRING, | |
}, | |
dob: { | |
type: Sequelize.DATE, | |
}, | |
rejected_at: { | |
type: Sequelize.DATE, | |
}, | |
created_at: { | |
type: Sequelize.DATE, | |
defaultValue: Sequelize.fn('NOW'), | |
allowNull: false, | |
}, | |
admin_user: { | |
type: Sequelize.VIRTUAL, | |
}, | |
waitlisted: { | |
type: Sequelize.BIGINT, | |
}, | |
}, | |
{ | |
timestamps: false, | |
underscored: true, | |
scopes: { | |
processed: { | |
where: { | |
status: { | |
[Op.in]: PROCESSED_APPLICATION_STATUSES, | |
}, | |
}, | |
}, | |
unprocessed: { | |
where: { | |
status: { | |
[Op.notIn]: PROCESSED_APPLICATION_STATUSES, | |
}, | |
}, | |
}, | |
}, | |
//if changes made in status then add new to application_status_logs with app id, status, admin user | |
hooks: { | |
afterSave: async function (application) { | |
if (application.changed('status')) { | |
await sequelize.models.application_status_logs.create({ | |
application_id: application.id, | |
status: application.status, | |
admin_user: application.admin_user, | |
}); | |
} | |
}, | |
//if status was changed to rejected add time to rejected_at field | |
beforeSave: async function (application) { | |
if ( | |
application.changed('status') && | |
application.status === 'rejected' | |
) { | |
application.rejected_at = Sequelize.fn('NOW'); | |
} | |
}, | |
afterCreate: async function (application) { | |
await application.updateToNextStatus(); | |
}, | |
afterUpdate: async function (application, options) { | |
let args = { | |
application_id: application.id, | |
user_id: application.user_id, | |
}; | |
if (application.changed('product')) { | |
args.product = application.product; | |
} | |
if (options.transaction) { | |
return options.transaction.afterCommit(() => { | |
return publish( | |
{ | |
name: 'application_updated', | |
args, | |
}, | |
`documentation.application.${application.status}`, | |
{ | |
priority: 10, | |
}, | |
); | |
}); | |
} else { | |
return publish( | |
{ | |
name: 'application_updated', | |
args, | |
}, | |
`documentation.application.${application.status}`, | |
{ | |
priority: 10, | |
}, | |
); | |
} | |
}, | |
}, | |
}, | |
); | |
Application.CIC_FEE = 5000000; | |
Application.PERSONAL_DATA_FIELDS = PERSONAL_DATA_FIELDS; | |
Application.prototype.getProcessingFeeAndLimit = function () { | |
const limit = 1000000; | |
return { | |
amount: this.getLimitExtensionFee( | |
limit + (this.id % 2 === 0 ? 100000 : 250000), | |
), | |
limit, | |
}; | |
}; | |
Application.prototype.getLimitExtensionFee = function (amount) { | |
return Math.ceil((amount * 1.0236) / 10000) * 10000; | |
}; | |
Application.prototype.findMissingPersonalDataFields = function (fields) { | |
const personal_data = {}; | |
for (let i = 0, len = fields.length; i < len; i++) { | |
if (this[fields[i]] === null) { | |
personal_data[fields[i]] = this[fields[i]]; | |
} | |
} | |
return personal_data; | |
}; | |
Application.prototype.evaluateApplicationCreditEligibility = async function ( | |
pan, | |
) { | |
let creditReport = await sequelize.models.credit_scores.findOne({ | |
where: { | |
application_id: this.id, | |
}, | |
}); | |
if (!creditReport) { | |
await this.update({ | |
status: 'credit-score', | |
}); | |
//if we dont find credit score from table we need to create new using id | |
return sequelize.models.credit_scores.create({ | |
application_id: this.id, | |
}); | |
} | |
if (creditReport.status === 'pending') { | |
// IF THE STATUS OF CREDIT REPORT IS PENDING WE FETCH THE ID AND NAMES OF USER | |
await creditReport.fetchReport(this.user_id, { | |
firstName: pan.extra_data.first_name, | |
surName: pan.extra_data.last_name, | |
}); | |
} else if (creditReport.status === 'completed' && creditReport.score_seen) { | |
if (creditReport.score !== null) { | |
// check if the pull failed due to no match | |
const result = await request({ | |
uri: 'http://wanda/predict', | |
method: 'POST', | |
body: { | |
application_id: this.id, | |
user_id: this.user_id, | |
experiment: this.experiment_id, | |
content: creditReport.extra_data, | |
}, | |
}); | |
if (result.statusCode === 200) { | |
if (result.body.prediction === 'rejected') { | |
await this.update({ | |
status: 'cic', | |
product: 'cic', | |
}); | |
} else { | |
await this.update({ | |
product: 'automated', | |
}); | |
} | |
} else { | |
console.error(result.body, this.experiment_id); | |
throw new GcError('Error, http://wanda/predict api returned non 200'); | |
} | |
} else { | |
await this.update({ | |
status: 'cic', | |
product: 'cic', | |
}); | |
} | |
} else if (creditReport.status === 'rejected') { | |
await this.update({ | |
status: 'cic', | |
product: 'cic', | |
}); | |
} else { | |
await this.update({ | |
status: 'credit-score', | |
}); | |
} | |
}; | |
Application.prototype.getApplicationCreditLimit = async function () { | |
let limit_values = { | |
min_limit: 0, | |
max_limit: 0, | |
}; | |
if (this.product === 'automated') { | |
limit_values = { | |
min_limit: 100, | |
max_limit: 1000, | |
}; | |
} else { | |
const result = await Application.fetchOrdersFromHappy( | |
{ | |
source: 'secured_card', | |
status: 'completed', | |
user_id: this.user_id, | |
}, | |
1, | |
); | |
if (result.statusCode === 200 && result.body.code === undefined) { | |
const securedCardOrder = result.body[0]; | |
if (securedCardOrder) { | |
//we find a match and processing for secured card | |
limit_values = { | |
min_limit: 2000, | |
max_limit: 2000, | |
}; | |
} else { | |
throw new GcError('failed_fetch'); | |
} | |
} | |
} | |
const condition = { | |
purpose: { | |
[Op.in]: ['processing-fee-payment', 'cic', 'limit-enhancement'], | |
}, | |
status: 'completed', | |
user_id: this.user_id, | |
}; | |
let orders = []; | |
let page = 0; | |
// eslint-disable-next-line no-constant-condition | |
while (true) { | |
const result = await Application.fetchOrdersFromHappy( | |
condition, | |
10, | |
page++, | |
); | |
if (result.statusCode === 200 && result.body.code === undefined) { | |
orders = orders.concat(result.body); | |
if (result.body.length < 10) { | |
break; | |
} | |
} else { | |
throw new GcError('failed_search'); | |
} | |
} | |
let creditLimit = 0; | |
for (let i = 0; i < orders.length; i++) { | |
creditLimit += orders[i].data.amount; | |
} | |
const contacts = await approvedReferenceContacts({ | |
user_id: this.user_id, | |
application_id: this.id, | |
}); | |
for (let i = 0; i < contacts.length; i++) { | |
let contact = contacts[i]; | |
if (contact.knows === 'accepted') { | |
if (contact.is_important) { | |
creditLimit += 1000000; | |
} else { | |
creditLimit += 100000; | |
} | |
} | |
} | |
limit_values.min_allowed_limit = | |
limit_values.min_allowed_limit + creditLimit / 10000; | |
limit_values.max_allowed_limit = | |
limit_values.max_allowed_limit + creditLimit / 10000; | |
return limit_values; | |
}; | |
Application.prototype.setRemainingApplicationStatus = async function ( | |
limitValue, | |
) { | |
console.warn('checking/giving limit', this.id); | |
if (limitValue) { | |
const result = await request({ | |
uri: 'http://ironman/v1/settings/activate', | |
body: { | |
user_id: this.user_id, | |
min_allowed_limit: limitValue.min_limit, | |
max_allowed_limit: limitValue.max_limit, | |
product: this.product, | |
}, | |
method: 'POST', | |
}); | |
if (result.statusCode !== 200) { | |
throw new GcError(`Error: ${JSON.stringify(result.body)}`); | |
} else { | |
console.warn( | |
'limit now exists', | |
this.id, | |
result.statusCode, | |
result.body.min_allowed_limit, | |
); | |
return this.updateAgreementAndContactStatus(); | |
} | |
} | |
}; | |
Application.prototype.updateToNextStatus = async function () { | |
if (PROCESSED_APPLICATION_STATUSES.includes(this.status)) { | |
return; | |
} | |
const personal_data = | |
this.findMissingPersonalDataFields(PERSONAL_DATA_FIELDS); | |
if (Object.keys(personal_data).length > 0) { | |
return this.update({ | |
status: 'personal', | |
}); | |
} | |
const pan = await sequelize.models.pan.findOne({ | |
attributes: ['status', 'extra_data', 'value', 'application_id'], | |
where: { | |
application_id: this.id, | |
}, | |
}); | |
if (!pan) { | |
console.warn('pan not submitted', this.id); | |
return this.update({ | |
status: 'pan', | |
}); | |
} | |
const aadhaar = await sequelize.models.aadhaar.findOne({ | |
where: { | |
application_id: this.id, | |
file_id: { | |
[Op.ne]: null, | |
}, | |
}, | |
}); | |
if (!aadhaar) { | |
console.warn('aadhaar not submitted', this.id); | |
return this.update({ | |
status: 'aadhaar', | |
}); | |
} | |
const result = await Application.fetchOrdersFromHappy( | |
{ | |
source: 'processing-fee-payment', | |
status: 'completed', | |
user_id: this.user_id, | |
}, | |
1, | |
); | |
if (result.statusCode === 200 && result.body.code === undefined) { | |
if (result.body.length === 0) { | |
return this.update({ | |
status: 'processing-fee-payment', | |
}); | |
} | |
} else { | |
throw new GcError('search_failed'); | |
} | |
if (pan.status === 'pending') { | |
return pan.verify(); | |
} else if (pan.status === 'rejected') { | |
return this.update({ | |
status: 'pan', | |
}); | |
} else if (pan.status === 'approved') { | |
if (aadhaar.status === 'pending') { | |
try { | |
return await aadhaar.verify(); | |
} catch (e) { | |
console.error(`Error: aadhaar ${e.message}`); | |
return this.update({ | |
status: 'aadhaar', | |
}); | |
} | |
} else if (aadhaar.status === 'approved') { | |
if (!this.product) { | |
await this.evaluateApplicationCreditEligibility(pan); | |
} | |
if (this.product) { | |
const limit = await this.getApplicationCreditLimit(); | |
return this.setRemainingApplicationStatus(limit); | |
} | |
} else if (aadhaar.status === 'rejected') { | |
return this.update({ | |
status: 'aadhaar', | |
}); | |
} | |
} | |
}; | |
Application.prototype.updateContactStatus = async function ( | |
requiredContacts = 1, | |
) { | |
if (requiredContacts === 0) { | |
return false; | |
} | |
const contactsShared = await hasUserEnoughContacts( | |
this.user_id, | |
requiredContacts, | |
); | |
if (!contactsShared) { | |
console.warn('user has not shared contacts', this.id); | |
await this.update({ | |
status: 'contacts', | |
}); | |
return true; | |
} | |
return false; | |
}; | |
Application.prototype.updateAgreementAndContactStatus = async function () { | |
const result = await request(`http://ironman/v1/settings/${this.user_id}`); | |
if (result.statusCode === 200) { | |
const setting = result.body; | |
// do we have agreement? | |
const agreement = await sequelize.models.agreements.findOne({ | |
where: { | |
application_id: this.id, | |
lender_id: setting.lender_id, | |
file_id: { | |
[Op.ne]: null, | |
}, | |
}, | |
}); | |
return this.update({ | |
status: agreement ? 'approved' : 'agreement', | |
}); | |
} | |
}; | |
Application.fetchOrdersFromHappy = async function ( | |
where, | |
limit = 1, | |
offset = 0, | |
) { | |
return request({ | |
uri: 'http://happy/v1/orders/search', | |
method: 'POST', | |
body: { | |
where, | |
limit, | |
offset, | |
}, | |
}); | |
}; | |
Application.prototype.publishEnhancedLimit = async function () { | |
let { min_allowed_limit, max_allowed_limit } = | |
await this.getApplicationCreditLimit(); | |
const body = { | |
user_id: this.user_id, | |
min_allowed_limit, | |
max_allowed_limit, | |
product: this.product, | |
}; | |
const result = await request({ | |
uri: 'http://ironman/v1/settings/activate', | |
body, | |
method: 'POST', | |
}); | |
if (result.statusCode !== 200 || result.body.code !== undefined) { | |
throw new GcError(result.body.code || 'failed_ironman'); | |
} | |
}; | |
module.exports = Application; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment