Merge pull request #1728 from siiky/fix/lam-903/recycler-notifs

LAM-903 Notifications issues for recyclers
This commit is contained in:
Rafael Taranto 2024-11-15 09:32:28 +00:00 committed by GitHub
commit 98ecff69ac
5 changed files with 96 additions and 168 deletions

View file

@ -51,6 +51,7 @@ const CASH_UNIT_CAPACITY = {
const CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES = 2 const CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES = 2
const CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES = 4 const CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES = 4
const CASH_OUT_MAXIMUM_AMOUNT_OF_RECYCLERS = 6
const AUTHENTICATOR_ISSUER_ENTITY = 'Lamassu' const AUTHENTICATOR_ISSUER_ENTITY = 'Lamassu'
const AUTH_TOKEN_EXPIRATION_TIME = '30 minutes' const AUTH_TOKEN_EXPIRATION_TIME = '30 minutes'
const REGISTRATION_TOKEN_EXPIRATION_TIME = '30 minutes' const REGISTRATION_TOKEN_EXPIRATION_TIME = '30 minutes'
@ -85,6 +86,7 @@ module.exports = {
CONFIRMATION_CODE, CONFIRMATION_CODE,
CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES, CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES,
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES, CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES,
CASH_OUT_MAXIMUM_AMOUNT_OF_RECYCLERS,
WALLET_SCORE_THRESHOLD, WALLET_SCORE_THRESHOLD,
RECEIPT, RECEIPT,
PSQL_URL, PSQL_URL,

View file

@ -6,6 +6,7 @@ const LOW_CRYPTO_BALANCE = 'LOW_CRYPTO_BALANCE'
const HIGH_CRYPTO_BALANCE = 'HIGH_CRYPTO_BALANCE' const HIGH_CRYPTO_BALANCE = 'HIGH_CRYPTO_BALANCE'
const CASH_BOX_FULL = 'CASH_BOX_FULL' const CASH_BOX_FULL = 'CASH_BOX_FULL'
const LOW_CASH_OUT = 'LOW_CASH_OUT' const LOW_CASH_OUT = 'LOW_CASH_OUT'
const LOW_RECYCLER_STACKER = 'LOW_RECYCLER_STACKER'
const SECURITY = 'SECURITY' const SECURITY = 'SECURITY'
const CODES_DISPLAY = { const CODES_DISPLAY = {
@ -41,6 +42,7 @@ module.exports = {
HIGH_CRYPTO_BALANCE, HIGH_CRYPTO_BALANCE,
CASH_BOX_FULL, CASH_BOX_FULL,
LOW_CASH_OUT, LOW_CASH_OUT,
LOW_RECYCLER_STACKER,
SECURITY, SECURITY,
CODES_DISPLAY, CODES_DISPLAY,
NETWORK_DOWN_TIME, NETWORK_DOWN_TIME,

View file

@ -10,6 +10,7 @@ const {
HIGH_CRYPTO_BALANCE, HIGH_CRYPTO_BALANCE,
CASH_BOX_FULL, CASH_BOX_FULL,
LOW_CASH_OUT, LOW_CASH_OUT,
LOW_RECYCLER_STACKER,
SECURITY SECURITY
} = require('./codes') } = require('./codes')
@ -80,6 +81,8 @@ function emailAlert (alert) {
return `Cash box full on ${alert.machineName} [${alert.notes} banknotes]` return `Cash box full on ${alert.machineName} [${alert.notes} banknotes]`
case LOW_CASH_OUT: case LOW_CASH_OUT:
return `Cassette for ${alert.denomination} ${alert.fiatCode} low [${alert.notes} banknotes]` return `Cassette for ${alert.denomination} ${alert.fiatCode} low [${alert.notes} banknotes]`
case LOW_RECYCLER_STACKER:
return `Recycler for ${alert.denomination} ${alert.fiatCode} low [${alert.notes} banknotes]`
case SECURITY: case SECURITY:
return `Cashbox removed on ${alert.machineName}` return `Cashbox removed on ${alert.machineName}`
} }

View file

@ -2,20 +2,27 @@ const _ = require('lodash/fp')
const queries = require('./queries') const queries = require('./queries')
const utils = require('./utils') const utils = require('./utils')
const codes = require('./codes')
const customers = require('../customers') const customers = require('../customers')
const {
const { NOTIFICATION_TYPES: { NOTIFICATION_TYPES: {
SECURITY, SECURITY,
COMPLIANCE, COMPLIANCE,
CRYPTO_BALANCE, CRYPTO_BALANCE,
FIAT_BALANCE, FIAT_BALANCE,
ERROR, ERROR,
HIGH_VALUE_TX, HIGH_VALUE_TX,
NORMAL_VALUE_TX } NORMAL_VALUE_TX
} = codes },
const { STALE, PING } = codes STALE,
PING,
HIGH_CRYPTO_BALANCE,
LOW_CRYPTO_BALANCE,
CASH_BOX_FULL,
LOW_CASH_OUT,
LOW_RECYCLER_STACKER,
} = require('./codes')
const sanctionsNotify = (customer, phone) => { const sanctionsNotify = (customer, phone) => {
const code = 'SANCTIONS' const code = 'SANCTIONS'
@ -71,9 +78,13 @@ const fiatBalancesNotify = (fiatWarnings) => {
const { cassette, deviceId } = o.detail const { cassette, deviceId } = o.detail
return cassette === balance.cassette && deviceId === balance.deviceId return cassette === balance.cassette && deviceId === balance.deviceId
}, notInvalidated)) return }, notInvalidated)) return
const message = balance.code === 'LOW_CASH_OUT' ? const message = balance.code === LOW_CASH_OUT ?
`Cash-out cassette ${balance.cassette} low or empty!` : `Cash-out cassette ${balance.cassette} low or empty!` :
`Cash box full or almost full!` balance.code === LOW_RECYCLER_STACKER ?
`Recycler ${balance.cassette} low or empty!` :
balance.code === CASH_BOX_FULL ?
`Cash box full or almost full!` :
`Cash box full or almost full!` /* Shouldn't happen */
const detailB = utils.buildDetail({ deviceId: balance.deviceId, cassette: balance.cassette }) const detailB = utils.buildDetail({ deviceId: balance.deviceId, cassette: balance.cassette })
return queries.addNotification(FIAT_BALANCE, message, detailB) return queries.addNotification(FIAT_BALANCE, message, detailB)
}) })
@ -105,7 +116,7 @@ const cryptoBalancesNotify = (cryptoWarnings) => {
}, notInvalidated)) return }, notInvalidated)) return
const fiat = utils.formatCurrency(balance.fiatBalance.balance, balance.fiatCode) const fiat = utils.formatCurrency(balance.fiatBalance.balance, balance.fiatCode)
const message = `${balance.code === 'HIGH_CRYPTO_BALANCE' ? 'High' : 'Low'} balance in ${balance.cryptoCode} [${fiat}]` const message = `${balance.code === HIGH_CRYPTO_BALANCE ? 'High' : 'Low'} balance in ${balance.cryptoCode} [${fiat}]`
const detailB = utils.buildDetail({ cryptoCode: balance.cryptoCode, code: balance.code }) const detailB = utils.buildDetail({ cryptoCode: balance.cryptoCode, code: balance.code })
return queries.addNotification(CRYPTO_BALANCE, message, detailB) return queries.addNotification(CRYPTO_BALANCE, message, detailB)
}) })
@ -113,8 +124,8 @@ const cryptoBalancesNotify = (cryptoWarnings) => {
} }
const balancesNotify = (balances) => { const balancesNotify = (balances) => {
const isCryptoCode = c => _.includes(c, ['HIGH_CRYPTO_BALANCE', 'LOW_CRYPTO_BALANCE']) const isCryptoCode = c => _.includes(c, [HIGH_CRYPTO_BALANCE, LOW_CRYPTO_BALANCE])
const isFiatCode = c => _.includes(c, ['LOW_CASH_OUT', 'CASH_BOX_FULL']) const isFiatCode = c => _.includes(c, [LOW_CASH_OUT, CASH_BOX_FULL, LOW_RECYCLER_STACKER])
const by = o => const by = o =>
isCryptoCode(o) ? 'crypto' : isCryptoCode(o) ? 'crypto' :
isFiatCode(o) ? 'fiat' : isFiatCode(o) ? 'fiat' :

View file

@ -24,7 +24,13 @@ const commissionMath = require('./commission-math')
const loyalty = require('./loyalty') const loyalty = require('./loyalty')
const transactionBatching = require('./tx-batching') const transactionBatching = require('./tx-batching')
const { CASH_UNIT_CAPACITY, CASH_OUT_DISPENSE_READY, CONFIRMATION_CODE } = require('./constants') const {
CASH_OUT_DISPENSE_READY,
CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES,
CASH_OUT_MAXIMUM_AMOUNT_OF_RECYCLERS,
CASH_UNIT_CAPACITY,
CONFIRMATION_CODE,
} = require('./constants')
const notifier = require('./notifier') const notifier = require('./notifier')
@ -682,169 +688,73 @@ function plugins (settings, deviceId) {
} }
function checkDeviceCashBalances (fiatCode, device) { function checkDeviceCashBalances (fiatCode, device) {
const cashOutConfig = configManager.getCashOut(device.deviceId, settings.config) const deviceId = device.deviceId
const denomination1 = cashOutConfig.cassette1
const denomination2 = cashOutConfig.cassette2
const denomination3 = cashOutConfig.cassette3
const denomination4 = cashOutConfig.cassette4
const denominationRecycler1 = cashOutConfig.recycler1
const denominationRecycler2 = cashOutConfig.recycler2
const denominationRecycler3 = cashOutConfig.recycler3
const denominationRecycler4 = cashOutConfig.recycler4
const denominationRecycler5 = cashOutConfig.recycler5
const denominationRecycler6 = cashOutConfig.recycler6
const cashOutEnabled = cashOutConfig.active
const isUnitLow = (have, max, limit) => cashOutEnabled && ((have / max) * 100) < limit
// const isUnitHigh = (have, max, limit) => cashOutEnabled && ((have / max) * 100) > limit
// const isUnitOutOfBounds = (have, max, lowerBound, upperBound) => isUnitLow(have, max, lowerBound) || isUnitHigh(have, max, upperBound)
const notifications = configManager.getNotifications(null, device.deviceId, settings.config)
const machineName = device.name const machineName = device.name
const notifications = configManager.getNotifications(null, deviceId, settings.config)
const cashInAlert = device.cashUnits.cashbox > notifications.cashInAlertThreshold const cashInAlerts = device.cashUnits.cashbox > notifications.cashInAlertThreshold
? { ? [{
code: 'CASH_BOX_FULL', code: 'CASH_BOX_FULL',
machineName, machineName,
deviceId: device.deviceId, deviceId,
notes: device.cashUnits.cashbox notes: device.cashUnits.cashbox
} }]
: null : []
const cassette1Alert = device.numberOfCassettes >= 1 && isUnitLow(device.cashUnits.cassette1, getCashUnitCapacity(device.model, 'cassette'), notifications.fillingPercentageCassette1) const cashOutConfig = configManager.getCashOut(deviceId, settings.config)
? { const cashOutEnabled = cashOutConfig.active
const isUnitLow = (have, max, limit) => ((have / max) * 100) < limit
if (!cashOutEnabled)
return cashInAlerts
const cassetteCapacity = getCashUnitCapacity(device.model, 'cassette')
const cassetteAlerts = Array(Math.min(device.numberOfCassettes ?? 0, CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES))
.fill(null)
.flatMap((_elem, idx) => {
const nth = idx + 1
const cassetteField = `cassette${nth}`
const notes = device.cashUnits[cassetteField]
const denomination = cashOutConfig[cassetteField]
const limit = notifications[`fillingPercentageCassette${nth}`]
return isUnitLow(notes, cassetteCapacity, limit) ?
[{
code: 'LOW_CASH_OUT', code: 'LOW_CASH_OUT',
cassette: 1, cassette: nth,
machineName, machineName,
deviceId: device.deviceId, deviceId,
notes: device.cashUnits.cassette1, notes,
denomination: denomination1, denomination,
fiatCode fiatCode
} }] :
: null []
})
const cassette2Alert = device.numberOfCassettes >= 2 && isUnitLow(device.cashUnits.cassette2, getCashUnitCapacity(device.model, 'cassette'), notifications.fillingPercentageCassette2) const recyclerCapacity = getCashUnitCapacity(device.model, 'recycler')
? { const recyclerAlerts = Array(Math.min(device.numberOfRecyclers ?? 0, CASH_OUT_MAXIMUM_AMOUNT_OF_RECYCLERS))
code: 'LOW_CASH_OUT', .fill(null)
cassette: 2, .flatMap((_elem, idx) => {
machineName, const nth = idx + 1
deviceId: device.deviceId, const recyclerField = `recycler${nth}`
notes: device.cashUnits.cassette2, const notes = device.cashUnits[recyclerField]
denomination: denomination2, const denomination = cashOutConfig[recyclerField]
fiatCode
}
: null
const cassette3Alert = device.numberOfCassettes >= 3 && isUnitLow(device.cashUnits.cassette3, getCashUnitCapacity(device.model, 'cassette'), notifications.fillingPercentageCassette3) const limit = notifications[`fillingPercentageRecycler${nth}`]
? { return isUnitLow(notes, recyclerCapacity, limit) ?
code: 'LOW_CASH_OUT', [{
cassette: 3,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.cassette3,
denomination: denomination3,
fiatCode
}
: null
const cassette4Alert = device.numberOfCassettes >= 4 && isUnitLow(device.cashUnits.cassette4, getCashUnitCapacity(device.model, 'cassette'), notifications.fillingPercentageCassette4)
? {
code: 'LOW_CASH_OUT',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.cassette4,
denomination: denomination4,
fiatCode
}
: null
const recycler1Alert = device.numberOfRecyclers >= 1 && isUnitLow(device.cashUnits.recycler1, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler1)
? {
code: 'LOW_RECYCLER_STACKER', code: 'LOW_RECYCLER_STACKER',
cassette: 4, cassette: nth, // @see DETAIL_TEMPLATE in /lib/notifier/utils.js
machineName, machineName,
deviceId: device.deviceId, deviceId,
notes: device.cashUnits.recycler1, notes,
denomination: denominationRecycler1, denomination,
fiatCode fiatCode
} }] :
: null []
})
const recycler2Alert = device.numberOfRecyclers >= 2 && isUnitLow(device.cashUnits.recycler2, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler2) return [].concat(cashInAlerts, cassetteAlerts, recyclerAlerts)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler2,
denomination: denominationRecycler2,
fiatCode
}
: null
const recycler3Alert = device.numberOfRecyclers >= 3 && isUnitLow(device.cashUnits.recycler3, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler3)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler3,
denomination: denominationRecycler3,
fiatCode
}
: null
const recycler4Alert = device.numberOfRecyclers >= 4 && isUnitLow(device.cashUnits.recycler4, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler4)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler4,
denomination: denominationRecycler4,
fiatCode
}
: null
const recycler5Alert = device.numberOfRecyclers >= 5 && isUnitLow(device.cashUnits.recycler5, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler5)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler5,
denomination: denominationRecycler5,
fiatCode
}
: null
const recycler6Alert = device.numberOfRecyclers >= 6 && isUnitLow(device.cashUnits.recycler6, getCashUnitCapacity(device.model, 'recycler'), notifications.fillingPercentageRecycler6)
? {
code: 'LOW_RECYCLER_STACKER',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cashUnits.recycler6,
denomination: denominationRecycler6,
fiatCode
}
: null
return _.compact([
cashInAlert,
cassette1Alert,
cassette2Alert,
cassette3Alert,
cassette4Alert,
recycler1Alert,
recycler2Alert,
recycler3Alert,
recycler4Alert,
recycler5Alert,
recycler6Alert
])
} }
function checkCryptoBalances (fiatCode, devices) { function checkCryptoBalances (fiatCode, devices) {