Commit a51fbb31 authored by hasan khaddour's avatar hasan khaddour

implement track CQRS.

parent b4370697
...@@ -7,18 +7,12 @@ ...@@ -7,18 +7,12 @@
<ItemGroup> <ItemGroup>
<Folder Include="Behaviors\AuthorizationBehavior\" /> <Folder Include="Behaviors\AuthorizationBehavior\" />
<Folder Include="Employees\UseCases\Commands\InreesEmployeeWorkHours\" /> <Folder Include="Employees\UseCases\Commands\IncreasEmployeeWorkHours\" />
<Folder Include="Projects\UseCases\Queries\GetProjectAttachments\" /> <Folder Include="Projects\UseCases\Queries\GetProjectAttachments\" />
<Folder Include="Tracks\UseCaes\Commands\AddStepsTrack\" />
<Folder Include="Tracks\UseCaes\Commands\UpdateEmployeeTrack\" /> <Folder Include="Tracks\UseCaes\Commands\UpdateEmployeeTrack\" />
<Folder Include="Tracks\UseCaes\Commands\UpdateStepTrack\" /> <Folder Include="Tracks\UseCaes\Commands\UpdateStepTrack\" />
<Folder Include="Tracks\UseCaes\Commands\RemoveTrack\" />
<Folder Include="Tracks\UseCaes\Queries\GetTrackById\" />
<Folder Include="Tracks\UseCaes\Queries\GetTracksByProject\" />
<Folder Include="Tracks\UseCaes\Queries\GetPlanTrack\" />
<Folder Include="Tracks\UseCaes\Queries\GetEmployeesTrack\" /> <Folder Include="Tracks\UseCaes\Queries\GetEmployeesTrack\" />
<Folder Include="Tracks\UseCaes\Queries\GetEmployeeTrack\" /> <Folder Include="Employees\UseCases\Queries\GetEmployeeTrackHistory\" />
<Folder Include="Tracks\UseCaes\Queries\GetStepTracks\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
......
...@@ -9,6 +9,10 @@ namespace PSManagement.Application.Projects.UseCases.Queries.ListAllProject ...@@ -9,6 +9,10 @@ namespace PSManagement.Application.Projects.UseCases.Queries.ListAllProject
string ProjectName, string ProjectName,
string TeamLeaderName, string TeamLeaderName,
string DepartmentName, string DepartmentName,
int? ProjectManagerId,
int? TeamLeaderId,
int? PageNumber,
int? PageSize,
string ProposerName string ProposerName
) : IQuery<Result<IEnumerable<ProjectDTO>>>; ) : IQuery<Result<IEnumerable<ProjectDTO>>>;
......
...@@ -35,6 +35,11 @@ namespace PSManagement.Application.Projects.UseCases.Queries.ListAllProject ...@@ -35,6 +35,11 @@ namespace PSManagement.Application.Projects.UseCases.Queries.ListAllProject
_specification.AddInclude(e => e.Executer); _specification.AddInclude(e => e.Executer);
_specification.AddInclude(e => e.Proposer); _specification.AddInclude(e => e.Proposer);
int pageNumber = request.PageNumber.HasValue && request.PageNumber.Value > 0 ? request.PageNumber.Value : 1;
int pageSize = request.PageSize.HasValue && request.PageSize.Value > 0 && request.PageSize.Value <= 30 ? request.PageSize.Value : 30;
_specification.ApplyPaging((pageNumber - 1) * pageSize, pageSize);
IEnumerable<Project> projects = await _projectsRepository.ListAsync(_specification); IEnumerable<Project> projects = await _projectsRepository.ListAsync(_specification);
if (!string.IsNullOrEmpty(request.DepartmentName)) if (!string.IsNullOrEmpty(request.DepartmentName))
...@@ -56,8 +61,13 @@ namespace PSManagement.Application.Projects.UseCases.Queries.ListAllProject ...@@ -56,8 +61,13 @@ namespace PSManagement.Application.Projects.UseCases.Queries.ListAllProject
{ {
projects = projects.Where(e => e.ProjectInfo.Name.ToLower().Contains(request.ProjectName.Trim().ToLower())); projects = projects.Where(e => e.ProjectInfo.Name.ToLower().Contains(request.ProjectName.Trim().ToLower()));
} }
if (request.ProjectManagerId.HasValue) {
projects = projects.Where(p => p.ProjectManagerId == request.ProjectManagerId);
}
if (request.TeamLeaderId.HasValue)
{
projects = projects.Where(p => p.TeamLeaderId == request.TeamLeaderId);
}
return Result.Success(_mapper.Map<IEnumerable<ProjectDTO>>(projects)); return Result.Success(_mapper.Map<IEnumerable<ProjectDTO>>(projects));
} }
} }
......
...@@ -8,5 +8,6 @@ namespace PSManagement.Application.Steps.UseCases.Queries.GetStepTrackHistory ...@@ -8,5 +8,6 @@ namespace PSManagement.Application.Steps.UseCases.Queries.GetStepTrackHistory
int StepId, int StepId,
int? PageNumber, int? PageNumber,
int ? PageSize int ? PageSize
) : IQuery<Result<IEnumerable<StepTrackDTO>>>; ) : IQuery<Result<IEnumerable<StepTrackDTO>>>;
} }
namespace PSManagement.Application.Tracks.Common using PSManagement.Domain.Tracking.ValueObjects;
namespace PSManagement.Application.Tracks.Common
{ {
public class EmployeeTrackDTO public class EmployeeTrackDTO
{ {
public int EmloyeeId { get; set; }
public int TrackId { get; set; }
public TrackInfo TrackInfo { get; set; }
public EmployeeWorkInfo EmployeeWorkInfo { get; set; }
public EmployeeWork EmployeeWork { get; set; }
public string Notes { get; set; }
} }
} }
\ No newline at end of file
using PSManagement.Domain.Projects.Entities; using PSManagement.Domain.Projects.Entities;
using PSManagement.Domain.Projects.ValueObjects; using PSManagement.Domain.Projects.ValueObjects;
using PSManagement.Domain.Tracking.ValueObjects;
using System; using System;
namespace PSManagement.Application.Tracks.Common namespace PSManagement.Application.Tracks.Common
...@@ -8,9 +9,12 @@ namespace PSManagement.Application.Tracks.Common ...@@ -8,9 +9,12 @@ namespace PSManagement.Application.Tracks.Common
{ {
public int StepId { get; set; } public int StepId { get; set; }
public int TrackId { get; set; } public int TrackId { get; set; }
public StepInfo StepInfo { get; set; }
public TrackInfo TrackInfo { get; set; }
public String ExecutionState { get; set; } public String ExecutionState { get; set; }
public DateTime TrackDate { get; set; } public DateTime TrackDate { get; set; }
public int ExecutionRatio { get; set; } public int ExecutionRatio { get; set; }
} }
......
using PSManagement.Application.Employees.Common; using PSManagement.Application.Employees.Common;
using PSManagement.Domain.Tracking.ValueObjects;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
...@@ -11,8 +12,8 @@ namespace PSManagement.Application.Tracks.Common ...@@ -11,8 +12,8 @@ namespace PSManagement.Application.Tracks.Common
{ {
public int Id { get; set; } public int Id { get; set; }
public bool IsCompleted { get; set; } public bool IsCompleted { get; set; }
public DateTime TrackDate { get; set; } public TrackInfo TrackInfo { get; set; }
public String TrackNote { get; set; } public String Notes { get; set; }
public int ProjectId { get; set; } public int ProjectId { get; set; }
} }
......
using Ardalis.Result; using Ardalis.Result;
using PSManagement.Domain.Tracking.Entities;
using PSManagement.Domain.Tracking.ValueObjects; using PSManagement.Domain.Tracking.ValueObjects;
using PSManagement.SharedKernel.CQRS.Command; using PSManagement.SharedKernel.CQRS.Command;
using System; using System;
namespace PSManagement.Application.Tracks.UseCaes.Commands.AddEmployeesTrack namespace PSManagement.Application.Tracks.UseCaes.Commands.AddEmployeeTrack
{ {
public record AddEmployeeTrackCommand( public record AddEmployeeTrackCommand(
int TrackId, int TrackId,
int EmployeeId, int EmployeeId,
EmployeeWorkInfo EmployeeWorkInfo , EmployeeWorkInfo EmployeeWorkInfo,
EmployeeWork EmployeeWork, EmployeeWork EmployeeWork,
String Notes, string Notes,
int ProjectId int ProjectId
) : ILoggableCommand<Result>; ) : ILoggableCommand<Result<int>>;
} }
using Ardalis.Result;
using AutoMapper;
using PSManagement.Domain.Steps.Repositories;
using PSManagement.Domain.Tracking;
using PSManagement.Domain.Tracking.DomainErrors;
using PSManagement.SharedKernel.CQRS.Command;
using PSManagement.SharedKernel.Interfaces;
using PSManagement.SharedKernel.Repositories;
using System.Threading;
using System.Threading.Tasks;
namespace PSManagement.Application.Tracks.UseCaes.Commands.AddEmployeeTrack
{
public class AddEmployeeTrackCommandHandler : ICommandHandler<AddEmployeeTrackCommand, Result<int>>
{
private readonly IRepository<EmployeeTrack> _employeeTracksRepository;
private readonly ITracksRepository _tracksRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public AddEmployeeTrackCommandHandler(
IUnitOfWork unitOfWork,
ITracksRepository tracksRepository,
IRepository<EmployeeTrack> employeeTracksRepository,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_tracksRepository = tracksRepository;
_employeeTracksRepository = employeeTracksRepository;
_mapper = mapper;
}
public async Task<Result<int>> Handle(AddEmployeeTrackCommand request, CancellationToken cancellationToken)
{
Track track = await _tracksRepository.GetByIdAsync(request.TrackId);
if (track is null)
{
return Result.Invalid(TracksErrors.InvalidEntryError);
}
EmployeeTrack employeeTrack = await _employeeTracksRepository.AddAsync(_mapper.Map<EmployeeTrack>(request));
await _unitOfWork.SaveChangesAsync();
return Result.Success(employeeTrack.Id);
}
}
}
using Ardalis.Result;
using PSManagement.SharedKernel.CQRS.Command;
using System;
namespace PSManagement.Application.Tracks.UseCaes.Commands.AddStepTrack
{
public record AddStepTrackCommand(
int StepId,
int TrackId,
string ExecutionState,
DateTime TrackDate,
int ExecutionRatio
) : ICommand<Result<int>>;
}
\ No newline at end of file
using Ardalis.Result;
using AutoMapper;
using PSManagement.Domain.Steps.Repositories;
using PSManagement.Domain.Tracking;
using PSManagement.Domain.Tracking.DomainErrors;
using PSManagement.Domain.Tracking.Entities;
using PSManagement.SharedKernel.CQRS.Command;
using PSManagement.SharedKernel.Interfaces;
using PSManagement.SharedKernel.Repositories;
using System.Threading;
using System.Threading.Tasks;
namespace PSManagement.Application.Tracks.UseCaes.Commands.AddStepTrack
{
public class AddStepTrackCommandHandler : ICommandHandler<AddStepTrackCommand, Result<int>>
{
private readonly IRepository<StepTrack> _stepTracksRepository;
private readonly ITracksRepository _tracksRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public AddStepTrackCommandHandler(
IUnitOfWork unitOfWork,
ITracksRepository tracksRepository,
IRepository<StepTrack> stepTracksRepository,
IMapper mapper)
{
_unitOfWork = unitOfWork;
_tracksRepository = tracksRepository;
_stepTracksRepository = stepTracksRepository;
_mapper = mapper;
}
public async Task<Result<int>> Handle(AddStepTrackCommand request, CancellationToken cancellationToken)
{
Track track = await _tracksRepository.GetByIdAsync(request.TrackId);
if (track is null)
{
return Result.Invalid(TracksErrors.InvalidEntryError);
}
StepTrack stepTrack = await _stepTracksRepository.AddAsync(_mapper.Map<StepTrack>(request));
await _unitOfWork.SaveChangesAsync();
return Result.Success(stepTrack.Id);
}
}
}
\ No newline at end of file
...@@ -6,7 +6,7 @@ namespace PSManagement.Application.Tracks.UseCaes.Commands.CompleteTrack ...@@ -6,7 +6,7 @@ namespace PSManagement.Application.Tracks.UseCaes.Commands.CompleteTrack
{ {
public record CompleteTrackCommand( public record CompleteTrackCommand(
int TrackId, int TrackId,
DateTime TrackDate, DateTime CompletionDate,
int ProjectId int ProjectId
) : ILoggableCommand<Result>; ) : ILoggableCommand<Result>;
} }
...@@ -34,12 +34,11 @@ namespace PSManagement.Application.Tracks.UseCaes.Commands.CompleteTrack ...@@ -34,12 +34,11 @@ namespace PSManagement.Application.Tracks.UseCaes.Commands.CompleteTrack
if (track is null) if (track is null)
{ {
return Result.Invalid(TracksErrror.InvalidEntryError); return Result.Invalid(TracksErrors.InvalidEntryError);
} }
track.AddDomainEvent(new TrackCompleteddEvent(request.ProjectId, request.TrackId, request.TrackDate)); track.Complete(request.CompletionDate);
await _unitOfWork.SaveChangesAsync(); await _unitOfWork.SaveChangesAsync();
return Result.Success(); return Result.Success();
......
...@@ -8,7 +8,7 @@ namespace PSManagement.Application.Tracks.UseCaes.Commands.CreateTrack ...@@ -8,7 +8,7 @@ namespace PSManagement.Application.Tracks.UseCaes.Commands.CreateTrack
{ {
public record CreateTrackCommand( public record CreateTrackCommand(
TrackInfo TrackInfo, TrackInfo TrackInfo,
String TrackNote , String Notes ,
int ProjectId int ProjectId
) : ILoggableCommand<Result<int>>; ) : ILoggableCommand<Result<int>>;
} }
using Ardalis.Result;
using PSManagement.SharedKernel.CQRS.Command;
namespace PSManagement.Application.Tracks.UseCaes.Commands.RemoveTrack
{
public record RemoveTrackCommand(
int TrackId
) : ILoggableCommand<Result>;
}
\ No newline at end of file
using Ardalis.Result;
using PSManagement.Domain.Steps.Repositories;
using PSManagement.Domain.Tracking;
using PSManagement.Domain.Tracking.DomainErrors;
using PSManagement.SharedKernel.CQRS.Command;
using PSManagement.SharedKernel.Interfaces;
using System.Threading;
using System.Threading.Tasks;
namespace PSManagement.Application.Tracks.UseCaes.Commands.RemoveTrack
{
public class RemoveTrackCommandHandler : ICommandHandler<RemoveTrackCommand, Result>
{
private readonly ITracksRepository _tracksRepository;
private readonly IUnitOfWork _unitOfWork;
public RemoveTrackCommandHandler(
IUnitOfWork unitOfWork,
ITracksRepository tracksRepository
)
{
_unitOfWork = unitOfWork;
_tracksRepository = tracksRepository;
}
public async Task<Result> Handle(RemoveTrackCommand request, CancellationToken cancellationToken)
{
Track track = await _tracksRepository.GetByIdAsync(request.TrackId);
if (track is null)
{
return Result.Invalid(TracksErrors.InvalidEntryError);
}
await _tracksRepository.DeleteAsync(track);
await _unitOfWork.SaveChangesAsync();
return Result.Success();
}
}
}
\ No newline at end of file
using Ardalis.Result;
using PSManagement.Application.Tracks.Common;
using PSManagement.SharedKernel.CQRS.Query;
using System.Collections.Generic;
namespace PSManagement.Application.Tracks.UseCaes.Queries.GetStepsTrack
{
public record GetStepsTrackQuery(
int TrackId
) : IQuery<Result<IEnumerable<StepTrackDTO>>>;
}
using Ardalis.Result;
using AutoMapper;
using PSManagement.Application.Tracks.Common;
using PSManagement.Domain.Steps.Repositories;
using PSManagement.Domain.Tracking.DomainErrors;
using PSManagement.Domain.Tracking.Entities;
using PSManagement.Domain.Tracking.Specification;
using PSManagement.SharedKernel.CQRS.Query;
using PSManagement.SharedKernel.Repositories;
using PSManagement.SharedKernel.Specification;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace PSManagement.Application.Tracks.UseCaes.Queries.GetStepsTrack
{
public class GetStepsTrackQueryHandler : IQueryHandler<GetStepsTrackQuery, Result<IEnumerable<StepTrackDTO>>>
{
private readonly ITracksRepository _tracksRepository;
private readonly IRepository<StepTrack> _stepTracksRepository;
private readonly IMapper _mapper;
private readonly BaseSpecification<StepTrack> _specification;
public GetStepsTrackQueryHandler(
IMapper mapper,
ITracksRepository tracksRepository,
IRepository<StepTrack> stepTracksRepository
)
{
_mapper = mapper;
_tracksRepository = tracksRepository;
_specification = new StepTrackSpecification();
_stepTracksRepository = stepTracksRepository;
}
public async Task<Result<IEnumerable<StepTrackDTO>>> Handle(GetStepsTrackQuery request, CancellationToken cancellationToken)
{
_specification.Criteria = t => t.TrackId == request.TrackId;
_specification.AddInclude(e => e.Track);
_specification.AddInclude(e => e.Step);
var track = await _tracksRepository.GetByIdAsync(request.TrackId);
if (track is null)
{
return Result.Invalid(TracksErrors.InvalidEntryError);
}
else
{
var stepTracks = await _stepTracksRepository.ListAsync(_specification);
return Result.Success(_mapper.Map<IEnumerable<StepTrackDTO>>(stepTracks));
}
}
}
}
using Ardalis.Result;
using PSManagement.Application.Tracks.Common;
using PSManagement.SharedKernel.CQRS.Query;
namespace PSManagement.Application.Tracks.UseCaes.Queries.GetTrackById
{
public record GetTrackByIdQuery(
int TrackId
) : IQuery<Result<TrackDTO>>;
}
using Ardalis.Result;
using AutoMapper;
using PSManagement.Application.Tracks.Common;
using PSManagement.Domain.Projects.Repositories;
using PSManagement.Domain.Steps.Repositories;
using PSManagement.Domain.Tracking;
using PSManagement.Domain.Tracking.DomainErrors;
using PSManagement.Domain.Tracking.Specification;
using PSManagement.SharedKernel.CQRS.Query;
using PSManagement.SharedKernel.Specification;
using System.Threading;
using System.Threading.Tasks;
namespace PSManagement.Application.Tracks.UseCaes.Queries.GetTrackById
{
public class GetTrackByIdQueryHandler : IQueryHandler<GetTrackByIdQuery, Result<TrackDTO>>
{
private readonly IProjectsRepository _projectsRepository;
private readonly ITracksRepository _tracksRepository;
private readonly IMapper _mapper;
private readonly BaseSpecification<Track> _specification;
public GetTrackByIdQueryHandler(
IMapper mapper,
IProjectsRepository projectsRepository,
ITracksRepository tracksRepository)
{
_mapper = mapper;
_projectsRepository = projectsRepository;
_specification = new TrackSpecification();
_tracksRepository = tracksRepository;
}
public async Task<Result<TrackDTO>> Handle(GetTrackByIdQuery request, CancellationToken cancellationToken)
{
_specification.AddInclude(e => e.Project);
Track track = await _tracksRepository.GetByIdAsync(request.TrackId,_specification);
if (track is null) {
return Result.Invalid(TracksErrors.InvalidEntryError);
}
return Result.Success(_mapper.Map<TrackDTO>(track));
}
}
}
using Ardalis.Result;
using PSManagement.Application.Tracks.Common;
using PSManagement.SharedKernel.CQRS.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PSManagement.Application.Tracks.UseCaes.Queries.GetTracksByProject
{
public record GetTracksByProjectQuery(
int ProjectId,
int? PageNumber,
int? PageSize
) : IQuery<Result<IEnumerable<TrackDTO>>>;
}
using Ardalis.Result;
using AutoMapper;
using PSManagement.Application.Tracks.Common;
using PSManagement.Domain.Projects.DomainErrors;
using PSManagement.Domain.Projects.Repositories;
using PSManagement.Domain.Steps.Repositories;
using PSManagement.Domain.Tracking;
using PSManagement.Domain.Tracking.Specification;
using PSManagement.SharedKernel.CQRS.Query;
using PSManagement.SharedKernel.Specification;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace PSManagement.Application.Tracks.UseCaes.Queries.GetTracksByProject
{
public class GetTracksByProjectQueryHandler : IQueryHandler<GetTracksByProjectQuery, Result<IEnumerable<TrackDTO>>>
{
private readonly IProjectsRepository _projectsRepository;
private readonly ITracksRepository _tracksRepository;
private readonly IMapper _mapper;
private readonly BaseSpecification<Track> _specification;
public GetTracksByProjectQueryHandler(
IMapper mapper,
IProjectsRepository projectsRepository,
ITracksRepository tracksRepository)
{
_mapper = mapper;
_projectsRepository = projectsRepository;
_specification = new TrackSpecification();
_tracksRepository = tracksRepository;
}
public async Task<Result<IEnumerable<TrackDTO>>> Handle(GetTracksByProjectQuery request, CancellationToken cancellationToken)
{
int pageNumber = request.PageNumber.HasValue && request.PageNumber.Value > 0 ? request.PageNumber.Value : 1;
int pageSize = request.PageSize.HasValue && request.PageSize.Value > 0 && request.PageSize.Value <= 30 ? request.PageSize.Value : 30;
_specification.ApplyPaging((pageNumber - 1) * pageSize, pageSize);
_specification.Criteria = t => t.ProjectId == request.ProjectId;
var project = await _projectsRepository.GetByIdAsync(request.ProjectId);
if (project is null)
{
return Result.Invalid(ProjectsErrors.InvalidEntryError);
}else {
var tracks = await _tracksRepository.ListAsync(_specification);
return Result.Success(_mapper.Map<IEnumerable<TrackDTO>>(tracks));
}
}
}
}
...@@ -7,7 +7,7 @@ using System.Threading.Tasks; ...@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace PSManagement.Domain.Tracking.DomainErrors namespace PSManagement.Domain.Tracking.DomainErrors
{ {
public class TracksErrror public class TracksErrors
{ {
public static DomainError InvalidEntryError { get; } = new("TrackError.InvalidEntry.", "Invalid Project Data"); public static DomainError InvalidEntryError { get; } = new("TrackError.InvalidEntry.", "Invalid Project Data");
......
using PSManagement.Domain.Employees.Entities; using PSManagement.Domain.Employees.Entities;
using PSManagement.Domain.Projects.Entities; using PSManagement.Domain.Projects.Entities;
using PSManagement.Domain.Tracking.DomainEvents;
using PSManagement.Domain.Tracking.Entities; using PSManagement.Domain.Tracking.Entities;
using PSManagement.Domain.Tracking.ValueObjects; using PSManagement.Domain.Tracking.ValueObjects;
using PSManagement.SharedKernel.Aggregate; using PSManagement.SharedKernel.Aggregate;
...@@ -25,5 +26,13 @@ namespace PSManagement.Domain.Tracking ...@@ -25,5 +26,13 @@ namespace PSManagement.Domain.Tracking
{ {
} }
public void Complete(DateTime completionDate)
{
TrackInfo = new (TrackInfo.TrackDate,true,TrackInfo.StatusDescription);
AddDomainEvent(new TrackCompleteddEvent(ProjectId, Id, completionDate));
}
} }
} }
using PSManagement.Domain.Tracking.Entities;
using PSManagement.SharedKernel.Specification;
using System;
using System.Linq.Expressions;
namespace PSManagement.Domain.Tracking.Specification
{
public class StepTrackSpecification : BaseSpecification<StepTrack>
{
public StepTrackSpecification(Expression<Func<StepTrack, bool>> criteria = null) : base(criteria)
{
}
}
}
using PSManagement.Domain.Tracking;
using PSManagement.SharedKernel.Specification;
using System;
using System.Linq.Expressions;
namespace PSManagement.Domain.Tracking.Specification
{
public class TrackSpecification : BaseSpecification<Track>
{
public TrackSpecification(Expression<Func<Track, bool>> criteria = null) : base(criteria)
{
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment