feat: cashbox history tab
This commit is contained in:
parent
683778cb7b
commit
dde26b8dc2
4 changed files with 326 additions and 17 deletions
|
|
@ -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')
|
||||
|
|
@ -280,7 +281,18 @@ const typeDefs = gql`
|
|||
cashbox: Int
|
||||
}
|
||||
|
||||
type CashboxBatch {
|
||||
id: ID
|
||||
deviceId: ID
|
||||
created: Date
|
||||
operationType: String
|
||||
customBillCount: Int
|
||||
performedBy: String
|
||||
bills: [Bills]
|
||||
}
|
||||
|
||||
type Query {
|
||||
cashboxBatches: [CashboxBatch]
|
||||
countries: [Country]
|
||||
currencies: [Currency]
|
||||
languages: [Language]
|
||||
|
|
@ -352,6 +364,7 @@ const typeDefs = gql`
|
|||
toggleClearNotification(id: ID!, read: Boolean!): Notification
|
||||
clearAllNotifications: Notification
|
||||
cancelCashOutTransaction(id: ID): Transaction
|
||||
editBatch(id: ID, performedBy: String): CashboxBatch
|
||||
}
|
||||
`
|
||||
|
||||
|
|
@ -378,6 +391,7 @@ const resolvers = {
|
|||
latestEvent: parent => machineEventsLoader.load(parent.deviceId)
|
||||
},
|
||||
Query: {
|
||||
cashboxBatches: () => cashbox.getBatches(),
|
||||
countries: () => countries,
|
||||
currencies: () => currencies,
|
||||
languages: () => languages,
|
||||
|
|
@ -449,7 +463,8 @@ 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),
|
||||
editBatch: (...[, { id, performedBy }]) => cashbox.editBatchById(id, performedBy)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
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()
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ 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 { Table as EditableTable } from 'src/components/editableTable'
|
||||
|
|
@ -10,10 +10,13 @@ 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 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'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
|
|
@ -108,6 +111,7 @@ const SET_CASSETTE_BILLS = gql`
|
|||
|
||||
const CashCassettes = () => {
|
||||
const classes = useStyles()
|
||||
const [showHistory, setShowHistory] = useState(false)
|
||||
|
||||
const { data } = useQuery(GET_MACHINES_AND_CONFIG)
|
||||
|
||||
|
|
@ -200,23 +204,39 @@ const CashCassettes = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<TitleSection title="Cash Cassettes" />
|
||||
<TitleSection
|
||||
title="Cash Cassettes"
|
||||
button={{
|
||||
text: 'Cashbox history',
|
||||
icon: HistoryIcon,
|
||||
inverseIcon: ReverseHistoryIcon,
|
||||
toggle: setShowHistory
|
||||
}}
|
||||
iconClassName={classes.listViewButton}
|
||||
/>
|
||||
<div className={classes.tableContainer}>
|
||||
<EditableTable
|
||||
error={error?.message}
|
||||
name="cashboxes"
|
||||
enableEdit
|
||||
enableEditText="Update"
|
||||
stripeWhen={isCashOutDisabled}
|
||||
elements={elements}
|
||||
data={machines}
|
||||
save={onSave}
|
||||
validationSchema={ValidationSchema}
|
||||
tbodyWrapperClass={classes.tBody}
|
||||
/>
|
||||
{!showHistory && (
|
||||
<>
|
||||
<EditableTable
|
||||
error={error?.message}
|
||||
name="cashboxes"
|
||||
enableEdit
|
||||
enableEditText="Update"
|
||||
stripeWhen={isCashOutDisabled}
|
||||
elements={elements}
|
||||
data={machines}
|
||||
save={onSave}
|
||||
validationSchema={ValidationSchema}
|
||||
tbodyWrapperClass={classes.tBody}
|
||||
/>
|
||||
|
||||
{data && R.isEmpty(machines) && (
|
||||
<EmptyTable message="No machines so far" />
|
||||
{data && R.isEmpty(machines) && (
|
||||
<EmptyTable message="No machines so far" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{showHistory && (
|
||||
<CashboxHistory machines={machines} currency={fiatCurrency} />
|
||||
)}
|
||||
</div>
|
||||
<CashCassettesFooter
|
||||
|
|
|
|||
253
new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js
Normal file
253
new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
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 [editing, setEditing] = useState(false)
|
||||
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 = {
|
||||
'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 save = row => {
|
||||
schema
|
||||
.isValid(fields)
|
||||
.then(() => {
|
||||
setError(false)
|
||||
editBatch({
|
||||
variables: { id: row.id, performedBy: fields?.performedBy }
|
||||
})
|
||||
})
|
||||
.catch(setError(true))
|
||||
return close()
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
setFields({})
|
||||
return setEditing(false)
|
||||
}
|
||||
|
||||
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: 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 => (
|
||||
<span>
|
||||
{R.sum(R.map(b => R.prop('fiat', b), it.bills))} {currency}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
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 (!editing)
|
||||
return R.isNil(it.performedBy) ? 'Unknown entity' : it.performedBy
|
||||
return (
|
||||
<TextInput
|
||||
onChange={e =>
|
||||
setFields({ ...fields, performedBy: e.target.value })
|
||||
}
|
||||
error={error}
|
||||
width={190 * 0.85}
|
||||
value={fields.performedBy ?? ''}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
header: 'Edit',
|
||||
width: 150,
|
||||
textAlign: 'right',
|
||||
view: it => {
|
||||
if (!editing)
|
||||
return (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setFields({})
|
||||
setEditing(true)
|
||||
}}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
)
|
||||
return (
|
||||
<div className={classes.saveAndCancel}>
|
||||
<Link type="submit" color="primary" onClick={() => save(it)}>
|
||||
Save
|
||||
</Link>
|
||||
<Link color="secondary" onClick={close}>
|
||||
Cancel
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
{!loading && (
|
||||
<DataTable
|
||||
name="cashboxHistory"
|
||||
elements={elements}
|
||||
data={batches}
|
||||
emptyText="No cashbox batches so far"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CashboxHistory
|
||||
Loading…
Add table
Add a link
Reference in a new issue