Skip to content

Commit

Permalink
fix(condo): DOMA-8545 fix employee with same email or phone existing …
Browse files Browse the repository at this point in the history
…validation (#4439)

* fix(condo): DOMA-8545 fix employee existing validation

* fix(condo): DOMA-8545 remove user deletedAt and user type from filters

* fix(condo): DOMA-8545 change if statement

(cherry picked from commit 0543211)
  • Loading branch information
nomerdvadcatpyat authored and toplenboren committed Mar 5, 2024
1 parent ed5ca54 commit c599243
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 181 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { useLayoutContext } from '@condo/domains/common/components/LayoutContext
import { PhoneInput } from '@condo/domains/common/components/PhoneInput'
import { useValidations } from '@condo/domains/common/hooks/useValidations'
import { EmployeeRoleSelect } from '@condo/domains/organization/components/EmployeeRoleSelect'
import { useEmployeeValidations } from '@condo/domains/organization/hooks/useEmployeeValidations'
import {
OrganizationEmployeeRole,
useInviteNewOrganizationEmployee,
Expand Down Expand Up @@ -70,11 +69,10 @@ export const CreateEmployeeForm: React.FC = () => {
)

const { changeMessage, requiredValidator, emailValidator, phoneValidator, trimValidator, specCharValidator } = useValidations()
const { alreadyRegisteredEmailValidator, alreadyRegisteredPhoneValidator } = useEmployeeValidations(organizationId)

const validations: { [key: string]: Rule[] } = {
phone: [requiredValidator, phoneValidator, alreadyRegisteredPhoneValidator],
email: [emailValidator, alreadyRegisteredEmailValidator],
phone: [requiredValidator, phoneValidator],
email: [emailValidator],
name: [
changeMessage(trimValidator, FullNameRequiredMessage),
changeMessage(specCharValidator, FullNameInvalidCharMessage),
Expand Down Expand Up @@ -143,7 +141,7 @@ export const CreateEmployeeForm: React.FC = () => {
return (
<>
<Row>
<Col lg={14} xs={24}>
<Col md={14} xs={24}>
<Row gutter={[0, 60]}>
<Col span={24}>
<Row gutter={[0, 40]}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import React, { useCallback, useEffect, useMemo } from 'react'
import { useApolloClient } from '@open-condo/next/apollo'
import { useAuth } from '@open-condo/next/auth'
import { useIntl } from '@open-condo/next/intl'
import { useOrganization } from '@open-condo/next/organization'
import { ActionBar, Alert, Button } from '@open-condo/ui'

import Input from '@condo/domains/common/components/antd/Input'
Expand All @@ -21,7 +20,6 @@ import { Loader } from '@condo/domains/common/components/Loader'
import Prompt from '@condo/domains/common/components/Prompt'
import { useValidations } from '@condo/domains/common/hooks/useValidations'
import { EmployeeRoleSelect } from '@condo/domains/organization/components/EmployeeRoleSelect'
import { useEmployeeValidations } from '@condo/domains/organization/hooks/useEmployeeValidations'
import { OrganizationEmployeeSpecialization } from '@condo/domains/organization/utils/clientSchema'
import { OrganizationEmployee, OrganizationEmployeeRole } from '@condo/domains/organization/utils/clientSchema'
import {
Expand Down Expand Up @@ -55,13 +53,10 @@ export const UpdateEmployeeForm: React.FC = () => {
const PromptTitle = intl.formatMessage({ id: 'form.prompt.title' })
const PromptHelpMessage = intl.formatMessage({ id: 'form.prompt.message' })

const { organization } = useOrganization()
const { query, push } = useRouter()
const classifiersLoader = new ClassifiersQueryRemote(useApolloClient())
const { breakpoints } = useLayoutContext()

const organizationId = get(organization, 'id')

const employeeId = String(get(query, 'id', ''))
const { obj: employee, loading: employeeLoading, error: employeeError } = OrganizationEmployee.useObject({ where: { id: employeeId } })
const { objs: employeeRoles, loading: employeeRolesLoading, error: employeeRolesError } = OrganizationEmployeeRole.useObjects({ where: { organization: { id: get(employee, ['organization', 'id']) } } })
Expand All @@ -77,9 +72,8 @@ export const UpdateEmployeeForm: React.FC = () => {
[organizationEmployeeSpecializations])

const { emailValidator } = useValidations()
const { alreadyRegisteredEmailValidator } = useEmployeeValidations(organizationId, employeeId)
const validations: { [key: string]: Rule[] } = {
email: [emailValidator, alreadyRegisteredEmailValidator],
email: [emailValidator],
}

const { user } = useAuth()
Expand Down
6 changes: 4 additions & 2 deletions apps/condo/domains/organization/constants/errors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const ALREADY_ACCEPTED_INVITATION = 'ALREADY_ACCEPTED_INVITATION'
const ALREADY_INVITED = 'ALREADY_INVITED'
const ALREADY_INVITED_EMAIL = 'ALREADY_INVITED_EMAIL'
const ALREADY_INVITED_PHONE = 'ALREADY_INVITED_PHONE'
const EMPTY_NAME_ERROR = '[name.is.too.short'
const TIN_TOO_SHORT_ERROR = '[tin.is.too.short'
const TIN_VALUE_INVALID = '[tin.value.is.invalid'
Expand All @@ -8,7 +9,8 @@ const PARENT_NOT_HOLDING = 'PARENT_NOT_HOLDING'

module.exports = {
ALREADY_ACCEPTED_INVITATION,
ALREADY_INVITED,
ALREADY_INVITED_EMAIL,
ALREADY_INVITED_PHONE,
EMPTY_NAME_ERROR,
TIN_TOO_SHORT_ERROR,
TIN_VALUE_INVALID,
Expand Down
97 changes: 0 additions & 97 deletions apps/condo/domains/organization/hooks/useEmployeeValidations.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const get = require('lodash/get')

const conf = require('@open-condo/config')
const { GQLError, GQLErrorCode: { BAD_USER_INPUT, INTERNAL_ERROR } } = require('@open-condo/keystone/errors')
const { getById } = require('@open-condo/keystone/schema')
const { getById, find } = require('@open-condo/keystone/schema')
const { GQLCustomSchema } = require('@open-condo/keystone/schema')

const { WRONG_FORMAT, NOT_FOUND, WRONG_PHONE_FORMAT, DV_VERSION_MISMATCH } = require('@condo/domains/common/constants/errors')
Expand All @@ -13,21 +13,29 @@ const { DIRTY_INVITE_NEW_EMPLOYEE_SMS_MESSAGE_TYPE, DIRTY_INVITE_NEW_EMPLOYEE_EM
const { sendMessage } = require('@condo/domains/notification/utils/serverSchema')
const access = require('@condo/domains/organization/access/InviteNewOrganizationEmployeeService')
const { HOLDING_TYPE } = require('@condo/domains/organization/constants/common')
const { ALREADY_ACCEPTED_INVITATION, ALREADY_INVITED_EMAIL, ALREADY_INVITED_PHONE, UNABLE_TO_REGISTER_USER } = require('@condo/domains/organization/constants/errors')
const { Organization, OrganizationEmployee, OrganizationEmployeeSpecialization } = require('@condo/domains/organization/utils/serverSchema')
const { REGISTER_NEW_USER_MUTATION } = require('@condo/domains/user/gql')
const guards = require('@condo/domains/organization/utils/serverSchema/guards')
const { createUserAndSendLoginData } = require('@condo/domains/user/utils/serverSchema')

const { ALREADY_ACCEPTED_INVITATION, ALREADY_INVITED, UNABLE_TO_REGISTER_USER } = require('../constants/errors')
const guards = require('../utils/serverSchema/guards')

const ERRORS = {
inviteNewOrganizationEmployee: {
ALREADY_INVITED: {
ALREADY_INVITED_EMAIL: {
mutation: 'inviteNewOrganizationEmployee',
code: BAD_USER_INPUT,
type: ALREADY_INVITED,
message: 'Already invited into the organization',
messageForUser: 'api.organization.inviteNewOrganizationEmployee.ALREADY_INVITED',
type: ALREADY_INVITED_EMAIL,
message: 'Employee with same email already invited into the organization',
messageForUser: 'api.organization.inviteNewOrganizationEmployee.ALREADY_INVITED_EMAIL',
variable: ['email'],
},
ALREADY_INVITED_PHONE: {
mutation: 'inviteNewOrganizationEmployee',
code: BAD_USER_INPUT,
type: ALREADY_INVITED_PHONE,
message: 'Employee with same phone already invited into the organization',
messageForUser: 'api.organization.inviteNewOrganizationEmployee.ALREADY_INVITED_PHONE',
variable: ['phone'],
},
WRONG_PHONE_FORMAT: {
mutation: 'inviteNewOrganizationEmployee',
Expand Down Expand Up @@ -125,10 +133,38 @@ const InviteNewOrganizationEmployeeService = new GQLCustomSchema('InviteNewOrgan
if (!phone) throw new GQLError(ERRORS.inviteNewOrganizationEmployee.WRONG_PHONE_FORMAT, context)
const userOrganization = await Organization.getOne(context, { id: organization.id })
let user = await guards.checkStaffUserExistency(context, email, phone)
const existedEmployee = await guards.checkEmployeeExistency(context, userOrganization, email, phone, user)

if (existedEmployee) {
throw new GQLError(ERRORS.inviteNewOrganizationEmployee.ALREADY_INVITED, context)
const sameOrganizationEmployees = await find('OrganizationEmployee', {
deletedAt: null,
organization: { id: userOrganization.id },
OR: [
{ phone },
{ user: { phone } },
email && { email },
email && { user: { email } },
].filter(Boolean),
})

if (sameOrganizationEmployees.length > 0) {
const sameEmployee = sameOrganizationEmployees[0]

if (sameEmployee.phone === phone) {
throw new GQLError(ERRORS.inviteNewOrganizationEmployee.ALREADY_INVITED_PHONE, context)
}
if (sameEmployee.email === email) {
throw new GQLError(ERRORS.inviteNewOrganizationEmployee.ALREADY_INVITED_EMAIL, context)
}

if (sameEmployee.user) {
const userWithSameData = await getById('User', sameEmployee.user)

if (userWithSameData.phone === phone) {
throw new GQLError(ERRORS.inviteNewOrganizationEmployee.ALREADY_INVITED_PHONE, context)
}
if (userWithSameData.email === email) {
throw new GQLError(ERRORS.inviteNewOrganizationEmployee.ALREADY_INVITED_EMAIL, context)
}
}
}

if (!user) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ describe('InviteNewOrganizationEmployeeService', () => {
})
})

describe('for Employee with duplicated phone', () => {
it('throws error with type ALREADY_INVITED', async () => {
describe('for Employee with duplicated data', () => {
it('throws error with type ALREADY_INVITED_EMAIL if email duplicated', async () => {
const client = await makeClientWithRegisteredOrganization()
const userAttrs = {
name: faker.name.firstName(),
Expand All @@ -180,28 +180,26 @@ describe('InviteNewOrganizationEmployeeService', () => {
await inviteNewOrganizationEmployee(client, client.organization, userAttrs, role)
const secondUserAttrs = {
...userAttrs,
email: createTestEmail(),
phone: createTestPhone(),
}

await catchErrorFrom(async () => {
await inviteNewOrganizationEmployee(client, client.organization, secondUserAttrs, role)
}, ({ errors }) => {
expect(errors).toMatchObject([{
message: 'Already invited into the organization',
message: 'Employee with same email already invited into the organization',
path: ['obj'],
extensions: {
mutation: 'inviteNewOrganizationEmployee',
code: 'BAD_USER_INPUT',
type: 'ALREADY_INVITED',
message: 'Already invited into the organization',
type: 'ALREADY_INVITED_EMAIL',
message: 'Employee with same email already invited into the organization',
},
}])
})
})
})

describe('for Employee with duplicated email', () => {
it('throws error with type ALREADY_INVITED', async () => {
it('throws error with type ALREADY_INVITED_PHONE if phone duplicated', async () => {
const client = await makeClientWithRegisteredOrganization()
const userAttrs = {
name: faker.name.firstName(),
Expand All @@ -213,52 +211,20 @@ describe('InviteNewOrganizationEmployeeService', () => {
await inviteNewOrganizationEmployee(client, client.organization, userAttrs, role)
const secondUserAttrs = {
...userAttrs,
phone: createTestPhone(),
email: createTestEmail(),
}

await catchErrorFrom(async () => {
await inviteNewOrganizationEmployee(client, client.organization, secondUserAttrs, role)
}, ({ errors }) => {
expect(errors).toMatchObject([{
message: 'Already invited into the organization',
path: ['obj'],
extensions: {
mutation: 'inviteNewOrganizationEmployee',
code: 'BAD_USER_INPUT',
type: 'ALREADY_INVITED',
message: 'Already invited into the organization',
},
}])
})
})
})

describe('for Employee with duplicated User', () => {
it('throws error with type ALREADY_INVITED', async () => {
const [categoryClassifier1] = await createTestTicketCategoryClassifier(admin)
const client = await makeClientWithRegisteredOrganization()
const inviteClient = await makeClientWithRegisteredOrganization()
const [role] = await createTestOrganizationEmployeeRole(client, client.organization)

const userAttrs = {
name: client.userAttrs.name,
email: client.userAttrs.email,
phone: inviteClient.userAttrs.phone,
}
const extraAttrs = {
specializations: [{ id: categoryClassifier1.id }],
}
await catchErrorFrom(async () => {
await inviteNewOrganizationEmployee(inviteClient, inviteClient.organization, userAttrs, role, extraAttrs)
}, ({ errors }) => {
expect(errors).toMatchObject([{
message: 'Already invited into the organization',
message: 'Employee with same phone already invited into the organization',
path: ['obj'],
extensions: {
mutation: 'inviteNewOrganizationEmployee',
code: 'BAD_USER_INPUT',
type: 'ALREADY_INVITED',
message: 'Already invited into the organization',
type: 'ALREADY_INVITED_PHONE',
message: 'Employee with same phone already invited into the organization',
},
}])
})
Expand Down
Loading

0 comments on commit c599243

Please sign in to comment.