feat: add emptyUnit bills table
feat: rework cash unit operations fix: multiple fixes for the empty unit workflow
This commit is contained in:
parent
797f074898
commit
45f722e724
8 changed files with 171 additions and 59 deletions
|
|
@ -2,10 +2,11 @@ const constants = require('./constants')
|
|||
const db = require('./db')
|
||||
const _ = require('lodash/fp')
|
||||
const uuid = require('uuid')
|
||||
const camelize = require('./utils')
|
||||
|
||||
function createCashboxBatch (deviceId, cashboxCount) {
|
||||
if (_.isEqual(0, cashboxCount)) throw new Error('Cash box is empty. Cash box batch could not be created.')
|
||||
const sql = `INSERT INTO cashbox_batches (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-box-empty') RETURNING *`
|
||||
const sql = `INSERT INTO cash_unit_operation (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-box-empty') RETURNING *`
|
||||
const sql2 = `
|
||||
UPDATE bills SET cashbox_batch_id=$1
|
||||
FROM cash_in_txs
|
||||
|
|
@ -13,9 +14,16 @@ function createCashboxBatch (deviceId, cashboxCount) {
|
|||
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])
|
||||
const sql3 = `
|
||||
UPDATE empty_unit_bills SET cashbox_batch_id=$1
|
||||
WHERE empty_unit_bills.device_id = $2 AND empty_unit_bills.cashbox_batch_id IS NULL`
|
||||
|
||||
return db.tx(t => {
|
||||
const batchId = uuid.v4()
|
||||
const q1 = t.none(sql, [batchId, deviceId])
|
||||
const q2 = t.none(sql2, [batchId, deviceId])
|
||||
const q3 = t.none(sql3, [batchId, deviceId])
|
||||
return t.batch([q1, q2, q3])
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -31,12 +39,14 @@ function updateMachineWithBatch (machineContext, oldCashboxCount) {
|
|||
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-box-empty')`, [batchId, deviceId])
|
||||
const q1 = t.none(`INSERT INTO cash_unit_operation (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-box-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, stacker1f=$6, stacker1r=$7, stacker2f=$8, stacker2r=$9, stacker3f=$10, stacker3r=$11 WHERE device_id=$12`, [
|
||||
const q3 = t.none(`UPDATE empty_unit_bills SET cashbox_batch_id=$1
|
||||
WHERE empty_unit_bills.device_id = $2 AND empty_unit_bills.cashbox_batch_id IS NULL`, [batchId, deviceId])
|
||||
const q4 = t.none(`UPDATE devices SET cashbox=$1, cassette1=$2, cassette2=$3, cassette3=$4, cassette4=$5, stacker1f=$6, stacker1r=$7, stacker2f=$8, stacker2r=$9, stacker3f=$10, stacker3r=$11 WHERE device_id=$12`, [
|
||||
cashUnits.cashbox,
|
||||
cashUnits.cassette1,
|
||||
cashUnits.cassette2,
|
||||
|
|
@ -51,29 +61,36 @@ function updateMachineWithBatch (machineContext, oldCashboxCount) {
|
|||
machineContext.deviceId
|
||||
])
|
||||
|
||||
return t.batch([q1, q2, q3])
|
||||
return t.batch([q1, q2, q3, q4])
|
||||
})
|
||||
}
|
||||
|
||||
function getBatches (from = new Date(0).toISOString(), until = new Date().toISOString()) {
|
||||
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 AS cb
|
||||
LEFT JOIN bills AS b ON cb.id = b.cashbox_batch_id
|
||||
WHERE cb.created >= $1 AND cb.created <= $2
|
||||
GROUP BY cb.id
|
||||
ORDER BY cb.created DESC
|
||||
SELECT cuo.id, cuo.device_id, cuo.created, cuo.operation_type, cuo.bill_count_override, cuo.performed_by, json_agg(bi.*) AS bills
|
||||
FROM cash_unit_operation AS cuo
|
||||
LEFT JOIN (
|
||||
SELECT b.id, b.fiat, b.fiat_code, b.created, b.cashbox_batch_id, cit.device_id AS device_id FROM bills b LEFT OUTER JOIN (SELECT id, device_id FROM cash_in_txs) AS cit ON cit.id = b.cash_in_txs_id UNION
|
||||
SELECT id, fiat, fiat_code, created, cashbox_batch_id, device_id FROM empty_unit_bills
|
||||
) AS bi ON cuo.id = bi.cashbox_batch_id
|
||||
WHERE cuo.created >= $1 AND cuo.created <= $2 AND cuo.operation_type = 'cash-box-empty'
|
||||
GROUP BY cuo.id
|
||||
ORDER BY cuo.created DESC
|
||||
`
|
||||
return db.any(sql, [from, until]).then(res => _.map(it => _.mapKeys(ite => _.camelCase(ite), it), res))
|
||||
|
||||
return db.any(sql, [from, until]).then(camelize)
|
||||
}
|
||||
|
||||
function editBatchById (id, performedBy) {
|
||||
const sql = `UPDATE cashbox_batches SET performed_by=$1 WHERE id=$2`
|
||||
const sql = `UPDATE cash_unit_operation SET performed_by=$1 WHERE id=$2 AND cuo.operation_type = 'cash-box-empty'`
|
||||
return db.none(sql, [performedBy, id])
|
||||
}
|
||||
|
||||
function getBillsByBatchId (id) {
|
||||
const sql = `SELECT * FROM bills WHERE cashbox_batch_id=$1`
|
||||
const sql = `SELECT bi.* FROM (
|
||||
SELECT b.id, b.fiat, b.fiat_code, b.created, b.cashbox_batch_id, cit.device_id AS device_id FROM bills b LEFT OUTER JOIN (SELECT id, device_id FROM cash_in_txs) AS cit ON cit.id = b.cash_in_txs_id UNION
|
||||
SELECT id, fiat, fiat_code, created, cashbox_batch_id, device_id FROM empty_unit_bills
|
||||
) AS bi WHERE bi.cashbox_batch_id=$1`
|
||||
return db.any(sql, [id])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const _ = require('lodash/fp')
|
||||
const pgp = require('pg-promise')()
|
||||
const axios = require('axios')
|
||||
const uuid = require('uuid')
|
||||
|
||||
const batching = require('./cashbox-batches')
|
||||
|
|
@ -13,6 +12,7 @@ const settingsLoader = require('./new-settings-loader')
|
|||
const notifierUtils = require('./notifier/utils')
|
||||
const notifierQueries = require('./notifier/queries')
|
||||
const { ApolloError } = require('apollo-server-errors');
|
||||
const { loadLatestConfig } = require('./new-settings-loader')
|
||||
|
||||
const fullyFunctionalStatus = { label: 'Fully functional', type: 'success' }
|
||||
const unresponsiveStatus = { label: 'Unresponsive', type: 'error' }
|
||||
|
|
@ -173,39 +173,72 @@ function setCassetteBills (rec) {
|
|||
})
|
||||
}
|
||||
|
||||
function emptyMachineUnits (deviceId, units) {
|
||||
return Promise.all([getMachine(deviceId), configManager.getCashOut(deviceId, getConfig())])
|
||||
function emptyMachineUnits ({ deviceId, newUnits, fiatCode }) {
|
||||
return loadLatestConfig()
|
||||
.then(config => Promise.all([getMachine(deviceId), configManager.getCashOut(deviceId, config)]))
|
||||
.then(([machine, cashoutSettings]) => {
|
||||
const movedBills = _.reduce(
|
||||
(acc, value) => ({
|
||||
...acc,
|
||||
[value]: {
|
||||
delta: machine[value] - units[value],
|
||||
denomination: cashoutSettings[value]
|
||||
operationName: `cash-${_.replace(/(cassette|stacker)/g, '$1-')(value)}-empty`,
|
||||
delta: newUnits[value] - machine.cashUnits[value],
|
||||
denomination: value !== 'cashbox' ? cashoutSettings[value] : null
|
||||
}
|
||||
}),
|
||||
{},
|
||||
_.keys(units)
|
||||
_.keys(newUnits)
|
||||
)
|
||||
|
||||
const sql = `UPDATE devices SET cassette1=$2, cassette2=$3, cassette3=$4, cassette4=$5, stacker1f=$6, stacker1r=$7, stacker2f=$8, stacker2r=$9, stacker3f=$10, stacker3r=$11 WHERE device_id=$1`
|
||||
return db.none(sql, [
|
||||
deviceId,
|
||||
_.defaultTo(0, units.cassette1),
|
||||
_.defaultTo(0, units.cassette2),
|
||||
_.defaultTo(0, units.cassette3),
|
||||
_.defaultTo(0, units.cassette4),
|
||||
_.defaultTo(0, units.stacker1f),
|
||||
_.defaultTo(0, units.stacker1r),
|
||||
_.defaultTo(0, units.stacker2f),
|
||||
_.defaultTo(0, units.stacker2r),
|
||||
_.defaultTo(0, units.stacker3f),
|
||||
_.defaultTo(0, units.stacker3r)
|
||||
])
|
||||
const operationNames = _.mapValues(it => it.operationName)(_.filter(it => Math.abs(it.delta) > 0)(_.omit(['cashbox'], movedBills)))
|
||||
const operationsToCreate = _.map(it => ({
|
||||
id: uuid.v4(),
|
||||
device_id: deviceId,
|
||||
operation_type: it
|
||||
}))(operationNames)
|
||||
|
||||
const billArr = _.reduce(
|
||||
(acc, value) => {
|
||||
const unit = movedBills[value]
|
||||
return _.concat(acc, _.times(() => ({
|
||||
id: uuid.v4(),
|
||||
fiat: unit.denomination,
|
||||
fiat_code: fiatCode,
|
||||
device_id: deviceId
|
||||
// TODO: Uncomment this if we decide to keep track of bills across multiple operations. For now, we'll just create the emptying operations for each unit affected, but not relate these events with individual bills and just use the field for the cashbox batch event
|
||||
// cash_unit_operation_id: _.find(it => it.operation_type === `cash-${_.replace(/(cassette|stacker)/g, '$1-')(value)}-empty`, operationsToCreate).id
|
||||
}), Math.abs(unit.delta)))
|
||||
},
|
||||
[],
|
||||
_.keys(_.omit(['cashbox'], movedBills))
|
||||
)
|
||||
|
||||
return db.tx(t => {
|
||||
const q1Cols = ['id', 'device_id', 'operation_type']
|
||||
const q1= t.none(pgp.helpers.insert(operationsToCreate, q1Cols, 'cash_unit_operation'))
|
||||
const q2Cols = ['id', 'fiat', 'fiat_code', 'device_id']
|
||||
const q2 = t.none(pgp.helpers.insert(billArr, q2Cols, 'empty_unit_bills'))
|
||||
const q3 = t.none(`UPDATE devices SET cashbox=$1, cassette1=$2, cassette2=$3, cassette3=$4, cassette4=$5, stacker1f=$6, stacker1r=$7, stacker2f=$8, stacker2r=$9, stacker3f=$10, stacker3r=$11 WHERE device_id=$12`, [
|
||||
_.defaultTo(machine.cashUnits.cashbox, newUnits.cashbox),
|
||||
_.defaultTo(machine.cashUnits.cassette1, newUnits.cassette1),
|
||||
_.defaultTo(machine.cashUnits.cassette2, newUnits.cassette2),
|
||||
_.defaultTo(machine.cashUnits.cassette3, newUnits.cassette3),
|
||||
_.defaultTo(machine.cashUnits.cassette4, newUnits.cassette4),
|
||||
_.defaultTo(machine.cashUnits.stacker1f, newUnits.stacker1f),
|
||||
_.defaultTo(machine.cashUnits.stacker1r, newUnits.stacker1r),
|
||||
_.defaultTo(machine.cashUnits.stacker2f, newUnits.stacker2f),
|
||||
_.defaultTo(machine.cashUnits.stacker2r, newUnits.stacker2r),
|
||||
_.defaultTo(machine.cashUnits.stacker3f, newUnits.stacker3f),
|
||||
_.defaultTo(machine.cashUnits.stacker3r, newUnits.stacker3r),
|
||||
deviceId
|
||||
])
|
||||
|
||||
return t.batch([q1, q2, q3])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function refillMachineUnits (deviceId, units) {
|
||||
function refillMachineUnits ({deviceId, newUnits}) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ const typeDef = gql`
|
|||
type Bill {
|
||||
id: ID
|
||||
fiat: Int
|
||||
fiatCode: String
|
||||
deviceId: ID
|
||||
created: Date
|
||||
cashboxBatchId: ID
|
||||
cashUnitOperationId: ID
|
||||
}
|
||||
|
||||
type Query {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ const getBills = filters => {
|
|||
SELECT id, device_id FROM cash_in_txs ${deviceStatement}
|
||||
) AS cit ON cit.id = b.cash_in_txs_id ${batchStatement(filters.batch)}`
|
||||
|
||||
return db.any(sql)
|
||||
.then(res => _.map(_.mapKeys(_.camelCase), res))
|
||||
const sql2 = `SELECT b.id, b.fiat, b.fiat_code, b.created, b.cashbox_batch_id, b.device_id FROM empty_unit_bills b ${deviceStatement} ${!_.isNil(filters.deviceId) && !_.isNil(filters.batch) ? `AND` : ``} ${batchStatement(filters.batch)}`
|
||||
|
||||
return Promise.all([db.any(sql), db.any(sql2)])
|
||||
.then(([bills, operationalBills]) => _.map(_.mapKeys(_.camelCase), _.concat(bills, operationalBills)))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -4,28 +4,20 @@ const router = express.Router()
|
|||
|
||||
const emptyUnitUpdateCounts = (req, res, next) => {
|
||||
const deviceId = req.deviceId
|
||||
const newUnits = req.body.newUnits
|
||||
const { units: newUnits, fiatCode } = req.body
|
||||
|
||||
return emptyMachineUnits({ deviceId, cashUnits: newUnits })
|
||||
.then(() => res.sendStatus(200))
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
return res.sendStatus(500)
|
||||
})
|
||||
.finally(next)
|
||||
return emptyMachineUnits({ deviceId, newUnits: newUnits, fiatCode })
|
||||
.then(() => res.status(200).send({ status: 'OK' }))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
const refillUnitUpdateCounts = (req, res, next) => {
|
||||
const deviceId = req.deviceId
|
||||
const newUnits = req.body.newUnits
|
||||
|
||||
return refillMachineUnits({ deviceId, cashUnits: newUnits })
|
||||
.then(() => res.sendStatus(200))
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
return res.sendStatus(500)
|
||||
})
|
||||
.finally(next)
|
||||
return refillMachineUnits({ deviceId, newUnits: newUnits })
|
||||
.then(() => res.status(200).send({ status: 'OK' }))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
router.post('/empty', emptyUnitUpdateCounts)
|
||||
|
|
|
|||
9
lib/utils.js
Normal file
9
lib/utils.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
const _ = require('lodash')
|
||||
|
||||
const camelize = obj =>
|
||||
_.transform(obj, (acc, value, key, target) => {
|
||||
const camelKey = _.isArray(target) ? key : _.camelCase(key.toString())
|
||||
acc[camelKey] = _.isObject(value) && !(value instanceof Date) ? camelize(value) : value
|
||||
})
|
||||
|
||||
module.exports = camelize
|
||||
|
|
@ -48,8 +48,65 @@ exports.up = function (next) {
|
|||
ADD COLUMN denomination_2r INTEGER,
|
||||
ADD COLUMN denomination_3f INTEGER,
|
||||
ADD COLUMN denomination_3r INTEGER`,
|
||||
`CREATE TYPE bill_destination_unit AS ENUM ('cashbox', 'stacker1f', 'stacker1r', 'stacker2f', 'stacker2r', 'stacker3f', 'stacker3r')`,
|
||||
`ALTER TABLE bills ADD COLUMN destination_unit bill_destination_unit NOT NULL DEFAULT 'cashbox'`
|
||||
`CREATE TYPE cash_unit AS ENUM (
|
||||
'cashbox',
|
||||
'cassette1',
|
||||
'cassette2',
|
||||
'cassette3',
|
||||
'cassette4',
|
||||
'stacker1f',
|
||||
'stacker1r',
|
||||
'stacker2f',
|
||||
'stacker2r',
|
||||
'stacker3f',
|
||||
'stacker3r'
|
||||
)`,
|
||||
`ALTER TABLE bills ADD COLUMN destination_unit cash_unit NOT NULL DEFAULT 'cashbox'`,
|
||||
`CREATE TYPE cash_unit_operation_type AS ENUM(
|
||||
'cash-box-empty',
|
||||
'cash-box-refill',
|
||||
'cash-cassette-1-refill',
|
||||
'cash-cassette-1-empty',
|
||||
'cash-cassette-1-count-change',
|
||||
'cash-cassette-2-refill',
|
||||
'cash-cassette-2-empty',
|
||||
'cash-cassette-2-count-change',
|
||||
'cash-cassette-3-refill',
|
||||
'cash-cassette-3-empty',
|
||||
'cash-cassette-3-count-change',
|
||||
'cash-cassette-4-refill',
|
||||
'cash-cassette-4-empty',
|
||||
'cash-cassette-4-count-change',
|
||||
'cash-stacker-1f-refill',
|
||||
'cash-stacker-1f-empty',
|
||||
'cash-stacker-1f-count-change',
|
||||
'cash-stacker-1r-refill',
|
||||
'cash-stacker-1r-empty',
|
||||
'cash-stacker-1r-count-change',
|
||||
'cash-stacker-2f-refill',
|
||||
'cash-stacker-2f-empty',
|
||||
'cash-stacker-2f-count-change',
|
||||
'cash-stacker-2r-refill',
|
||||
'cash-stacker-2r-empty',
|
||||
'cash-stacker-2r-count-change',
|
||||
'cash-stacker-3f-refill',
|
||||
'cash-stacker-3f-empty',
|
||||
'cash-stacker-3f-count-change',
|
||||
'cash-stacker-3r-refill',
|
||||
'cash-stacker-3r-empty',
|
||||
'cash-stacker-3r-count-change'
|
||||
)`,
|
||||
`ALTER TABLE cashbox_batches ALTER COLUMN operation_type TYPE cash_unit_operation_type USING operation_type::text::cash_unit_operation_type`,
|
||||
`ALTER TABLE cashbox_batches RENAME TO cash_unit_operation`,
|
||||
`DROP TYPE cashbox_batch_type`,
|
||||
`CREATE TABLE empty_unit_bills (
|
||||
id UUID PRIMARY KEY,
|
||||
fiat INTEGER NOT NULL,
|
||||
fiat_code TEXT NOT NULL,
|
||||
created TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
device_id TEXT REFERENCES devices (device_id),
|
||||
cashbox_batch_id UUID REFERENCES cash_unit_operation (id)
|
||||
)`
|
||||
]
|
||||
|
||||
db.multi(sql, next)
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ const GET_BATCHES = gql`
|
|||
performedBy
|
||||
bills {
|
||||
fiat
|
||||
deviceId
|
||||
created
|
||||
fiatCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +71,8 @@ const CashboxHistory = ({ machines, currency, timezone }) => {
|
|||
|
||||
const { data: batchesData, loading: batchesLoading } = useQuery(GET_BATCHES)
|
||||
|
||||
console.log(batchesData)
|
||||
|
||||
/* const [editBatch] = useMutation(EDIT_BATCH, {
|
||||
refetchQueries: () => ['cashboxBatches']
|
||||
}) */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue