feat: use customer Autocomplete on discount creation

This commit is contained in:
Sérgio Salgado 2021-07-28 02:04:21 +01:00 committed by Josh Harvey
parent 43ff04b2af
commit 847b87ecb0
8 changed files with 94 additions and 189 deletions

View file

@ -32,20 +32,14 @@ function getAvailableIndividualDiscounts () {
const sql = `SELECT * from individual_discounts WHERE soft_deleted=false` const sql = `SELECT * from individual_discounts WHERE soft_deleted=false`
return db.any(sql).then(res => _.map(it => ({ return db.any(sql).then(res => _.map(it => ({
id: it.id, id: it.id,
idType: _.camelCase(it.identification), customerId: it.customer_id,
value: it.value,
discount: it.discount discount: it.discount
}), res)) }), res))
} }
function createIndividualDiscount (idType, value, discount) { function createIndividualDiscount (customerId, discount) {
const idTypes = { const sql = `INSERT INTO individual_discounts (id, customer_id, discount) VALUES ($1, $2, $3)`
phone: 'phone', return db.none(sql, [uuid.v4(), customerId, discount])
idNumber: 'id_number'
}
const sql = `INSERT INTO individual_discounts (id, identification, value, discount) VALUES ($1, $2, $3, $4)`
return db.none(sql, [uuid.v4(), idTypes[idType], value, discount])
} }
function deleteIndividualDiscount (id) { function deleteIndividualDiscount (id) {
@ -53,38 +47,6 @@ function deleteIndividualDiscount (id) {
return db.none(sql, [id]) return db.none(sql, [id])
} }
function getCustomersWithDiscounts (discounts) {
let phoneNumbers = []
let idCardNumbers = []
_.each(it => {
switch (it.idType) {
case 'phone':
phoneNumbers.push(it.value)
break
case 'idNumber':
idCardNumbers.push(it.value)
break
default:
break
}
}, discounts)
if (_.isEmpty(phoneNumbers) && _.isEmpty(idCardNumbers)) {
return Promise.resolve([])
}
const phoneNumbersSql = _.map(pgp.as.text, phoneNumbers).join(',')
const idCardNumbersSql = _.map(pgp.as.text, idCardNumbers).join(',')
const hasPhoneNumbers = !_.isEmpty(phoneNumbers)
const hasIDNumbers = !_.isEmpty(idCardNumbers)
const sql = `SELECT * FROM customers WHERE ${hasPhoneNumbers ? `phone IN ($1^)` : ``} ${hasPhoneNumbers && hasIDNumbers ? `OR` : ``} ${hasIDNumbers ? `id_card_data_number IN ($2^)` : ``}`
return db.any(sql, [phoneNumbersSql, idCardNumbersSql])
.then(res => _.map(it => it ? _.mapKeys(_.camelCase, it) : null, res))
}
module.exports = { module.exports = {
getAvailablePromoCodes, getAvailablePromoCodes,
getPromoCode, getPromoCode,
@ -93,6 +55,5 @@ module.exports = {
getNumberOfAvailablePromoCodes, getNumberOfAvailablePromoCodes,
getAvailableIndividualDiscounts, getAvailableIndividualDiscounts,
createIndividualDiscount, createIndividualDiscount,
deleteIndividualDiscount, deleteIndividualDiscount
getCustomersWithDiscounts
} }

View file

@ -8,7 +8,7 @@ const resolvers = {
Mutation: { Mutation: {
createPromoCode: (...[, { code, discount }]) => loyalty.createPromoCode(code, discount), createPromoCode: (...[, { code, discount }]) => loyalty.createPromoCode(code, discount),
deletePromoCode: (...[, { codeId }]) => loyalty.deletePromoCode(codeId), deletePromoCode: (...[, { codeId }]) => loyalty.deletePromoCode(codeId),
createIndividualDiscount: (...[, { idType, value, discount }]) => loyalty.createIndividualDiscount(idType, value, discount), createIndividualDiscount: (...[, { customerId, discount }]) => loyalty.createIndividualDiscount(customerId, discount),
deleteIndividualDiscount: (...[, { discountId }]) => loyalty.deleteIndividualDiscount(discountId) deleteIndividualDiscount: (...[, { discountId }]) => loyalty.deleteIndividualDiscount(discountId)
} }
} }

View file

@ -3,23 +3,10 @@ const { gql } = require('apollo-server-express')
const typeDef = gql` const typeDef = gql`
type IndividualDiscount { type IndividualDiscount {
id: ID! id: ID!
idType: DiscountIdentificationType customerId: ID!
value: String!
discount: Int discount: Int
} }
input IndividualDiscountInput {
id: ID
idType: DiscountIdentificationType
value: String
discount: Int
}
enum DiscountIdentificationType {
phone
idNumber
}
type PromoCode { type PromoCode {
id: ID! id: ID!
code: String! code: String!
@ -34,7 +21,7 @@ const typeDef = gql`
type Mutation { type Mutation {
createPromoCode(code: String!, discount: Int!): PromoCode @auth createPromoCode(code: String!, discount: Int!): PromoCode @auth
deletePromoCode(codeId: ID!): PromoCode @auth deletePromoCode(codeId: ID!): PromoCode @auth
createIndividualDiscount(idType: DiscountIdentificationType!, value: String!, discount: Int!): IndividualDiscount @auth createIndividualDiscount(customerId: ID!, discount: Int!): IndividualDiscount @auth
deleteIndividualDiscount(discountId: ID!): IndividualDiscount @auth deleteIndividualDiscount(discountId: ID!): IndividualDiscount @auth
} }
` `

View file

@ -260,6 +260,8 @@ function plugins (settings, deviceId) {
const areThereAvailablePromoCodes = arr[arr.length - 2] > 0 const areThereAvailablePromoCodes = arr[arr.length - 2] > 0
const individualDiscounts = arr[arr.length - 1] const individualDiscounts = arr[arr.length - 1]
console.log('individualDiscounts', individualDiscounts)
return { return {
cassettes, cassettes,
rates: buildRates(tickers), rates: buildRates(tickers),

View file

@ -2,15 +2,13 @@ var db = require('./db')
exports.up = function (next) { exports.up = function (next) {
var sql = [ var sql = [
`CREATE TYPE individual_discount_identification_type AS ENUM('phone', 'id_number')`,
`CREATE TABLE individual_discounts ( `CREATE TABLE individual_discounts (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
identification individual_discount_identification_type NOT NULL, customer_id UUID NOT NULL REFERENCES customers(id),
value TEXT NOT NULL,
discount SMALLINT NOT NULL, discount SMALLINT NOT NULL,
soft_deleted BOOLEAN DEFAULT false soft_deleted BOOLEAN DEFAULT false
)`, )`,
`CREATE UNIQUE INDEX uq_individual_discount ON individual_discounts (identification, value) WHERE NOT soft_deleted` `CREATE UNIQUE INDEX uq_individual_discount ON individual_discounts (customer_id) WHERE NOT soft_deleted`
] ]
db.multi(sql, next) db.multi(sql, next)

View file

@ -6,26 +6,28 @@ const styles = {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
'& > *:first-child': { '& > *:first-child': {
marginRight: 10 marginLeft: 0
},
'& > *': {
marginLeft: 6
},
'& > *:nth-child(3)': {
marginLeft: 15
} }
}, },
form: { form: {
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
height: '100%', height: '100%',
'& > *:first-child': {
marginTop: 10
},
'& > *': { '& > *': {
marginBottom: 20 marginBottom: 20
} }
}, },
radioGroup: { customerAutocomplete: {
display: 'flex', width: 350
flexDirection: 'row',
'& > *': {
marginLeft: 15
},
'& > *:first-child': {
marginLeft: 0
}
}, },
discountRateWrapper: { discountRateWrapper: {
display: 'flex', display: 'flex',

View file

@ -1,6 +1,6 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import classNames from 'classnames'
import { Form, Formik, Field } from 'formik' import { Form, Formik, Field } from 'formik'
import * as R from 'ramda'
import React from 'react' import React from 'react'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -8,11 +8,7 @@ import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal' import Modal from 'src/components/Modal'
import { Tooltip } from 'src/components/Tooltip' import { Tooltip } from 'src/components/Tooltip'
import { Button } from 'src/components/buttons' import { Button } from 'src/components/buttons'
import { import { NumberInput, Autocomplete } from 'src/components/inputs/formik'
NumberInput,
RadioGroup,
TextInput
} from 'src/components/inputs/formik'
import { H3, TL1, P } from 'src/components/typography' import { H3, TL1, P } from 'src/components/typography'
import styles from './IndividualDiscount.styles' import styles from './IndividualDiscount.styles'
@ -20,42 +16,23 @@ import styles from './IndividualDiscount.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const initialValues = { const initialValues = {
idType: '', customer: '',
value: '',
discount: '' discount: ''
} }
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
idType: Yup.string() customer: Yup.string().required('A customer is required!'),
.required('An identification type is required!')
.trim(),
value: Yup.string()
.required('A value is required!')
.trim()
.min(3, 'Value should have at least 3 characters!')
.max(20, 'Value should have a maximum of 20 characters!'),
discount: Yup.number() discount: Yup.number()
.required('A discount rate is required!') .required('A discount rate is required!')
.min(0, 'Discount rate should be a positive number!') .min(0, 'Discount rate should be a positive number!')
.max(100, 'Discount rate should have a maximum value of 100%!') .max(100, 'Discount rate should have a maximum value of 100%!')
}) })
const radioOptions = [
{
code: 'phone',
display: 'Phone number'
},
{
code: 'idNumber',
display: 'ID card number'
}
]
const getErrorMsg = (formikErrors, formikTouched, mutationError) => { const getErrorMsg = (formikErrors, formikTouched, mutationError) => {
if (!formikErrors || !formikTouched) return null if (!formikErrors || !formikTouched) return null
if (mutationError) return 'Internal server error' if (mutationError) return 'Internal server error'
if (formikErrors.idType && formikTouched.idType) return formikErrors.idType if (formikErrors.customer && formikTouched.customer)
if (formikErrors.value && formikTouched.value) return formikErrors.value return formikErrors.customer
if (formikErrors.discount && formikTouched.discount) if (formikErrors.discount && formikTouched.discount)
return formikErrors.discount return formikErrors.discount
return null return null
@ -66,25 +43,21 @@ const IndividualDiscountModal = ({
setShowModal, setShowModal,
onClose, onClose,
creationError, creationError,
addDiscount addDiscount,
customers
}) => { }) => {
const classes = useStyles() const classes = useStyles()
const handleAddDiscount = (idType, value, discount) => { const handleAddDiscount = (customer, discount) => {
addDiscount({ addDiscount({
variables: { variables: {
idType: idType, customerId: customer,
value: value,
discount: parseInt(discount) discount: parseInt(discount)
} }
}) })
setShowModal(false) setShowModal(false)
} }
const idTypeClass = (formikErrors, formikTouched) => ({
[classes.error]: formikErrors.idType && formikTouched.idType
})
return ( return (
<> <>
{showModal && ( {showModal && (
@ -100,33 +73,29 @@ const IndividualDiscountModal = ({
validateOnChange={false} validateOnChange={false}
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={({ idType, value, discount }) => { onSubmit={({ customer, discount }) => {
handleAddDiscount(idType, value, discount) handleAddDiscount(customer, discount)
}}> }}>
{({ values, errors, touched }) => ( {({ errors, touched }) => (
<Form id="individual-discount-form" className={classes.form}> <Form id="individual-discount-form" className={classes.form}>
<div> <div className={classes.customerAutocomplete}>
<H3 className={classNames(idTypeClass(errors, touched))}>
Select customer identification option
</H3>
<Field <Field
component={RadioGroup} name="customer"
name="idType" label="Select a customer"
className={classes.radioGroup} component={Autocomplete}
options={radioOptions} fullWidth
options={R.map(it => ({
code: it.id,
display: `${it.idCardData.firstName ?? ``}${
it.idCardData.firstName && it.idCardData.lastName
? ` `
: ``
}${it.idCardData.lastName ?? ``} (${it.phone})`
}))(customers)}
labelProp="display"
valueProp="code"
/> />
</div> </div>
<Field
name="value"
label={`Enter customer ${
values.idType === 'idNumber' ? `ID` : `phone`
} number`}
autoFocus
size="lg"
autoComplete="off"
width={338}
component={TextInput}
/>
<div> <div>
<div className={classes.discountRateWrapper}> <div className={classes.discountRateWrapper}>
<H3>Define discount rate</H3> <H3>Define discount rate</H3>

View file

@ -1,4 +1,4 @@
import { useQuery, useLazyQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles, Box } from '@material-ui/core' import { makeStyles, Box } from '@material-ui/core'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
@ -21,8 +21,7 @@ const GET_INDIVIDUAL_DISCOUNTS = gql`
query individualDiscounts { query individualDiscounts {
individualDiscounts { individualDiscounts {
id id
idType customerId
value
discount discount
} }
} }
@ -37,27 +36,20 @@ const DELETE_DISCOUNT = gql`
` `
const CREATE_DISCOUNT = gql` const CREATE_DISCOUNT = gql`
mutation createIndividualDiscount( mutation createIndividualDiscount($customerId: ID!, $discount: Int!) {
$idType: DiscountIdentificationType! createIndividualDiscount(customerId: $customerId, discount: $discount) {
$value: String!
$discount: Int!
) {
createIndividualDiscount(
idType: $idType
value: $value
discount: $discount
) {
id id
} }
} }
` `
const GET_CUSTOMERS_WITH_DISCOUNTS = gql` const GET_CUSTOMERS = gql`
query getCustomersWithDiscounts($discounts: [IndividualDiscountInput]!) { {
getCustomersWithDiscounts(discounts: $discounts) { customers {
id id
phone phone
idCardData idCardData
phone
} }
} }
` `
@ -72,26 +64,9 @@ const IndividualDiscounts = () => {
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const toggleModal = () => setShowModal(!showModal) const toggleModal = () => setShowModal(!showModal)
const [ const { data: discountResponse, loading } = useQuery(GET_INDIVIDUAL_DISCOUNTS)
getCustomers, const { data: customerData, loading: customerLoading } = useQuery(
{ data: customerData, loading: customerLoading } GET_CUSTOMERS
] = useLazyQuery(GET_CUSTOMERS_WITH_DISCOUNTS)
const { data: discountResponse, loading } = useQuery(
GET_INDIVIDUAL_DISCOUNTS,
{
onCompleted: res => {
const discounts = R.map(it =>
R.pick(['id', 'idType', 'value', 'discount'])(it)
)(res.individualDiscounts)
return getCustomers({
variables: {
discounts: discounts
}
})
}
}
) )
const [createDiscount, { error: creationError }] = useMutation( const [createDiscount, { error: creationError }] = useMutation(
@ -101,6 +76,11 @@ const IndividualDiscounts = () => {
} }
) )
const getCustomer = id => {
const customers = R.path(['customers'])(customerData)
return R.find(R.propEq('id', id))(customers)
}
const [deleteDiscount] = useMutation(DELETE_DISCOUNT, { const [deleteDiscount] = useMutation(DELETE_DISCOUNT, {
onError: ({ message }) => { onError: ({ message }) => {
const errorMessage = message ?? 'Error while deleting row' const errorMessage = message ?? 'Error while deleting row'
@ -110,25 +90,27 @@ const IndividualDiscounts = () => {
refetchQueries: () => ['individualDiscounts'] refetchQueries: () => ['individualDiscounts']
}) })
const findCustomer = (customers = [], idType, value) =>
R.find(it =>
idType === 'phone'
? it.phone === value
: it.idCardData.documentNumber === value
)(customers)
const elements = [ const elements = [
{ {
header: 'Identification', header: 'Identification',
width: 312, width: 312,
textAlign: 'left', textAlign: 'left',
size: 'sm', size: 'sm',
view: t => ( view: t => {
<div className={classes.identification}> const customer = getCustomer(t.customerId)
{t.idType === 'phone' ? <PhoneIdIcon /> : <CardIdIcon />} return (
{t.value} <div className={classes.identification}>
</div> <PhoneIdIcon />
) <span>{customer.phone}</span>
{customer.idCardData?.documentNumber && (
<>
<CardIdIcon />
<span>{customer.idCardData.documentNumber}</span>
</>
)}
</div>
)
}
}, },
{ {
header: 'Name', header: 'Name',
@ -136,14 +118,17 @@ const IndividualDiscounts = () => {
textAlign: 'left', textAlign: 'left',
size: 'sm', size: 'sm',
view: t => { view: t => {
const customer = findCustomer( const customer = getCustomer(t.customerId)
customerData?.getCustomersWithDiscounts, if (R.isNil(customer.idCardData)) {
t.idType, return <>{'-'}</>
t.value }
)
if (R.isNil(customer)) return <>{'-'}</>
return ( return (
<>{`${customer.idCardData.firstName} ${customer.idCardData.lastName}`}</> <>{`${customer.idCardData.firstName ?? ``}${
customer.idCardData.firstName && customer.idCardData.lastName
? ` `
: ``
}${customer.idCardData.lastName ?? ``}`}</>
) )
} }
}, },
@ -228,6 +213,7 @@ const IndividualDiscounts = () => {
}} }}
creationError={creationError} creationError={creationError}
addDiscount={createDiscount} addDiscount={createDiscount}
customers={R.path(['customers'])(customerData)}
/> />
</> </>
) )