feat: add empty and refill unit machine actions

fix: remove certain actions from the state middleware after being consumed by the poller
This commit is contained in:
Sérgio Salgado 2023-05-03 18:26:46 +01:00
parent 2e9bb3c7df
commit 797f074898
10 changed files with 163 additions and 2 deletions

View file

@ -182,7 +182,7 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
state.pids = _.update(operatorId, _.set(deviceId, { pid, ts: Date.now() }), state.pids)
return _.flow(
const res = _.flow(
_.pick(['areThereAvailablePromoCodes', 'balances', 'cassettes', 'stackers', 'coins', 'rates']),
_.update('cassettes', massageCassettes),
@ -225,7 +225,20 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
_.set('reboot', !!pid && state.reboots?.[operatorId]?.[deviceId] === pid),
_.set('shutdown', !!pid && state.shutdowns?.[operatorId]?.[deviceId] === pid),
_.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid),
_.set('emptyUnit', !!pid && state.emptyUnit?.[operatorId]?.[deviceId] === pid),
_.set('refillUnit', !!pid && state.refillUnit?.[operatorId]?.[deviceId] === pid),
)(pq)
// Clean up the state middleware and prevent commands from being issued more than once
if (!_.isNil(state.emptyUnit?.[operatorId]?.[deviceId])) {
delete state.emptyUnit?.[operatorId]?.[deviceId]
}
if (!_.isNil(state.refillUnit?.[operatorId]?.[deviceId])) {
delete state.refillUnit?.[operatorId]?.[deviceId]
}
return res
}

View file

@ -196,6 +196,8 @@ type DynamicConfig {
reboot: Boolean!
shutdown: Boolean!
restartServices: Boolean!
emptyUnit: Boolean!
refillUnit: Boolean!
}
type Configs {

View file

@ -173,6 +173,42 @@ function setCassetteBills (rec) {
})
}
function emptyMachineUnits (deviceId, units) {
return Promise.all([getMachine(deviceId), configManager.getCashOut(deviceId, getConfig())])
.then(([machine, cashoutSettings]) => {
const movedBills = _.reduce(
(acc, value) => ({
...acc,
[value]: {
delta: machine[value] - units[value],
denomination: cashoutSettings[value]
}
}),
{},
_.keys(units)
)
const sql = `UPDATE devices SET cassette1=$2, cassette2=$3, cassette3=$4, cassette4=$5, stacker1f=$6, stacker1r=$7, stacker2f=$8, stacker2r=$9, stacker3f=$10, stacker3r=$11 WHERE device_id=$1`
return db.none(sql, [
deviceId,
_.defaultTo(0, units.cassette1),
_.defaultTo(0, units.cassette2),
_.defaultTo(0, units.cassette3),
_.defaultTo(0, units.cassette4),
_.defaultTo(0, units.stacker1f),
_.defaultTo(0, units.stacker1r),
_.defaultTo(0, units.stacker2f),
_.defaultTo(0, units.stacker2r),
_.defaultTo(0, units.stacker3f),
_.defaultTo(0, units.stacker3r)
])
})
}
function refillMachineUnits (deviceId, units) {
return Promise.resolve()
}
function unpair (rec) {
return pairing.unpair(rec.deviceId)
}
@ -204,6 +240,24 @@ function restartServices (rec) {
)])
}
function emptyUnit (rec) {
return db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
{
action: 'emptyUnit',
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
}
)])
}
function refillUnit (rec) {
return db.none('NOTIFY $1:name, $2', ['machineAction', JSON.stringify(
{
action: 'refillUnit',
value: _.pick(['deviceId', 'operatorId', 'action'], rec)
}
)])
}
function setMachine (rec, operatorId) {
rec.operatorId = operatorId
switch (rec.action) {
@ -215,6 +269,8 @@ function setMachine (rec, operatorId) {
case 'reboot': return reboot(rec)
case 'shutdown': return shutdown(rec)
case 'restartServices': return restartServices(rec)
case 'emptyUnit': return emptyUnit(rec)
case 'refillUnit': return refillUnit(rec)
default: throw new Error('No such action: ' + rec.action)
}
}
@ -288,5 +344,7 @@ module.exports = {
updateNetworkHeartbeat,
getNetworkPerformance,
getNetworkHeartbeat,
getConfig
getConfig,
emptyMachineUnits,
refillMachineUnits
}

View file

@ -37,6 +37,14 @@ function machineAction (type, value) {
logger.debug(`Restarting services of machine '${deviceId}' from operator ${operatorId}`)
state.restartServicesMap[operatorId] = { [deviceId]: pid }
break
case 'emptyUnit':
logger.debug(`Emptying units from machine '${deviceId}' from operator ${operatorId}`)
state.emptyUnit[operatorId] = { [deviceId]: pid }
break
case 'refillUnit':
logger.debug(`Refilling stackers from machine '${deviceId}' from operator ${operatorId}`)
state.refillUnit[operatorId] = { [deviceId]: pid }
break
default:
break
}

View file

@ -15,6 +15,8 @@ module.exports = (function () {
reboots: {},
shutdowns: {},
restartServicesMap: {},
emptyUnit: {},
refillUnit: {},
mnemonic: null
}
}())

