diff --git a/lib/cashbox-batches.js b/lib/cashbox-batches.js new file mode 100644 index 00000000..18e7e3c1 --- /dev/null +++ b/lib/cashbox-batches.js @@ -0,0 +1,37 @@ +const db = require('./db') +const _ = require('lodash/fp') +const uuid = require('uuid') + +function createCashboxBatch (deviceId, cashboxCount) { + if (_.isEqual(0, cashboxCount)) throw new Error('Cashbox is empty. Cashbox batch could not be created.') + const sql = `INSERT INTO cashbox_batches (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-in-empty') RETURNING *` + const sql2 = ` + 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 + ` + return db.tx(async t => { + const newBatch = await t.oneOrNone(sql, [uuid.v4(), deviceId]) + return t.oneOrNone(sql2, [newBatch.id, newBatch.device_id]) + }) +} + +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 FROM cashbox_batches cb LEFT JOIN bills b ON cb.id=b.cashbox_batch_id GROUP BY cb.id` + return db.any(sql).then(res => _.map(it => _.mapKeys(ite => _.camelCase(ite), it), res)) +} + +function editBatchById (id, performedBy) { + const sql = `UPDATE cashbox_batches SET performed_by=$1 WHERE id=$2` + return db.none(sql, [performedBy, id]) +} + +function getBillsByBatchId (id) { + const sql = `SELECT * FROM bills WHERE cashbox_batch_id=$1` + return db.any(sql, [id]) +} + +module.exports = { createCashboxBatch, getBatches, getBillsByBatchId, editBatchById } diff --git a/lib/machine-loader.js b/lib/machine-loader.js index 6d3b6a10..0576dbc1 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -118,7 +118,7 @@ function getMachine (machineId, config) { .then(([machine, events, config]) => { const pings = checkPings([machine]) - return [machine].map(addName(pings, events, config))[0] + return addName(pings, events, config)(machine) }) } diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index a81fbc84..5987f298 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -5,6 +5,7 @@ const { GraphQLJSON, GraphQLJSONObject } = require('graphql-type-json') const got = require('got') const DataLoader = require('dataloader') +const cashbox = require('../../cashbox-batches') const machineLoader = require('../../machine-loader') const customers = require('../../customers') const { machineAction } = require('../machines') @@ -273,14 +274,25 @@ const typeDefs = gql` valid: Boolean } - type Bills { + type Bill { fiat: Int deviceId: ID created: Date cashbox: Int } + type CashboxBatch { + id: ID + deviceId: ID + created: Date + operationType: String + customBillCount: Int + performedBy: String + bills: [Bill] + } + type Query { + cashboxBatches: [CashboxBatch] countries: [Country] currencies: [Currency] languages: [Language] @@ -315,7 +327,7 @@ const typeDefs = gql` notifications: [Notification] alerts: [Notification] hasUnreadNotifications: Boolean - bills: [Bills] + bills: [Bill] } type SupportLogsResponse { @@ -352,6 +364,8 @@ const typeDefs = gql` toggleClearNotification(id: ID!, read: Boolean!): Notification clearAllNotifications: Notification cancelCashOutTransaction(id: ID): Transaction + createBatch(deviceId: ID, cashboxCount: Int): CashboxBatch + editBatch(id: ID, performedBy: String): CashboxBatch } ` @@ -378,6 +392,7 @@ const resolvers = { latestEvent: parent => machineEventsLoader.load(parent.deviceId) }, Query: { + cashboxBatches: () => cashbox.getBatches(), countries: () => countries, currencies: () => currencies, languages: () => languages, @@ -449,7 +464,9 @@ const resolvers = { deletePromoCode: (...[, { codeId }]) => promoCodeManager.deletePromoCode(codeId), toggleClearNotification: (...[, { id, read }]) => notifierQueries.setRead(id, read), clearAllNotifications: () => notifierQueries.markAllAsRead(), - cancelCashOutTransaction: (...[, { id }]) => cashOutTx.cancel(id) + cancelCashOutTransaction: (...[, { id }]) => cashOutTx.cancel(id), + createBatch: (...[, { deviceId, cashboxCount }]) => cashbox.createCashboxBatch(deviceId, cashboxCount), + editBatch: (...[, { id, performedBy }]) => cashbox.editBatchById(id, performedBy) } } diff --git a/lib/routes.js b/lib/routes.js index 119038cf..d8e7774e 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -10,6 +10,7 @@ const express = require('express') const nmd = require('nano-markdown') const semver = require('semver') +const cashbox = require('./cashbox-batches') const dbErrorCodes = require('./db-error-codes') const options = require('./options') const logger = require('./logger') @@ -222,6 +223,14 @@ function stateChange (req, res, next) { .catch(next) } +function notifyCashboxRemoval (req, res, next) { + return machineLoader.getMachine(req.deviceId) + .then(machineLoader => cashbox.createCashboxBatch(req.deviceId, machineLoader.cashbox)) + .then(() => machineLoader.setMachine({ deviceId: req.deviceId, action: 'emptyCashInBills' })) + .then(() => res.status(200).send({ status: 'OK' })) + .catch(next) +} + function verifyUser (req, res, next) { const pi = plugins(req.settings, req.deviceId) pi.verifyUser(req.body) @@ -546,6 +555,7 @@ app.use(filterOldRequests) app.get('/poll', poll) app.get('/terms_conditions', getTermsConditions) app.post('/state', stateChange) +app.post('/cashbox/removal', notifyCashboxRemoval) app.post('/network/heartbeat', networkHeartbeat) app.post('/network/performance', networkPerformance) diff --git a/migrations/1616528363530-add_cashbox_batches.js b/migrations/1616528363530-add_cashbox_batches.js new file mode 100644 index 00000000..bfb88db3 --- /dev/null +++ b/migrations/1616528363530-add_cashbox_batches.js @@ -0,0 +1,26 @@ +var db = require('./db') + +exports.up = function (next) { + var sqls = [ + `create table cashbox_batches ( + id uuid PRIMARY KEY, + device_id text REFERENCES devices (device_id), + created timestamptz NOT NULL default now() + )`, + + `ALTER TABLE bills ADD COLUMN legacy boolean DEFAULT false`, + + `ALTER TABLE bills ADD COLUMN cashbox_batch_id uuid`, + + `ALTER TABLE bills ADD CONSTRAINT cashbox_batch_id + FOREIGN KEY (cashbox_batch_id) + REFERENCES cashbox_batches (id)`, + + `UPDATE bills SET legacy = 'true'` + ] + db.multi(sqls, next) +} + +exports.down = function (next) { + next() +} diff --git a/migrations/1617967601902-add-batches-type.js b/migrations/1617967601902-add-batches-type.js new file mode 100644 index 00000000..20d64368 --- /dev/null +++ b/migrations/1617967601902-add-batches-type.js @@ -0,0 +1,25 @@ +var db = require('./db') + +exports.up = function (next) { + var sqls = [ + `CREATE TYPE cashbox_batch_type AS ENUM( + 'cash-in-empty', + 'cash-out-1-refill', + 'cash-out-1-empty', + 'cash-out-2-refill', + 'cash-out-2-empty', + 'cash-out-3-refill', + 'cash-out-3-empty', + 'cash-out-4-refill', + 'cash-out-4-empty' + )`, + `ALTER TABLE cashbox_batches ADD COLUMN operation_type cashbox_batch_type NOT NULL`, + `ALTER TABLE cashbox_batches ADD COLUMN bill_count_override SMALLINT`, + `ALTER TABLE cashbox_batches ADD COLUMN performed_by VARCHAR(64)` + ] + db.multi(sqls, next) +} + +exports.down = function (next) { + next() +} diff --git a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js index dfcea4dc..04a556fd 100644 --- a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js +++ b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js @@ -2,18 +2,24 @@ import { useQuery, 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 TitleSection from 'src/components/layout/TitleSection' import { EmptyTable } from 'src/components/table' +import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' +import { ReactComponent as ReverseHistoryIcon } from 'src/styling/icons/circle buttons/history/white.svg' +import { ReactComponent as HistoryIcon } from 'src/styling/icons/circle buttons/history/zodiac.svg' import { fromNamespace } from 'src/utils/config' import styles from './CashCassettes.styles.js' import CashCassettesFooter from './CashCassettesFooter' +import CashboxHistory from './CashboxHistory' +import Wizard from './Wizard/Wizard' const useStyles = makeStyles(styles) @@ -106,16 +112,28 @@ const SET_CASSETTE_BILLS = gql` } ` +const CREATE_BATCH = gql` + mutation createBatch($deviceId: ID, $cashboxCount: Int) { + createBatch(deviceId: $deviceId, cashboxCount: $cashboxCount) { + id + } + } +` + const CashCassettes = () => { const classes = useStyles() + const [showHistory, setShowHistory] = useState(false) const { data } = useQuery(GET_MACHINES_AND_CONFIG) + const [wizard, setWizard] = useState(false) + const [machineId, setMachineId] = useState('') const machines = R.path(['machines'])(data) ?? [] const config = R.path(['config'])(data) ?? {} const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, { refetchQueries: () => ['getData'] }) + const [createBatch] = useMutation(CREATE_BATCH) const bills = R.groupBy(bill => bill.deviceId)(R.path(['bills'])(data) ?? []) const deviceIds = R.uniq( R.map(R.prop('deviceId'))(R.path(['bills'])(data) ?? []) @@ -126,10 +144,23 @@ const CashCassettes = () => { const maxNumberOfCassettes = Math.max( ...R.map(it => it.numberOfCassettes, machines) ) + 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 onSave = ( - ...[, { id, cashbox, cassette1, cassette2, cassette3, cassette4 }] - ) => { return setCassetteBills({ variables: { action: 'setCassetteBills', @@ -198,25 +229,55 @@ const CashCassettes = () => { 1 ) + elements.push({ + name: 'edit', + header: 'Edit', + width: 87, + view: (value, { id }) => { + return ( + { + setMachineId(id) + setWizard(true) + }}> + + + ) + } + }) + return ( <> - +
- + {!showHistory && ( + <> + - {data && R.isEmpty(machines) && ( - + {data && R.isEmpty(machines) && ( + + )} + + )} + {showHistory && ( + )}
{ bills={bills} deviceIds={deviceIds} /> + {wizard && ( + { + setWizard(false) + }} + error={error?.message} + save={onSave} + locale={locale} + /> + )} ) } diff --git a/new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js b/new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js new file mode 100644 index 00000000..b3e71917 --- /dev/null +++ b/new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js @@ -0,0 +1,263 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import gql from 'graphql-tag' +import moment from 'moment' +import * as R from 'ramda' +import React, { useState } from 'react' +import * as Yup from 'yup' + +import { Link, IconButton } from 'src/components/buttons' +import { TextInput } from 'src/components/inputs' +import { NumberInput } from 'src/components/inputs/formik' +import DataTable from 'src/components/tables/DataTable' +import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' +import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' +import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' + +const GET_BATCHES = gql` + query cashboxBatches { + cashboxBatches { + id + deviceId + created + operationType + customBillCount + performedBy + bills { + fiat + deviceId + created + cashbox + } + } + } +` + +const EDIT_BATCH = gql` + mutation editBatch($id: ID, $performedBy: String) { + editBatch(id: $id, performedBy: $performedBy) { + id + } + } +` + +const styles = { + operationType: { + marginLeft: 8 + }, + operationTypeWrapper: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center' + }, + saveAndCancel: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between' + } +} + +const schema = Yup.object().shape({ + performedBy: Yup.string().nullable() +}) + +const useStyles = makeStyles(styles) + +const CashboxHistory = ({ machines, currency }) => { + const classes = useStyles() + const [error, setError] = useState(false) + const [fields, setFields] = useState([]) + + const { data, loading } = useQuery(GET_BATCHES) + + const [editBatch] = useMutation(EDIT_BATCH, { + refetchQueries: () => ['cashboxBatches'] + }) + + const batches = R.path(['cashboxBatches'])(data) + + const getOperationRender = R.reduce( + (ret, i) => + R.pipe( + R.assoc( + `cash-out-${i}-refill`, + <> + + Cash-out {i} refill + + ), + R.assoc( + `cash-out-${i}-empty`, + <> + + Cash-out {i} emptied + + ) + )(ret), + { + 'cash-in-empty': ( + <> + + Cash-in emptied + + ) + }, + R.range(1, 5) + ) + + const save = row => { + const field = R.find(f => f.id === row.id, fields) + const performedBy = field.performedBy === '' ? null : field.performedBy + + schema + .isValid(field) + .then(() => { + setError(false) + editBatch({ + variables: { id: row.id, performedBy: performedBy } + }) + }) + .catch(setError(true)) + return close(row.id) + } + + const close = id => { + setFields(R.filter(f => f.id !== id, fields)) + } + + const notEditing = id => !R.any(R.propEq('id', id), fields) + + const elements = [ + { + name: 'operation', + header: 'Operation', + width: 200, + textAlign: 'left', + view: it => ( +
+ {getOperationRender[it.operationType]} +
+ ) + }, + { + name: 'machine', + header: 'Machine', + width: 200, + textAlign: 'left', + view: it => { + return R.find(R.propEq('id', it.deviceId))(machines).name + } + }, + { + name: 'billCount', + header: 'Bill Count', + width: 115, + textAlign: 'left', + input: NumberInput, + inputProps: { + decimalPlaces: 0 + }, + view: it => + R.isNil(it.customBillCount) ? it.bills.length : it.customBillCount + }, + { + name: 'total', + header: 'Total', + width: 100, + textAlign: 'right', + view: it => ( + + {R.sum(R.map(b => R.prop('fiat', b), it.bills))} {currency} + + ) + }, + { + name: 'date', + header: 'Date', + width: 135, + textAlign: 'right', + view: it => moment.utc(it.created).format('YYYY-MM-DD') + }, + { + name: 'time', + header: 'Time (h:m)', + width: 125, + textAlign: 'right', + view: it => moment.utc(it.created).format('HH:mm') + }, + { + name: 'performedBy', + header: 'Performed by', + width: 180, + textAlign: 'left', + view: it => { + if (notEditing(it.id)) + return R.isNil(it.performedBy) ? 'Unknown entity' : it.performedBy + return ( + + setFields( + R.map( + f => + f.id === it.id ? { ...f, performedBy: e.target.value } : f, + fields + ) + ) + } + error={error} + width={190 * 0.85} + value={R.prop( + 'performedBy', + R.find(f => f.id === it.id, fields) + )} + /> + ) + } + }, + { + name: '', + header: 'Edit', + width: 150, + textAlign: 'right', + view: it => { + if (notEditing(it.id)) + return ( + { + setFields([ + ...fields, + { id: it.id, performedBy: it.performedBy } + ]) + }}> + + + ) + return ( +
+ save(it)}> + Save + + close(it.id)}> + Cancel + +
+ ) + } + } + ] + + return ( + <> + {!loading && ( + + )} + + ) +} + +export default CashboxHistory diff --git a/new-lamassu-admin/src/pages/Maintenance/Wizard/Wizard.js b/new-lamassu-admin/src/pages/Maintenance/Wizard/Wizard.js new file mode 100644 index 00000000..a7c99998 --- /dev/null +++ b/new-lamassu-admin/src/pages/Maintenance/Wizard/Wizard.js @@ -0,0 +1,98 @@ +import * as R from 'ramda' +import React, { useState } from 'react' +import * as Yup from 'yup' + +import Modal from 'src/components/Modal' + +import WizardSplash from './WizardSplash' +import WizardStep from './WizardStep' + +const MODAL_WIDTH = 554 +const MODAL_HEIGHT = 520 +const CASHBOX_DEFAULT_CAPACITY = 500 + +const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => { + const [{ step, config }, setState] = useState({ + step: 0, + config: { active: true } + }) + + const numberOfCassettes = R.isEmpty(cashoutSettings) + ? 0 + : machine.numberOfCassettes + const LAST_STEP = numberOfCassettes + 1 + + const title = `Update counts` + const isLastStep = step === LAST_STEP + + const onContinue = it => { + const cashbox = config?.wasCashboxEmptied === 'YES' ? 0 : machine?.cashbox + + if (isLastStep) { + const { cassette1, cassette2, cassette3, cassette4 } = R.map(parseInt, it) + save(machine.id, cashbox, cassette1, cassette2, cassette3, cassette4) + return onClose() + } + + const newConfig = R.merge(config, it) + setState({ + step: step + 1, + config: newConfig + }) + } + + const makeCassetteSteps = R.pipe( + R.add(1), + R.range(1), + R.map(i => ({ + type: `cassette ${i}`, + schema: Yup.object().shape({ + [`cassette${i}`]: Yup.number() + .positive() + .integer() + .required() + .min(0) + .max(CASHBOX_DEFAULT_CAPACITY) + }) + })) + ) + + const steps = R.prepend( + { + type: 'cashbox', + schema: Yup.object().shape({ + wasCashboxEmptied: Yup.string().required() + }) + }, + makeCassetteSteps(numberOfCassettes) + ) + + return ( + + {step === 0 && ( + onContinue()} /> + )} + {step !== 0 && ( + + )} + + ) +} + +export default Wizard diff --git a/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardSplash.js b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardSplash.js new file mode 100644 index 00000000..a5cb66ff --- /dev/null +++ b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardSplash.js @@ -0,0 +1,81 @@ +import { makeStyles } from '@material-ui/core' +import React from 'react' + +import { Button } from 'src/components/buttons' +import { H1, P, Info2 } from 'src/components/typography' +import filledCassettes from 'src/styling/icons/cassettes/both-filled.svg' +import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg' +import { comet } from 'src/styling/variables' + +const styles = { + button: { + margin: [[35, 'auto', 0, 'auto']] + }, + modalContent: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + flex: 1, + padding: [[0, 34]] + }, + splashTitle: { + marginTop: 15 + }, + warningInfo: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + marginTop: 15 + }, + warningIcon: { + width: 25, + height: 25, + marginRight: 8, + display: 'block' + }, + warningText: { + flexBasis: '100%', + flexGrow: 1 + }, + machineName: { + margin: [[5, 0]], + color: comet + } +} + +const useStyles = makeStyles(styles) + +const WizardSplash = ({ name, onContinue }) => { + const classes = useStyles() + + return ( +
+ cassette +

+ Update counts +

+ + {name} + +
+ +

+ Before updating counts on Lamassu Admin, make sure you've done it + before on the machines. +

+
+
+ +

+ For cash-out cassettes, please make sure you've removed the remaining + bills before adding the new ones. +

+
+ +
+ ) +} + +export default WizardSplash diff --git a/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js new file mode 100644 index 00000000..8de43f87 --- /dev/null +++ b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js @@ -0,0 +1,294 @@ +import { makeStyles } from '@material-ui/core' +import classnames from 'classnames' +import { Formik, Form, Field } from 'formik' +import * as R from 'ramda' +import React from 'react' + +import Stepper from 'src/components/Stepper' +import { Tooltip } from 'src/components/Tooltip' +import { Button } from 'src/components/buttons' +import { Cashbox } from 'src/components/inputs/cashbox/Cashbox' +import { NumberInput, RadioGroup } from 'src/components/inputs/formik' +import { Info2, H4, P, Info1 } from 'src/components/typography' +import cashbox from 'src/styling/icons/cassettes/acceptor-left.svg' +import cassetteOne from 'src/styling/icons/cassettes/dispenser-1.svg' +import cassetteTwo from 'src/styling/icons/cassettes/dispenser-2.svg' +import tejo3CassetteOne from 'src/styling/icons/cassettes/tejo/3-cassettes/3-cassettes-open-1-left.svg' +import tejo3CassetteTwo from 'src/styling/icons/cassettes/tejo/3-cassettes/3-cassettes-open-2-left.svg' +import tejo3CassetteThree from 'src/styling/icons/cassettes/tejo/3-cassettes/3-cassettes-open-3-left.svg' +import tejo4CassetteOne from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-1-left.svg' +import tejo4CassetteTwo from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-2-left.svg' +import tejo4CassetteThree from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-3-left.svg' +import tejo4CassetteFour from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-4-left.svg' +import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' +import { comet } from 'src/styling/variables' + +const styles = { + content: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + flex: 1, + paddingBottom: 32 + }, + titleDiv: { + marginBottom: 32 + }, + title: { + margin: [[0, 0, 12, 0]], + color: comet + }, + stepImage: { + width: 148, + height: 196 + }, + form: { + paddingBottom: 95 + }, + verticalAlign: { + display: 'flex', + flexDirection: 'column' + }, + horizontalAlign: { + display: 'flex', + flexDirection: 'row' + }, + centerAlignment: { + alignItems: 'center' + }, + lineAlignment: { + alignItems: 'baseline' + }, + fullWidth: { + margin: [[0, 'auto']], + flexBasis: 'auto' + }, + formWrapper: { + flexBasis: '100%', + display: 'flex', + justifyContent: 'center' + }, + submit: { + float: 'right' + }, + cashboxBills: { + marginRight: 5 + }, + cassetteCashbox: { + width: 40, + height: 35 + }, + cassetteFormTitle: { + marginTop: 18 + }, + cassetteFormTitleContent: { + marginLeft: 10, + marginRight: 25 + }, + smBottomMargin: { + marginBottom: 25 + }, + fiatTotal: { + color: comet + } +} + +const useStyles = makeStyles(styles) + +const cassetesArtworks = (numberOfCassettes, step) => + [ + [cassetteOne, cassetteTwo], + [tejo3CassetteOne, tejo3CassetteTwo, tejo3CassetteThree], + [tejo4CassetteOne, tejo4CassetteTwo, tejo4CassetteThree, tejo4CassetteFour] + ][numberOfCassettes - 2][step - 2] + +const WizardStep = ({ + step, + name, + machine, + cashoutSettings, + cassetteCapacity, + error, + lastStep, + steps, + fiatCurrency, + onContinue +}) => { + const classes = useStyles() + + const label = lastStep ? 'Finish' : 'Confirm' + + const stepOneRadioOptions = [ + { display: 'Yes', code: 'YES' }, + { display: 'No', code: 'NO' } + ] + + const cassetteField = `cassette${step - 1}` + const numberOfCassettes = machine.numberOfCassettes + const originalCassetteCount = machine?.[cassetteField] + const cassetteDenomination = cashoutSettings?.[cassetteField] + + const cassetteCount = values => values[cassetteField] || originalCassetteCount + const cassetteTotal = values => cassetteCount(values) * cassetteDenomination + const getPercentage = R.pipe( + cassetteCount, + count => 100 * (count / cassetteCapacity), + R.clamp(0, 100) + ) + + return ( +
+
+ {name} + +
+ + {step === 1 && ( + + {({ values }) => ( +
+
+ cassette +
+
+

Did you empty the cash-in box?

+ +
+

Since previous update

+ +

+ Number of bills inside the cashbox, since the last + cashbox changes. +

+
+
+
+ + {machine?.cashbox} + +

accepted bills

+
+
+
+
+ +
+ )} +
+ )} + + {step > 1 && ( + + {({ values }) => ( +
+
+ cassette +
+
+
+
+ +

+ Cash-out {step - 1} (dispenser) +

+
+ +
+

Refill bill count

+
+ +

+ {cassetteDenomination} {fiatCurrency} bills loaded +

+
+

+ = {cassetteTotal(values)} {fiatCurrency} +

+
+
+
+ +
+ )} +
+ )} +
+ ) +} + +export default WizardStep diff --git a/new-lamassu-admin/src/styling/icons/cassettes/acceptor-left-filled.svg b/new-lamassu-admin/src/styling/icons/cassettes/acceptor-left-filled.svg new file mode 100644 index 00000000..c2a9b20c --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/cassettes/acceptor-left-filled.svg @@ -0,0 +1 @@ +acceptor-left-filled \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/cassettes/acceptor-left.svg b/new-lamassu-admin/src/styling/icons/cassettes/acceptor-left.svg new file mode 100644 index 00000000..06519d18 --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/cassettes/acceptor-left.svg @@ -0,0 +1 @@ +acceptor-left \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/cassettes/both-filled.svg b/new-lamassu-admin/src/styling/icons/cassettes/both-filled.svg new file mode 100644 index 00000000..6104c6dd --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/cassettes/both-filled.svg @@ -0,0 +1 @@ +both-filled \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/cassettes/dispenser-1.svg b/new-lamassu-admin/src/styling/icons/cassettes/dispenser-1.svg new file mode 100644 index 00000000..a255b0b3 --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/cassettes/dispenser-1.svg @@ -0,0 +1 @@ +v2-1 \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/cassettes/dispenser-2.svg b/new-lamassu-admin/src/styling/icons/cassettes/dispenser-2.svg new file mode 100644 index 00000000..68c9ea1b --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/cassettes/dispenser-2.svg @@ -0,0 +1 @@ +v2-2 \ No newline at end of file