Skip to content

Commit

Permalink
Merge pull request #344 from setlife-network/hotfix/1.0.3
Browse files Browse the repository at this point in the history
Hotfix/1.0.3
  • Loading branch information
otech47 committed Feb 27, 2021
2 parents 2cc1b90 + f492eb9 commit 8ad9977
Show file tree
Hide file tree
Showing 31 changed files with 1,416 additions and 227 deletions.
19 changes: 19 additions & 0 deletions api/migrations/20210223183216-addCurrencyToRates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction(t => {
return Promise.all([
queryInterface.addColumn('Rates', 'currency', {
type: Sequelize.DataTypes.STRING
}, { transaction: t })
])
})
},

down: async (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction(t => {
return Promise.all([
queryInterface.removeColumn('Rates', 'currency', { transaction: t })
])
})
}
};
3 changes: 3 additions & 0 deletions api/models/Rate.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ module.exports = (sequelize) => {
type: DataTypes.STRING,
allowNull: false
},
currency: {
type: DataTypes.STRING
},
contributor_id: { //FK
type: DataTypes.INTEGER(11),
references: {
Expand Down
58 changes: 56 additions & 2 deletions api/schema/resolvers/ProjectResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,59 @@ module.exports = {
where: whereConditions
})
},
totalAllocated: async (project, args, { models }) => {
validateDatesFormat({
fromDate: args.fromDate,
toDate: args.toDate
})
const whereConditions = {}
whereConditions['project_id'] = project.id
if (args.confirmedOnly) {
whereConditions['payment_id'] = {
[Op.ne]: args.confirmedOnly && null
}
}
if (args.fromDate || args.endDate) {
whereCondition['date_paid'] = {
[Op.between]: [
args.fromDate
? args.fromDate
: moment.utc(1),
args.toDate
? args.toDate
: moment.utc()
]
}
}
const totalAllocated = await models.Allocation.findOne({
attributes: [[fn('sum', col('amount')), 'amount']],
raw: true,
where: {
...whereConditions
}
})
return totalAllocated ? totalAllocated.amount : 0
},
totalIncurredPayments: async (project, args, { models }) => {
const total = await models.Payment.findOne({
attributes: [[fn('sum', col('amount')), 'totalIncurred']],
where: {
date_paid: {
[Op.eq]: null
},
},
include: [
{
model: models.Allocation,
where: {
project_id: project.id
},
required: true
}
]
})
return total ? total.dataValues.totalIncurred : 0
},
totalPaid: async (project, args, { models }) => {
validateDatesFormat({
fromDate: args.fromDate,
Expand Down Expand Up @@ -372,9 +425,10 @@ module.exports = {
model: models.Allocation,
where: {
project_id: project.id
}
},
required: true
}
],
]
})
return total
? total.dataValues.totalPaid
Expand Down
1 change: 0 additions & 1 deletion api/schema/resolvers/RateResolver.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module.exports = {

Rate: {
contributor: (rate, args, { models }) => {
return models.Contributor.findByPk(rate.contributor_id)
Expand Down
11 changes: 9 additions & 2 deletions api/schema/types/ProjectType.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,20 @@ module.exports = gql`
contributor_id: Int
): TimeSpent
timeSpentPerContributor(
fromDate: String
fromDate: String,
toDate: String
): [TimeSpent]
#calculated properties
totalAllocated(
fromDate: String,
toDate: String,
confirmedOnly: Boolean
): Int
totalPaid(
fromDate: String,
toDate: String
): Int
): Int # The total paid from the client, takes start date and ed date as attributes
totalIncurredPayments: Int # The total incurred not paid from the client
}
type AverageIssueCost {
Expand Down
2 changes: 2 additions & 0 deletions api/schema/types/RateType.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = gql`
type: String!
contributor_id: Int!
total_expected_hours: Int
currency: String
contributor: Contributor
}
Expand All @@ -16,6 +17,7 @@ module.exports = gql`
total_expected_hours: Int
hourly_rate: String
type: String
currency: String
contributor_id: Int
}
Expand Down
4 changes: 2 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import ClientsListPage from './pages/ClientsListPage'
import ContributorDetailPage from './pages/ContributorDetailPage'
import HomePage from './pages/HomePage'
import LoginPage from './pages/LoginPage'
import PaymentsAddPage from './pages/PaymentsAddPage'
import AddPaymentPage from './pages/AddPaymentPage'
import ProjectDetailPage from './pages/ProjectDetailPage'
import ProjectsListPage from './pages/ProjectsListPage'

Expand Down Expand Up @@ -61,7 +61,7 @@ class App extends React.Component {
/>
<PrivateRoute
path='/clients/:clientId/payments/add'
component={PaymentsAddPage}
component={AddPaymentPage}
/>
<PrivateRoute
path='/projects/:projectId'
Expand Down
193 changes: 193 additions & 0 deletions src/components/AddPaymentForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import React, { useEffect, useState } from 'react'
import { useQuery, useMutation } from '@apollo/client'
import { useHistory } from 'react-router-dom'
import {
Box,
Button,
FormControl,
Grid,
Snackbar,
TextField,
Typography
} from '@material-ui/core'
import Alert from '@material-ui/lab/Alert'
import {
MuiPickersUtilsProvider,
KeyboardDatePicker
} from '@material-ui/pickers'
import CurrencyTextField from '@unicef/material-ui-currency-textfield'
import moment from 'moment'
import MomentUtils from '@date-io/moment'

import LoadingProgress from './LoadingProgress'
import { GET_CLIENT_INFO } from '../operations/queries/ClientQueries'
import { GET_CLIENT_PAYMENTS } from '../operations/queries/PaymentQueries'
import { CREATE_PAYMENT } from '../operations/mutations/PaymentMutations'
import {
selectCurrencyInformation
} from '../scripts/selectors'

const AddPaymentForm = (props) => {

const {
clientId
} = props

const history = useHistory()

const {
data: dataClient,
error: errorClient,
loading: loadingProject
} = useQuery(GET_CLIENT_INFO, {
variables: {
id: Number(clientId)
}
})

const [createPayment, {
dataNewPayment,
loadingNewPayment,
errorNewPayment
}] = useMutation(CREATE_PAYMENT, {
refetchQueries: [{
query: GET_CLIENT_PAYMENTS,
variables: {
clientId: Number(clientId)
}
}]
})

const handleAlertClose = (event, reason) => {
if (reason === 'clickaway') {
return
}
setDisplayError(false)
}
const handleCreatePayment = async () => {
const variables = {
amount: paymentAmount,
client_id: Number(clientId),
date_incurred: dateIncurred,
date_paid: datePaid
}
const newPayment = await createPayment({ variables })
if (loadingNewPayment) return <LoadingProgress/>
if (newPayment.errors) {
setCreatePaymentError(`${Object.keys(newPayment.errors[0].extensions.exception.fields)[0]}`)
setDisplayError(true)
} else {
history.push(`/clients/${clientId}`)
}
}
const handleDateIncurredChange = (date) => {
setDateIncurred(moment(date['_d']).format('YYYY-MM-DD'))
}
const handleDatePaidChange = (date) => {
setDatePaid(moment(date['_d']).format('YYYY-MM-DD'))
}
const handlePaymentAmountChange = (input) => {
setInvalidPaymentAmountInput(false)
const amount = Number(input.replace(/\D/g, ''))
setPaymentAmount(amount)
}

const [createPaymentError, setCreatePaymentError] = useState('')
const [dateIncurred, setDateIncurred] = useState(null)
const [datePaid, setDatePaid] = useState(null)
const [disableAdd, setDisableAdd] = useState(true)
const [displayError, setDisplayError] = useState(false)
const [invalidPaymentAmountInput, setInvalidPaymentAmountInput] = useState(false)
const [paymentAmount, setPaymentAmount] = useState(null)

useEffect(() => {
if (dateIncurred && paymentAmount) {
setDisableAdd(false)
}
})

if (loadingProject) return <LoadingProgress/>
if (errorClient) return `Error! ${errorClient}`

const { getClientById: client } = dataClient

const currencyInformation = selectCurrencyInformation({
currency: client.currency
})

return (
<FormControl
fullWidth
align='left'
>
<Grid container spacing={5}>
<Grid item xs={12}>
<Grid container>
<Grid item xs={12} sm={6} lg={4}>
<CurrencyTextField
fullWidth
label='Payment amount'
variant='outlined'
currencySymbol={`${currencyInformation['symbol']}`}
minimumValue='0'
outputFormat='string'
decimalCharacter={`${currencyInformation['decimal']}`}
digitGroupSeparator={`${currencyInformation['thousand']}`}
onChange={(event) => handlePaymentAmountChange(event.target.value)}
/>
</Grid>
</Grid>
</Grid>
<Grid item xs={12} sm={6} lg={4}>
<MuiPickersUtilsProvider utils={MomentUtils}>
<KeyboardDatePicker
fullWidth
disableToolbar
variant='inline'
format='MM/DD/YYYY'
margin='normal'
label='Payment date incurred'
value={dateIncurred}
onChange={handleDateIncurredChange}
/>
</MuiPickersUtilsProvider>
</Grid>
<Grid item xs={12} sm={6} lg={4}>
<MuiPickersUtilsProvider utils={MomentUtils}>
<KeyboardDatePicker
fullWidth
disableToolbar
variant='inline'
format='MM/DD/YYYY'
margin='normal'
label='Payment date paid'
value={datePaid}
onChange={handleDatePaidChange}
/>
</MuiPickersUtilsProvider>
</Grid>
<Grid item xs={12}>
<Button
variant='contained'
color='primary'
disabled={disableAdd}
onClick={handleCreatePayment}
>
{`Add Payment`}
</Button>
</Grid>
</Grid>
<Snackbar
open={displayError}
autoHideDuration={4000}
onClose={handleAlertClose}
>
<Alert severity='error'>
{`${createPaymentError}`}
</Alert>
</Snackbar>
</FormControl>
)
}

export default AddPaymentForm
Loading

0 comments on commit 8ad9977

Please sign in to comment.