feat: add stacker counter field to the machine
feat: use the stacker amount to properly render the cash cassettes table feat: add stacker notifications to the plugins backend
This commit is contained in:
parent
211c4b1ea7
commit
2638bd1717
14 changed files with 519 additions and 138 deletions
36
bin/lamassu-update-stackers
Normal file
36
bin/lamassu-update-stackers
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
require('../lib/environment-helper')
|
||||||
|
|
||||||
|
const _ = require('lodash')
|
||||||
|
const db = require('../lib/db')
|
||||||
|
|
||||||
|
if (process.argv.length !== 4) {
|
||||||
|
console.log('Usage: lamassu-update-stackers <device_id> <number_of_stackers>')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isFinite(parseInt(process.argv[3]))) {
|
||||||
|
console.log('Error: <number_of_stackers> is not a valid number (%s)', err)
|
||||||
|
process.exit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt(process.argv[3]) > 3 || parseInt(process.argv[3]) < 1) {
|
||||||
|
console.log('Error: <number_of_stackers> is out of range. Should be a number between 1 and 3')
|
||||||
|
process.exit(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deviceId = process.argv[2]
|
||||||
|
const numberOfStackers = parseInt(process.argv[3])
|
||||||
|
|
||||||
|
const query = `UPDATE devices SET number_of_stackers = $1 WHERE device_id = $2`
|
||||||
|
|
||||||
|
db.none(query, [numberOfStackers, deviceId])
|
||||||
|
.then(() => {
|
||||||
|
console.log('Success! Device %s updated to %s stackers', deviceId, numberOfStackers)
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log('Error: %s', err)
|
||||||
|
process.exit(3)
|
||||||
|
})
|
||||||
|
|
@ -35,6 +35,7 @@ function toMachineObject (r) {
|
||||||
stacker3r: r.stacker3r
|
stacker3r: r.stacker3r
|
||||||
},
|
},
|
||||||
numberOfCassettes: r.number_of_cassettes,
|
numberOfCassettes: r.number_of_cassettes,
|
||||||
|
numberOfStackers: r.number_of_stackers,
|
||||||
version: r.version,
|
version: r.version,
|
||||||
model: r.model,
|
model: r.model,
|
||||||
pairedAt: new Date(r.created),
|
pairedAt: new Date(r.created),
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const typeDef = gql`
|
||||||
model: String
|
model: String
|
||||||
cashUnits: CashUnits
|
cashUnits: CashUnits
|
||||||
numberOfCassettes: Int
|
numberOfCassettes: Int
|
||||||
|
numberOfStackers: Int
|
||||||
statuses: [MachineStatus]
|
statuses: [MachineStatus]
|
||||||
latestEvent: MachineEvent
|
latestEvent: MachineEvent
|
||||||
downloadSpeed: String
|
downloadSpeed: String
|
||||||
|
|
|
||||||
|
|
@ -645,6 +645,12 @@ function plugins (settings, deviceId) {
|
||||||
const denomination2 = cashOutConfig.cassette2
|
const denomination2 = cashOutConfig.cassette2
|
||||||
const denomination3 = cashOutConfig.cassette3
|
const denomination3 = cashOutConfig.cassette3
|
||||||
const denomination4 = cashOutConfig.cassette4
|
const denomination4 = cashOutConfig.cassette4
|
||||||
|
const denomination1f = cashOutConfig.stacker1f
|
||||||
|
const denomination1r = cashOutConfig.stacker1r
|
||||||
|
const denomination2f = cashOutConfig.stacker2f
|
||||||
|
const denomination2r = cashOutConfig.stacker2r
|
||||||
|
const denomination3f = cashOutConfig.stacker3f
|
||||||
|
const denomination3r = cashOutConfig.stacker3r
|
||||||
const cashOutEnabled = cashOutConfig.active
|
const cashOutEnabled = cashOutConfig.active
|
||||||
const isCassetteLow = (have, max, limit) => cashOutEnabled && ((have / max) * 100) < limit
|
const isCassetteLow = (have, max, limit) => cashOutEnabled && ((have / max) * 100) < limit
|
||||||
|
|
||||||
|
|
@ -709,7 +715,91 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
|
||||||
return _.compact([cashInAlert, cassette1Alert, cassette2Alert, cassette3Alert, cassette4Alert])
|
const stacker1fAlert = device.numberOfStackers >= 1 && isCassetteLow(device.cashUnits.stacker1f, CASSETTE_MAX_CAPACITY, notifications.fillingPercentageStacker1f)
|
||||||
|
? {
|
||||||
|
code: 'LOW_CASH_OUT',
|
||||||
|
cassette: 4,
|
||||||
|
machineName,
|
||||||
|
deviceId: device.deviceId,
|
||||||
|
notes: device.cashUnits.stacker1f,
|
||||||
|
denomination: denomination1f,
|
||||||
|
fiatCode
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
|
const stacker1rAlert = device.numberOfStackers >= 1 && isCassetteLow(device.cashUnits.stacker1r, CASSETTE_MAX_CAPACITY, notifications.fillingPercentageStacker1r)
|
||||||
|
? {
|
||||||
|
code: 'LOW_CASH_OUT',
|
||||||
|
cassette: 4,
|
||||||
|
machineName,
|
||||||
|
deviceId: device.deviceId,
|
||||||
|
notes: device.cashUnits.stacker1r,
|
||||||
|
denomination: denomination1r,
|
||||||
|
fiatCode
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
|
const stacker2fAlert = device.numberOfStackers >= 2 && isCassetteLow(device.cashUnits.stacker2f, CASSETTE_MAX_CAPACITY, notifications.fillingPercentageStacker2f)
|
||||||
|
? {
|
||||||
|
code: 'LOW_CASH_OUT',
|
||||||
|
cassette: 4,
|
||||||
|
machineName,
|
||||||
|
deviceId: device.deviceId,
|
||||||
|
notes: device.cashUnits.stacker1f,
|
||||||
|
denomination: denomination1f,
|
||||||
|
fiatCode
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
|
const stacker2rAlert = device.numberOfStackers >= 2 && isCassetteLow(device.cashUnits.stacker2r, CASSETTE_MAX_CAPACITY, notifications.fillingPercentageStacker2r)
|
||||||
|
? {
|
||||||
|
code: 'LOW_CASH_OUT',
|
||||||
|
cassette: 4,
|
||||||
|
machineName,
|
||||||
|
deviceId: device.deviceId,
|
||||||
|
notes: device.cashUnits.stacker2r,
|
||||||
|
denomination: denomination2r,
|
||||||
|
fiatCode
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
|
const stacker3fAlert = device.numberOfStackers >= 3 && isCassetteLow(device.cashUnits.stacker3f, CASSETTE_MAX_CAPACITY, notifications.fillingPercentageStacker3f)
|
||||||
|
? {
|
||||||
|
code: 'LOW_CASH_OUT',
|
||||||
|
cassette: 4,
|
||||||
|
machineName,
|
||||||
|
deviceId: device.deviceId,
|
||||||
|
notes: device.cashUnits.stacker3f,
|
||||||
|
denomination: denomination3f,
|
||||||
|
fiatCode
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
|
const stacker3rAlert = device.numberOfStackers >= 3 && isCassetteLow(device.cashUnits.stacker3r, CASSETTE_MAX_CAPACITY, notifications.fillingPercentageStacker3r)
|
||||||
|
? {
|
||||||
|
code: 'LOW_CASH_OUT',
|
||||||
|
cassette: 4,
|
||||||
|
machineName,
|
||||||
|
deviceId: device.deviceId,
|
||||||
|
notes: device.cashUnits.stacker3r,
|
||||||
|
denomination: denomination3r,
|
||||||
|
fiatCode
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
|
return _.compact([
|
||||||
|
cashInAlert,
|
||||||
|
cassette1Alert,
|
||||||
|
cassette2Alert,
|
||||||
|
cassette3Alert,
|
||||||
|
cassette4Alert,
|
||||||
|
stacker1fAlert,
|
||||||
|
stacker1rAlert,
|
||||||
|
stacker2fAlert,
|
||||||
|
stacker2rAlert,
|
||||||
|
stacker3fAlert,
|
||||||
|
stacker3rAlert
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCryptoBalances (fiatCode, devices) {
|
function checkCryptoBalances (fiatCode, devices) {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@ exports.up = function (next) {
|
||||||
ADD COLUMN stacker2f INTEGER NOT NULL DEFAULT 0,
|
ADD COLUMN stacker2f INTEGER NOT NULL DEFAULT 0,
|
||||||
ADD COLUMN stacker2r INTEGER NOT NULL DEFAULT 0,
|
ADD COLUMN stacker2r INTEGER NOT NULL DEFAULT 0,
|
||||||
ADD COLUMN stacker3f INTEGER NOT NULL DEFAULT 0,
|
ADD COLUMN stacker3f INTEGER NOT NULL DEFAULT 0,
|
||||||
ADD COLUMN stacker3r INTEGER NOT NULL DEFAULT 0`,
|
ADD COLUMN stacker3r INTEGER NOT NULL DEFAULT 0
|
||||||
|
ADD COLUMN number_of_stackers INTEGER NOT NULL DEFAULT 0`,
|
||||||
`ALTER TABLE cash_out_txs
|
`ALTER TABLE cash_out_txs
|
||||||
ADD COLUMN provisioned_1f INTEGER,
|
ADD COLUMN provisioned_1f INTEGER,
|
||||||
ADD COLUMN provisioned_1r INTEGER,
|
ADD COLUMN provisioned_1r INTEGER,
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ const GET_INFO = gql`
|
||||||
stacker3r
|
stacker3r
|
||||||
}
|
}
|
||||||
numberOfCassettes
|
numberOfCassettes
|
||||||
|
numberOfStackers
|
||||||
}
|
}
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const MODAL_WIDTH = 554
|
||||||
const MODAL_HEIGHT = 520
|
const MODAL_HEIGHT = 520
|
||||||
|
|
||||||
const Wizard = ({ machine, locale, onClose, save, error }) => {
|
const Wizard = ({ machine, locale, onClose, save, error }) => {
|
||||||
const LAST_STEP = machine.numberOfCassettes + 1
|
const LAST_STEP = machine.numberOfCassettes + machine.numberOfStackers + 1
|
||||||
const [{ step, config }, setState] = useState({
|
const [{ step, config }, setState] = useState({
|
||||||
step: 0,
|
step: 0,
|
||||||
config: { active: true }
|
config: { active: true }
|
||||||
|
|
@ -46,7 +46,8 @@ const Wizard = ({ machine, locale, onClose, save, error }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const steps = R.map(
|
const steps = R.concat(
|
||||||
|
R.map(
|
||||||
it => ({
|
it => ({
|
||||||
type: `cassette${it}`,
|
type: `cassette${it}`,
|
||||||
display: `Cassette ${it}`,
|
display: `Cassette ${it}`,
|
||||||
|
|
@ -58,6 +59,35 @@ const Wizard = ({ machine, locale, onClose, save, error }) => {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
R.range(1, machine.numberOfCassettes + 1)
|
R.range(1, machine.numberOfCassettes + 1)
|
||||||
|
),
|
||||||
|
R.chain(
|
||||||
|
it => [
|
||||||
|
{
|
||||||
|
type: `stacker${it}f`,
|
||||||
|
display: `Stacker ${it}F`,
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: options,
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: `stacker${it}r`,
|
||||||
|
display: `Stacker ${it}R`,
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: options,
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
R.range(
|
||||||
|
machine.numberOfCassettes + 1,
|
||||||
|
machine.numberOfCassettes + machine.numberOfStackers + 1
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const schema = () =>
|
const schema = () =>
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ const GET_DATA = gql`
|
||||||
stacker3r
|
stacker3r
|
||||||
}
|
}
|
||||||
numberOfCassettes
|
numberOfCassettes
|
||||||
|
numberOfStackers
|
||||||
statuses {
|
statuses {
|
||||||
label
|
label
|
||||||
type
|
type
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,43 @@ const ValidationSchema = Yup.object().shape({
|
||||||
.required('Required')
|
.required('Required')
|
||||||
.integer()
|
.integer()
|
||||||
.min(0)
|
.min(0)
|
||||||
.max(500)
|
.max(500),
|
||||||
|
stacker1f: Yup.number()
|
||||||
|
.label('Stacker 1F')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker1r: Yup.number()
|
||||||
|
.label('Stacker 1R')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker2f: Yup.number()
|
||||||
|
.label('Stacker 2F')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker2r: Yup.number()
|
||||||
|
.label('Stacker 2R')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker3f: Yup.number()
|
||||||
|
.label('Stacker 3F')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker3r: Yup.number()
|
||||||
|
.label('Stacker 3R')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60)
|
||||||
})
|
})
|
||||||
|
|
||||||
const SET_CASSETTE_BILLS = gql`
|
const SET_CASSETTE_BILLS = gql`
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ const GET_INFO = gql`
|
||||||
stacker3r
|
stacker3r
|
||||||
}
|
}
|
||||||
numberOfCassettes
|
numberOfCassettes
|
||||||
|
numberOfStackers
|
||||||
statuses {
|
statuses {
|
||||||
label
|
label
|
||||||
type
|
type
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ import Modal from 'src/components/Modal'
|
||||||
import { IconButton, Button } from 'src/components/buttons'
|
import { IconButton, Button } from 'src/components/buttons'
|
||||||
import { Table as EditableTable } from 'src/components/editableTable'
|
import { Table as EditableTable } from 'src/components/editableTable'
|
||||||
import { RadioGroup } from 'src/components/inputs'
|
import { RadioGroup } from 'src/components/inputs'
|
||||||
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 TitleSection from 'src/components/layout/TitleSection'
|
||||||
import { EmptyTable } from 'src/components/table'
|
import { EmptyTable } from 'src/components/table'
|
||||||
import { P, Label1 } from 'src/components/typography'
|
import { P, Label1 } from 'src/components/typography'
|
||||||
|
|
@ -20,40 +18,16 @@ import { ReactComponent as ReverseHistoryIcon } from 'src/styling/icons/circle b
|
||||||
import { ReactComponent as HistoryIcon } from 'src/styling/icons/circle buttons/history/zodiac.svg'
|
import { ReactComponent as HistoryIcon } from 'src/styling/icons/circle buttons/history/zodiac.svg'
|
||||||
import { fromNamespace, toNamespace } from 'src/utils/config'
|
import { fromNamespace, toNamespace } from 'src/utils/config'
|
||||||
import { MANUAL, AUTOMATIC } from 'src/utils/constants'
|
import { MANUAL, AUTOMATIC } from 'src/utils/constants'
|
||||||
import { hasRecycler } from 'src/utils/machine'
|
|
||||||
import { onlyFirstToUpper } from 'src/utils/string'
|
import { onlyFirstToUpper } from 'src/utils/string'
|
||||||
|
|
||||||
import styles from './CashCassettes.styles'
|
import styles from './CashCassettes.styles'
|
||||||
import CashCassettesFooter from './CashCassettesFooter'
|
import CashCassettesFooter from './CashCassettesFooter'
|
||||||
import CashboxHistory from './CashboxHistory'
|
import CashboxHistory from './CashboxHistory'
|
||||||
import Wizard from './Wizard/Wizard'
|
import Wizard from './Wizard/Wizard'
|
||||||
|
import helper from './helper'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const widthsByNumberOfCassettes = {
|
|
||||||
2: {
|
|
||||||
machine: 250,
|
|
||||||
cashbox: 260,
|
|
||||||
cassette: 300,
|
|
||||||
cassetteGraph: 80,
|
|
||||||
editWidth: 90
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
machine: 220,
|
|
||||||
cashbox: 215,
|
|
||||||
cassette: 225,
|
|
||||||
cassetteGraph: 60,
|
|
||||||
editWidth: 90
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
machine: 190,
|
|
||||||
cashbox: 180,
|
|
||||||
cassette: 185,
|
|
||||||
cassetteGraph: 50,
|
|
||||||
editWidth: 90
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ValidationSchema = Yup.object().shape({
|
const ValidationSchema = Yup.object().shape({
|
||||||
name: Yup.string().required(),
|
name: Yup.string().required(),
|
||||||
cashbox: Yup.number()
|
cashbox: Yup.number()
|
||||||
|
|
@ -85,7 +59,43 @@ const ValidationSchema = Yup.object().shape({
|
||||||
.required()
|
.required()
|
||||||
.integer()
|
.integer()
|
||||||
.min(0)
|
.min(0)
|
||||||
.max(500)
|
.max(500),
|
||||||
|
stacker1f: Yup.number()
|
||||||
|
.label('Stacker 1F')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker1r: Yup.number()
|
||||||
|
.label('Stacker 1R')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker2f: Yup.number()
|
||||||
|
.label('Stacker 2F')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker2r: Yup.number()
|
||||||
|
.label('Stacker 2R')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker3f: Yup.number()
|
||||||
|
.label('Stacker 3F')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60),
|
||||||
|
stacker3r: Yup.number()
|
||||||
|
.label('Stacker 3R')
|
||||||
|
.required('Required')
|
||||||
|
.integer()
|
||||||
|
.min(0)
|
||||||
|
.max(60)
|
||||||
})
|
})
|
||||||
|
|
||||||
const GET_MACHINES_AND_CONFIG = gql`
|
const GET_MACHINES_AND_CONFIG = gql`
|
||||||
|
|
@ -108,6 +118,7 @@ const GET_MACHINES_AND_CONFIG = gql`
|
||||||
stacker3r
|
stacker3r
|
||||||
}
|
}
|
||||||
numberOfCassettes
|
numberOfCassettes
|
||||||
|
numberOfStackers
|
||||||
}
|
}
|
||||||
unpairedMachines {
|
unpairedMachines {
|
||||||
id: deviceId
|
id: deviceId
|
||||||
|
|
@ -177,12 +188,11 @@ const CashCassettes = () => {
|
||||||
const [machineId, setMachineId] = useState('')
|
const [machineId, setMachineId] = useState('')
|
||||||
|
|
||||||
const machines = R.path(['machines'])(data) ?? []
|
const machines = R.path(['machines'])(data) ?? []
|
||||||
const [nonRecyclerMachines, recyclerMachines] = R.partition(hasRecycler)(
|
const [stackerMachines, nonStackerMachines] = R.partition(
|
||||||
machines
|
it => it.numberOfStackers > 0
|
||||||
)
|
)(machines)
|
||||||
const unpairedMachines = R.path(['unpairedMachines'])(data) ?? []
|
const unpairedMachines = R.path(['unpairedMachines'])(data) ?? []
|
||||||
const config = R.path(['config'])(data) ?? {}
|
const config = R.path(['config'])(data) ?? {}
|
||||||
const fillingPercentageSettings = fromNamespace('notifications', config)
|
|
||||||
const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, {
|
const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, {
|
||||||
refetchQueries: () => ['getData']
|
refetchQueries: () => ['getData']
|
||||||
})
|
})
|
||||||
|
|
@ -200,10 +210,6 @@ const CashCassettes = () => {
|
||||||
const cashout = data?.config && fromNamespace('cashOut')(data.config)
|
const cashout = data?.config && fromNamespace('cashOut')(data.config)
|
||||||
const locale = data?.config && fromNamespace('locale')(data.config)
|
const locale = data?.config && fromNamespace('locale')(data.config)
|
||||||
const fiatCurrency = locale?.fiatCurrency
|
const fiatCurrency = locale?.fiatCurrency
|
||||||
const maxNumberOfCassettes = Math.max(
|
|
||||||
...R.map(it => it.numberOfCassettes, nonRecyclerMachines),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
const getCashoutSettings = id => fromNamespace(id)(cashout)
|
const getCashoutSettings = id => fromNamespace(id)(cashout)
|
||||||
const isCashOutDisabled = ({ id }) => !getCashoutSettings(id).active
|
const isCashOutDisabled = ({ id }) => !getCashoutSettings(id).active
|
||||||
|
|
@ -243,85 +249,23 @@ const CashCassettes = () => {
|
||||||
setSelectedRadio(selectedRadio)
|
setSelectedRadio(selectedRadio)
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = [
|
const nonStackerElements = helper.getElements(
|
||||||
{
|
nonStackerMachines,
|
||||||
name: 'name',
|
classes,
|
||||||
header: 'Machine',
|
config,
|
||||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.machine,
|
bills,
|
||||||
view: name => <>{name}</>,
|
setMachineId,
|
||||||
input: ({ field: { value: name } }) => <>{name}</>
|
setWizard
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cashbox',
|
|
||||||
header: 'Cash box',
|
|
||||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.cashbox,
|
|
||||||
view: (_, { id, cashUnits }) => (
|
|
||||||
<CashIn
|
|
||||||
currency={{ code: fiatCurrency }}
|
|
||||||
notes={cashUnits.cashbox}
|
|
||||||
total={R.sum(R.map(it => it.fiat, bills[id] ?? []))}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
input: NumberInput,
|
|
||||||
inputProps: {
|
|
||||||
decimalPlaces: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
R.until(
|
|
||||||
R.gt(R.__, maxNumberOfCassettes),
|
|
||||||
it => {
|
|
||||||
elements.push({
|
|
||||||
name: `cassette${it}`,
|
|
||||||
header: `Cassette ${it}`,
|
|
||||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.cassette,
|
|
||||||
stripe: true,
|
|
||||||
doubleHeader: 'Cash-out',
|
|
||||||
view: (_, { id, cashUnits }) => (
|
|
||||||
<CashOut
|
|
||||||
className={classes.cashbox}
|
|
||||||
denomination={getCashoutSettings(id)?.[`cassette${it}`]}
|
|
||||||
currency={{ code: fiatCurrency }}
|
|
||||||
notes={cashUnits[`cassette${it}`]}
|
|
||||||
width={
|
|
||||||
widthsByNumberOfCassettes[maxNumberOfCassettes]?.cassetteGraph
|
|
||||||
}
|
|
||||||
threshold={
|
|
||||||
fillingPercentageSettings[`fillingPercentageCassette${it}`]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
isHidden: ({ numberOfCassettes }) => it > numberOfCassettes,
|
|
||||||
input: CashCassetteInput,
|
|
||||||
inputProps: {
|
|
||||||
decimalPlaces: 0,
|
|
||||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.cassetteGraph,
|
|
||||||
inputClassName: classes.cashbox
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return R.add(1, it)
|
|
||||||
},
|
|
||||||
1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
elements.push({
|
const stackerElements = helper.getElements(
|
||||||
name: 'edit',
|
stackerMachines,
|
||||||
header: 'Edit',
|
classes,
|
||||||
width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.editWidth,
|
config,
|
||||||
textAlign: 'center',
|
bills,
|
||||||
view: (_, { id }) => {
|
setMachineId,
|
||||||
return (
|
setWizard
|
||||||
<IconButton
|
|
||||||
onClick={() => {
|
|
||||||
setMachineId(id)
|
|
||||||
setWizard(true)
|
|
||||||
}}>
|
|
||||||
<EditIcon />
|
|
||||||
</IconButton>
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!dataLoading && (
|
!dataLoading && (
|
||||||
|
|
@ -381,18 +325,18 @@ const CashCassettes = () => {
|
||||||
error={error?.message}
|
error={error?.message}
|
||||||
name="cashboxes"
|
name="cashboxes"
|
||||||
stripeWhen={isCashOutDisabled}
|
stripeWhen={isCashOutDisabled}
|
||||||
elements={elements}
|
elements={nonStackerElements}
|
||||||
data={nonRecyclerMachines}
|
data={nonStackerMachines}
|
||||||
validationSchema={ValidationSchema}
|
validationSchema={ValidationSchema}
|
||||||
tbodyWrapperClass={classes.tBody}
|
tbodyWrapperClass={classes.tBody}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EditableTable
|
<EditableTable
|
||||||
error={error?.message}
|
error={error?.message}
|
||||||
name="cashboxes"
|
name="recyclerCashboxes"
|
||||||
stripeWhen={isCashOutDisabled}
|
stripeWhen={isCashOutDisabled}
|
||||||
elements={elements}
|
elements={stackerElements}
|
||||||
data={recyclerMachines}
|
data={stackerMachines}
|
||||||
validationSchema={ValidationSchema}
|
validationSchema={ValidationSchema}
|
||||||
tbodyWrapperClass={classes.tBody}
|
tbodyWrapperClass={classes.tBody}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
241
new-lamassu-admin/src/pages/Maintenance/helper.js
Normal file
241
new-lamassu-admin/src/pages/Maintenance/helper.js
Normal file
|
|
@ -0,0 +1,241 @@
|
||||||
|
import * as R from 'ramda'
|
||||||
|
|
||||||
|
import { IconButton } from 'src/components/buttons'
|
||||||
|
import { CashOut, CashIn } from 'src/components/inputs/cashbox/Cashbox'
|
||||||
|
import { NumberInput, CashCassetteInput } from 'src/components/inputs/formik'
|
||||||
|
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
||||||
|
import { fromNamespace } from 'src/utils/config'
|
||||||
|
|
||||||
|
const widthsByCashUnits = {
|
||||||
|
2: {
|
||||||
|
machine: 250,
|
||||||
|
cashbox: 260,
|
||||||
|
cassette: 300,
|
||||||
|
unitGraph: 80,
|
||||||
|
editWidth: 90
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
machine: 220,
|
||||||
|
cashbox: 215,
|
||||||
|
cassette: 225,
|
||||||
|
unitGraph: 60,
|
||||||
|
editWidth: 90
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
machine: 190,
|
||||||
|
cashbox: 180,
|
||||||
|
cassette: 185,
|
||||||
|
unitGraph: 50,
|
||||||
|
editWidth: 90
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
machine: 170,
|
||||||
|
cashbox: 140,
|
||||||
|
cassette: 160,
|
||||||
|
unitGraph: 45,
|
||||||
|
editWidth: 90
|
||||||
|
},
|
||||||
|
6: {
|
||||||
|
machine: 150,
|
||||||
|
cashbox: 130,
|
||||||
|
cassette: 142,
|
||||||
|
unitGraph: 45,
|
||||||
|
editWidth: 70
|
||||||
|
},
|
||||||
|
7: {
|
||||||
|
machine: 140,
|
||||||
|
cashbox: 115,
|
||||||
|
cassette: 125,
|
||||||
|
unitGraph: 40,
|
||||||
|
editWidth: 70
|
||||||
|
},
|
||||||
|
8: {
|
||||||
|
machine: 100,
|
||||||
|
cashbox: 115,
|
||||||
|
cassette: 122,
|
||||||
|
unitGraph: 35,
|
||||||
|
editWidth: 70
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaxNumberOfCassettesMap = machines =>
|
||||||
|
Math.max(...R.map(it => it.numberOfCassettes, machines), 0)
|
||||||
|
|
||||||
|
const getMaxNumberOfStackersMap = machines =>
|
||||||
|
Math.max(...R.map(it => it.numberOfStackers, machines), 0)
|
||||||
|
|
||||||
|
// Each stacker counts as two cash units (front and rear)
|
||||||
|
const getMaxNumberOfCashUnits = machines =>
|
||||||
|
Math.max(
|
||||||
|
...R.map(it => it.numberOfCassettes + it.numberOfStackers * 2, machines),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
const getElements = (
|
||||||
|
machines,
|
||||||
|
classes,
|
||||||
|
config,
|
||||||
|
bills,
|
||||||
|
setMachineId,
|
||||||
|
setWizard
|
||||||
|
) => {
|
||||||
|
const fillingPercentageSettings = fromNamespace('notifications', config)
|
||||||
|
const locale = fromNamespace('locale')(config)
|
||||||
|
const cashout = fromNamespace('cashOut')(config)
|
||||||
|
const fiatCurrency = locale?.fiatCurrency
|
||||||
|
|
||||||
|
const getCashoutSettings = id => fromNamespace(id)(cashout)
|
||||||
|
|
||||||
|
const elements = [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
header: 'Machine',
|
||||||
|
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.machine,
|
||||||
|
view: name => <>{name}</>,
|
||||||
|
input: ({ field: { value: name } }) => <>{name}</>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cashbox',
|
||||||
|
header: 'Cash box',
|
||||||
|
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.cashbox,
|
||||||
|
view: (_, { id, cashUnits }) => (
|
||||||
|
<CashIn
|
||||||
|
currency={{ code: fiatCurrency }}
|
||||||
|
notes={cashUnits.cashbox}
|
||||||
|
total={R.sum(R.map(it => it.fiat, bills[id] ?? []))}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
input: NumberInput,
|
||||||
|
inputProps: {
|
||||||
|
decimalPlaces: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
R.until(
|
||||||
|
R.gt(R.__, getMaxNumberOfCassettesMap(machines)),
|
||||||
|
it => {
|
||||||
|
elements.push({
|
||||||
|
name: `cassette${it}`,
|
||||||
|
header: `Cassette ${it}`,
|
||||||
|
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.cassette,
|
||||||
|
stripe: true,
|
||||||
|
doubleHeader: 'Cash-out',
|
||||||
|
view: (_, { id, cashUnits }) => (
|
||||||
|
<CashOut
|
||||||
|
className={classes.cashbox}
|
||||||
|
denomination={getCashoutSettings(id)?.[`cassette${it}`]}
|
||||||
|
currency={{ code: fiatCurrency }}
|
||||||
|
notes={cashUnits[`cassette${it}`]}
|
||||||
|
width={
|
||||||
|
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph
|
||||||
|
}
|
||||||
|
threshold={
|
||||||
|
fillingPercentageSettings[`fillingPercentageCassette${it}`]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isHidden: ({ numberOfCassettes }) => it > numberOfCassettes,
|
||||||
|
input: CashCassetteInput,
|
||||||
|
inputProps: {
|
||||||
|
decimalPlaces: 0,
|
||||||
|
width:
|
||||||
|
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph,
|
||||||
|
inputClassName: classes.cashbox
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return R.add(1, it)
|
||||||
|
},
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
R.until(
|
||||||
|
R.gt(R.__, getMaxNumberOfStackersMap(machines)),
|
||||||
|
it => {
|
||||||
|
elements.push(
|
||||||
|
{
|
||||||
|
name: `stacker${it}f`,
|
||||||
|
header: `Stacker ${it}F`,
|
||||||
|
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.cassette,
|
||||||
|
stripe: true,
|
||||||
|
doubleHeader: 'Cash recycling',
|
||||||
|
view: (_, { id, cashUnits }) => (
|
||||||
|
<CashOut
|
||||||
|
className={classes.cashbox}
|
||||||
|
denomination={getCashoutSettings(id)?.[`stacker${it}f`]}
|
||||||
|
currency={{ code: fiatCurrency }}
|
||||||
|
notes={cashUnits[`stacker${it}f`]}
|
||||||
|
width={
|
||||||
|
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph
|
||||||
|
}
|
||||||
|
threshold={
|
||||||
|
fillingPercentageSettings[`fillingPercentageStacker${it}f`]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isHidden: ({ numberOfStackers }) => it > numberOfStackers,
|
||||||
|
input: CashCassetteInput,
|
||||||
|
inputProps: {
|
||||||
|
decimalPlaces: 0,
|
||||||
|
width:
|
||||||
|
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph,
|
||||||
|
inputClassName: classes.cashbox
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `stacker${it}r`,
|
||||||
|
header: `Stacker ${it}R`,
|
||||||
|
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.cassette,
|
||||||
|
stripe: true,
|
||||||
|
doubleHeader: 'Cash recycling',
|
||||||
|
view: (_, { id, cashUnits }) => (
|
||||||
|
<CashOut
|
||||||
|
className={classes.cashbox}
|
||||||
|
denomination={getCashoutSettings(id)?.[`stacker${it}r`]}
|
||||||
|
currency={{ code: fiatCurrency }}
|
||||||
|
notes={cashUnits[`stacker${it}r`]}
|
||||||
|
width={
|
||||||
|
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph
|
||||||
|
}
|
||||||
|
threshold={
|
||||||
|
fillingPercentageSettings[`fillingPercentageStacker${it}r`]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
isHidden: ({ numberOfStackers }) => it > numberOfStackers,
|
||||||
|
input: CashCassetteInput,
|
||||||
|
inputProps: {
|
||||||
|
decimalPlaces: 0,
|
||||||
|
width:
|
||||||
|
widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.unitGraph,
|
||||||
|
inputClassName: classes.cashbox
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return R.add(1, it)
|
||||||
|
},
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
elements.push({
|
||||||
|
name: 'edit',
|
||||||
|
header: 'Edit',
|
||||||
|
width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.editWidth,
|
||||||
|
textAlign: 'center',
|
||||||
|
view: (_, { id }) => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setMachineId(id)
|
||||||
|
setWizard(true)
|
||||||
|
}}>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return elements
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { getElements }
|
||||||
|
|
@ -28,6 +28,7 @@ const GET_INFO = gql`
|
||||||
name
|
name
|
||||||
deviceId
|
deviceId
|
||||||
numberOfCassettes
|
numberOfCassettes
|
||||||
|
numberOfStackers
|
||||||
}
|
}
|
||||||
cryptoCurrencies {
|
cryptoCurrencies {
|
||||||
code
|
code
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@ const modelPrettifier = {
|
||||||
grandola: 'Grândola'
|
grandola: 'Grândola'
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasRecycler = machine =>
|
|
||||||
machine.model === 'aveiro' || machine.model === 'grandola'
|
|
||||||
|
|
||||||
const cashUnitCapacity = {
|
const cashUnitCapacity = {
|
||||||
tejo: {
|
tejo: {
|
||||||
cashbox: 1000,
|
cashbox: 1000,
|
||||||
|
|
@ -22,4 +19,4 @@ const cashUnitCapacity = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { modelPrettifier, cashUnitCapacity, hasRecycler }
|
export { modelPrettifier, cashUnitCapacity }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue