diff --git a/lib/bill-math.js b/lib/bill-math.js index 0b7c5a1a..8b143873 100644 --- a/lib/bill-math.js +++ b/lib/bill-math.js @@ -1,225 +1,46 @@ const _ = require('lodash/fp') -const uuid = require('uuid') +const sumService = require('@haensl/subset-sum') -const MAX_AMOUNT_OF_SOLUTIONS = 10000 -const MAX_BRUTEFORCE_ITERATIONS = 10000000 - -function newSolution(cassettes, c0, c1, c2, c3, shouldFlip) { - return [ - { - provisioned: shouldFlip ? cassettes[0].count - c0 : c0, - denomination: cassettes[0].denomination +const getSolution = (units, amount) => { + const billList = _.reduce( + (acc, value) => { + acc.push(..._.times(_.constant(value.denomination), value.count)) + return acc }, - { - provisioned: shouldFlip ? cassettes[1].count - c1 : c1, - denomination: cassettes[1].denomination + [], + units + ) + + const solver = sumService.subsetSum(billList, amount.toNumber()) + const solution = _.countBy(Math.floor, solver.next().value) + return _.reduce( + (acc, value) => { + acc.push({ denomination: _.toNumber(value), provisioned: solution[value] }) + return acc }, - { - provisioned: shouldFlip ? cassettes[2].count - c2 : c2, - denomination: cassettes[2].denomination - }, - { - provisioned: shouldFlip ? cassettes[3].count - c3 : c3, - denomination: cassettes[3].denomination - } - ] + [], + _.keys(solution) + ) } -function mergeCassettes(cassettes) { - const map = {} - - _.forEach(it => { - if (!map[it.denomination]) { - map[it.denomination] = 0 - } - map[it.denomination] += it.count - }, cassettes) - - return _.map(it => ({ denomination: it, count: map[it] }), _.keys(map)) -} - -function unmergeCassettes(cassettes, output) { - const map = {} - - _.forEach(it => { - if (!map[it.denomination]) { - map[it.denomination] = 0 - } - map[it.denomination] += it.provisioned - }, output) - - const response = [] - _.forEach(it => { - const value = { - denomination: it.denomination, - id: uuid.v4() - } - - const amountNeeded = map[it.denomination] - if (!amountNeeded) { - return response.push({ provisioned: 0, ...value }) - } - - if (amountNeeded < it.count) { - map[it.denomination] = 0 - return response.push({ provisioned: amountNeeded, ...value }) - } - - map[it.denomination] -= it.count - return response.push({ provisioned: it.count, ...value }) - }, cassettes) - - return response -} - -function makeChangeDuo(cassettes, amount) { - // Initialize empty cassettes in case of undefined, due to same denomination across all cassettes results in a single merged cassette - const small = cassettes[0] ?? { denomination: 0, count: 0 } - const large = cassettes[1] ?? { denomination: 0, count: 0 } - - const largeDenom = large.denomination - const smallDenom = small.denomination - const largeBills = Math.min(large.count, Math.floor(amount / largeDenom)) - const amountNum = amount.toNumber() - - for (let i = largeBills; i >= 0; i--) { - const remainder = amountNum - largeDenom * i - - if (remainder % smallDenom !== 0) continue - const smallCount = remainder / smallDenom - if (smallCount > small.count) continue - return [ - { - provisioned: smallCount, - denomination: small.denomination, - id: uuid.v4() - }, - { provisioned: i, denomination: largeDenom, id: uuid.v4() } - ] - } - - return [] +const solutionToOriginalUnits = (solution, units) => { + const billsLeft = _.clone(_.fromPairs(_.map(it => [it.denomination, it.provisioned])(solution))) + return _.reduce( + (acc, value) => { + const unit = units[value] + const billsToAssign = _.clamp(0, unit.count)(_.isNaN(billsLeft[unit.denomination]) || _.isNil(billsLeft[unit.denomination]) ? 0 : billsLeft[unit.denomination]) + acc.push({ name: unit.name, denomination: unit.denomination, provisioned: billsToAssign }) + billsLeft[unit.denomination] -= billsToAssign + return acc + }, + [], + _.range(0, _.size(units)) + ) } function makeChange(outCassettes, amount) { - const available = _.reduce( - (res, val) => res + val.count * val.denomination, - 0, - outCassettes - ) - - if (available < amount) { - console.log(`Tried to dispense more than was available for amount ${amount.toNumber()} with cassettes ${JSON.stringify(outCassettes)}`) - return null - } - - const cassettes = mergeCassettes(outCassettes) - const result = - _.size(cassettes) >= 3 - ? makeChangeDynamic(cassettes, amount, available) - : makeChangeDuo(cassettes, amount) - - if (!result.length) return null - return unmergeCassettes(outCassettes, result) -} - -function makeChangeDynamicBruteForce(outCassettes, amount, available) { - const solutions = [] - let x = 0 - - const shouldFlip = amount > _.max(_.map(it => it.denomination * it.count, outCassettes)) - const amountNum = shouldFlip ? available - amount : amount - - const cassettes = shouldFlip ? _.reverse(outCassettes) : outCassettes - const { denomination: denomination0, count: count0 } = cassettes[0] - const { denomination: denomination1, count: count1 } = cassettes[1] - const { denomination: denomination2, count: count2 } = cassettes[2] - const { denomination: denomination3, count: count3 } = cassettes[3] - - const startTime = new Date().getTime() - - loop1: for (let i = 0; i <= count0; i++) { - const firstSum = i * denomination0 - - for (let j = 0; j <= count1; j++) { - const secondSum = firstSum + j * denomination1 - if (secondSum > amountNum) break - - if (secondSum === amountNum) { - solutions.push(newSolution(cassettes, i, j, 0, 0, shouldFlip)) - } - - for (let k = 0; k <= count2; k++) { - const thirdSum = secondSum + k * denomination2 - if (thirdSum > amountNum) break - - if (denomination2 === 0) break - - if (thirdSum === amountNum) { - solutions.push(newSolution(cassettes, i, j, k, 0, shouldFlip)) - } - - for (let l = 0; l <= count3; l++) { - if ((x > MAX_AMOUNT_OF_SOLUTIONS && solutions.length >= 1) || x > MAX_BRUTEFORCE_ITERATIONS) break loop1 - x++ - const fourthSum = thirdSum + l * denomination3 - if (fourthSum > amountNum) break - - if (denomination3 === 0) break - - if (fourthSum === amountNum) { - solutions.push(newSolution(cassettes, i, j, k, l, shouldFlip)) - } - } - } - } - } - - const endTime = new Date().getTime() - - console.log(`Exiting bruteforce after ${x} tries. Took ${endTime - startTime} ms`) - return solutions -} - -function makeChangeDynamic(cassettes, amount, available) { - while (_.size(cassettes) < 4) { - cassettes.push({ denomination: 0, count: 0 }) - } - - const amountNum = amount.toNumber() - - const solutions = makeChangeDynamicBruteForce(cassettes, amountNum, available) - - const sortedSolutions = _.sortBy(it => { - const arr = [] - - for (let la = 0; la < 4; la++) { - arr.push(cassettes[la].count - it[la].provisioned) - } - - if (arr.length < 2) return Infinity - return _.max(arr) - _.min(arr) - }, solutions) - - const cleanSolution = _.filter( - it => it.denomination > 0, - _.head(sortedSolutions) - ) - - const response = cleanSolution - - // Final sanity check - let total = 0 - _.forEach(it => { - total += it.provisioned * it.denomination - }, response) - - if (total === amountNum) return response - - console.log( - `Failed to find a solution for ${amountNum} with cassettes ${JSON.stringify(cassettes)}` - ) - return [] + const solution = getSolution(outCassettes, amount) + return solutionToOriginalUnits(solution, outCassettes) } module.exports = { makeChange } diff --git a/lib/cash-out/cash-out-actions.js b/lib/cash-out/cash-out-actions.js index bae631fb..13676d58 100644 --- a/lib/cash-out/cash-out-actions.js +++ b/lib/cash-out/cash-out-actions.js @@ -40,10 +40,11 @@ function mapDispense (tx) { const res = {} _.forEach(it => { - res[`provisioned_${it + 1}`] = bills[it].provisioned - res[`denomination_${it + 1}`] = bills[it].denomination - res[`dispensed_${it + 1}`] = bills[it].dispensed - res[`rejected_${it + 1}`] = bills[it].rejected + const suffix = bills[it].name.replace(/cassette|stacker/gi, '') + res[`provisioned_${suffix}`] = bills[it].provisioned + res[`denomination_${suffix}`] = bills[it].denomination + res[`dispensed_${suffix}`] = bills[it].dispensed + res[`rejected_${suffix}`] = bills[it].rejected }, _.times(_.identity(), _.size(bills))) return res diff --git a/lib/cash-out/cash-out-helper.js b/lib/cash-out/cash-out-helper.js index 5cca2a94..66b263a3 100644 --- a/lib/cash-out/cash-out-helper.js +++ b/lib/cash-out/cash-out-helper.js @@ -17,6 +17,32 @@ case else 'Pending' end` +const MAX_CASSETTES = 4 +const MAX_STACKERS = 3 + +const BILL_FIELDS = [ + 'denomination1', + 'denomination2', + 'denomination3', + 'denomination4', + 'denomination1f', + 'denomination1r', + 'denomination2f', + 'denomination2r', + 'denomination3f', + 'denomination3r', + 'provisioned1', + 'provisioned2', + 'provisioned3', + 'provisioned4', + 'provisioned1f', + 'provisioned1r', + 'provisioned2f', + 'provisioned2r', + 'provisioned3f', + 'provisioned3r' +] + module.exports = { redeemableTxs, toObj, toDb, REDEEMABLE_AGE, CASH_OUT_TRANSACTION_STATES } const mapValuesWithKey = _.mapValues.convert({cap: false}) @@ -43,23 +69,37 @@ function convertBigNumFields (obj) { } function convertField (key) { - return _.snakeCase(key) + return _.includes('denomination', key) || _.includes('provisioned', key) ? key : _.snakeCase(key) } function addDbBills (tx) { const bills = tx.bills if (_.isEmpty(bills)) return tx - const billsObj = { - provisioned1: bills[0]?.provisioned ?? 0, - provisioned2: bills[1]?.provisioned ?? 0, - provisioned3: bills[2]?.provisioned ?? 0, - provisioned4: bills[3]?.provisioned ?? 0, - denomination1: bills[0]?.denomination ?? 0, - denomination2: bills[1]?.denomination ?? 0, - denomination3: bills[2]?.denomination ?? 0, - denomination4: bills[3]?.denomination ?? 0 - } + const billFields = _.map(it => _.replace(/(denomination|provisioned)/g, '$1_')(it), BILL_FIELDS) + + const billsObj = _.flow( + _.reduce( + (acc, value) => { + const suffix = value.name.replace(/cassette|stacker/gi, '') + return { + ...acc, + [`provisioned_${suffix}`]: value.provisioned, + [`denomination_${suffix}`]: value.denomination + } + }, + {} + ), + it => { + const missingKeys = _.reduce( + (acc, value) => { + return _.assign({ [value]: 0 })(acc) + }, + {} + )(_.difference(billFields, _.keys(it))) + return _.assign(missingKeys, it) + } + )(bills) return _.assign(tx, billsObj) } @@ -78,7 +118,7 @@ function toObj (row) { let newObj = {} keys.forEach(key => { - const objKey = _.camelCase(key) + const objKey = key.match(/denomination|provisioned/g) ? key.replace(/_/g, '') : _.camelCase(key) if (key === 'received_crypto_atoms' && row[key]) { newObj[objKey] = new BN(row[key]) return @@ -93,35 +133,28 @@ function toObj (row) { newObj.direction = 'cashOut' - const billFields = ['denomination1', 'denomination2', 'denomination3', 'denomination4', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4'] + if (_.every(_.isNil, _.at(BILL_FIELDS, newObj))) return newObj + if (_.some(_.isNil, _.at(BILL_FIELDS, newObj))) throw new Error('Missing cassette values') - if (_.every(_.isNil, _.at(billFields, newObj))) return newObj - if (_.some(_.isNil, _.at(billFields, newObj))) throw new Error('Missing cassette values') - - const billFieldsArr = [ - { - denomination: newObj.denomination1, - provisioned: newObj.provisioned1 - }, - { - denomination: newObj.denomination2, - provisioned: newObj.provisioned2 - }, - { - denomination: newObj.denomination3, - provisioned: newObj.provisioned3 - }, - { - denomination: newObj.denomination4, - provisioned: newObj.provisioned4 - } - ] + const billFieldsArr = _.concat( + _.map(it => ({ name: `cassette${it + 1}`, denomination: newObj[`denomination${it + 1}`], provisioned: newObj[`provisioned${it + 1}`] }))(_.range(0, MAX_CASSETTES)), + _.reduce( + (acc, value) => { + acc.push( + { name: `stacker${value + 1}f`, denomination: newObj[`denomination${value + 1}f`], provisioned: newObj[`provisioned${value + 1}f`] }, + { name: `stacker${value + 1}r`, denomination: newObj[`denomination${value + 1}r`], provisioned: newObj[`provisioned${value + 1}r`] } + ) + return acc + }, + [] + )(_.range(0, MAX_STACKERS)) + ) // There can't be bills with denomination === 0. // If a bill has denomination === 0, then that cassette is not set and should be filtered out. const bills = _.filter(it => it.denomination > 0, billFieldsArr) - return _.set('bills', bills, _.omit(billFields, newObj)) + return _.set('bills', bills, _.omit(BILL_FIELDS, newObj)) } function redeemableTxs (deviceId) { @@ -129,7 +162,10 @@ function redeemableTxs (deviceId) { where device_id=$1 and redeem=$2 and dispense=$3 - and provisioned_1 is not null + and ( + provisioned_1 is not null or provisioned_2 is not null or provisioned_3 is not null or provisioned_4 is not null or + provisioned_1f is not null or provisioned_1r is not null or provisioned_2f is not null or provisioned_2r is not null or provisioned_3f is not null or provisioned_3r is not null + ) and extract(epoch from (now() - greatest(created, confirmed_at))) < $4` return db.any(sql, [deviceId, true, false, REDEEMABLE_AGE]) diff --git a/lib/cash-out/cash-out-tx.js b/lib/cash-out/cash-out-tx.js index b36135db..1fa2a898 100644 --- a/lib/cash-out/cash-out-tx.js +++ b/lib/cash-out/cash-out-tx.js @@ -56,14 +56,15 @@ function postProcess (txVector, justAuthorized, pi) { } if ((newTx.dispense && !oldTx.dispense) || (newTx.redeem && !oldTx.redeem)) { - return pi.buildAvailableCassettes(newTx.id) - .then(cassettes => { + return pi.buildAvailableUnits(newTx.id) + .then(_units => { + const units = _.concat(_units.cassettes, _units.stackers) logger.silly('Computing bills to dispense:', { txId: newTx.id, - cassettes: cassettes.cassettes, + units: units, fiat: newTx.fiat }) - const bills = billMath.makeChange(cassettes.cassettes, newTx.fiat) + const bills = billMath.makeChange(units, newTx.fiat) logger.silly('Bills to dispense:', JSON.stringify(bills)) if (!bills) throw httpError('Out of bills', INSUFFICIENT_FUNDS_CODE) @@ -73,8 +74,9 @@ function postProcess (txVector, justAuthorized, pi) { const rec = {} _.forEach(it => { - rec[`provisioned_${it + 1}`] = bills[it].provisioned - rec[`denomination_${it + 1}`] = bills[it].denomination + const suffix = bills[it].name.replace(/cassette|stacker/gi, '') + rec[`provisioned_${suffix}`] = bills[it].provisioned + rec[`denomination_${suffix}`] = bills[it].denomination }, _.times(_.identity(), _.size(bills))) return cashOutActions.logAction(db, 'provisionNotes', rec, newTx) diff --git a/lib/graphql/types.js b/lib/graphql/types.js index 26bac048..5c96df82 100644 --- a/lib/graphql/types.js +++ b/lib/graphql/types.js @@ -165,11 +165,13 @@ type DynamicCoinValues { } type PhysicalCassette { + name: String! denomination: Int! count: Int! } type PhysicalStacker { + name: String! denomination: Int! count: Int! } diff --git a/lib/new-admin/services/transactions.js b/lib/new-admin/services/transactions.js index 4cdd182a..02aca14c 100644 --- a/lib/new-admin/services/transactions.js +++ b/lib/new-admin/services/transactions.js @@ -153,9 +153,11 @@ function advancedBatch (data) { 'cryptoCode', 'fiat', 'fiatCode', 'fee', 'status', 'fiatProfit', 'cryptoAmount', 'dispense', 'notified', 'redeem', 'phone', 'error', 'created', 'confirmedAt', 'hdIndex', 'swept', 'timedout', - 'dispenseConfirmed', 'provisioned1', 'provisioned2', - 'denomination1', 'denomination2', 'errorCode', 'customerId', - 'txVersion', 'publishedAt', 'termsAccepted', 'layer2Address', + 'dispenseConfirmed', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4', + 'provisioned1f', 'provisioned1r', 'provisioned2f', 'provisioned2r', 'provisioned3f', 'provisioned3r', + 'denomination1', 'denomination2', 'denomination3', 'denomination4', + 'denomination1f', 'denomination1r', 'denomination2f', 'denomination2r', 'denomination3f', 'denomination3r', + 'errorCode', 'customerId', 'txVersion', 'publishedAt', 'termsAccepted', 'layer2Address', 'commissionPercentage', 'rawTickerPrice', 'receivedCryptoAtoms', 'discount', 'txHash', 'customerPhone', 'customerIdCardDataNumber', 'customerIdCardDataExpiration', 'customerIdCardData', 'customerName', 'sendTime', diff --git a/lib/pairing.js b/lib/pairing.js index f6942591..bc613db9 100644 --- a/lib/pairing.js +++ b/lib/pairing.js @@ -10,6 +10,7 @@ const CA_PATH = process.env.CA_PATH // A machine on an older version (no multicassette code) could be paired with a server with multicassette code. // This makes sure that the server stores a default value const DEFAULT_NUMBER_OF_CASSETTES = 2 +const DEFAULT_NUMBER_OF_STACKERS = 0 function pullToken (token) { const sql = `delete from pairing_tokens @@ -36,16 +37,16 @@ function unpair (deviceId) { ) } -function pair (token, deviceId, machineModel, numOfCassettes = DEFAULT_NUMBER_OF_CASSETTES) { +function pair (token, deviceId, machineModel, numOfCassettes = DEFAULT_NUMBER_OF_CASSETTES, numOfStackers = DEFAULT_NUMBER_OF_STACKERS) { return pullToken(token) .then(r => { if (r.expired) return false - const insertSql = `insert into devices (device_id, name, number_of_cassettes) values ($1, $2, $3) + const insertSql = `insert into devices (device_id, name, number_of_cassettes, number_of_stackers) values ($1, $2, $3) on conflict (device_id) do update set paired=TRUE, display=TRUE` - return db.none(insertSql, [deviceId, r.name, numOfCassettes]) + return db.none(insertSql, [deviceId, r.name, numOfCassettes, numOfStackers]) .then(() => true) }) .catch(err => { diff --git a/lib/plugins.js b/lib/plugins.js index 384a5df3..fa6fe92f 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -116,7 +116,7 @@ function plugins (settings, deviceId) { const sumTxs = (sum, tx) => { // cash-out-helper sends 0 as fallback value, need to filter it out as there are no '0' denominations - const bills = _.filter(it => it.denomination > 0, tx.bills) + const bills = _.filter(it => _.includes('cassette', it.name) && it.denomination > 0, tx.bills) const sameDenominations = a => a[0]?.denomination === a[1]?.denomination const doDenominationsMatch = _.every(sameDenominations, _.zip(cassettes, bills)) @@ -139,6 +139,7 @@ function plugins (settings, deviceId) { const computedCassettes = [] _.forEach(it => { computedCassettes.push({ + name: cassettes[it].name, denomination: cassettes[it].denomination, count: counts[it] }) @@ -152,7 +153,7 @@ function plugins (settings, deviceId) { const sumTxs = (sum, tx) => { // cash-out-helper sends 0 as fallback value, need to filter it out as there are no '0' denominations - const bills = _.filter(it => it.denomination > 0, tx.bills) + const bills = _.filter(it => _.includes('stacker', it.name) && it.denomination > 0, tx.bills) const sameDenominations = a => a[0]?.denomination === a[1]?.denomination const doDenominationsMatch = _.every(sameDenominations, _.zip(stackers, bills)) @@ -175,6 +176,7 @@ function plugins (settings, deviceId) { const computedStackers = [] _.forEach(it => { computedStackers.push({ + name: stackers[it].name, denomination: stackers[it].denomination, count: counts[it] }) @@ -210,6 +212,7 @@ function plugins (settings, deviceId) { const cassettes = [] _.forEach(it => { cassettes.push({ + name: `cassette${it + 1}`, denomination: parseInt(denominations[it], 10), count: parseInt(counts[it], 10) }) @@ -241,10 +244,10 @@ function plugins (settings, deviceId) { const denominations = [] _.forEach(it => { - denominations.push(cashOutConfig[`stacker${it + 1}f`], cashOutConfig[`stacker${it + 1}r`]) + denominations.push([cashOutConfig[`stacker${it + 1}f`], cashOutConfig[`stacker${it + 1}r`]]) }, _.times(_.identity(), _stackers.numberOfStackers)) - const virtualStackers = [Math.max(...denominations) * 2] + const virtualStackers = [Math.max(..._.flatten(denominations)) * 2] const counts = _stackers.counts @@ -255,10 +258,16 @@ function plugins (settings, deviceId) { const stackers = [] _.forEach(it => { stackers.push({ - denomination: parseInt(denominations[it], 10), - count: parseInt(counts[it], 10) + name: `stacker${it + 1}f`, + denomination: parseInt(denominations[it][0], 10), + count: parseInt(counts[it][0], 10) }) - }, _.times(_.identity(), _stackers.numberOfStackers * 2)) + stackers.push({ + name: `stacker${it + 1}r`, + denomination: parseInt(denominations[it][1], 10), + count: parseInt(counts[it][1], 10) + }) + }, _.times(_.identity(), _stackers.numberOfStackers)) try { return { @@ -275,6 +284,11 @@ function plugins (settings, deviceId) { }) } + function buildAvailableUnits (excludeTxId) { + return Promise.all([buildAvailableCassettes(excludeTxId), buildAvailableStackers(excludeTxId)]) + .then(([cassettes, stackers]) => ({ cassettes: cassettes.cassettes, stackers: stackers.stackers })) + } + function fetchCurrentConfigVersion () { const sql = `select id from user_config where type=$1 @@ -1051,7 +1065,6 @@ function plugins (settings, deviceId) { sendMessage, checkBalances, getMachineNames, - buildAvailableCassettes, buy, sell, getNotificationConfig, @@ -1062,7 +1075,8 @@ function plugins (settings, deviceId) { isValidWalletScore, getTransactionHash, getInputAddresses, - isWalletScoringEnabled + isWalletScoringEnabled, + buildAvailableUnits } } diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index 17369a1d..617320c9 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -48,7 +48,7 @@ exports.stackerCounts = function stackerCounts (deviceId) { .then(row => { const counts = [] _.forEach(it => { - counts.push(row[`stacker${it + 1}f`], row[`stacker${it + 1}r`]) + counts.push([row[`stacker${it + 1}f`], row[`stacker${it + 1}r`]]) }, _.times(_.identity(), row.number_of_stackers)) return { numberOfStackers: row.number_of_stackers, counts } diff --git a/lib/routes/pairingRoutes.js b/lib/routes/pairingRoutes.js index 0b8a66f8..b7eb1cff 100644 --- a/lib/routes/pairingRoutes.js +++ b/lib/routes/pairingRoutes.js @@ -11,8 +11,9 @@ function pair (req, res, next) { const deviceId = req.deviceId const model = req.query.model const numOfCassettes = req.query.numOfCassettes + const numOfStackers = req.query.numOfStackers - return pairing.pair(token, deviceId, model, numOfCassettes) + return pairing.pair(token, deviceId, model, numOfCassettes, numOfStackers) .then(isValid => { if (isValid) return res.json({ status: 'paired' }) throw httpError('Pairing failed') diff --git a/migrations/1681428616990-aveiro-recycler-settings.js b/migrations/1681428616990-aveiro-recycler-settings.js index 86ea6dd5..f31a2b44 100644 --- a/migrations/1681428616990-aveiro-recycler-settings.js +++ b/migrations/1681428616990-aveiro-recycler-settings.js @@ -33,7 +33,7 @@ exports.up = function (next) { ADD COLUMN stacker2f INTEGER NOT NULL DEFAULT 0, ADD COLUMN stacker2r 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 ADD COLUMN provisioned_1f INTEGER, diff --git a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js index efb27a1b..4e4c4251 100644 --- a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js +++ b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js @@ -3,7 +3,6 @@ import { DialogActions, makeStyles, Box } from '@material-ui/core' import gql from 'graphql-tag' import * as R from 'ramda' import React, { useState } from 'react' -import * as Yup from 'yup' import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper' import Modal from 'src/components/Modal' @@ -28,76 +27,6 @@ import helper from './helper' const useStyles = makeStyles(styles) -const ValidationSchema = Yup.object().shape({ - name: Yup.string().required(), - cashbox: Yup.number() - .label('Cash box') - .required() - .integer() - .min(0) - .max(1000), - cassette1: Yup.number() - .label('Cassette 1') - .required() - .integer() - .min(0) - .max(500), - cassette2: Yup.number() - .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) - .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` query getData($billFilters: JSONObject) { machines { @@ -327,7 +256,6 @@ const CashCassettes = () => { stripeWhen={isCashOutDisabled} elements={nonStackerElements} data={nonStackerMachines} - validationSchema={ValidationSchema} tbodyWrapperClass={classes.tBody} /> @@ -337,7 +265,6 @@ const CashCassettes = () => { stripeWhen={isCashOutDisabled} elements={stackerElements} data={stackerMachines} - validationSchema={ValidationSchema} tbodyWrapperClass={classes.tBody} /> diff --git a/new-lamassu-admin/src/pages/Maintenance/helper.js b/new-lamassu-admin/src/pages/Maintenance/helper.js index 4ed73bc3..3074ca39 100644 --- a/new-lamassu-admin/src/pages/Maintenance/helper.js +++ b/new-lamassu-admin/src/pages/Maintenance/helper.js @@ -5,6 +5,7 @@ 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' +import { cashUnitCapacity } from 'src/utils/machine' const widthsByCashUnits = { 2: { @@ -121,12 +122,13 @@ const getElements = ( width: widthsByCashUnits[getMaxNumberOfCashUnits(machines)]?.cassette, stripe: true, doubleHeader: 'Cash-out', - view: (_, { id, cashUnits }) => ( + view: (_, { id, model, cashUnits }) => ( ( + view: (_, { id, model, cashUnits }) => ( ( + view: (_, { id, model, cashUnits }) => (