add employee management

This commit is contained in:
cuqmbr 2025-05-16 15:22:44 +03:00
parent f4611f029f
commit 5982fa7285
Signed by: cuqmbr
GPG Key ID: 0AA446880C766199
50 changed files with 2555 additions and 8 deletions

View File

@ -0,0 +1,6 @@
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.Common.Interfaces
.Persistence.Repositories;
public interface EmployeeRepository : BaseRepository<Employee> { }

View File

@ -28,6 +28,8 @@ public interface UnitOfWork : IDisposable
CompanyRepository CompanyRepository { get; } CompanyRepository CompanyRepository { get; }
EmployeeRepository EmployeeRepository { get; }
int Save(); int Save();
Task<int> SaveAsync(CancellationToken cancellationToken); Task<int> SaveAsync(CancellationToken cancellationToken);

View File

@ -0,0 +1,23 @@
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR;
using cuqmbr.TravelGuide.Application.Employees.Models;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.AddEmployee;
public record AddEmployeeCommand : IRequest<EmployeeDto>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Patronymic { get; set; }
public Sex Sex { get; set; }
public DateOnly BirthDate { get; set; }
public Guid CompanyGuid { get; set; }
public ICollection<EmployeeDocumentModel> Documents { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.AddEmployee;
public class AddEmployeeCommandAuthorizer :
AbstractRequestAuthorizer<AddEmployeeCommand>
{
private readonly SessionUserService _sessionUserService;
public AddEmployeeCommandAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(AddEmployeeCommand request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,80 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using cuqmbr.TravelGuide.Domain.Entities;
using AutoMapper;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.AddEmployee;
public class AddEmployeeCommandHandler :
IRequestHandler<AddEmployeeCommand, EmployeeDto>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
private readonly IStringLocalizer _localizer;
public AddEmployeeCommandHandler(
UnitOfWork unitOfWork,
IMapper mapper,
IStringLocalizer localizer)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_localizer = localizer;
}
public async Task<EmployeeDto> Handle(
AddEmployeeCommand request,
CancellationToken cancellationToken)
{
var parentEntity = await _unitOfWork.CompanyRepository.GetOneAsync(
e => e.Guid == request.CompanyGuid, cancellationToken);
if (parentEntity == null)
{
throw new NotFoundException(
$"Parent entity with Guid: {request.CompanyGuid} not found.");
}
var entity = await _unitOfWork.EmployeeRepository.GetOneAsync(
e =>
e.FirstName == request.FirstName &&
e.LastName == request.LastName &&
e.Patronymic == request.Patronymic &&
e.Sex == request.Sex &&
e.BirthDate == request.BirthDate &&
e.CompanyId == parentEntity.Id,
cancellationToken);
if (entity != null)
{
throw new DuplicateEntityException();
}
entity = new Employee()
{
FirstName = request.FirstName,
LastName = request.LastName,
Patronymic = request.Patronymic,
Sex = request.Sex,
BirthDate = request.BirthDate,
Documents = request.Documents.Select(
d => new EmployeeDocument()
{
DocumentType = d.DocumentType,
Information = d.Information
})
.ToArray(),
Company = parentEntity
};
entity = await _unitOfWork.EmployeeRepository.AddOneAsync(
entity, cancellationToken);
await _unitOfWork.SaveAsync(cancellationToken);
_unitOfWork.Dispose();
return _mapper.Map<EmployeeDto>(entity);
}
}

View File

@ -0,0 +1,83 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.AddEmployee;
public class AddEmployeeCommandValidator : AbstractValidator<AddEmployeeCommand>
{
public AddEmployeeCommandValidator(
IStringLocalizer localizer,
SessionCultureService cultureService)
{
RuleFor(e => e.FirstName)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(32)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
32));
RuleFor(e => e.LastName)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(32)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
32));
RuleFor(e => e.Patronymic)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(32)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
32));
RuleFor(e => e.Sex)
.Must((e, s) => Sex.Enumerations.ContainsValue(s))
.WithMessage(
String.Format(
localizer["FluentValidation.MustBeInEnum"],
String.Join(
", ",
Sex.Enumerations.Values.Select(e => e.Name))));
RuleFor(e => e.BirthDate)
.GreaterThanOrEqualTo(DateOnly.FromDateTime(DateTime.UtcNow.AddYears(-100)))
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
DateOnly.FromDateTime(DateTime.UtcNow.AddYears(-100))));
RuleForEach(e => e.Documents).ChildRules(d =>
{
d.RuleFor(d => d.DocumentType)
.Must(dt => DocumentType.Enumerations.ContainsValue(dt))
.WithMessage(
String.Format(
localizer["FluentValidation.MustBeInEnum"],
String.Join(
", ",
DocumentType.Enumerations.Values.Select(e => e.Name))));
d.RuleFor(d => d.Information)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(256)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
256));
});
}
}

View File

@ -0,0 +1,8 @@
using MediatR;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.DeleteEmployee;
public record DeleteEmployeeCommand : IRequest
{
public Guid Guid { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.DeleteEmployee;
public class DeleteEmployeeCommandAuthorizer :
AbstractRequestAuthorizer<DeleteEmployeeCommand>
{
private readonly SessionUserService _sessionUserService;
public DeleteEmployeeCommandAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(DeleteEmployeeCommand request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,37 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.DeleteEmployee;
public class DeleteEmployeeCommandHandler : IRequestHandler<DeleteEmployeeCommand>
{
private readonly UnitOfWork _unitOfWork;
public DeleteEmployeeCommandHandler(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task Handle(
DeleteEmployeeCommand request,
CancellationToken cancellationToken)
{
var entity = await _unitOfWork.EmployeeRepository.GetOneAsync(
e => e.Guid == request.Guid, cancellationToken);
if (entity == null)
{
throw new NotFoundException();
}
// TODO: Check for Vehicles that using this employee in Enrollments
// Delete if there are no such Vehicles
await _unitOfWork.EmployeeRepository.DeleteOneAsync(
entity, cancellationToken);
await _unitOfWork.SaveAsync(cancellationToken);
_unitOfWork.Dispose();
}
}

View File

@ -0,0 +1,14 @@
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.DeleteEmployee;
public class DeleteEmployeeCommandValidator : AbstractValidator<DeleteEmployeeCommand>
{
public DeleteEmployeeCommandValidator(IStringLocalizer localizer)
{
RuleFor(v => v.Guid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -0,0 +1,25 @@
using MediatR;
using cuqmbr.TravelGuide.Domain.Enums;
using cuqmbr.TravelGuide.Application.Employees.Models;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.UpdateEmployee;
public record UpdateEmployeeCommand : IRequest<EmployeeDto>
{
public Guid Guid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Patronymic { get; set; }
public Sex Sex { get; set; }
public DateOnly BirthDate { get; set; }
public Guid CompanyGuid { get; set; }
public ICollection<EmployeeDocumentModel> Documents { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.UpdateEmployee;
public class UpdateEmployeeCommandAuthorizer :
AbstractRequestAuthorizer<UpdateEmployeeCommand>
{
private readonly SessionUserService _sessionUserService;
public UpdateEmployeeCommandAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(UpdateEmployeeCommand request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,108 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using AutoMapper;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
using Microsoft.Extensions.Localization;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.UpdateEmployee;
public class UpdateEmployeeCommandHandler :
IRequestHandler<UpdateEmployeeCommand, EmployeeDto>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public IStringLocalizer _localizer { get; set; }
public UpdateEmployeeCommandHandler(
UnitOfWork unitOfWork,
IMapper mapper,
IStringLocalizer localizer)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_localizer = localizer;
}
public async Task<EmployeeDto> Handle(
UpdateEmployeeCommand request,
CancellationToken cancellationToken)
{
var parentEntity = await _unitOfWork.CompanyRepository.GetOneAsync(
e => e.Guid == request.CompanyGuid, cancellationToken);
if (parentEntity == null)
{
throw new NotFoundException(
$"Parent entity with Guid: {request.CompanyGuid} not found.");
}
var entity = await _unitOfWork.EmployeeRepository.GetOneAsync(
e =>
e.FirstName == request.FirstName &&
e.LastName == request.LastName &&
e.Patronymic == request.Patronymic &&
e.Sex == request.Sex &&
e.BirthDate == request.BirthDate &&
e.CompanyId == parentEntity.Id &&
e.Guid != request.Guid,
cancellationToken);
if (entity != null)
{
throw new DuplicateEntityException();
}
entity = await _unitOfWork.EmployeeRepository.GetOneAsync(
e => e.Guid == request.Guid, e => e.Documents, cancellationToken);
if (entity == null)
{
throw new NotFoundException();
}
entity.Guid = request.Guid;
entity.FirstName = request.FirstName;
entity.LastName = request.LastName;
entity.Patronymic = request.Patronymic;
entity.Sex = request.Sex;
entity.BirthDate = request.BirthDate;
entity.CompanyId = parentEntity.Id;
entity.Company = parentEntity;
var requestEmployeeDocuments = request.Documents.Select(
d => new EmployeeDocument()
{
DocumentType = d.DocumentType,
Information = d.Information
});
var commonEmployeeDocuments = entity.Documents.IntersectBy(
requestEmployeeDocuments.Select(
ed => (ed.DocumentType, ed.Information)),
ed => (ed.DocumentType, ed.Information));
var newEmployeeDocuments = requestEmployeeDocuments.ExceptBy(
entity.Documents.Select(ed => (ed.DocumentType, ed.Information)),
ed => (ed.DocumentType, ed.Information));
var combinedEmployeeDocuments = commonEmployeeDocuments.UnionBy(
newEmployeeDocuments, ed => (ed.DocumentType, ed.Information));
entity.Documents = combinedEmployeeDocuments.ToList();
entity = await _unitOfWork.EmployeeRepository.UpdateOneAsync(
entity, cancellationToken);
await _unitOfWork.SaveAsync(cancellationToken);
_unitOfWork.Dispose();
return _mapper.Map<EmployeeDto>(entity);
}
}

View File

@ -0,0 +1,87 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Domain.Enums;
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Employees.Commands.UpdateEmployee;
public class UpdateEmployeeCommandValidator : AbstractValidator<UpdateEmployeeCommand>
{
public UpdateEmployeeCommandValidator(
IStringLocalizer localizer,
SessionCultureService cultureService)
{
RuleFor(v => v.Guid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
RuleFor(e => e.FirstName)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(32)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
32));
RuleFor(e => e.LastName)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(32)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
32));
RuleFor(e => e.Patronymic)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(32)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
32));
RuleFor(e => e.Sex)
.Must((e, s) => Sex.Enumerations.ContainsValue(s))
.WithMessage(
String.Format(
localizer["FluentValidation.MustBeInEnum"],
String.Join(
", ",
Sex.Enumerations.Values.Select(e => e.Name))));
RuleFor(e => e.BirthDate)
.GreaterThanOrEqualTo(DateOnly.FromDateTime(DateTime.UtcNow.AddYears(-100)))
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
DateOnly.FromDateTime(DateTime.UtcNow.AddYears(-100))));
RuleForEach(e => e.Documents).ChildRules(d =>
{
d.RuleFor(d => d.DocumentType)
.Must(dt => DocumentType.Enumerations.ContainsValue(dt))
.WithMessage(
String.Format(
localizer["FluentValidation.MustBeInEnum"],
String.Join(
", ",
DocumentType.Enumerations.Values.Select(e => e.Name))));
d.RuleFor(d => d.Information)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"])
.MaximumLength(256)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
256));
});
}
}

View File

@ -0,0 +1,19 @@
using cuqmbr.TravelGuide.Application.Common.Mappings;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.Employees;
public sealed class EmployeeDocumentDto : IMapFrom<EmployeeDocument>
{
public string DocumentType { get; set; }
public string Information { get; set; }
public void Mapping(MappingProfile profile)
{
profile.CreateMap<EmployeeDocument, EmployeeDocumentDto>()
.ForMember(
d => d.DocumentType,
opt => opt.MapFrom(s => s.DocumentType.Name));
}
}

View File

@ -0,0 +1,38 @@
using cuqmbr.TravelGuide.Application.Common.Mappings;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Application.Employees;
public sealed class EmployeeDto : IMapFrom<Employee>
{
public Guid Uuid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Patronymic { get; set; }
public string Sex { get; set; }
public DateOnly BirthDate { get; set; }
public Guid CompanyUuid { get; set; }
public ICollection<EmployeeDocumentDto> Documents { get; set; }
public void Mapping(MappingProfile profile)
{
profile.CreateMap<Employee, EmployeeDto>()
.ForMember(
d => d.Uuid,
opt => opt.MapFrom(s => s.Guid))
.ForMember(
d => d.Sex,
opt => opt.MapFrom(s => s.Sex.Name))
.ForMember(
d => d.CompanyUuid,
opt => opt.MapFrom(s => s.Company.Guid));
}
}

View File

@ -0,0 +1,10 @@
using cuqmbr.TravelGuide.Domain.Enums;
namespace cuqmbr.TravelGuide.Application.Employees.Models;
public sealed class EmployeeDocumentModel
{
public DocumentType DocumentType { get; set; }
public string Information { get; set; }
}

View File

@ -0,0 +1,8 @@
using MediatR;
namespace cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployee;
public record GetEmployeeQuery : IRequest<EmployeeDto>
{
public Guid Guid { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployee;
public class GetEmployeeQueryAuthorizer :
AbstractRequestAuthorizer<GetEmployeeQuery>
{
private readonly SessionUserService _sessionUserService;
public GetEmployeeQueryAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(GetEmployeeQuery request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,48 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using cuqmbr.TravelGuide.Application.Common.Exceptions;
using AutoMapper;
namespace cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployee;
public class GetEmployeeQueryHandler :
IRequestHandler<GetEmployeeQuery, EmployeeDto>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public GetEmployeeQueryHandler(
UnitOfWork unitOfWork,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
public async Task<EmployeeDto> Handle(
GetEmployeeQuery request,
CancellationToken cancellationToken)
{
var entity = await _unitOfWork.EmployeeRepository.GetOneAsync(
e => e.Guid == request.Guid, e => e.Documents,
cancellationToken);
if (entity == null)
{
throw new NotFoundException();
}
// Hydrate employees with companies
var company = await _unitOfWork.CompanyRepository.GetOneAsync(
e => e.Id == entity.CompanyId, cancellationToken);
entity.Company = company;
_unitOfWork.Dispose();
return _mapper.Map<EmployeeDto>(entity);
}
}

View File

@ -0,0 +1,14 @@
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployee;
public class GetEmployeeQueryValidator : AbstractValidator<GetEmployeeQuery>
{
public GetEmployeeQueryValidator(IStringLocalizer localizer)
{
RuleFor(v => v.Guid)
.NotEmpty()
.WithMessage(localizer["FluentValidation.NotEmpty"]);
}
}

View File

@ -0,0 +1,24 @@
using cuqmbr.TravelGuide.Application.Common.Models;
using cuqmbr.TravelGuide.Domain.Enums;
using MediatR;
namespace cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployeesPage;
public record GetEmployeesPageQuery : IRequest<PaginatedList<EmployeeDto>>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string Search { get; set; } = String.Empty;
public string Sort { get; set; } = String.Empty;
public Sex? Sex { get; set; }
public DateOnly? BirthDateGreaterThanOrEqualTo { get; set; }
public DateOnly? BirthDateLessThanOrEqualTo { get; set; }
public Guid? CompanyGuid { get; set; }
}

View File

@ -0,0 +1,31 @@
using cuqmbr.TravelGuide.Application.Common.Authorization;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using cuqmbr.TravelGuide.Application.Common.Models;
using MediatR.Behaviors.Authorization;
namespace cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployeesPage;
public class GetEmployeesPageQueryAuthorizer :
AbstractRequestAuthorizer<GetEmployeesPageQuery>
{
private readonly SessionUserService _sessionUserService;
public GetEmployeesPageQueryAuthorizer(SessionUserService sessionUserService)
{
_sessionUserService = sessionUserService;
}
public override void BuildPolicy(GetEmployeesPageQuery request)
{
UseRequirement(new MustBeAuthenticatedRequirement
{
IsAuthenticated= _sessionUserService.IsAuthenticated
});
UseRequirement(new MustBeInRolesRequirement
{
RequiredRoles = [IdentityRole.Administrator],
UserRoles = _sessionUserService.Roles
});
}
}

View File

@ -0,0 +1,78 @@
using MediatR;
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
using AutoMapper;
using cuqmbr.TravelGuide.Application.Common.Models;
using cuqmbr.TravelGuide.Application.Common.Extensions;
namespace cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployeesPage;
public class GetEmployeesPageQueryHandler :
IRequestHandler<GetEmployeesPageQuery, PaginatedList<EmployeeDto>>
{
private readonly UnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public GetEmployeesPageQueryHandler(
UnitOfWork unitOfWork,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
public async Task<PaginatedList<EmployeeDto>> Handle(
GetEmployeesPageQuery request,
CancellationToken cancellationToken)
{
var paginatedList = await _unitOfWork.EmployeeRepository.GetPageAsync(
e =>
(e.FirstName.ToLower().Contains(request.Search.ToLower()) ||
e.LastName.ToLower().Contains(request.Search.ToLower()) ||
e.Patronymic.ToLower().Contains(request.Search.ToLower()) ||
e.Documents
.Select(d => d.Information.ToLower())
.Contains(request.Search.ToLower())) &&
(request.CompanyGuid != null
? e.Company.Guid == request.CompanyGuid
: true) &&
(request.Sex != null
? e.Sex == request.Sex
: true) &&
(request.BirthDateLessThanOrEqualTo != null
? e.BirthDate <= request.BirthDateLessThanOrEqualTo
: true) &&
(request.BirthDateGreaterThanOrEqualTo != null
? e.BirthDate >= request.BirthDateGreaterThanOrEqualTo
: true),
e => e.Documents,
request.PageNumber, request.PageSize,
cancellationToken);
// Hydrate employees with companies
var companies = await _unitOfWork.CompanyRepository.GetPageAsync(
e => paginatedList.Items.Select(e => e.CompanyId).Contains(e.Id),
1, paginatedList.Items.Count, cancellationToken);
foreach (var employee in paginatedList.Items)
{
employee.Company =
companies.Items.First(c => c.Id == employee.CompanyId);
}
var mappedItems = _mapper
.ProjectTo<EmployeeDto>(paginatedList.Items.AsQueryable());
mappedItems = QueryableExtension<EmployeeDto>
.ApplySort(mappedItems, request.Sort);
_unitOfWork.Dispose();
return new PaginatedList<EmployeeDto>(
mappedItems.ToList(),
paginatedList.TotalCount, request.PageNumber,
request.PageSize);
}
}

View File

@ -0,0 +1,43 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
using FluentValidation;
using Microsoft.Extensions.Localization;
namespace cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployeesPage;
public class GetEmployeesPageQueryValidator : AbstractValidator<GetEmployeesPageQuery>
{
public GetEmployeesPageQueryValidator(
IStringLocalizer localizer,
SessionCultureService cultureService)
{
RuleFor(v => v.PageNumber)
.GreaterThanOrEqualTo(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
1));
RuleFor(v => v.PageSize)
.GreaterThanOrEqualTo(1)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.GreaterThanOrEqualTo"],
1))
.LessThanOrEqualTo(50)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.LessThanOrEqualTo"],
50));
RuleFor(v => v.Search)
.MaximumLength(64)
.WithMessage(
String.Format(
cultureService.Culture,
localizer["FluentValidation.MaximumLength"],
64));
}
}

View File

@ -0,0 +1,19 @@
namespace cuqmbr.TravelGuide.Application.Employees.ViewModels;
public sealed class AddEmployeeViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Patronymic { get; set; }
public string Sex { get; set; }
public DateOnly BirthDate { get; set; }
public Guid CompanyUuid { get; set; }
public ICollection<EmployeeDocumentViewModel> Documents { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace cuqmbr.TravelGuide.Application.Employees.ViewModels;
public sealed class EmployeeDocumentViewModel
{
public string DocumentType { get; set; }
public string Information { get; set; }
}

View File

@ -0,0 +1,12 @@
namespace cuqmbr.TravelGuide.Application.Employees.ViewModels;
public sealed class GetEmployeesPageFilterViewModel
{
public string? Sex { get; set; }
public DateOnly? BirthDateGreaterThanOrEqualTo { get; set; }
public DateOnly? BirthDateLessThanOrEqualTo { get; set; }
public Guid? CompanyUuid { get; set; }
}

View File

@ -0,0 +1,19 @@
namespace cuqmbr.TravelGuide.Application.Employees.ViewModels;
public sealed class UpdateEmployeeViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Patronymic { get; set; }
public string Sex { get; set; }
public DateOnly BirthDate { get; set; }
public Guid CompanyUuid { get; set; }
public ICollection<EmployeeDocumentViewModel> Documents { get; set; }
}

View File

@ -2,8 +2,8 @@
"FluentValidation": { "FluentValidation": {
"MaximumLength": "Must less than {0:G} characters.", "MaximumLength": "Must less than {0:G} characters.",
"NotEmpty": "Must not be empty.", "NotEmpty": "Must not be empty.",
"GreaterThanOrEqualTo": "Must be greater than or equal to {0:G}.", "GreaterThanOrEqualTo": "Must be greater than or equal to {0}.",
"LessThanOrEqualTo": "Must be less than or equal to {0:G}.", "LessThanOrEqualTo": "Must be less than or equal to {0}.",
"MustBeInEnum": "Must be one of the following: {0}.", "MustBeInEnum": "Must be one of the following: {0}.",
"IsEmail": "Must be a valid email address according to RFC 5321.", "IsEmail": "Must be a valid email address according to RFC 5321.",
"IsPhoneNumber": "Must be a valid phone number according to ITU-T E.164 with no separator characters." "IsPhoneNumber": "Must be a valid phone number according to ITU-T E.164 with no separator characters."

View File

@ -11,5 +11,7 @@ public sealed class Company : EntityBase
public string ContactPhoneNumber { get; set; } public string ContactPhoneNumber { get; set; }
public ICollection<Employee> Employees { get; set; }
public ICollection<Vehicle> Vehicles { get; set; } public ICollection<Vehicle> Vehicles { get; set; }
} }

View File

@ -0,0 +1,23 @@
using cuqmbr.TravelGuide.Domain.Enums;
namespace cuqmbr.TravelGuide.Domain.Entities;
public sealed class Employee : EntityBase
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Patronymic { get; set; }
public Sex Sex { get; set; }
public DateOnly BirthDate { get; set; }
public long CompanyId { get; set; }
public Company Company { get; set; }
public ICollection<EmployeeDocument> Documents { get; set; }
}

View File

@ -0,0 +1,15 @@
using cuqmbr.TravelGuide.Domain.Enums;
namespace cuqmbr.TravelGuide.Domain.Entities;
public sealed class EmployeeDocument : EntityBase
{
public DocumentType DocumentType { get; set; }
public string Information { get; set; }
public long EmployeeId { get; set; }
public Employee Employee { get; set; }
}

View File

@ -20,4 +20,7 @@ public class VehicleEnrollment : EntityBase
public ICollection<RouteAddressDetail> RouteAddressDetails { get; set; } public ICollection<RouteAddressDetail> RouteAddressDetails { get; set; }
public ICollection<Ticket> Tickets { get; set; }
} }

View File

@ -0,0 +1,16 @@
namespace cuqmbr.TravelGuide.Domain.Enums;
// Do not forget to update the schema of your database when changing
// this class (if you use it with a database)
public abstract class DocumentType : Enumeration<DocumentType>
{
public static readonly DocumentType Passport = new PassportDocumentType();
protected DocumentType(int value, string name) : base(value, name) { }
private sealed class PassportDocumentType : DocumentType
{
public PassportDocumentType() : base(0, "passport") { }
}
}

22
src/Domain/Enums/Sex.cs Normal file
View File

@ -0,0 +1,22 @@
namespace cuqmbr.TravelGuide.Domain.Enums;
// Do not forget to update the schema of your database when changing
// this class (if you use it with a database)
public abstract class Sex : Enumeration<Sex>
{
public static readonly Sex Male = new MaleSex();
public static readonly Sex Female = new FemaleSex();
protected Sex(int value, string name) : base(value, name) { }
private sealed class MaleSex : Sex
{
public MaleSex() : base(Int32.MaxValue, "male") { }
}
private sealed class FemaleSex : Sex
{
public FemaleSex() : base(Int32.MinValue, "female") { }
}
}

View File

@ -0,0 +1,212 @@
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using cuqmbr.TravelGuide.Application.Common.Models;
using cuqmbr.TravelGuide.Application.Common.ViewModels;
using cuqmbr.TravelGuide.Domain.Enums;
using cuqmbr.TravelGuide.Application.Employees;
using cuqmbr.TravelGuide.Application.Employees.Commands.AddEmployee;
using cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployeesPage;
using cuqmbr.TravelGuide.Application.Employees.Queries.GetEmployee;
using cuqmbr.TravelGuide.Application.Employees.Commands.UpdateEmployee;
using cuqmbr.TravelGuide.Application.Employees.Commands.DeleteEmployee;
using cuqmbr.TravelGuide.Application.Employees.ViewModels;
using cuqmbr.TravelGuide.Application.Employees.Models;
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
[Route("employees")]
public class EmployeesController : ControllerBase
{
[HttpPost]
[SwaggerOperation("Add an employee")]
[SwaggerResponse(
StatusCodes.Status201Created, "Object successfuly created",
typeof(EmployeeDto))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "One or more addresses was not found",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<ActionResult<EmployeeDto>> Add(
[FromBody] AddEmployeeViewModel viewModel,
CancellationToken cancellationToken)
{
return StatusCode(
StatusCodes.Status201Created,
await Mediator.Send(
new AddEmployeeCommand()
{
FirstName = viewModel.FirstName,
LastName = viewModel.LastName,
Patronymic = viewModel.Patronymic,
Sex = Sex.FromName(viewModel.Sex),
BirthDate = viewModel.BirthDate,
Documents = viewModel.Documents.Select(
e => new EmployeeDocumentModel()
{
DocumentType = DocumentType.FromName(e.DocumentType),
Information = e.Information
}).ToArray(),
CompanyGuid = viewModel.CompanyUuid
},
cancellationToken));
}
[HttpGet]
[SwaggerOperation("Get a list of all employees")]
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful",
typeof(PaginatedList<EmployeeDto>))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<PaginatedList<EmployeeDto>> GetPage(
[FromQuery] PageQuery pageQuery, [FromQuery] SearchQuery searchQuery,
[FromQuery] SortQuery sortQuery,
[FromQuery] GetEmployeesPageFilterViewModel filterQuery,
CancellationToken cancellationToken)
{
return await Mediator.Send(
new GetEmployeesPageQuery()
{
PageNumber = pageQuery.PageNumber,
PageSize = pageQuery.PageSize,
Search = searchQuery.Search,
Sort = sortQuery.Sort,
Sex = Sex.FromName(filterQuery.Sex),
BirthDateLessThanOrEqualTo =
filterQuery.BirthDateLessThanOrEqualTo,
BirthDateGreaterThanOrEqualTo =
filterQuery.BirthDateGreaterThanOrEqualTo,
CompanyGuid = filterQuery.CompanyUuid
},
cancellationToken);
}
[HttpGet("{uuid:guid}")]
[SwaggerOperation("Get an employee by uuid")]
[SwaggerResponse(
StatusCodes.Status200OK, "Request successful", typeof(EmployeeDto))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Object not found", typeof(EmployeeDto))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<EmployeeDto> Get(
[FromRoute] Guid uuid,
CancellationToken cancellationToken)
{
return await Mediator.Send(new GetEmployeeQuery() { Guid = uuid },
cancellationToken);
}
[HttpPut("{uuid:guid}")]
[SwaggerOperation("Update an employee")]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Object already exists",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Object not found", typeof(EmployeeDto))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "One or more addresses was not found",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<EmployeeDto> Update(
[FromRoute] Guid uuid,
[FromBody] UpdateEmployeeViewModel viewModel,
CancellationToken cancellationToken)
{
return await Mediator.Send(
new UpdateEmployeeCommand()
{
Guid = uuid,
FirstName = viewModel.FirstName,
LastName = viewModel.LastName,
Patronymic = viewModel.Patronymic,
Sex = Sex.FromName(viewModel.Sex),
BirthDate = viewModel.BirthDate,
Documents = viewModel.Documents.Select(
e => new EmployeeDocumentModel()
{
DocumentType = DocumentType.FromName(e.DocumentType),
Information = e.Information
}).ToArray(),
CompanyGuid = viewModel.CompanyUuid
},
cancellationToken);
}
[HttpDelete("{uuid:guid}")]
[SwaggerOperation("Delete an employee")]
[SwaggerResponse(StatusCodes.Status204NoContent, "Request successful")]
[SwaggerResponse(
StatusCodes.Status400BadRequest, "Input data validation error",
typeof(HttpValidationProblemDetails))]
[SwaggerResponse(
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status403Forbidden,
"Not enough privileges to perform an action",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status404NotFound, "Object not found",
typeof(ProblemDetails))]
[SwaggerResponse(
StatusCodes.Status500InternalServerError, "Internal server error",
typeof(ProblemDetails))]
public async Task<IActionResult> Delete(
[FromRoute] Guid uuid,
CancellationToken cancellationToken)
{
await Mediator.Send(
new DeleteEmployeeCommand() { Guid = uuid },
cancellationToken);
return StatusCode(StatusCodes.Status204NoContent);
}
}

View File

@ -1,5 +1,4 @@
using cuqmbr.TravelGuide.Domain.Enums; using cuqmbr.TravelGuide.Domain.Enums;
using cuqmbr.TravelGuide.Domain.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using cuqmbr.TravelGuide.Persistence.TypeConverters; using cuqmbr.TravelGuide.Persistence.TypeConverters;
@ -10,11 +9,6 @@ public class InMemoryDbContext : DbContext
public InMemoryDbContext(DbContextOptions<InMemoryDbContext> options) public InMemoryDbContext(DbContextOptions<InMemoryDbContext> options)
: base(options) { } : base(options) { }
public DbSet<Country> Countries { get => Set<Country>(); }
public DbSet<Region> Regions { get => Set<Region>(); }
public DbSet<City> Cities { get => Set<City>(); }
public DbSet<Address> Addresses { get => Set<Address>(); }
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)
{ {
base.OnModelCreating(builder); base.OnModelCreating(builder);

View File

@ -27,6 +27,7 @@ public sealed class InMemoryUnitOfWork : UnitOfWork
RouteAddressRepository = RouteAddressRepository =
new InMemoryRouteAddressRepository(_dbContext); new InMemoryRouteAddressRepository(_dbContext);
CompanyRepository = new InMemoryCompanyRepository(_dbContext); CompanyRepository = new InMemoryCompanyRepository(_dbContext);
EmployeeRepository = new InMemoryEmployeeRepository(_dbContext);
} }
public CountryRepository CountryRepository { get; init; } public CountryRepository CountryRepository { get; init; }
@ -53,6 +54,8 @@ public sealed class InMemoryUnitOfWork : UnitOfWork
public CompanyRepository CompanyRepository { get; init; } public CompanyRepository CompanyRepository { get; init; }
public EmployeeRepository EmployeeRepository { get; init; }
public int Save() public int Save()
{ {
return _dbContext.SaveChanges(); return _dbContext.SaveChanges();

View File

@ -0,0 +1,11 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Persistence.InMemory.Repositories;
public sealed class InMemoryEmployeeRepository :
InMemoryBaseRepository<Employee>, EmployeeRepository
{
public InMemoryEmployeeRepository(InMemoryDbContext dbContext)
: base(dbContext) { }
}

View File

@ -0,0 +1,81 @@
using cuqmbr.TravelGuide.Domain.Entities;
using cuqmbr.TravelGuide.Domain.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
public class EmployeeConfiguration : BaseConfiguration<Employee>
{
public override void Configure(EntityTypeBuilder<Employee> builder)
{
builder
.Property(e => e.Sex)
.HasColumnName("sex")
.IsRequired(true);
builder
.ToTable(
"employees",
e => e.HasCheckConstraint(
"ck_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(e => e.Sex)
.Metadata.GetColumnName()}",
$"{builder.Property(e => e.Sex)
.Metadata.GetColumnName()} IN ('{String
.Join("', '", Sex.Enumerations
.Values.Select(v => v.Name))}')"));
base.Configure(builder);
builder
.Property(e => e.FirstName)
.HasColumnName("first_name")
.HasColumnType("varchar(32)")
.IsRequired(true);
builder
.Property(e => e.LastName)
.HasColumnName("last_name")
.HasColumnType("varchar(32)")
.IsRequired(true);
builder
.Property(e => e.Patronymic)
.HasColumnName("patronymic")
.HasColumnType("varchar(32)")
.IsRequired(true);
builder
.Property(e => e.BirthDate)
.HasColumnName("birth_date")
.HasColumnType("date")
.IsRequired(true);
builder
.Property(e => e.CompanyId)
.HasColumnName("company_id")
.HasColumnType("bigint")
.IsRequired(true);
builder
.HasOne(e => e.Company)
.WithMany(c => c.Employees)
.HasForeignKey(e => e.CompanyId)
.HasConstraintName(
"fk_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(e => e.CompanyId).Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasIndex(e => e.CompanyId)
.HasDatabaseName(
"ix_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(e => e.CompanyId).Metadata.GetColumnName()}");
}
}

View File

@ -0,0 +1,63 @@
using cuqmbr.TravelGuide.Domain.Entities;
using cuqmbr.TravelGuide.Domain.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
public class EmployeeDocumentConfiguration : BaseConfiguration<EmployeeDocument>
{
public override void Configure(EntityTypeBuilder<EmployeeDocument> builder)
{
builder
.Property(ed => ed.DocumentType)
.HasColumnName("document_type")
.IsRequired(true);
builder
.ToTable(
"employee_documents",
ed => ed.HasCheckConstraint(
"ck_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(ed => ed.DocumentType)
.Metadata.GetColumnName()}",
$"{builder.Property(ed => ed.DocumentType)
.Metadata.GetColumnName()} IN ('{String
.Join("', '", DocumentType.Enumerations
.Values.Select(v => v.Name))}')"));
base.Configure(builder);
builder
.Property(ed => ed.Information)
.HasColumnName("information")
.HasColumnType("varchar(256)")
.IsRequired(true);
builder
.Property(ed => ed.EmployeeId)
.HasColumnName("employee_id")
.HasColumnType("bigint")
.IsRequired(true);
builder
.HasOne(ed => ed.Employee)
.WithMany(ed => ed.Documents)
.HasForeignKey(ed => ed.EmployeeId)
.HasConstraintName(
"fk_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(ed => ed.EmployeeId).Metadata.GetColumnName()}")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasIndex(ed => ed.EmployeeId)
.HasDatabaseName(
"ix_" +
$"{builder.Metadata.GetTableName()}_" +
$"{builder.Property(ed => ed.EmployeeId).Metadata.GetColumnName()}");
}
}

View File

@ -0,0 +1,841 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using cuqmbr.TravelGuide.Persistence.PostgreSql;
#nullable disable
namespace Persistence.PostgreSql.Migrations
{
[DbContext(typeof(PostgreSqlDbContext))]
[Migration("20250515164353_Add_Employee_and_EmployeeDocument")]
partial class Add_Employee_and_EmployeeDocument
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("application")
.HasAnnotation("ProductVersion", "9.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.HasSequence("addresses_id_sequence");
modelBuilder.HasSequence("cities_id_sequence");
modelBuilder.HasSequence("companies_id_sequence");
modelBuilder.HasSequence("countries_id_sequence");
modelBuilder.HasSequence("employee_documents_id_sequence");
modelBuilder.HasSequence("employees_id_sequence");
modelBuilder.HasSequence("regions_id_sequence");
modelBuilder.HasSequence("route_address_details_id_sequence");
modelBuilder.HasSequence("route_addresses_id_sequence");
modelBuilder.HasSequence("routes_id_sequence");
modelBuilder.HasSequence("vehicle_enrollments_id_sequence");
modelBuilder.HasSequence("vehicles_id_sequence");
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.addresses_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "addresses_id_sequence");
b.Property<long>("CityId")
.HasColumnType("bigint")
.HasColumnName("city_id");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<double>("Latitude")
.HasColumnType("double precision");
b.Property<double>("Longitude")
.HasColumnType("double precision");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(128)")
.HasColumnName("name");
b.Property<string>("VehicleType")
.IsRequired()
.HasColumnType("varchar(16)")
.HasColumnName("vehicle_type");
b.HasKey("Id")
.HasName("pk_addresses");
b.HasAlternateKey("Guid")
.HasName("altk_addresses_uuid");
b.HasIndex("CityId")
.HasDatabaseName("ix_addresses_city_id");
b.ToTable("addresses", "application", t =>
{
t.HasCheckConstraint("ck_addresses_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
});
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.cities_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "cities_id_sequence");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("name");
b.Property<long>("RegionId")
.HasColumnType("bigint")
.HasColumnName("region_id");
b.HasKey("Id")
.HasName("pk_cities");
b.HasAlternateKey("Guid")
.HasName("altk_cities_uuid");
b.HasIndex("RegionId")
.HasDatabaseName("ix_cities_region_id");
b.ToTable("cities", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Company", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.companies_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "companies_id_sequence");
b.Property<string>("ContactEmail")
.IsRequired()
.HasColumnType("varchar(256)")
.HasColumnName("contact_email");
b.Property<string>("ContactPhoneNumber")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("contact_phone_number");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("LegalAddress")
.IsRequired()
.HasColumnType("varchar(256)")
.HasColumnName("legal_address");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("name");
b.HasKey("Id")
.HasName("pk_companies");
b.HasAlternateKey("Guid")
.HasName("altk_companies_uuid");
b.ToTable("companies", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Country", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.countries_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "countries_id_sequence");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("name");
b.HasKey("Id")
.HasName("pk_countries");
b.HasAlternateKey("Guid")
.HasName("altk_countries_uuid");
b.ToTable("countries", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.employees_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "employees_id_sequence");
b.Property<DateOnly>("BirthDate")
.HasColumnType("date")
.HasColumnName("birth_date");
b.Property<long>("CompanyId")
.HasColumnType("bigint")
.HasColumnName("company_id");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("varchar(32)")
.HasColumnName("first_name");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("varchar(32)")
.HasColumnName("last_name");
b.Property<string>("Patronymic")
.IsRequired()
.HasColumnType("varchar(32)")
.HasColumnName("patronymic");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("varchar(32)")
.HasColumnName("sex");
b.HasKey("Id")
.HasName("pk_employees");
b.HasAlternateKey("Guid")
.HasName("altk_employees_uuid");
b.HasIndex("CompanyId")
.HasDatabaseName("ix_employees_company_id");
b.ToTable("employees", "application", t =>
{
t.HasCheckConstraint("ck_employees_sex", "sex IN ('male', 'female')");
});
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.EmployeeDocument", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.employee_documents_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "employee_documents_id_sequence");
b.Property<string>("DocumentType")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("document_type");
b.Property<long>("EmployeeId")
.HasColumnType("bigint")
.HasColumnName("employee_id");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("Information")
.IsRequired()
.HasColumnType("varchar(256)")
.HasColumnName("information");
b.HasKey("Id")
.HasName("pk_employee_documents");
b.HasAlternateKey("Guid")
.HasName("altk_employee_documents_uuid");
b.HasIndex("EmployeeId")
.HasDatabaseName("ix_employee_documents_employee_id");
b.ToTable("employee_documents", "application", t =>
{
t.HasCheckConstraint("ck_employee_documents_document_type", "document_type IN ('passport')");
});
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.regions_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "regions_id_sequence");
b.Property<long>("CountryId")
.HasColumnType("bigint")
.HasColumnName("country_id");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("name");
b.HasKey("Id")
.HasName("pk_regions");
b.HasAlternateKey("Guid")
.HasName("altk_regions_uuid");
b.HasIndex("CountryId")
.HasDatabaseName("ix_regions_country_id");
b.ToTable("regions", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.routes_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "routes_id_sequence");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("name");
b.Property<string>("VehicleType")
.IsRequired()
.HasColumnType("varchar(16)")
.HasColumnName("vehicle_type");
b.HasKey("Id")
.HasName("pk_routes");
b.HasAlternateKey("Guid")
.HasName("altk_routes_uuid");
b.ToTable("routes", "application", t =>
{
t.HasCheckConstraint("ck_routes_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
});
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.route_addresses_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "route_addresses_id_sequence");
b.Property<long>("AddressId")
.HasColumnType("bigint")
.HasColumnName("address_id");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<short>("Order")
.HasColumnType("smallint")
.HasColumnName("order");
b.Property<long>("RouteId")
.HasColumnType("bigint")
.HasColumnName("route_id");
b.HasKey("Id")
.HasName("pk_route_addresses");
b.HasAlternateKey("Guid")
.HasName("altk_route_addresses_uuid");
b.HasAlternateKey("AddressId", "RouteId", "Order")
.HasName("altk_route_addresses_address_id_route_id_order");
b.HasIndex("AddressId")
.HasDatabaseName("ix_route_addresses_address_id");
b.HasIndex("RouteId")
.HasDatabaseName("ix_route_addresses_route_id");
b.ToTable("route_addresses", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddressDetail", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.route_address_details_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "route_address_details_id_sequence");
b.Property<decimal>("CostToNextAddress")
.HasColumnType("numeric(24,12)")
.HasColumnName("cost_to_next_address");
b.Property<TimeSpan>("CurrentAddressStopTime")
.HasColumnType("interval")
.HasColumnName("current_address_stop_time");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<long>("RouteAddressId")
.HasColumnType("bigint")
.HasColumnName("route_address_id");
b.Property<TimeSpan>("TimeToNextAddress")
.HasColumnType("interval")
.HasColumnName("time_to_next_address");
b.Property<long>("VehicleEnrollmentId")
.HasColumnType("bigint")
.HasColumnName("vehicle_enrollment_id");
b.HasKey("Id")
.HasName("pk_route_address_details");
b.HasAlternateKey("Guid")
.HasName("altk_route_address_details_uuid");
b.HasIndex("RouteAddressId")
.HasDatabaseName("ix_route_address_details_route_address_id");
b.HasIndex("VehicleEnrollmentId")
.HasDatabaseName("ix_route_address_details_vehicle_enrollment_id");
b.ToTable("route_address_details", "application");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.vehicles_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "vehicles_id_sequence");
b.Property<long>("CompanyId")
.HasColumnType("bigint")
.HasColumnName("company_id");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("VehicleType")
.IsRequired()
.HasColumnType("varchar(16)")
.HasColumnName("vehicle_type");
b.HasKey("Id")
.HasName("pk_vehicles");
b.HasAlternateKey("Guid")
.HasName("altk_vehicles_uuid");
b.HasIndex("CompanyId")
.HasDatabaseName("ix_vehicles_company_id");
b.ToTable("vehicles", "application", t =>
{
t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
});
b.HasDiscriminator<string>("VehicleType");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.vehicle_enrollments_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "vehicle_enrollments_id_sequence");
b.Property<string>("Currency")
.IsRequired()
.HasColumnType("varchar(8)")
.HasColumnName("currency");
b.Property<DateTimeOffset>("DepartureTime")
.HasColumnType("timestamptz")
.HasColumnName("departure_time");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<long>("RouteId")
.HasColumnType("bigint")
.HasColumnName("route_id");
b.Property<long>("VehicleId")
.HasColumnType("bigint")
.HasColumnName("vehicle_id");
b.HasKey("Id")
.HasName("pk_vehicle_enrollments");
b.HasAlternateKey("Guid")
.HasName("altk_vehicle_enrollments_uuid");
b.HasIndex("RouteId")
.HasDatabaseName("ix_vehicle_enrollments_route_id");
b.HasIndex("VehicleId")
.HasDatabaseName("ix_vehicle_enrollments_vehicle_id");
b.ToTable("vehicle_enrollments", "application", t =>
{
t.HasCheckConstraint("ck_vehicle_enrollments_currency", "currency IN ('DEFAULT', 'USD', 'EUR', 'UAH')");
});
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Aircraft", b =>
{
b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle");
b.Property<short>("Capacity")
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("smallint")
.HasColumnName("capacity");
b.Property<string>("Model")
.IsRequired()
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("varchar(64)")
.HasColumnName("model");
b.Property<string>("Number")
.IsRequired()
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("varchar(32)")
.HasColumnName("number");
b.ToTable(t =>
{
t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
});
b.HasDiscriminator().HasValue("aircraft");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Bus", b =>
{
b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle");
b.Property<short>("Capacity")
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("smallint")
.HasColumnName("capacity");
b.Property<string>("Model")
.IsRequired()
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("varchar(64)")
.HasColumnName("model");
b.Property<string>("Number")
.IsRequired()
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("varchar(32)")
.HasColumnName("number");
b.ToTable(t =>
{
t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
});
b.HasDiscriminator().HasValue("bus");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Train", b =>
{
b.HasBaseType("cuqmbr.TravelGuide.Domain.Entities.Vehicle");
b.Property<short>("Capacity")
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("smallint")
.HasColumnName("capacity");
b.Property<string>("Model")
.IsRequired()
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("varchar(64)")
.HasColumnName("model");
b.Property<string>("Number")
.IsRequired()
.ValueGeneratedOnUpdateSometimes()
.HasColumnType("varchar(32)")
.HasColumnName("number");
b.ToTable(t =>
{
t.HasCheckConstraint("ck_vehicles_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
});
b.HasDiscriminator().HasValue("train");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.City", "City")
.WithMany("Addresses")
.HasForeignKey("CityId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_addresses_city_id");
b.Navigation("City");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Region", "Region")
.WithMany("Cities")
.HasForeignKey("RegionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_cities_region_id");
b.Navigation("Region");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Company", "Company")
.WithMany("Employees")
.HasForeignKey("CompanyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_employees_company_id");
b.Navigation("Company");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.EmployeeDocument", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Employee", "Employee")
.WithMany("Documents")
.HasForeignKey("EmployeeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_employee_documents_employee_id");
b.Navigation("Employee");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Country", "Country")
.WithMany("Regions")
.HasForeignKey("CountryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_regions_country_id");
b.Navigation("Country");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Address", "Address")
.WithMany("AddressRoutes")
.HasForeignKey("AddressId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_route_addresses_address_id");
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route")
.WithMany("RouteAddresses")
.HasForeignKey("RouteId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_route_addresses_route_id");
b.Navigation("Address");
b.Navigation("Route");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddressDetail", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", "RouteAddress")
.WithMany("Details")
.HasForeignKey("RouteAddressId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_route_address_details_route_address_id");
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", "VehicleEnrollment")
.WithMany("RouteAddressDetails")
.HasForeignKey("VehicleEnrollmentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_route_address_details_vehicle_enrollment_id");
b.Navigation("RouteAddress");
b.Navigation("VehicleEnrollment");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Company", "Company")
.WithMany("Vehicles")
.HasForeignKey("CompanyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_vehicles_company_id");
b.Navigation("Company");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route")
.WithMany("VehicleEnrollments")
.HasForeignKey("RouteId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_vehicle_enrollments_route_id");
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Vehicle", "Vehicle")
.WithMany("Enrollments")
.HasForeignKey("VehicleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_vehicle_enrollments_vehicle_id");
b.Navigation("Route");
b.Navigation("Vehicle");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
{
b.Navigation("AddressRoutes");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
{
b.Navigation("Addresses");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Company", b =>
{
b.Navigation("Employees");
b.Navigation("Vehicles");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Country", b =>
{
b.Navigation("Regions");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b =>
{
b.Navigation("Documents");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
{
b.Navigation("Cities");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b =>
{
b.Navigation("RouteAddresses");
b.Navigation("VehicleEnrollments");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
{
b.Navigation("Details");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Vehicle", b =>
{
b.Navigation("Enrollments");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.VehicleEnrollment", b =>
{
b.Navigation("RouteAddressDetails");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,108 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Persistence.PostgreSql.Migrations
{
/// <inheritdoc />
public partial class Add_Employee_and_EmployeeDocument : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateSequence(
name: "employee_documents_id_sequence",
schema: "application");
migrationBuilder.CreateSequence(
name: "employees_id_sequence",
schema: "application");
migrationBuilder.CreateTable(
name: "employees",
schema: "application",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.employees_id_sequence')"),
first_name = table.Column<string>(type: "varchar(32)", nullable: false),
last_name = table.Column<string>(type: "varchar(32)", nullable: false),
patronymic = table.Column<string>(type: "varchar(32)", nullable: false),
sex = table.Column<string>(type: "varchar(32)", nullable: false),
birth_date = table.Column<DateOnly>(type: "date", nullable: false),
company_id = table.Column<long>(type: "bigint", nullable: false),
uuid = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_employees", x => x.id);
table.UniqueConstraint("altk_employees_uuid", x => x.uuid);
table.CheckConstraint("ck_employees_sex", "sex IN ('male', 'female')");
table.ForeignKey(
name: "fk_employees_company_id",
column: x => x.company_id,
principalSchema: "application",
principalTable: "companies",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "employee_documents",
schema: "application",
columns: table => new
{
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.employee_documents_id_sequence')"),
document_type = table.Column<string>(type: "varchar(64)", nullable: false),
information = table.Column<string>(type: "varchar(256)", nullable: false),
employee_id = table.Column<long>(type: "bigint", nullable: false),
uuid = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_employee_documents", x => x.id);
table.UniqueConstraint("altk_employee_documents_uuid", x => x.uuid);
table.CheckConstraint("ck_employee_documents_document_type", "document_type IN ('passport')");
table.ForeignKey(
name: "fk_employee_documents_employee_id",
column: x => x.employee_id,
principalSchema: "application",
principalTable: "employees",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_employee_documents_employee_id",
schema: "application",
table: "employee_documents",
column: "employee_id");
migrationBuilder.CreateIndex(
name: "ix_employees_company_id",
schema: "application",
table: "employees",
column: "company_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "employee_documents",
schema: "application");
migrationBuilder.DropTable(
name: "employees",
schema: "application");
migrationBuilder.DropSequence(
name: "employee_documents_id_sequence",
schema: "application");
migrationBuilder.DropSequence(
name: "employees_id_sequence",
schema: "application");
}
}
}

View File

@ -31,6 +31,10 @@ namespace Persistence.PostgreSql.Migrations
modelBuilder.HasSequence("countries_id_sequence"); modelBuilder.HasSequence("countries_id_sequence");
modelBuilder.HasSequence("employee_documents_id_sequence");
modelBuilder.HasSequence("employees_id_sequence");
modelBuilder.HasSequence("regions_id_sequence"); modelBuilder.HasSequence("regions_id_sequence");
modelBuilder.HasSequence("route_address_details_id_sequence"); modelBuilder.HasSequence("route_address_details_id_sequence");
@ -198,6 +202,106 @@ namespace Persistence.PostgreSql.Migrations
b.ToTable("countries", "application"); b.ToTable("countries", "application");
}); });
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.employees_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "employees_id_sequence");
b.Property<DateOnly>("BirthDate")
.HasColumnType("date")
.HasColumnName("birth_date");
b.Property<long>("CompanyId")
.HasColumnType("bigint")
.HasColumnName("company_id");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("varchar(32)")
.HasColumnName("first_name");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("varchar(32)")
.HasColumnName("last_name");
b.Property<string>("Patronymic")
.IsRequired()
.HasColumnType("varchar(32)")
.HasColumnName("patronymic");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("varchar(32)")
.HasColumnName("sex");
b.HasKey("Id")
.HasName("pk_employees");
b.HasAlternateKey("Guid")
.HasName("altk_employees_uuid");
b.HasIndex("CompanyId")
.HasDatabaseName("ix_employees_company_id");
b.ToTable("employees", "application", t =>
{
t.HasCheckConstraint("ck_employees_sex", "sex IN ('male', 'female')");
});
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.EmployeeDocument", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasColumnName("id")
.HasDefaultValueSql("nextval('application.employee_documents_id_sequence')");
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "employee_documents_id_sequence");
b.Property<string>("DocumentType")
.IsRequired()
.HasColumnType("varchar(64)")
.HasColumnName("document_type");
b.Property<long>("EmployeeId")
.HasColumnType("bigint")
.HasColumnName("employee_id");
b.Property<Guid>("Guid")
.HasColumnType("uuid")
.HasColumnName("uuid");
b.Property<string>("Information")
.IsRequired()
.HasColumnType("varchar(256)")
.HasColumnName("information");
b.HasKey("Id")
.HasName("pk_employee_documents");
b.HasAlternateKey("Guid")
.HasName("altk_employee_documents_uuid");
b.HasIndex("EmployeeId")
.HasDatabaseName("ix_employee_documents_employee_id");
b.ToTable("employee_documents", "application", t =>
{
t.HasCheckConstraint("ck_employee_documents_document_type", "document_type IN ('passport')");
});
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b => modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
{ {
b.Property<long>("Id") b.Property<long>("Id")
@ -564,6 +668,30 @@ namespace Persistence.PostgreSql.Migrations
b.Navigation("Region"); b.Navigation("Region");
}); });
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Company", "Company")
.WithMany("Employees")
.HasForeignKey("CompanyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_employees_company_id");
b.Navigation("Company");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.EmployeeDocument", b =>
{
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Employee", "Employee")
.WithMany("Documents")
.HasForeignKey("EmployeeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_employee_documents_employee_id");
b.Navigation("Employee");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b => modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
{ {
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Country", "Country") b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Country", "Country")
@ -663,6 +791,8 @@ namespace Persistence.PostgreSql.Migrations
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Company", b => modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Company", b =>
{ {
b.Navigation("Employees");
b.Navigation("Vehicles"); b.Navigation("Vehicles");
}); });
@ -671,6 +801,11 @@ namespace Persistence.PostgreSql.Migrations
b.Navigation("Regions"); b.Navigation("Regions");
}); });
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Employee", b =>
{
b.Navigation("Documents");
});
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b => modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
{ {
b.Navigation("Cities"); b.Navigation("Cities");

View File

@ -44,6 +44,16 @@ public class PostgreSqlDbContext : DbContext
.HaveColumnType("varchar(8)") .HaveColumnType("varchar(8)")
.HaveConversion<CurrencyConverter>(); .HaveConversion<CurrencyConverter>();
builder
.Properties<DocumentType>()
.HaveColumnType("varchar(64)")
.HaveConversion<DocumentTypeConverter>();
builder
.Properties<Sex>()
.HaveColumnType("varchar(32)")
.HaveConversion<SexConverter>();
builder builder
.Properties<DateTimeOffset>() .Properties<DateTimeOffset>()
.HaveConversion<DateTimeOffsetConverter>(); .HaveConversion<DateTimeOffsetConverter>();

View File

@ -27,6 +27,7 @@ public sealed class PostgreSqlUnitOfWork : UnitOfWork
RouteAddressRepository = RouteAddressRepository =
new PostgreSqlRouteAddressRepository(_dbContext); new PostgreSqlRouteAddressRepository(_dbContext);
CompanyRepository = new PostgreSqlCompanyRepository(_dbContext); CompanyRepository = new PostgreSqlCompanyRepository(_dbContext);
EmployeeRepository = new PostgreSqlEmployeeRepository(_dbContext);
} }
public CountryRepository CountryRepository { get; init; } public CountryRepository CountryRepository { get; init; }
@ -53,6 +54,8 @@ public sealed class PostgreSqlUnitOfWork : UnitOfWork
public CompanyRepository CompanyRepository { get; init; } public CompanyRepository CompanyRepository { get; init; }
public EmployeeRepository EmployeeRepository { get; init; }
public int Save() public int Save()
{ {
return _dbContext.SaveChanges(); return _dbContext.SaveChanges();

View File

@ -0,0 +1,11 @@
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
using cuqmbr.TravelGuide.Domain.Entities;
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Repositories;
public sealed class PostgreSqlEmployeeRepository :
PostgreSqlBaseRepository<Employee>, EmployeeRepository
{
public PostgreSqlEmployeeRepository(PostgreSqlDbContext dbContext)
: base(dbContext) { }
}

View File

@ -0,0 +1,13 @@
using cuqmbr.TravelGuide.Domain.Enums;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace cuqmbr.TravelGuide.Persistence.TypeConverters;
public class DocumentTypeConverter : ValueConverter<DocumentType, string>
{
public DocumentTypeConverter()
: base(
v => v.Name,
v => DocumentType.FromName(v))
{ }
}

View File

@ -0,0 +1,13 @@
using cuqmbr.TravelGuide.Domain.Enums;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace cuqmbr.TravelGuide.Persistence.TypeConverters;
public class SexConverter : ValueConverter<Sex, string>
{
public SexConverter()
: base(
v => v.Name,
v => Sex.FromName(v))
{ }
}