Добавлены модели и функциональность для управления сотрудниками, включая создание, обновление и удаление сотрудников через GraphQL. Обновлены компоненты для отображения списка сотрудников и их расписания, улучшен интерфейс взаимодействия с пользователем. Реализованы функции экспорта отчетов в CSV и TXT форматах, добавлены новые интерфейсы и типы данных для сотрудников.

This commit is contained in:
Bivekich
2025-07-17 23:55:11 +03:00
parent 3e2a03da8c
commit d361364716
13 changed files with 3444 additions and 428 deletions

View File

@ -874,4 +874,78 @@ export const REMOVE_FROM_FAVORITES = gql`
}
}
}
`
// Мутации для сотрудников
export const CREATE_EMPLOYEE = gql`
mutation CreateEmployee($input: CreateEmployeeInput!) {
createEmployee(input: $input) {
success
message
employee {
id
firstName
lastName
middleName
birthDate
avatar
position
department
hireDate
salary
status
phone
email
emergencyContact
emergencyPhone
createdAt
updatedAt
}
}
}
`
export const UPDATE_EMPLOYEE = gql`
mutation UpdateEmployee($id: ID!, $input: UpdateEmployeeInput!) {
updateEmployee(id: $id, input: $input) {
success
message
employee {
id
firstName
lastName
middleName
birthDate
avatar
passportSeries
passportNumber
passportIssued
passportDate
address
position
department
hireDate
salary
status
phone
email
emergencyContact
emergencyPhone
createdAt
updatedAt
}
}
}
`
export const DELETE_EMPLOYEE = gql`
mutation DeleteEmployee($id: ID!) {
deleteEmployee(id: $id)
}
`
export const UPDATE_EMPLOYEE_SCHEDULE = gql`
mutation UpdateEmployeeSchedule($input: UpdateScheduleInput!) {
updateEmployeeSchedule(input: $input)
}
`

View File

@ -476,4 +476,66 @@ export const GET_MY_FAVORITES = gql`
}
}
}
`
// Запросы для сотрудников
export const GET_MY_EMPLOYEES = gql`
query GetMyEmployees {
myEmployees {
id
firstName
lastName
middleName
birthDate
avatar
passportSeries
passportNumber
passportIssued
passportDate
address
position
department
hireDate
salary
status
phone
email
telegram
whatsapp
passportPhoto
emergencyContact
emergencyPhone
createdAt
updatedAt
}
}
`
export const GET_EMPLOYEE = gql`
query GetEmployee($id: ID!) {
employee(id: $id) {
id
firstName
lastName
middleName
birthDate
avatar
passportSeries
passportNumber
passportIssued
passportDate
address
position
department
hireDate
salary
status
phone
email
emergencyContact
emergencyPhone
createdAt
updatedAt
}
}
`

View File

@ -676,6 +676,72 @@ export const resolvers = {
})
return favorites.map(favorite => favorite.product)
},
// Сотрудники организации
myEmployees: async (_: unknown, __: unknown, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }
})
}
const currentUser = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true }
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Доступно только для фулфилмент центров')
}
const employees = await prisma.employee.findMany({
where: { organizationId: currentUser.organization.id },
include: {
organization: true
},
orderBy: { createdAt: 'desc' }
})
return employees
},
// Получение сотрудника по ID
employee: async (_: unknown, args: { id: string }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }
})
}
const currentUser = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true }
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Доступно только для фулфилмент центров')
}
const employee = await prisma.employee.findFirst({
where: {
id: args.id,
organizationId: currentUser.organization.id
},
include: {
organization: true
}
})
return employee
}
},
@ -3041,6 +3107,206 @@ export const resolvers = {
message: 'Ошибка при удалении из избранного'
}
}
},
// Создать сотрудника
createEmployee: async (_: unknown, args: { input: any }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }
})
}
const currentUser = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true }
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Доступно только для фулфилмент центров')
}
try {
const employee = await prisma.employee.create({
data: {
...args.input,
organizationId: currentUser.organization.id,
birthDate: args.input.birthDate ? new Date(args.input.birthDate) : undefined,
passportDate: args.input.passportDate ? new Date(args.input.passportDate) : undefined,
hireDate: new Date(args.input.hireDate)
},
include: {
organization: true
}
})
return {
success: true,
message: 'Сотрудник успешно добавлен',
employee
}
} catch (error) {
console.error('Error creating employee:', error)
return {
success: false,
message: 'Ошибка при создании сотрудника'
}
}
},
// Обновить сотрудника
updateEmployee: async (_: unknown, args: { id: string; input: any }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }
})
}
const currentUser = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true }
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Доступно только для фулфилмент центров')
}
try {
const employee = await prisma.employee.update({
where: {
id: args.id,
organizationId: currentUser.organization.id
},
data: {
...args.input,
birthDate: args.input.birthDate ? new Date(args.input.birthDate) : undefined,
passportDate: args.input.passportDate ? new Date(args.input.passportDate) : undefined,
hireDate: args.input.hireDate ? new Date(args.input.hireDate) : undefined
},
include: {
organization: true
}
})
return {
success: true,
message: 'Сотрудник успешно обновлен',
employee
}
} catch (error) {
console.error('Error updating employee:', error)
return {
success: false,
message: 'Ошибка при обновлении сотрудника'
}
}
},
// Удалить сотрудника
deleteEmployee: async (_: unknown, args: { id: string }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }
})
}
const currentUser = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true }
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Доступно только для фулфилмент центров')
}
try {
await prisma.employee.delete({
where: {
id: args.id,
organizationId: currentUser.organization.id
}
})
return true
} catch (error) {
console.error('Error deleting employee:', error)
return false
}
},
// Обновить табель сотрудника
updateEmployeeSchedule: async (_: unknown, args: { input: any }, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' }
})
}
const currentUser = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true }
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
if (currentUser.organization.type !== 'FULFILLMENT') {
throw new GraphQLError('Доступно только для фулфилмент центров')
}
try {
// Проверяем что сотрудник принадлежит организации
const employee = await prisma.employee.findFirst({
where: {
id: args.input.employeeId,
organizationId: currentUser.organization.id
}
})
if (!employee) {
throw new GraphQLError('Сотрудник не найден')
}
// Создаем или обновляем запись табеля
await prisma.employeeSchedule.upsert({
where: {
employeeId_date: {
employeeId: args.input.employeeId,
date: new Date(args.input.date)
}
},
create: {
employeeId: args.input.employeeId,
date: new Date(args.input.date),
status: args.input.status,
hoursWorked: args.input.hoursWorked,
notes: args.input.notes
},
update: {
status: args.input.status,
hoursWorked: args.input.hoursWorked,
notes: args.input.notes
}
})
return true
} catch (error) {
console.error('Error updating employee schedule:', error)
return false
}
}
},
@ -3120,5 +3386,61 @@ export const resolvers = {
}
return parent.updatedAt
}
},
Employee: {
birthDate: (parent: { birthDate?: Date | string | null }) => {
if (!parent.birthDate) return null
if (parent.birthDate instanceof Date) {
return parent.birthDate.toISOString()
}
return parent.birthDate
},
passportDate: (parent: { passportDate?: Date | string | null }) => {
if (!parent.passportDate) return null
if (parent.passportDate instanceof Date) {
return parent.passportDate.toISOString()
}
return parent.passportDate
},
hireDate: (parent: { hireDate: Date | string }) => {
if (parent.hireDate instanceof Date) {
return parent.hireDate.toISOString()
}
return parent.hireDate
},
createdAt: (parent: { createdAt: Date | string }) => {
if (parent.createdAt instanceof Date) {
return parent.createdAt.toISOString()
}
return parent.createdAt
},
updatedAt: (parent: { updatedAt: Date | string }) => {
if (parent.updatedAt instanceof Date) {
return parent.updatedAt.toISOString()
}
return parent.updatedAt
}
},
EmployeeSchedule: {
date: (parent: { date: Date | string }) => {
if (parent.date instanceof Date) {
return parent.date.toISOString()
}
return parent.date
},
createdAt: (parent: { createdAt: Date | string }) => {
if (parent.createdAt instanceof Date) {
return parent.createdAt.toISOString()
}
return parent.createdAt
},
updatedAt: (parent: { updatedAt: Date | string }) => {
if (parent.updatedAt instanceof Date) {
return parent.updatedAt.toISOString()
}
return parent.updatedAt
}
}
}