View file

@ -80,6 +80,8 @@ const typeDef = gql`
reboot
shutdown
restartServices
emptyUnit
refillUnit
}
type Query {

View file

@ -16,6 +16,7 @@ const populateDeviceId = require('./middlewares/populateDeviceId')
const populateSettings = require('./middlewares/populateSettings')
const recordPing = require('./middlewares/recordPing')
const unitsRoutes = require('./routes/unitsRoutes')
const cashboxRoutes = require('./routes/cashboxRoutes')
const customerRoutes = require('./routes/customerRoutes')
const logsRoutes = require('./routes/logsRoutes')
@ -82,6 +83,7 @@ app.use('/customer', customerRoutes)
app.use('/tx', txRoutes)
app.use('/logs', logsRoutes)
app.use('/units', unitsRoutes)
graphQLServer.applyMiddleware({ app })

View file

@ -96,6 +96,8 @@ function poll (req, res, next) {
const reboot = pid && state.reboots?.[operatorId]?.[deviceId] === pid
const shutdown = pid && state.shutdowns?.[operatorId]?.[deviceId] === pid
const restartServices = pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid
const emptyUnit = pid && state.emptyUnit?.[operatorId]?.[deviceId] === pid
const refillUnit = pid && state.refillUnit?.[operatorId]?.[deviceId] === pid
const langs = localeConfig.languages
const locale = {
@ -119,6 +121,8 @@ function poll (req, res, next) {
reboot,
shutdown,
restartServices,
emptyUnit,
refillUnit,
hasLightning,
receipt,
operatorInfo,

34
lib/routes/unitsRoutes.js Normal file
View file

@ -0,0 +1,34 @@
const express = require('express')
const { emptyMachineUnits, refillMachineUnits } = require('../machine-loader')
const router = express.Router()
const emptyUnitUpdateCounts = (req, res, next) => {
const deviceId = req.deviceId
const newUnits = req.body.newUnits
return emptyMachineUnits({ deviceId, cashUnits: newUnits })
.then(() => res.sendStatus(200))
.catch(e => {
console.error(e)
return res.sendStatus(500)
})
.finally(next)
}
const refillUnitUpdateCounts = (req, res, next) => {
const deviceId = req.deviceId
const newUnits = req.body.newUnits
return refillMachineUnits({ deviceId, cashUnits: newUnits })
.then(() => res.sendStatus(200))
.catch(e => {
console.error(e)
return res.sendStatus(500)
})
.finally(next)
}
router.post('/empty', emptyUnitUpdateCounts)
router.post('/refill', refillUnitUpdateCounts)
module.exports = router

View file

@ -185,6 +185,42 @@ const MachineActions = memo(({ machine, onActionSuccess }) => {
}}>
Restart Services
</ActionButton>
{machine.model === 'aveiro' && (
<ActionButton
color="primary"
className={classes.inlineChip}
Icon={RebootIcon}
InverseIcon={RebootReversedIcon}
disabled={loading}
onClick={() => {
setAction({
command: 'emptyUnit',
display: 'Empty',
message:
"Triggering this action will move all cash inside the machine towards its cashbox (if possible), allowing for the collection of cash from the machine via only its cashbox. Depending on how full the cash units are, it's possible that this action will need to be used more than once to ensure that the unit is left completely empty."
})
}}>
Empty Unit
</ActionButton>
)}
{machine.model === 'aveiro' && (
<ActionButton
color="primary"
className={classes.inlineChip}
Icon={RebootIcon}
InverseIcon={RebootReversedIcon}
disabled={loading}
onClick={() => {
setAction({
command: 'refillUnit',
display: 'Refill',
message:
'Triggering this action will refill the stackers in this machine, by using bills present in its cassettes. This action may require manual operation of the cassettes and close attention to make sure that the denominations in the cassettes match the denominations in the stackers.'
})
}}>
Refill Unit
</ActionButton>
)}
</div>
<ConfirmDialog
disabled={disabled}