From 797f07489812660d9a9c0770db8c595fa9317403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Salgado?= Date: Wed, 3 May 2023 18:26:46 +0100 Subject: [PATCH] feat: add empty and refill unit machine actions fix: remove certain actions from the state middleware after being consumed by the poller --- lib/graphql/resolvers.js | 15 ++++- lib/graphql/types.js | 2 + lib/machine-loader.js | 60 ++++++++++++++++++- lib/middlewares/populateSettings.js | 8 +++ lib/middlewares/state.js | 2 + lib/new-admin/graphql/types/machine.type.js | 2 + lib/routes.js | 2 + lib/routes/pollingRoutes.js | 4 ++ lib/routes/unitsRoutes.js | 34 +++++++++++ .../machineActions/MachineActions.js | 36 +++++++++++ 10 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 lib/routes/unitsRoutes.js diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js index ea7100bc..c97d48d3 100644 --- a/lib/graphql/resolvers.js +++ b/lib/graphql/resolvers.js @@ -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 } diff --git a/lib/graphql/types.js b/lib/graphql/types.js index 2de2b338..c3df301e 100644 --- a/lib/graphql/types.js +++ b/lib/graphql/types.js @@ -196,6 +196,8 @@ type DynamicConfig { reboot: Boolean! shutdown: Boolean! restartServices: Boolean! + emptyUnit: Boolean! + refillUnit: Boolean! } type Configs { diff --git a/lib/machine-loader.js b/lib/machine-loader.js index 96bc0c26..3eb90820 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -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 } diff --git a/lib/middlewares/populateSettings.js b/lib/middlewares/populateSettings.js index 5f36fa69..52224783 100644 --- a/lib/middlewares/populateSettings.js +++ b/lib/middlewares/populateSettings.js @@ -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 } diff --git a/lib/middlewares/state.js b/lib/middlewares/state.js index 2599e259..f26f082d 100644 --- a/lib/middlewares/state.js +++ b/lib/middlewares/state.js @@ -15,6 +15,8 @@ module.exports = (function () { reboots: {}, shutdowns: {}, restartServicesMap: {}, + emptyUnit: {}, + refillUnit: {}, mnemonic: null } }()) diff --git a/lib/new-admin/graphql/types/machine.type.js b/lib/new-admin/graphql/types/machine.type.js index 8583474f..631f64ef 100644 --- a/lib/new-admin/graphql/types/machine.type.js +++ b/lib/new-admin/graphql/types/machine.type.js @@ -80,6 +80,8 @@ const typeDef = gql` reboot shutdown restartServices + emptyUnit + refillUnit } type Query { diff --git a/lib/routes.js b/lib/routes.js index e3611152..726e53fd 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -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 }) diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js index a5515c02..f5bad40e 100644 --- a/lib/routes/pollingRoutes.js +++ b/lib/routes/pollingRoutes.js @@ -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, diff --git a/lib/routes/unitsRoutes.js b/lib/routes/unitsRoutes.js new file mode 100644 index 00000000..70894be2 --- /dev/null +++ b/lib/routes/unitsRoutes.js @@ -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 diff --git a/new-lamassu-admin/src/components/machineActions/MachineActions.js b/new-lamassu-admin/src/components/machineActions/MachineActions.js index 06c1d6b1..7fe55751 100644 --- a/new-lamassu-admin/src/components/machineActions/MachineActions.js +++ b/new-lamassu-admin/src/components/machineActions/MachineActions.js @@ -185,6 +185,42 @@ const MachineActions = memo(({ machine, onActionSuccess }) => { }}> Restart Services + {machine.model === 'aveiro' && ( + { + 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 + + )} + {machine.model === 'aveiro' && ( + { + 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 + + )}