View File

@ -43,6 +43,10 @@ export const typeDefs = gql`
# Избранные товары пользователя
myFavorites: [Product!]!
# Сотрудники организации
myEmployees: [Employee!]!
employee(id: ID!): Employee
}
type Mutation {
@ -107,6 +111,12 @@ export const typeDefs = gql`
# Работа с избранным
addToFavorites(productId: ID!): FavoritesResponse!
removeFromFavorites(productId: ID!): FavoritesResponse!
# Работа с сотрудниками
createEmployee(input: CreateEmployeeInput!): EmployeeResponse!
updateEmployee(id: ID!, input: UpdateEmployeeInput!): EmployeeResponse!
deleteEmployee(id: ID!): Boolean!
updateEmployeeSchedule(input: UpdateScheduleInput!): Boolean!
}
# Типы данных
@ -470,6 +480,132 @@ export const typeDefs = gql`
favorites: [Product!]
}
# Типы для сотрудников
type Employee {
id: ID!
firstName: String!
lastName: String!
middleName: String
birthDate: String
avatar: String
passportPhoto: String
passportSeries: String
passportNumber: String
passportIssued: String
passportDate: String
address: String
position: String!
department: String
hireDate: String!
salary: Float
status: EmployeeStatus!
phone: String!
email: String
telegram: String
whatsapp: String
emergencyContact: String
emergencyPhone: String
scheduleRecords: [EmployeeSchedule!]!
organization: Organization!
createdAt: String!
updatedAt: String!
}
enum EmployeeStatus {
ACTIVE
VACATION
SICK
FIRED
}
type EmployeeSchedule {
id: ID!
date: String!
status: ScheduleStatus!
hoursWorked: Float
notes: String
employee: Employee!
createdAt: String!
updatedAt: String!
}
enum ScheduleStatus {
WORK
WEEKEND
VACATION
SICK
ABSENT
}
input CreateEmployeeInput {
firstName: String!
lastName: String!
middleName: String
birthDate: String
avatar: String
passportPhoto: String
passportSeries: String
passportNumber: String
passportIssued: String
passportDate: String
address: String
position: String!
department: String
hireDate: String!
salary: Float
phone: String!
email: String
telegram: String
whatsapp: String
emergencyContact: String
emergencyPhone: String
}
input UpdateEmployeeInput {
firstName: String
lastName: String
middleName: String
birthDate: String
avatar: String
passportPhoto: String
passportSeries: String
passportNumber: String
passportIssued: String
passportDate: String
address: String
position: String
department: String
hireDate: String
salary: Float
status: EmployeeStatus
phone: String
email: String
telegram: String
whatsapp: String
emergencyContact: String
emergencyPhone: String
}
input UpdateScheduleInput {
employeeId: ID!
date: String!
status: ScheduleStatus!
hoursWorked: Float
notes: String
}
type EmployeeResponse {
success: Boolean!
message: String!
employee: Employee
}
type EmployeesResponse {
success: Boolean!
message: String!
employees: [Employee!]!
}
# JSON скаляр
scalar JSON
`