From 5788b6216d6b9f5a58810afbc390ad25a5ccd5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Salgado?= Date: Wed, 1 Sep 2021 00:54:51 +0100 Subject: [PATCH] feat: add support for more than two cassettes --- lib/machine-loader.js | 8 +- lib/new-admin/graphql/schema.js | 6 +- lib/plugins.js | 59 ++++++++++++-- lib/plugins/wallet/mock-wallet/mock-wallet.js | 2 +- lib/postgresql_interface.js | 4 +- ...1630432869178-add-more-cassette-support.js | 39 +++++++++ .../src/pages/Cashout/Cashout.js | 5 ++ new-lamassu-admin/src/pages/Cashout/Wizard.js | 34 ++++++-- .../src/pages/Cashout/WizardStep.js | 17 ++-- new-lamassu-admin/src/pages/Cashout/helper.js | 52 ++++++++++-- .../MachineComponents/Cassettes/Cassettes.js | 4 +- .../src/pages/Maintenance/CashCassettes.js | 80 +++++++++++++++++-- .../pages/Maintenance/CashCassettesFooter.js | 19 +++-- 13 files changed, 279 insertions(+), 50 deletions(-) create mode 100644 migrations/1630432869178-add-more-cassette-support.js diff --git a/lib/machine-loader.js b/lib/machine-loader.js index a51aa8ac..cd387efe 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -23,6 +23,8 @@ function getMachines () { cashbox: r.cashbox, cassette1: r.cassette1, cassette2: r.cassette2, + cassette3: r.cassette3, + cassette4: r.cassette4, version: r.version, model: r.model, pairedAt: new Date(r.created), @@ -100,6 +102,8 @@ function getMachine (machineId, config) { cashbox: r.cashbox, cassette1: r.cassette1, cassette2: r.cassette2, + cassette3: r.cassette3, + cassette4: r.cassette4, version: r.version, model: r.model, pairedAt: new Date(r.created), @@ -133,8 +137,8 @@ function emptyCashInBills (rec) { } function setCassetteBills (rec) { - const sql = 'update devices set cashbox=$1, cassette1=$2, cassette2=$3 where device_id=$4' - return db.none(sql, [rec.cashbox, rec.cassettes[0], rec.cassettes[1], rec.deviceId]) + const sql = 'update devices set cashbox=$1, cassette1=$2, cassette2=$3, cassette3=$4, cassette4=$5 where device_id=$6' + return db.none(sql, [rec.cashbox, rec.cassettes[0], rec.cassettes[1], rec.cassettes[2], rec.cassettes[3], rec.deviceId]) } function unpair (rec) { diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index f4756ab9..096913d7 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -77,6 +77,8 @@ const typeDefs = gql` cashbox: Int cassette1: Int cassette2: Int + cassette3: Int + cassette4: Int statuses: [MachineStatus] latestEvent: MachineEvent downloadSpeed: String @@ -333,7 +335,7 @@ const typeDefs = gql` } type Mutation { - machineAction(deviceId:ID!, action: MachineAction!, cashbox: Int, cassette1: Int, cassette2: Int, newName: String): Machine + machineAction(deviceId:ID!, action: MachineAction!, cashbox: Int, cassette1: Int, cassette2: Int, cassette3: Int, cassette4: Int, newName: String): Machine setCustomer(customerId: ID!, customerInput: CustomerInput): Customer saveConfig(config: JSONObject): JSONObject # resetConfig(schemaVersion: Int): JSONObject @@ -421,7 +423,7 @@ const resolvers = { bills: () => bills.getBills() }, Mutation: { - machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, newName }]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, newName }), + machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }), createPairingTotem: (...[, { name }]) => pairing.totem(name), saveAccounts: (...[, { accounts }]) => settingsLoader.saveAccounts(accounts), // resetAccounts: (...[, { schemaVersion }]) => settingsLoader.resetAccounts(schemaVersion), diff --git a/lib/plugins.js b/lib/plugins.js index 0cca0bd4..c8e28e95 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -110,7 +110,14 @@ function plugins (settings, deviceId) { const sumTxs = (sum, tx) => { const bills = tx.bills const sameDenominations = a => a[0].denomination === a[1].denomination - const doDenominationsMatch = _.every(sameDenominations, _.zip(cassettes, bills)) + + console.log('tx', tx) + console.log('cassettes', cassettes) + console.log('bills', bills) + const doDenominationsMatch = _.every(it => { + console.log('it', it) + return sameDenominations(it) + }, _.zip(cassettes, bills)) if (!doDenominationsMatch) { throw new Error('Denominations don\'t add up, cassettes were changed.') @@ -119,7 +126,7 @@ function plugins (settings, deviceId) { return _.map(r => r[0] + r[1].provisioned, _.zip(sum, tx.bills)) } - const provisioned = _.reduce(sumTxs, [0, 0], redeemableTxs) + const provisioned = _.reduce(sumTxs, Array(_.size(cassettes)).fill(0), redeemableTxs) const zipped = _.zip(_.map('count', cassettes), provisioned) const counts = _.map(r => r[0] - r[1], zipped) @@ -144,9 +151,9 @@ function plugins (settings, deviceId) { if (!cashOutConfig.active) return Promise.resolve() - const denominations = [cashOutConfig.top, cashOutConfig.bottom] + const denominations = [cashOutConfig.cassette1, cashOutConfig.cassette2, cashOutConfig.cassette3, cashOutConfig.cassette4] - const virtualCassettes = [Math.max(cashOutConfig.top, cashOutConfig.bottom) * 2] + const virtualCassettes = [Math.max(...denominations) * 2] return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)]) .then(([rec, _redeemableTxs]) => { @@ -164,6 +171,14 @@ function plugins (settings, deviceId) { { denomination: parseInt(denominations[1], 10), count: parseInt(counts[1], 10) + }, + { + denomination: parseInt(denominations[2], 10), + count: parseInt(counts[2], 10) + }, + { + denomination: parseInt(denominations[3], 10), + count: parseInt(counts[3], 10) } ] @@ -245,6 +260,8 @@ function plugins (settings, deviceId) { const coinsWithoutRate = _.map(mapCoinSettings, coinParams) const areThereAvailablePromoCodes = arr[arr.length - 1] > 0 + console.log('cassettes', cassettes) + return { cassettes, rates: buildRates(tickers), @@ -305,7 +322,7 @@ function plugins (settings, deviceId) { function dispenseAck (tx) { const cashOutConfig = configManager.getCashOut(deviceId, settings.config) - const cassettes = [cashOutConfig.top, cashOutConfig.bottom] + const cassettes = [cashOutConfig.cassette1, cashOutConfig.cassette2, cashOutConfig.cassette3, cashOutConfig.cassette4] return dbm.addDispense(deviceId, tx, cassettes) } @@ -560,8 +577,10 @@ function plugins (settings, deviceId) { function checkDeviceCashBalances (fiatCode, device) { const cashOutConfig = configManager.getCashOut(device.deviceId, settings.config) - const denomination1 = cashOutConfig.top - const denomination2 = cashOutConfig.bottom + const denomination1 = cashOutConfig.cassette1 + const denomination2 = cashOutConfig.cassette2 + const denomination3 = cashOutConfig.cassette3 + const denomination4 = cashOutConfig.cassette4 const cashOutEnabled = cashOutConfig.active const notifications = configManager.getNotifications(null, device.deviceId, settings.config) @@ -601,7 +620,31 @@ function plugins (settings, deviceId) { } : null - return _.compact([cashInAlert, cassette1Alert, cassette2Alert]) + const cassette3Alert = cashOutEnabled && device.cassette3 < notifications.fiatBalanceCassette3 + ? { + code: 'LOW_CASH_OUT', + cassette: 3, + machineName, + deviceId: device.deviceId, + notes: device.cassette3, + denomination: denomination3, + fiatCode + } + : null + + const cassette4Alert = cashOutEnabled && device.cassette4 < notifications.fiatBalanceCassette4 + ? { + code: 'LOW_CASH_OUT', + cassette: 4, + machineName, + deviceId: device.deviceId, + notes: device.cassette4, + denomination: denomination4, + fiatCode + } + : null + + return _.compact([cashInAlert, cassette1Alert, cassette2Alert, cassette3Alert, cassette4Alert]) } function checkCryptoBalances (fiatCode, devices) { diff --git a/lib/plugins/wallet/mock-wallet/mock-wallet.js b/lib/plugins/wallet/mock-wallet/mock-wallet.js index abaff6f7..17e0e4eb 100644 --- a/lib/plugins/wallet/mock-wallet/mock-wallet.js +++ b/lib/plugins/wallet/mock-wallet/mock-wallet.js @@ -7,7 +7,7 @@ const NAME = 'FakeWallet' const SECONDS = 1000 const PUBLISH_TIME = 3 * SECONDS const AUTHORIZE_TIME = PUBLISH_TIME + 5 * SECONDS -const CONFIRM_TIME = AUTHORIZE_TIME + 120 * SECONDS +const CONFIRM_TIME = AUTHORIZE_TIME + 10 * SECONDS let t0 diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index eb030e91..864dc44a 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -26,12 +26,12 @@ exports.recordDeviceEvent = function recordDeviceEvent (deviceId, event) { } exports.cassetteCounts = function cassetteCounts (deviceId) { - const sql = 'SELECT cassette1, cassette2 FROM devices ' + + const sql = 'SELECT cassette1, cassette2, cassette3, cassette4 FROM devices ' + 'WHERE device_id=$1' return db.one(sql, [deviceId]) .then(row => { - const counts = [row.cassette1, row.cassette2] + const counts = [row.cassette1, row.cassette2, row.cassette3, row.cassette4] return {counts} }) } diff --git a/migrations/1630432869178-add-more-cassette-support.js b/migrations/1630432869178-add-more-cassette-support.js new file mode 100644 index 00000000..3fd0c8d4 --- /dev/null +++ b/migrations/1630432869178-add-more-cassette-support.js @@ -0,0 +1,39 @@ +var db = require('./db') +const _ = require('lodash/fp') +const { saveConfig, loadLatest } = require('../lib/new-settings-loader') +const { getMachines } = require('../lib/machine-loader') + +exports.up = function (next) { + var sql = [ + 'ALTER TABLE devices ADD COLUMN cassette3 INTEGER NOT NULL DEFAULT 0', + 'ALTER TABLE devices ADD COLUMN cassette4 INTEGER NOT NULL DEFAULT 0', + 'ALTER TABLE cash_out_txs ADD COLUMN provisioned_3 INTEGER', + 'ALTER TABLE cash_out_txs ADD COLUMN provisioned_4 INTEGER', + 'ALTER TABLE cash_out_txs ADD COLUMN denomination_3 INTEGER', + 'ALTER TABLE cash_out_txs ADD COLUMN denomination_4 INTEGER' + ] + + return Promise.all([loadLatest(), getMachines()]) + .then(([config, machines]) => { + const formattedMachines = _.map(it => _.pick(['deviceId'], it), machines) + const newConfig = _.reduce((acc, value) => { + if(_.includes(`cashOut_${value.deviceId}_top`, _.keys(config.config))) { + acc[`cashOut_${value.deviceId}_cassette1`] = config.config[`cashOut_${value.deviceId}_top`] + } + + if(_.includes(`cashOut_${value.deviceId}_bottom`, _.keys(config.config))) { + acc[`cashOut_${value.deviceId}_cassette2`] = config.config[`cashOut_${value.deviceId}_bottom`] + } + + return acc + }, {}, formattedMachines) + + return saveConfig(newConfig) + .then(() => db.multi(sql, next)) + .catch(err => next(err)) + }) +} + +exports.down = function (next) { + next() +} diff --git a/new-lamassu-admin/src/pages/Cashout/Cashout.js b/new-lamassu-admin/src/pages/Cashout/Cashout.js index d07527cf..38b471ab 100644 --- a/new-lamassu-admin/src/pages/Cashout/Cashout.js +++ b/new-lamassu-admin/src/pages/Cashout/Cashout.js @@ -41,6 +41,8 @@ const GET_INFO = gql` cashbox cassette1 cassette2 + cassette3 + cassette4 } config } @@ -62,6 +64,9 @@ const CashOut = ({ name: SCREEN_KEY }) => { } const config = data?.config && fromNamespace(SCREEN_KEY)(data.config) + + console.log('config', config) + const fudgeFactorActive = config?.fudgeFactorActive ?? false const locale = data?.config && fromNamespace('locale')(data.config) const machines = data?.machines ?? [] diff --git a/new-lamassu-admin/src/pages/Cashout/Wizard.js b/new-lamassu-admin/src/pages/Cashout/Wizard.js index 11259af1..d1132583 100644 --- a/new-lamassu-admin/src/pages/Cashout/Wizard.js +++ b/new-lamassu-admin/src/pages/Cashout/Wizard.js @@ -11,7 +11,7 @@ import WizardSplash from './WizardSplash' import WizardStep from './WizardStep' import { DenominationsSchema } from './helper' -const LAST_STEP = 4 +const LAST_STEP = 6 const MODAL_WIDTH = 554 const MODAL_HEIGHT = 520 @@ -52,8 +52,8 @@ const Wizard = ({ machine, locale, onClose, save, error }) => { const steps = [ { - type: 'top', - display: 'Cassette 1 (Top)', + type: 'cassette1', + display: 'Cassette 1', component: Autocomplete, inputProps: { options: R.map(it => ({ code: it, display: it }))(options), @@ -62,7 +62,7 @@ const Wizard = ({ machine, locale, onClose, save, error }) => { } }, { - type: 'bottom', + type: 'cassette2', display: 'Cassette 2', component: Autocomplete, inputProps: { @@ -71,6 +71,26 @@ const Wizard = ({ machine, locale, onClose, save, error }) => { valueProp: 'code' } }, + { + type: 'cassette3', + display: 'Cassette 3', + component: Autocomplete, + inputProps: { + options: R.map(it => ({ code: it, display: it }))(options), + labelProp: 'display', + valueProp: 'code' + } + }, + { + type: 'cassette4', + display: 'Cassette 4', + component: Autocomplete, + inputProps: { + options: R.map(it => ({ code: it, display: it }))(options), + labelProp: 'display', + valueProp: 'code' + } + }, { type: 'zeroConfLimit', display: '0-conf Limit', @@ -82,8 +102,10 @@ const Wizard = ({ machine, locale, onClose, save, error }) => { const schema = () => Yup.object().shape({ - top: Yup.number().required(), - bottom: step >= 2 ? Yup.number().required() : Yup.number() + cassette1: Yup.number().required(), + cassette2: step >= 2 ? Yup.number().required() : Yup.number(), + cassette3: step >= 3 ? Yup.number().required() : Yup.number(), + cassette4: step >= 4 ? Yup.number().required() : Yup.number() }) return ( diff --git a/new-lamassu-admin/src/pages/Cashout/WizardStep.js b/new-lamassu-admin/src/pages/Cashout/WizardStep.js index 4fe2a384..f58d73a7 100644 --- a/new-lamassu-admin/src/pages/Cashout/WizardStep.js +++ b/new-lamassu-admin/src/pages/Cashout/WizardStep.js @@ -31,22 +31,29 @@ const WizardStep = ({ const cassetesArtworks = { 1: cassetteOne, - 2: cassetteTwo + 2: cassetteTwo, + 3: cassetteOne, + 4: cassetteTwo } return (
{name} - +
- {step <= 2 && ( + {step <= 4 && (
@@ -95,7 +102,7 @@ const WizardStep = ({ )} - {step === 3 && ( + {step === 5 && ( { editable: false }, { - name: 'top', - header: 'Cassette 1 (Top)', + name: 'cassette1', + header: 'Cassette 1', size: 'sm', stripe: true, width: 200, @@ -45,8 +55,34 @@ const getElements = (machines, { fiatCurrency } = {}) => { suffix: fiatCurrency }, { - name: 'bottom', - header: 'Cassette 2 (Bottom)', + name: 'cassette2', + header: 'Cassette 2', + size: 'sm', + stripe: true, + textAlign: 'right', + width: 200, + input: NumberInput, + inputProps: { + decimalPlaces: 0 + }, + suffix: fiatCurrency + }, + { + name: 'cassette3', + header: 'Cassette 3', + size: 'sm', + stripe: true, + textAlign: 'right', + width: 200, + input: NumberInput, + inputProps: { + decimalPlaces: 0 + }, + suffix: fiatCurrency + }, + { + name: 'cassette4', + header: 'Cassette 4', size: 'sm', stripe: true, textAlign: 'right', diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js index 1f881809..4834d782 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js @@ -90,7 +90,7 @@ const CashCassettes = ({ machine, config, refetchData }) => { view: (value, { deviceId }) => ( @@ -109,7 +109,7 @@ const CashCassettes = ({ machine, config, refetchData }) => { return ( diff --git a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js index e411e5dc..62d043d6 100644 --- a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js +++ b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js @@ -26,13 +26,25 @@ const ValidationSchema = Yup.object().shape({ .min(0) .max(1000), cassette1: Yup.number() - .label('Cassette 1 (top)') + .label('Cassette 1') .required() .integer() .min(0) .max(500), cassette2: Yup.number() - .label('Cassette 2 (bottom)') + .label('Cassette 2') + .required() + .integer() + .min(0) + .max(500), + cassette3: Yup.number() + .label('Cassette 3') + .required() + .integer() + .min(0) + .max(500), + cassette4: Yup.number() + .label('Cassette 4') .required() .integer() .min(0) @@ -47,6 +59,8 @@ const GET_MACHINES_AND_CONFIG = gql` cashbox cassette1 cassette2 + cassette3 + cassette4 } config } @@ -69,6 +83,8 @@ const SET_CASSETTE_BILLS = gql` $cashbox: Int! $cassette1: Int! $cassette2: Int! + $cassette3: Int! + $cassette4: Int! ) { machineAction( deviceId: $deviceId @@ -76,11 +92,15 @@ const SET_CASSETTE_BILLS = gql` cashbox: $cashbox cassette1: $cassette1 cassette2: $cassette2 + cassette3: $cassette3 + cassette4: $cassette4 ) { deviceId cashbox cassette1 cassette2 + cassette3 + cassette4 } } ` @@ -103,14 +123,18 @@ const CashCassettes = () => { const locale = data?.config && fromNamespace('locale')(data.config) const fiatCurrency = locale?.fiatCurrency - const onSave = (...[, { id, cashbox, cassette1, cassette2 }]) => { + const onSave = ( + ...[, { id, cashbox, cassette1, cassette2, cassette3, cassette4 }] + ) => { return setCassetteBills({ variables: { action: 'setCassetteBills', deviceId: id, cashbox, cassette1, - cassette2 + cassette2, + cassette3, + cassette4 } }) } @@ -139,13 +163,13 @@ const CashCassettes = () => { }, { name: 'cassette1', - header: 'Cassette 1 (Top)', + header: 'Cassette 1', width: 265, stripe: true, view: (value, { id }) => ( @@ -157,14 +181,54 @@ const CashCassettes = () => { }, { name: 'cassette2', - header: 'Cassette 2 (Bottom)', + header: 'Cassette 2', width: 265, stripe: true, view: (value, { id }) => { return ( + ) + }, + input: CashCassetteInput, + inputProps: { + decimalPlaces: 0 + } + }, + { + name: 'cassette3', + header: 'Cassette 3', + width: 265, + stripe: true, + view: (value, { id }) => { + return ( + + ) + }, + input: CashCassetteInput, + inputProps: { + decimalPlaces: 0 + } + }, + { + name: 'cassette4', + header: 'Cassette 4', + width: 265, + stripe: true, + view: (value, { id }) => { + return ( + diff --git a/new-lamassu-admin/src/pages/Maintenance/CashCassettesFooter.js b/new-lamassu-admin/src/pages/Maintenance/CashCassettesFooter.js index 53cfe9dc..04d1de99 100644 --- a/new-lamassu-admin/src/pages/Maintenance/CashCassettesFooter.js +++ b/new-lamassu-admin/src/pages/Maintenance/CashCassettesFooter.js @@ -25,16 +25,23 @@ const CashCassettesFooter = ({ const classes = useStyles() const cashout = config && fromNamespace('cashOut')(config) const getCashoutSettings = id => fromNamespace(id)(cashout) - const reducerFn = (acc, { cassette1, cassette2, id }) => { - const topDenomination = getCashoutSettings(id).top ?? 0 - const bottomDenomination = getCashoutSettings(id).bottom ?? 0 + const reducerFn = ( + acc, + { cassette1, cassette2, cassette3, cassette4, id } + ) => { + const cassette1Denomination = getCashoutSettings(id).cassette1 ?? 0 + const cassette2Denomination = getCashoutSettings(id).cassette2 ?? 0 + const cassette3Denomination = getCashoutSettings(id).cassette3 ?? 0 + const cassette4Denomination = getCashoutSettings(id).cassette4 ?? 0 return [ - (acc[0] += cassette1 * topDenomination), - (acc[1] += cassette2 * bottomDenomination) + (acc[0] += cassette1 * cassette1Denomination), + (acc[1] += cassette2 * cassette2Denomination), + (acc[2] += cassette3 * cassette3Denomination), + (acc[3] += cassette4 * cassette4Denomination) ] } - const totalInCassettes = R.sum(R.reduce(reducerFn, [0, 0], machines)) + const totalInCassettes = R.sum(R.reduce(reducerFn, [0, 0, 0, 0], machines)) /* const totalInCashBox = R.sum( R.flatten(