feat: cashbox history tab
feat: add information fields to cashbox_batches table
This commit is contained in:
parent
642016efeb
commit
21708aa75c
8 changed files with 267 additions and 13 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
function createCashboxBatch (rec) {
|
function createCashboxBatch (rec) {
|
||||||
const sql = 'INSERT INTO cashbox_batches (device_id, created) VALUES ($1, now()) RETURNING *'
|
const sql = 'INSERT INTO cashbox_batches (device_id, created) VALUES ($1, now()) RETURNING *'
|
||||||
|
|
@ -15,4 +16,23 @@ function createCashboxBatch (rec) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { createCashboxBatch }
|
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 => ({
|
||||||
|
id: it.id,
|
||||||
|
deviceId: it.device_id,
|
||||||
|
created: it.created,
|
||||||
|
operationType: it.operation_type,
|
||||||
|
billCountOverride: it.bill_count_override,
|
||||||
|
performedBy: it.performed_by,
|
||||||
|
bills: it.bills
|
||||||
|
}), res))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBillsByBatchId (id) {
|
||||||
|
const sql = `SELECT * FROM bills WHERE cashbox_batch_id=$1`
|
||||||
|
return db.any(sql, [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createCashboxBatch, getBatches, getBillsByBatchId }
|
||||||
|
|
|
||||||
9
lib/new-admin/graphql/resolvers/cashbox.resolver.js
Normal file
9
lib/new-admin/graphql/resolvers/cashbox.resolver.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
const cashbox = require('../../../cashbox-batches')
|
||||||
|
|
||||||
|
const resolvers = {
|
||||||
|
Query: {
|
||||||
|
cashboxBatches: () => cashbox.getBatches()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = resolvers
|
||||||
|
|
@ -2,6 +2,7 @@ const { mergeResolvers } = require('@graphql-tools/merge')
|
||||||
|
|
||||||
const bill = require('./bill.resolver')
|
const bill = require('./bill.resolver')
|
||||||
const blacklist = require('./blacklist.resolver')
|
const blacklist = require('./blacklist.resolver')
|
||||||
|
const cashbox = require('./cashbox.resolver')
|
||||||
const config = require('./config.resolver')
|
const config = require('./config.resolver')
|
||||||
const currency = require('./currency.resolver')
|
const currency = require('./currency.resolver')
|
||||||
const customer = require('./customer.resolver')
|
const customer = require('./customer.resolver')
|
||||||
|
|
@ -22,6 +23,7 @@ const version = require('./version.resolver')
|
||||||
const resolvers = [
|
const resolvers = [
|
||||||
bill,
|
bill,
|
||||||
blacklist,
|
blacklist,
|
||||||
|
cashbox,
|
||||||
config,
|
config,
|
||||||
currency,
|
currency,
|
||||||
customer,
|
customer,
|
||||||
|
|
|
||||||
19
lib/new-admin/graphql/types/cashbox.type.js
Normal file
19
lib/new-admin/graphql/types/cashbox.type.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
const { gql } = require('apollo-server-express')
|
||||||
|
|
||||||
|
const typeDef = gql`
|
||||||
|
type CashboxBatch {
|
||||||
|
id: ID
|
||||||
|
deviceId: ID
|
||||||
|
created: Date
|
||||||
|
operationType: String
|
||||||
|
customBillCount: Int
|
||||||
|
performedBy: String
|
||||||
|
bills: [Bill]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
cashboxBatches: [CashboxBatch]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
module.exports = typeDef
|
||||||
|
|
@ -2,6 +2,7 @@ const { mergeTypeDefs } = require('@graphql-tools/merge')
|
||||||
|
|
||||||
const bill = require('./bill.type')
|
const bill = require('./bill.type')
|
||||||
const blacklist = require('./blacklist.type')
|
const blacklist = require('./blacklist.type')
|
||||||
|
const cashbox = require('./cashbox.type')
|
||||||
const config = require('./config.type')
|
const config = require('./config.type')
|
||||||
const currency = require('./currency.type')
|
const currency = require('./currency.type')
|
||||||
const customer = require('./customer.type')
|
const customer = require('./customer.type')
|
||||||
|
|
@ -22,6 +23,7 @@ const version = require('./version.type')
|
||||||
const types = [
|
const types = [
|
||||||
bill,
|
bill,
|
||||||
blacklist,
|
blacklist,
|
||||||
|
cashbox,
|
||||||
config,
|
config,
|
||||||
currency,
|
currency,
|
||||||
customer,
|
customer,
|
||||||
|
|
|
||||||
21
migrations/1617967601902-add-batches-type.js
Normal file
21
migrations/1617967601902-add-batches-type.js
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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'
|
||||||
|
)`,
|
||||||
|
`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()
|
||||||
|
}
|
||||||
|
|
@ -12,10 +12,13 @@ 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 { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
||||||
|
import { ReactComponent as ReverseListingViewIcon } from 'src/styling/icons/circle buttons/listing-view/white.svg'
|
||||||
|
import { ReactComponent as ListingViewIcon } from 'src/styling/icons/circle buttons/listing-view/zodiac.svg'
|
||||||
import { fromNamespace } from 'src/utils/config'
|
import { fromNamespace } from 'src/utils/config'
|
||||||
|
|
||||||
import styles from './CashCassettes.styles.js'
|
import styles from './CashCassettes.styles.js'
|
||||||
import CashCassettesFooter from './CashCassettesFooter'
|
import CashCassettesFooter from './CashCassettesFooter'
|
||||||
|
import CashboxHistory from './CashboxHistory'
|
||||||
import Wizard from './Wizard/Wizard'
|
import Wizard from './Wizard/Wizard'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
@ -90,6 +93,7 @@ const SET_CASSETTE_BILLS = gql`
|
||||||
|
|
||||||
const CashCassettes = () => {
|
const CashCassettes = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const [showHistory, setShowHistory] = useState(false)
|
||||||
|
|
||||||
const { data } = useQuery(GET_MACHINES_AND_CONFIG)
|
const { data } = useQuery(GET_MACHINES_AND_CONFIG)
|
||||||
const [wizard, setWizard] = useState(false)
|
const [wizard, setWizard] = useState(false)
|
||||||
|
|
@ -200,21 +204,37 @@ const CashCassettes = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TitleSection title="Cash Cassettes" />
|
<TitleSection
|
||||||
|
title="Cash Cassettes"
|
||||||
|
button={{
|
||||||
|
text: 'Cashbox history',
|
||||||
|
icon: ListingViewIcon,
|
||||||
|
inverseIcon: ReverseListingViewIcon,
|
||||||
|
toggle: setShowHistory
|
||||||
|
}}
|
||||||
|
iconClassName={classes.listViewButton}
|
||||||
|
/>
|
||||||
<div className={classes.tableContainer}>
|
<div className={classes.tableContainer}>
|
||||||
<EditableTable
|
{!showHistory && (
|
||||||
error={error?.message}
|
<>
|
||||||
name="cashboxes"
|
<EditableTable
|
||||||
stripeWhen={isCashOutDisabled}
|
error={error?.message}
|
||||||
elements={elements}
|
name="cashboxes"
|
||||||
data={machines}
|
enableEdit
|
||||||
validationSchema={ValidationSchema}
|
stripeWhen={isCashOutDisabled}
|
||||||
tbodyWrapperClass={classes.tBody}
|
elements={elements}
|
||||||
/>
|
data={machines}
|
||||||
|
save={onSave}
|
||||||
|
validationSchema={ValidationSchema}
|
||||||
|
tbodyWrapperClass={classes.tBody}
|
||||||
|
/>
|
||||||
|
|
||||||
{data && R.isEmpty(machines) && (
|
{data && R.isEmpty(machines) && (
|
||||||
<EmptyTable message="No machines so far" />
|
<EmptyTable message="No machines so far" />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
{showHistory && <CashboxHistory machines={machines} />}
|
||||||
</div>
|
</div>
|
||||||
<CashCassettesFooter
|
<CashCassettesFooter
|
||||||
currencyCode={fiatCurrency}
|
currencyCode={fiatCurrency}
|
||||||
|
|
|
||||||
161
new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js
Normal file
161
new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
import { useQuery } 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 from 'react'
|
||||||
|
|
||||||
|
import { NumberInput } from 'src/components/inputs/formik'
|
||||||
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
|
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 styles = {
|
||||||
|
operationType: {
|
||||||
|
marginLeft: 8
|
||||||
|
},
|
||||||
|
operationTypeWrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const CashboxHistory = ({ machines }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
const { data } = useQuery(GET_BATCHES)
|
||||||
|
|
||||||
|
const batches = R.path(['cashboxBatches'])(data)
|
||||||
|
|
||||||
|
const getOperationRender = {
|
||||||
|
'cash-in-empty': (
|
||||||
|
<>
|
||||||
|
<TxInIcon />
|
||||||
|
<span className={classes.operationType}>Cash-in emptied</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
'cash-out-1-refill': (
|
||||||
|
<>
|
||||||
|
<TxOutIcon />
|
||||||
|
<span className={classes.operationType}>Cash-out 1 refill</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
'cash-out-1-empty': (
|
||||||
|
<>
|
||||||
|
<TxOutIcon />
|
||||||
|
<span className={classes.operationType}>Cash-out 1 emptied</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
'cash-out-2-refill': (
|
||||||
|
<>
|
||||||
|
<TxOutIcon />
|
||||||
|
<span className={classes.operationType}>Cash-out 2 refill</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
'cash-out-2-empty': (
|
||||||
|
<>
|
||||||
|
<TxOutIcon />
|
||||||
|
<span className={classes.operationType}>Cash-out 2 emptied</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = [
|
||||||
|
{
|
||||||
|
name: 'operation',
|
||||||
|
header: 'Operation',
|
||||||
|
width: 200,
|
||||||
|
textAlign: 'left',
|
||||||
|
view: it => (
|
||||||
|
<div className={classes.operationTypeWrapper}>
|
||||||
|
{getOperationRender[it.operationType]}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'machine',
|
||||||
|
header: 'Machine',
|
||||||
|
width: 190,
|
||||||
|
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: 125,
|
||||||
|
textAlign: 'right',
|
||||||
|
view: it => R.sum(R.map(b => R.prop('fiat', b), it.bills))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'date',
|
||||||
|
header: 'Date',
|
||||||
|
width: 125,
|
||||||
|
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: 200,
|
||||||
|
textAlign: 'left',
|
||||||
|
view: it => (R.isNil(it.performedBy) ? 'Unknown entity' : it.performedBy)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
header: 'Edit',
|
||||||
|
width: 120,
|
||||||
|
textAlign: 'right',
|
||||||
|
view: it => 'aaaaa'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DataTable name="cashboxHistory" elements={elements} data={batches} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CashboxHistory
|
||||||
Loading…
Add table
Add a link
Reference in a new issue