From ec90776d2a4359835d2421d0fdae381b54c7c45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Salgado?= Date: Mon, 29 Nov 2021 23:24:04 +0000 Subject: [PATCH] feat: create a new batching function which pairs with machine value updates refactor: abstract amount of cassettes from the cassette wizard fix: dashboard cassettes --- lib/cashbox-batches.js | 31 ++++++- lib/constants.js | 6 +- lib/machine-loader.js | 12 ++- .../graphql/resolvers/machine.resolver.js | 3 +- .../MachineComponents/Cassettes/Cassettes.js | 74 +++++++++++----- .../src/pages/Machines/Machines.js | 86 ++++++++++--------- .../src/pages/Maintenance/CashCassettes.js | 37 ++------ .../src/pages/Maintenance/Wizard/Wizard.js | 31 ++++++- new-lamassu-admin/src/utils/number.js | 5 +- 9 files changed, 180 insertions(+), 105 deletions(-) diff --git a/lib/cashbox-batches.js b/lib/cashbox-batches.js index d1bee08e..c3b8ba99 100644 --- a/lib/cashbox-batches.js +++ b/lib/cashbox-batches.js @@ -1,3 +1,4 @@ +const constants = require('./constants') const db = require('./db') const _ = require('lodash/fp') const uuid = require('uuid') @@ -18,6 +19,34 @@ function createCashboxBatch (deviceId, cashboxCount) { }) } +function updateMachineWithBatch (machineContext, oldCashboxCount) { + const isValidContext = _.has(['deviceId', 'cashbox', 'cassettes'], machineContext) + const isCassetteAmountWithinRange = _.inRange(constants.CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES, constants.CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES + 1, _.size(machineContext.cassettes)) + if (!isValidContext && !isCassetteAmountWithinRange) + throw new Error('Insufficient info to create a new cashbox batch') + if (_.isEqual(0, oldCashboxCount)) throw new Error('Cashbox is empty. Cashbox batch could not be created.') + + return db.tx(t => { + const deviceId = machineContext.deviceId + const batchId = uuid.v4() + const q1 = t.none(`INSERT INTO cashbox_batches (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-in-empty')`, [batchId, deviceId]) + const q2 = t.none(`UPDATE bills SET cashbox_batch_id=$1 FROM cash_in_txs + WHERE bills.cash_in_txs_id = cash_in_txs.id AND + cash_in_txs.device_id = $2 AND + bills.cashbox_batch_id IS NULL`, [batchId, deviceId]) + const q3 = t.none(`UPDATE devices SET cashbox=$1, cassette1=$2, cassette2=$3, cassette3=$4, cassette4=$5 WHERE device_id=$6`, [ + machineContext.cashbox, + machineContext.cassettes[0], + machineContext.cassettes[1], + machineContext.cassettes[2], + machineContext.cassettes[3], + machineContext.deviceId + ]) + + return t.batch([q1, q2, q3]) + }) +} + function getBatches () { const sql = ` SELECT cb.id, cb.device_id, cb.created, cb.operation_type, cb.bill_count_override, cb.performed_by, json_agg(b.*) AS bills @@ -39,4 +68,4 @@ function getBillsByBatchId (id) { return db.any(sql, [id]) } -module.exports = { createCashboxBatch, getBatches, getBillsByBatchId, editBatchById } +module.exports = { createCashboxBatch, updateMachineWithBatch, getBatches, getBillsByBatchId, editBatchById } diff --git a/lib/constants.js b/lib/constants.js index 3ea252fb..7b7c16e7 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -7,6 +7,8 @@ const anonymousCustomer = { const CASSETTE_MAX_CAPACITY = 500 +const CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES = 2 +const CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES = 4 const AUTHENTICATOR_ISSUER_ENTITY = 'Lamassu' const AUTH_TOKEN_EXPIRATION_TIME = '30 minutes' const REGISTRATION_TOKEN_EXPIRATION_TIME = '30 minutes' @@ -30,5 +32,7 @@ module.exports = { USER_SESSIONS_TABLE_NAME, USER_SESSIONS_CLEAR_INTERVAL, CASH_OUT_DISPENSE_READY, - CONFIRMATION_CODE + CONFIRMATION_CODE, + CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES, + CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES } diff --git a/lib/machine-loader.js b/lib/machine-loader.js index 310a17f3..b2ae3afb 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -3,6 +3,7 @@ const pgp = require('pg-promise')() const axios = require('axios') const uuid = require('uuid') +const batching = require('./cashbox-batches') const db = require('./db') const pairing = require('./pairing') const { checkPings, checkStuckScreen } = require('./notifier') @@ -140,8 +141,15 @@ function emptyCashInBills (rec) { } function setCassetteBills (rec) { - 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]) + return db.oneOrNone(`SELECT cashbox FROM devices WHERE device_id=$1 LIMIT 1`, [rec.deviceId]) + .then(oldCashboxValue => { + if (_.isNil(oldCashboxValue) || rec.cashbox === oldCashboxValue.cashbox) { + 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]) + } + + return batching.updateMachineWithBatch({ ...rec, oldCashboxValue }) + }) } function unpair (rec) { diff --git a/lib/new-admin/graphql/resolvers/machine.resolver.js b/lib/new-admin/graphql/resolvers/machine.resolver.js index 61960cba..85521311 100644 --- a/lib/new-admin/graphql/resolvers/machine.resolver.js +++ b/lib/new-admin/graphql/resolvers/machine.resolver.js @@ -18,7 +18,8 @@ const resolvers = { machine: (...[, { deviceId }]) => machineLoader.getMachine(deviceId) }, Mutation: { - machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }, context]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }, context) + machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }, context]) => + machineAction({ deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }, context) } } 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 70616252..8d51b5df 100644 --- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js +++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Cassettes/Cassettes.js @@ -2,12 +2,15 @@ import { useMutation } from '@apollo/react-hooks' import { makeStyles } from '@material-ui/core' import gql from 'graphql-tag' import * as R from 'ramda' -import React from 'react' +import React, { useState } from 'react' import * as Yup from 'yup' +import { IconButton } from 'src/components/buttons' import { Table as EditableTable } from 'src/components/editableTable' import { CashOut, CashIn } from 'src/components/inputs/cashbox/Cashbox' import { NumberInput, CashCassetteInput } from 'src/components/inputs/formik' +import Wizard from 'src/pages/Maintenance/Wizard/Wizard' +import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' import { fromNamespace } from 'src/utils/config' import styles from './Cassettes.styles' @@ -82,6 +85,8 @@ const SET_CASSETTE_BILLS = gql` const CashCassettes = ({ machine, config, refetchData }) => { const classes = useStyles() + const [wizard, setWizard] = useState(false) + const cashout = config && fromNamespace('cashOut')(config) const locale = config && fromNamespace('locale')(config) const fillingPercentageSettings = @@ -147,39 +152,62 @@ const CashCassettes = ({ machine, config, refetchData }) => { 1 ) + elements.push({ + name: 'edit', + header: 'Edit', + width: 87, + view: () => { + return ( + { + setWizard(true) + }}> + + + ) + } + }) + const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, { refetchQueries: () => refetchData() }) - const onSave = ( - ...[, { deviceId, cashbox, cassette1, cassette2, cassette3, cassette4 }] - ) => { - return setCassetteBills({ + const onSave = (_, cashbox, cassettes) => + setCassetteBills({ variables: { action: 'setCassetteBills', - deviceId: deviceId, + deviceId: machine.deviceId, cashbox, - cassette1, - cassette2, - cassette3, - cassette4 + ...cassettes } }) - } return machine.name ? ( - + <> + + {wizard && ( + { + setWizard(false) + }} + error={error?.message} + save={onSave} + locale={locale} + /> + )} + ) : null } diff --git a/new-lamassu-admin/src/pages/Machines/Machines.js b/new-lamassu-admin/src/pages/Machines/Machines.js index fe071b17..89c13f24 100644 --- a/new-lamassu-admin/src/pages/Machines/Machines.js +++ b/new-lamassu-admin/src/pages/Machines/Machines.js @@ -54,7 +54,7 @@ const getMachineID = path => path.slice(path.lastIndexOf('/') + 1) const Machines = () => { const location = useLocation() - const { data, refetch } = useQuery(GET_INFO, { + const { data, loading, refetch } = useQuery(GET_INFO, { variables: { deviceId: getMachineID(location.pathname) } @@ -70,50 +70,52 @@ const Machines = () => { const machineID = R.path(['deviceId'])(machine) ?? null return ( - - - -
- }> - - - Dashboard - - - - {machineName} - - - + !loading && ( + + + +
+ }> + + + Dashboard + + + + {machineName} + + + +
+
+
+ +
+
+ {'Details'} +
+
+
+ {'Cash cassettes'} + +
+
+ {'Latest transactions'} + +
+
+ {'Commissions'} + +
- -
-
- {'Details'} -
-
-
- {'Cash cassettes'} - -
-
- {'Latest transactions'} - -
-
- {'Commissions'} - -
-
-
- + ) ) } diff --git a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js index eef88947..c722bf68 100644 --- a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js +++ b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js @@ -62,14 +62,6 @@ const ValidationSchema = Yup.object().shape({ .max(500) }) -const CREATE_BATCH = gql` - mutation createBatch($deviceId: ID, $cashboxCount: Int) { - createBatch(deviceId: $deviceId, cashboxCount: $cashboxCount) { - id - } - } -` - const GET_MACHINES_AND_CONFIG = gql` query getData { machines { @@ -146,7 +138,6 @@ const CashCassettes = () => { const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, { refetchQueries: () => ['getData'] }) - const [createBatch] = useMutation(CREATE_BATCH) const [saveConfig] = useMutation(SAVE_CONFIG, { onCompleted: () => setEditingSchema(false), refetchQueries: () => ['getData'] @@ -163,32 +154,17 @@ const CashCassettes = () => { ...R.map(it => it.numberOfCassettes, machines), 0 ) - const cashboxCounts = R.reduce( - (ret, m) => R.assoc(m.id, m.cashbox, ret), - {}, - machines - ) - const onSave = (id, cashbox, cassette1, cassette2, cassette3, cassette4) => { - const oldCashboxCount = cashboxCounts[id] - if (cashbox < oldCashboxCount) { - createBatch({ - variables: { - deviceId: id, - cashboxCount: oldCashboxCount - } - }) - } + const getCashoutSettings = id => fromNamespace(id)(cashout) + const isCashOutDisabled = ({ id }) => !getCashoutSettings(id).active + const onSave = (id, cashbox, cassettes) => { return setCassetteBills({ variables: { action: 'setCassetteBills', deviceId: id, cashbox, - cassette1, - cassette2, - cassette3, - cassette4 + ...cassettes } }) } @@ -208,9 +184,6 @@ const CashCassettes = () => { } } - const getCashoutSettings = id => fromNamespace(id)(cashout) - const isCashOutDisabled = ({ id }) => !getCashoutSettings(id).active - const radioButtonOptions = [ { display: 'Automatic', code: AUTOMATIC }, { display: 'Manual', code: MANUAL } @@ -299,7 +272,7 @@ const CashCassettes = () => { { const [{ step, config }, setState] = useState({ step: 0, @@ -27,6 +30,17 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => { const title = `Update counts` const isLastStep = step === LAST_STEP + const buildCassetteObj = cassetteInput => { + return R.reduce( + (acc, value) => { + acc[value] = defaultToZero(cassetteInput[value]) + return acc + }, + {}, + CASSETTE_FIELDS + ) + } + const onContinue = it => { const newConfig = R.merge(config, it) @@ -37,9 +51,9 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => { ].includes('YES') const cashbox = wasCashboxEmptied ? 0 : machine?.cashbox + const cassettes = buildCassetteObj(it) - const { cassette1, cassette2, cassette3, cassette4 } = R.map(parseInt, it) - save(machine.id, cashbox, cassette1, cassette2, cassette3, cassette4) + save(machine.id, cashbox, cassettes) return onClose() } @@ -66,6 +80,18 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => { })) ) + const makeInitialValues = () => + !R.isEmpty(cashoutSettings) + ? R.reduce( + (acc, value) => { + acc[`cassette${value}`] = '' + return acc + }, + {}, + R.range(1, numberOfCassettes + 1) + ) + : {} + const steps = R.prepend( { type: 'cashbox', @@ -99,6 +125,7 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => { steps={steps} fiatCurrency={locale.fiatCurrency} onContinue={onContinue} + initialValues={makeInitialValues()} /> )} diff --git a/new-lamassu-admin/src/utils/number.js b/new-lamassu-admin/src/utils/number.js index e05d5796..f0c7e15c 100644 --- a/new-lamassu-admin/src/utils/number.js +++ b/new-lamassu-admin/src/utils/number.js @@ -4,4 +4,7 @@ const isValidNumber = R.both(R.is(Number), R.complement(R.equals(NaN))) const transformNumber = value => (isValidNumber(value) ? value : null) -export { transformNumber } +const defaultToZero = value => + isValidNumber(parseInt(value)) ? parseInt(value) : 0 + +export { defaultToZero, transformNumber }