Merge pull request #1076 from siiky/fix/lam-298/custom_info_requests_flow
fix: custom info requests flow
This commit is contained in:
commit
564374108b
4 changed files with 31 additions and 186 deletions
204
lib/customers.js
204
lib/customers.js
|
|
@ -40,10 +40,6 @@ const TX_PASSTHROUGH_ERROR_CODES = ['operatorCancel']
|
||||||
function add (customer) {
|
function add (customer) {
|
||||||
const sql = 'insert into customers (id, phone, phone_at) values ($1, $2, now()) returning *'
|
const sql = 'insert into customers (id, phone, phone_at) values ($1, $2, now()) returning *'
|
||||||
return db.one(sql, [uuid.v4(), customer.phone])
|
return db.one(sql, [uuid.v4(), customer.phone])
|
||||||
.then(populateOverrideUsernames)
|
|
||||||
.then(computeStatus)
|
|
||||||
.then(populateDailyVolume)
|
|
||||||
.then(getCustomInfoRequestsData)
|
|
||||||
.then(camelize)
|
.then(camelize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,8 +57,6 @@ function add (customer) {
|
||||||
function get (phone) {
|
function get (phone) {
|
||||||
const sql = 'select * from customers where phone=$1'
|
const sql = 'select * from customers where phone=$1'
|
||||||
return db.oneOrNone(sql, [phone])
|
return db.oneOrNone(sql, [phone])
|
||||||
.then(populateDailyVolume)
|
|
||||||
.then(getCustomInfoRequestsData)
|
|
||||||
.then(camelize)
|
.then(camelize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,7 +72,7 @@ function get (phone) {
|
||||||
*
|
*
|
||||||
* @returns {Promise} Newly updated Customer
|
* @returns {Promise} Newly updated Customer
|
||||||
*/
|
*/
|
||||||
function update (id, data, userToken, txId) {
|
function update (id, data, userToken) {
|
||||||
const formattedData = _.omit(['id'], _.mapKeys(_.snakeCase, data))
|
const formattedData = _.omit(['id'], _.mapKeys(_.snakeCase, data))
|
||||||
|
|
||||||
const enhancedUpdateData = enhanceAtFields(enhanceOverrideFields(formattedData, userToken))
|
const enhancedUpdateData = enhanceAtFields(enhanceOverrideFields(formattedData, userToken))
|
||||||
|
|
@ -88,14 +82,8 @@ function update (id, data, userToken, txId) {
|
||||||
' where id=$1 returning *'
|
' where id=$1 returning *'
|
||||||
|
|
||||||
return db.one(sql, [id])
|
return db.one(sql, [id])
|
||||||
.then(customerData => {
|
.then(assignCustomerData)
|
||||||
return getEditedData(id)
|
|
||||||
.then(customerEditedData => selectLatestData(customerData, customerEditedData))
|
|
||||||
})
|
|
||||||
.then(addComplianceOverrides(id, updateData, userToken))
|
.then(addComplianceOverrides(id, updateData, userToken))
|
||||||
.then(populateOverrideUsernames)
|
|
||||||
.then(computeStatus)
|
|
||||||
.then((it) => populateDailyVolume(it, txId))
|
|
||||||
.then(getCustomInfoRequestsData)
|
.then(getCustomInfoRequestsData)
|
||||||
.then(camelize)
|
.then(camelize)
|
||||||
}
|
}
|
||||||
|
|
@ -317,74 +305,17 @@ const updateSubscriberData = (customerId, data, userToken) => {
|
||||||
* @param {string} userToken Acting user's token
|
* @param {string} userToken Acting user's token
|
||||||
*
|
*
|
||||||
* @returns {object} Customer found
|
* @returns {object} Customer found
|
||||||
|
*
|
||||||
|
* Used for the machine.
|
||||||
*/
|
*/
|
||||||
function getById (id, userToken) {
|
function getById (id, userToken) {
|
||||||
const sql = 'select * from customers where id=$1'
|
const sql = 'select * from customers where id=$1'
|
||||||
return db.oneOrNone(sql, [id])
|
return db.oneOrNone(sql, [id])
|
||||||
.then(populateOverrideUsernames)
|
.then(assignCustomerData)
|
||||||
.then(computeStatus)
|
|
||||||
.then(populateDailyVolume)
|
|
||||||
.then(getCustomInfoRequestsData)
|
.then(getCustomInfoRequestsData)
|
||||||
.then(camelize)
|
.then(camelize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get and calculate customer's daily volume
|
|
||||||
* for both cash_in & cash_out txs
|
|
||||||
*
|
|
||||||
* @name getDailyVolume
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @param {string} id Customer's id
|
|
||||||
* @param {string} txId current tx, to be ignored in the query
|
|
||||||
* @returns {Bignumber} Customer's daily volume
|
|
||||||
*/
|
|
||||||
function getDailyVolume (id, txId) {
|
|
||||||
const queries = txId ? getDailyVolumeMinusCurrentTxQueries(id, txId) : getDailyVolumeQueries(id)
|
|
||||||
return Promise.all(queries).then(([cashIn, cashOut]) => {
|
|
||||||
const dailyVolume = new BN(cashIn.total).plus(cashOut.total)
|
|
||||||
const hoursTillLimitClear = getHoursTillLimitClear(cashIn.maxdate, cashOut.maxdate)
|
|
||||||
return { dailyVolume, hoursTillLimitClear }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDailyVolumeQueries (id) {
|
|
||||||
return [
|
|
||||||
db.one(`select coalesce(sum(fiat), 0) as total, max(created) as maxdate from cash_in_txs
|
|
||||||
where customer_id=$1
|
|
||||||
and created > now() - interval '1 day'`, [id]),
|
|
||||||
db.one(`select coalesce(sum(fiat), 0) as total, max(created) as maxdate from cash_out_txs
|
|
||||||
where customer_id=$1
|
|
||||||
and created > now() - interval '1 day'`, [id])
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDailyVolumeMinusCurrentTxQueries (id, txId) {
|
|
||||||
return [
|
|
||||||
db.one(`select coalesce(sum(fiat), 0) as total, max(created) as maxdate from cash_in_txs
|
|
||||||
where customer_id=$1
|
|
||||||
and id!=$2
|
|
||||||
and created > now() - interval '1 day'`, [id, txId]),
|
|
||||||
db.one(`select coalesce(sum(fiat), 0) as total, max(created) as maxdate from cash_out_txs
|
|
||||||
where customer_id=$1
|
|
||||||
and id!=$2
|
|
||||||
and created > now() - interval '1 day'`, [id, txId])
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHoursTillLimitClear (cashInDate, cashOutDate) {
|
|
||||||
let startDate = new Date()
|
|
||||||
startDate = sub({ days: 1 }, startDate)
|
|
||||||
|
|
||||||
const cashInMoment = new Date(cashInDate || startDate)
|
|
||||||
const cashOutMoment = new Date(cashOutDate || startDate)
|
|
||||||
|
|
||||||
const cashInDuration = differenceInHours(cashInMoment, startDate)
|
|
||||||
const cashOutDuration = differenceInHours(cashOutMoment, startDate)
|
|
||||||
|
|
||||||
return _.ceil(_.max([cashInDuration, cashOutDuration, 0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Camelize customer fields
|
* Camelize customer fields
|
||||||
* Note: return null if customer is undefined
|
* Note: return null if customer is undefined
|
||||||
|
|
@ -399,22 +330,11 @@ function camelize (customer) {
|
||||||
return customer ? _.mapKeys(_.camelCase, customer) : null
|
return customer ? _.mapKeys(_.camelCase, customer) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function camelizeDeep (customer) {
|
||||||
* Populate customer object
|
return _.flow(
|
||||||
* with dailyVolume information
|
camelize,
|
||||||
*
|
it => ({ ...it, notes: (it.notes ?? []).map(camelize) })
|
||||||
* @name populateDailyVolume
|
)(customer)
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @param {object} customer Customer object
|
|
||||||
* @returns {object} Customer object populated with dailyVolume
|
|
||||||
*/
|
|
||||||
function populateDailyVolume (customer, txId) {
|
|
||||||
if (!customer) return
|
|
||||||
return getDailyVolume(customer.id, txId).then(({ dailyVolume, hoursTillLimitClear }) => {
|
|
||||||
let withHours = _.set('hours_till_limit_clear', hoursTillLimitClear, customer)
|
|
||||||
return _.set('daily_volume', dailyVolume, withHours)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -527,76 +447,6 @@ function addComplianceOverrides (id, customer, userToken) {
|
||||||
.then(() => customer)
|
.then(() => customer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute status field
|
|
||||||
*
|
|
||||||
* Status field indicates the last
|
|
||||||
* compliance user has verified
|
|
||||||
*
|
|
||||||
* @name computeStatus
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @param {object} Customer object
|
|
||||||
* @returns {object} Customer populated with status field
|
|
||||||
*/
|
|
||||||
function computeStatus (customer) {
|
|
||||||
if (!customer) return null
|
|
||||||
/**
|
|
||||||
* Populate with status field
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const status = _.maxBy('value', [{
|
|
||||||
label: 'Phone',
|
|
||||||
value: customer.phone_at
|
|
||||||
}, {
|
|
||||||
label: 'ID card',
|
|
||||||
value: customer.id_card_at
|
|
||||||
}, {
|
|
||||||
label: 'Sanctions',
|
|
||||||
value: customer.sanctions_at
|
|
||||||
}, {
|
|
||||||
label: 'Front camera',
|
|
||||||
value: customer.front_camera_at
|
|
||||||
}, {
|
|
||||||
label: 'ID card image',
|
|
||||||
value: customer.id_card_image_at
|
|
||||||
}])
|
|
||||||
|
|
||||||
return _.assign(customer, {
|
|
||||||
status: _.get('label', status)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate the customer object with user names
|
|
||||||
* for override fields ( fields ending with _override_by )
|
|
||||||
*
|
|
||||||
* @name populateOverrideUsernames
|
|
||||||
* @function
|
|
||||||
*
|
|
||||||
* @param {object} customer Customer object to populate
|
|
||||||
* @returns {promise} Customer with populated *by_name fields
|
|
||||||
*/
|
|
||||||
function populateOverrideUsernames (customer) {
|
|
||||||
const fieldsToUpdate = _.map(field => {
|
|
||||||
return {
|
|
||||||
token: customer[field + '_override_by'] || customer[field + '_override_by_old'],
|
|
||||||
field: field + '_override_by_name'
|
|
||||||
}
|
|
||||||
}, getComplianceTypes())
|
|
||||||
const queryTokens = _.map('token', fieldsToUpdate)
|
|
||||||
|
|
||||||
return users.getByIds(queryTokens)
|
|
||||||
.then(usersList => {
|
|
||||||
return _.map(userField => {
|
|
||||||
const user = _.find({ token: userField.token }, usersList)
|
|
||||||
return {
|
|
||||||
[userField.field]: user ? user.name : null
|
|
||||||
}
|
|
||||||
}, fieldsToUpdate)
|
|
||||||
})
|
|
||||||
.then(_.reduce(_.assign, customer))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query all customers
|
* Query all customers
|
||||||
|
|
@ -613,10 +463,7 @@ function batch () {
|
||||||
order by created desc limit $2`
|
order by created desc limit $2`
|
||||||
return db.any(sql, [ anonymous.uuid, NUM_RESULTS ])
|
return db.any(sql, [ anonymous.uuid, NUM_RESULTS ])
|
||||||
.then(customers => Promise.all(_.map(customer => {
|
.then(customers => Promise.all(_.map(customer => {
|
||||||
return populateOverrideUsernames(customer)
|
return getCustomInfoRequestsData(customer)
|
||||||
.then(computeStatus)
|
|
||||||
.then(populateDailyVolume)
|
|
||||||
.then(getCustomInfoRequestsData)
|
|
||||||
.then(camelize)
|
.then(camelize)
|
||||||
}, customers)))
|
}, customers)))
|
||||||
}
|
}
|
||||||
|
|
@ -673,11 +520,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
|
||||||
AND ($7 IS NULL OR id_card_data::json->>'documentNumber' = $7)
|
AND ($7 IS NULL OR id_card_data::json->>'documentNumber' = $7)
|
||||||
limit $3`
|
limit $3`
|
||||||
return db.any(sql, [ passableErrorCodes, anonymous.uuid, NUM_RESULTS, phone, name, address, id ])
|
return db.any(sql, [ passableErrorCodes, anonymous.uuid, NUM_RESULTS, phone, name, address, id ])
|
||||||
.then(customers => Promise.all(_.map(customer => {
|
.then(customers => Promise.all(_.map(camelizeDeep, customers)))
|
||||||
return populateOverrideUsernames(customer)
|
|
||||||
.then(camelize)
|
|
||||||
.then(it => ({ ...it, notes: (it.notes ?? []).map(camelize) }))
|
|
||||||
}, customers)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -686,6 +529,8 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
|
||||||
* transactions
|
* transactions
|
||||||
*
|
*
|
||||||
* @returns {array} A single customer instance with non edited
|
* @returns {array} A single customer instance with non edited
|
||||||
|
*
|
||||||
|
* Used for the server.
|
||||||
*/
|
*/
|
||||||
function getCustomerById (id) {
|
function getCustomerById (id) {
|
||||||
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
||||||
|
|
@ -723,13 +568,13 @@ function getCustomerById (id) {
|
||||||
WHERE c.id = $2
|
WHERE c.id = $2
|
||||||
) AS cl WHERE rn = 1`
|
) AS cl WHERE rn = 1`
|
||||||
return db.oneOrNone(sql, [passableErrorCodes, id])
|
return db.oneOrNone(sql, [passableErrorCodes, id])
|
||||||
.then(customerData => {
|
.then(assignCustomerData)
|
||||||
return getEditedData(id)
|
.then(camelizeDeep)
|
||||||
.then(customerEditedData => selectLatestData(customerData, customerEditedData))
|
}
|
||||||
})
|
|
||||||
.then(populateOverrideUsernames)
|
function assignCustomerData (customer) {
|
||||||
.then(camelize)
|
return getEditedData(customer.id)
|
||||||
.then(it => ({ ...it, notes: (it.notes ?? []).map(camelize) }))
|
.then(customerEditedData => selectLatestData(customer, customerEditedData))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -755,13 +600,12 @@ function selectLatestData (customerData, customerEditedData) {
|
||||||
'name'
|
'name'
|
||||||
]
|
]
|
||||||
_.map(field => {
|
_.map(field => {
|
||||||
let fieldName = field
|
|
||||||
if (_.includes(field, ['front_camera', 'id_card_photo'])) fieldName = fieldName + '_path'
|
|
||||||
const atField = field + '_at'
|
const atField = field + '_at'
|
||||||
const byField = field + '_by'
|
const byField = field + '_by'
|
||||||
if (!_.has(fieldName, customerData) || !_.has(fieldName, customerEditedData)) return
|
if (_.includes(field, ['front_camera', 'id_card_photo'])) field = field + '_path'
|
||||||
|
if (!_.has(field, customerData) || !_.has(field, customerEditedData)) return
|
||||||
if (customerData[atField] < customerEditedData[atField]) {
|
if (customerData[atField] < customerEditedData[atField]) {
|
||||||
customerData[fieldName] = customerEditedData[fieldName]
|
customerData[field] = customerEditedData[field]
|
||||||
customerData[atField] = customerEditedData[atField]
|
customerData[atField] = customerEditedData[atField]
|
||||||
customerData[byField] = customerEditedData[byField]
|
customerData[byField] = customerEditedData[byField]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,10 @@ const setCustomerData = (customerId, infoRequestId, data) => {
|
||||||
INSERT INTO customers_custom_info_requests (customer_id, info_request_id, customer_data)
|
INSERT INTO customers_custom_info_requests (customer_id, info_request_id, customer_data)
|
||||||
VALUES ($1, $2, $3)
|
VALUES ($1, $2, $3)
|
||||||
ON CONFLICT (customer_id, info_request_id)
|
ON CONFLICT (customer_id, info_request_id)
|
||||||
DO UPDATE SET customer_data = $3`
|
DO UPDATE SET customer_data = $3,
|
||||||
|
override = 'automatic',
|
||||||
|
override_by = NULL,
|
||||||
|
override_at = NULL`
|
||||||
return db.none(sql, [customerId, infoRequestId, data])
|
return db.none(sql, [customerId, infoRequestId, data])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ const httpError = require('../route-helpers').httpError
|
||||||
const notifier = require('../notifier')
|
const notifier = require('../notifier')
|
||||||
const respond = require('../respond')
|
const respond = require('../respond')
|
||||||
const { getTx } = require('../new-admin/services/transactions.js')
|
const { getTx } = require('../new-admin/services/transactions.js')
|
||||||
const { getCustomerById } = require('../customers')
|
|
||||||
const machineLoader = require('../machine-loader')
|
const machineLoader = require('../machine-loader')
|
||||||
const { loadLatestConfig } = require('../new-settings-loader')
|
const { loadLatestConfig } = require('../new-settings-loader')
|
||||||
const customInfoRequestQueries = require('../new-admin/services/customInfoRequests')
|
const customInfoRequestQueries = require('../new-admin/services/customInfoRequests')
|
||||||
|
|
@ -142,7 +141,7 @@ function updateTxCustomerPhoto (req, res, next) {
|
||||||
function buildSms (data, receiptOptions) {
|
function buildSms (data, receiptOptions) {
|
||||||
return Promise.all([getTx(data.session, data.txClass), loadLatestConfig()])
|
return Promise.all([getTx(data.session, data.txClass), loadLatestConfig()])
|
||||||
.then(([tx, config]) => {
|
.then(([tx, config]) => {
|
||||||
return Promise.all([getCustomerById(tx.customer_id), machineLoader.getMachine(tx.device_id, config)])
|
return Promise.all([customers.getCustomerById(tx.customer_id), machineLoader.getMachine(tx.device_id, config)])
|
||||||
.then(([customer, deviceConfig]) => {
|
.then(([customer, deviceConfig]) => {
|
||||||
const formattedTx = _.mapKeys(_.camelCase)(tx)
|
const formattedTx = _.mapKeys(_.camelCase)(tx)
|
||||||
const localeConfig = configManager.getLocale(formattedTx.deviceId, config)
|
const localeConfig = configManager.getLocale(formattedTx.deviceId, config)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const _ = require('lodash/fp')
|
||||||
const compliance = require('../compliance')
|
const compliance = require('../compliance')
|
||||||
const complianceTriggers = require('../compliance-triggers')
|
const complianceTriggers = require('../compliance-triggers')
|
||||||
const configManager = require('../new-config-manager')
|
const configManager = require('../new-config-manager')
|
||||||
const { get, add, getEditedData, selectLatestData, getCustomerById, update } = require('../customers')
|
const { get, add, getById, update } = require('../customers')
|
||||||
const httpError = require('../route-helpers').httpError
|
const httpError = require('../route-helpers').httpError
|
||||||
const plugins = require('../plugins')
|
const plugins = require('../plugins')
|
||||||
const Tx = require('../tx')
|
const Tx = require('../tx')
|
||||||
|
|
@ -26,8 +26,7 @@ function addOrUpdateCustomer (req) {
|
||||||
|
|
||||||
return add(req.body)
|
return add(req.body)
|
||||||
})
|
})
|
||||||
.then(customer => Promise.all([getEditedData(customer.id), getCustomerById(customer.id)]))
|
.then(customer => getById(customer.id))
|
||||||
.then(([customerEditedData, customerOriginalData]) => selectLatestData(customerOriginalData, customerEditedData))
|
|
||||||
.then(customer => {
|
.then(customer => {
|
||||||
// BACKWARDS_COMPATIBILITY 7.5
|
// BACKWARDS_COMPATIBILITY 7.5
|
||||||
// machines before 7.5 expect customer with sanctions result
|
// machines before 7.5 expect customer with sanctions result
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue