[CLAUDE] Phase1.2: CRUD Master + Permission Matrix + FE admin pages
Backend:
- Domain/Master: Supplier (+ SupplierType 5 loai), Project, Department (AuditableEntity)
- Domain/Identity: MenuItem, Permission, MenuKeys const (12 menu)
- EF Configurations voi unique Code + query filter IsDeleted
- DbSets + IApplicationDbContext interface update
- Application: PagedResult + PagedRequest generic
- Application/Master CQRS CRUD 3 entity (Create/Update/Delete/Get/List voi paging search sort)
- Application/Permissions: GetMyMenuTree (union OR role, filter tree), ListMenuItems, ListPermissionsByRole, UpsertPermission (guard admin khong tu giam quyen), ListRoles
- Api/Authorization: MenuPermissionRequirement + Handler (Admin bypass, query DB)
- Program.cs: register 48 policy {menu}.{action} tu MenuKeys x Actions
- Api/Controllers: Suppliers, Projects, Departments, Menus, Roles, Permissions
- DbInitializer: seed 12 menu + admin full CRUD permissions
- Migration AddMasterData + AddPermissions
Frontend (fe-admin):
- Types: menuKeys.ts const, menu.ts (MenuNode/Role/Permission), master.ts (Supplier/Project/Department + SupplierType const-object)
- AuthContext: load menu from /menus/me, cache localStorage, refreshMenu()
- usePermission hook + PermissionGuard component (wrap button)
- UI kit them: Dialog (modal overlay), Textarea, Select
- Generic: DataTable (column config, sortable, loading, empty) + Pagination
- PageHeader component
- apiError helper extract message tu ProblemDetails
- Layout rewrite: render menu dong tu AuthContext.menu (MenuGroup collapsible + NavLink + lucide icon map)
- Pages: master/Suppliers, master/Projects, master/Departments (CRUD + search + sort + paging + Dialog form)
- Page system/Permissions: ma tran Role x MenuKey x CRUD checkbox (tick tu dong PUT upsert)
- App.tsx them 4 route moi
Bug fix:
- MenuPermissionHandler: EF expression tree khong support switch expression -> tach switch ra ngoai AnyAsync
- TS erasableSyntaxOnly khong cho enum -> SupplierType const-object pattern (typeof[keyof])
E2E verified via Vite proxy:
- GET /menus/me -> 6 root + 6 child nodes (12 menus)
- GET /roles -> 12 roles
- POST/GET/PUT/DELETE /suppliers -> full CRUD, soft delete OK
- tsc -b fe-admin pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,128 @@
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Exceptions;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Application.Common.Models;
|
||||
using SolutionErp.Domain.Master;
|
||||
|
||||
namespace SolutionErp.Application.Master.Departments;
|
||||
|
||||
public record DepartmentDto(
|
||||
Guid Id,
|
||||
string Code,
|
||||
string Name,
|
||||
Guid? ManagerUserId,
|
||||
string? Note,
|
||||
DateTime CreatedAt,
|
||||
DateTime? UpdatedAt);
|
||||
|
||||
public record ListDepartmentsQuery : PagedRequest, IRequest<PagedResult<DepartmentDto>>;
|
||||
|
||||
public class ListDepartmentsQueryHandler(IApplicationDbContext db) : IRequestHandler<ListDepartmentsQuery, PagedResult<DepartmentDto>>
|
||||
{
|
||||
public async Task<PagedResult<DepartmentDto>> Handle(ListDepartmentsQuery request, CancellationToken ct)
|
||||
{
|
||||
var query = db.Departments.AsNoTracking();
|
||||
if (!string.IsNullOrWhiteSpace(request.Search))
|
||||
{
|
||||
var s = request.Search.Trim();
|
||||
query = query.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
|
||||
}
|
||||
query = (request.SortBy, request.SortDesc) switch
|
||||
{
|
||||
("code", true) => query.OrderByDescending(x => x.Code),
|
||||
("code", false) => query.OrderBy(x => x.Code),
|
||||
("name", true) => query.OrderByDescending(x => x.Name),
|
||||
("name", false) => query.OrderBy(x => x.Name),
|
||||
_ => query.OrderByDescending(x => x.CreatedAt),
|
||||
};
|
||||
var total = await query.CountAsync(ct);
|
||||
var items = await query
|
||||
.Skip((request.Page - 1) * request.PageSize).Take(request.PageSize)
|
||||
.Select(x => new DepartmentDto(x.Id, x.Code, x.Name, x.ManagerUserId, x.Note, x.CreatedAt, x.UpdatedAt))
|
||||
.ToListAsync(ct);
|
||||
return new PagedResult<DepartmentDto>(items, total, request.Page, request.PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
public record GetDepartmentQuery(Guid Id) : IRequest<DepartmentDto>;
|
||||
|
||||
public class GetDepartmentQueryHandler(IApplicationDbContext db) : IRequestHandler<GetDepartmentQuery, DepartmentDto>
|
||||
{
|
||||
public async Task<DepartmentDto> Handle(GetDepartmentQuery request, CancellationToken ct)
|
||||
{
|
||||
var x = await db.Departments.AsNoTracking().FirstOrDefaultAsync(p => p.Id == request.Id, ct)
|
||||
?? throw new NotFoundException("Department", request.Id);
|
||||
return new DepartmentDto(x.Id, x.Code, x.Name, x.ManagerUserId, x.Note, x.CreatedAt, x.UpdatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
public record CreateDepartmentCommand(string Code, string Name, Guid? ManagerUserId, string? Note) : IRequest<Guid>;
|
||||
|
||||
public class CreateDepartmentCommandValidator : AbstractValidator<CreateDepartmentCommand>
|
||||
{
|
||||
public CreateDepartmentCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateDepartmentCommandHandler(IApplicationDbContext db) : IRequestHandler<CreateDepartmentCommand, Guid>
|
||||
{
|
||||
public async Task<Guid> Handle(CreateDepartmentCommand request, CancellationToken ct)
|
||||
{
|
||||
if (await db.Departments.AnyAsync(x => x.Code == request.Code, ct))
|
||||
throw new ConflictException($"Mã phòng ban '{request.Code}' đã tồn tại.");
|
||||
var entity = new Department
|
||||
{
|
||||
Code = request.Code, Name = request.Name,
|
||||
ManagerUserId = request.ManagerUserId, Note = request.Note,
|
||||
};
|
||||
db.Departments.Add(entity);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return entity.Id;
|
||||
}
|
||||
}
|
||||
|
||||
public record UpdateDepartmentCommand(Guid Id, string Code, string Name, Guid? ManagerUserId, string? Note) : IRequest;
|
||||
|
||||
public class UpdateDepartmentCommandValidator : AbstractValidator<UpdateDepartmentCommand>
|
||||
{
|
||||
public UpdateDepartmentCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Id).NotEmpty();
|
||||
RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateDepartmentCommandHandler(IApplicationDbContext db) : IRequestHandler<UpdateDepartmentCommand>
|
||||
{
|
||||
public async Task Handle(UpdateDepartmentCommand request, CancellationToken ct)
|
||||
{
|
||||
var entity = await db.Departments.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
|
||||
?? throw new NotFoundException("Department", request.Id);
|
||||
if (entity.Code != request.Code && await db.Departments.AnyAsync(x => x.Code == request.Code && x.Id != request.Id, ct))
|
||||
throw new ConflictException($"Mã phòng ban '{request.Code}' đã tồn tại.");
|
||||
entity.Code = request.Code;
|
||||
entity.Name = request.Name;
|
||||
entity.ManagerUserId = request.ManagerUserId;
|
||||
entity.Note = request.Note;
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
public record DeleteDepartmentCommand(Guid Id) : IRequest;
|
||||
|
||||
public class DeleteDepartmentCommandHandler(IApplicationDbContext db) : IRequestHandler<DeleteDepartmentCommand>
|
||||
{
|
||||
public async Task Handle(DeleteDepartmentCommand request, CancellationToken ct)
|
||||
{
|
||||
var entity = await db.Departments.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
|
||||
?? throw new NotFoundException("Department", request.Id);
|
||||
db.Departments.Remove(entity);
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Exceptions;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Application.Common.Models;
|
||||
using SolutionErp.Domain.Master;
|
||||
|
||||
namespace SolutionErp.Application.Master.Projects;
|
||||
|
||||
public record ProjectDto(
|
||||
Guid Id,
|
||||
string Code,
|
||||
string Name,
|
||||
DateTime? StartDate,
|
||||
DateTime? EndDate,
|
||||
Guid? ManagerUserId,
|
||||
decimal? BudgetTotal,
|
||||
string? Note,
|
||||
DateTime CreatedAt,
|
||||
DateTime? UpdatedAt);
|
||||
|
||||
// ===================== LIST =====================
|
||||
public record ListProjectsQuery : PagedRequest, IRequest<PagedResult<ProjectDto>>;
|
||||
|
||||
public class ListProjectsQueryHandler(IApplicationDbContext db) : IRequestHandler<ListProjectsQuery, PagedResult<ProjectDto>>
|
||||
{
|
||||
public async Task<PagedResult<ProjectDto>> Handle(ListProjectsQuery request, CancellationToken ct)
|
||||
{
|
||||
var query = db.Projects.AsNoTracking();
|
||||
if (!string.IsNullOrWhiteSpace(request.Search))
|
||||
{
|
||||
var s = request.Search.Trim();
|
||||
query = query.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
|
||||
}
|
||||
query = (request.SortBy, request.SortDesc) switch
|
||||
{
|
||||
("code", true) => query.OrderByDescending(x => x.Code),
|
||||
("code", false) => query.OrderBy(x => x.Code),
|
||||
("name", true) => query.OrderByDescending(x => x.Name),
|
||||
("name", false) => query.OrderBy(x => x.Name),
|
||||
_ => query.OrderByDescending(x => x.CreatedAt),
|
||||
};
|
||||
var total = await query.CountAsync(ct);
|
||||
var items = await query
|
||||
.Skip((request.Page - 1) * request.PageSize).Take(request.PageSize)
|
||||
.Select(x => new ProjectDto(x.Id, x.Code, x.Name, x.StartDate, x.EndDate, x.ManagerUserId, x.BudgetTotal, x.Note, x.CreatedAt, x.UpdatedAt))
|
||||
.ToListAsync(ct);
|
||||
return new PagedResult<ProjectDto>(items, total, request.Page, request.PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== GET =====================
|
||||
public record GetProjectQuery(Guid Id) : IRequest<ProjectDto>;
|
||||
|
||||
public class GetProjectQueryHandler(IApplicationDbContext db) : IRequestHandler<GetProjectQuery, ProjectDto>
|
||||
{
|
||||
public async Task<ProjectDto> Handle(GetProjectQuery request, CancellationToken ct)
|
||||
{
|
||||
var x = await db.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == request.Id, ct)
|
||||
?? throw new NotFoundException("Project", request.Id);
|
||||
return new ProjectDto(x.Id, x.Code, x.Name, x.StartDate, x.EndDate, x.ManagerUserId, x.BudgetTotal, x.Note, x.CreatedAt, x.UpdatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== CREATE =====================
|
||||
public record CreateProjectCommand(
|
||||
string Code, string Name, DateTime? StartDate, DateTime? EndDate,
|
||||
Guid? ManagerUserId, decimal? BudgetTotal, string? Note) : IRequest<Guid>;
|
||||
|
||||
public class CreateProjectCommandValidator : AbstractValidator<CreateProjectCommand>
|
||||
{
|
||||
public CreateProjectCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||
RuleFor(x => x.BudgetTotal).GreaterThanOrEqualTo(0).When(x => x.BudgetTotal.HasValue);
|
||||
RuleFor(x => x).Must(x => !x.StartDate.HasValue || !x.EndDate.HasValue || x.EndDate >= x.StartDate)
|
||||
.WithMessage("Ngày kết thúc phải sau ngày bắt đầu");
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateProjectCommandHandler(IApplicationDbContext db) : IRequestHandler<CreateProjectCommand, Guid>
|
||||
{
|
||||
public async Task<Guid> Handle(CreateProjectCommand request, CancellationToken ct)
|
||||
{
|
||||
if (await db.Projects.AnyAsync(x => x.Code == request.Code, ct))
|
||||
throw new ConflictException($"Mã dự án '{request.Code}' đã tồn tại.");
|
||||
var entity = new Project
|
||||
{
|
||||
Code = request.Code, Name = request.Name,
|
||||
StartDate = request.StartDate, EndDate = request.EndDate,
|
||||
ManagerUserId = request.ManagerUserId, BudgetTotal = request.BudgetTotal, Note = request.Note,
|
||||
};
|
||||
db.Projects.Add(entity);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return entity.Id;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== UPDATE =====================
|
||||
public record UpdateProjectCommand(
|
||||
Guid Id, string Code, string Name, DateTime? StartDate, DateTime? EndDate,
|
||||
Guid? ManagerUserId, decimal? BudgetTotal, string? Note) : IRequest;
|
||||
|
||||
public class UpdateProjectCommandValidator : AbstractValidator<UpdateProjectCommand>
|
||||
{
|
||||
public UpdateProjectCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Id).NotEmpty();
|
||||
RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||
RuleFor(x => x.BudgetTotal).GreaterThanOrEqualTo(0).When(x => x.BudgetTotal.HasValue);
|
||||
RuleFor(x => x).Must(x => !x.StartDate.HasValue || !x.EndDate.HasValue || x.EndDate >= x.StartDate)
|
||||
.WithMessage("Ngày kết thúc phải sau ngày bắt đầu");
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateProjectCommandHandler(IApplicationDbContext db) : IRequestHandler<UpdateProjectCommand>
|
||||
{
|
||||
public async Task Handle(UpdateProjectCommand request, CancellationToken ct)
|
||||
{
|
||||
var entity = await db.Projects.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
|
||||
?? throw new NotFoundException("Project", request.Id);
|
||||
if (entity.Code != request.Code && await db.Projects.AnyAsync(x => x.Code == request.Code && x.Id != request.Id, ct))
|
||||
throw new ConflictException($"Mã dự án '{request.Code}' đã tồn tại.");
|
||||
entity.Code = request.Code;
|
||||
entity.Name = request.Name;
|
||||
entity.StartDate = request.StartDate;
|
||||
entity.EndDate = request.EndDate;
|
||||
entity.ManagerUserId = request.ManagerUserId;
|
||||
entity.BudgetTotal = request.BudgetTotal;
|
||||
entity.Note = request.Note;
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== DELETE =====================
|
||||
public record DeleteProjectCommand(Guid Id) : IRequest;
|
||||
|
||||
public class DeleteProjectCommandHandler(IApplicationDbContext db) : IRequestHandler<DeleteProjectCommand>
|
||||
{
|
||||
public async Task Handle(DeleteProjectCommand request, CancellationToken ct)
|
||||
{
|
||||
var entity = await db.Projects.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
|
||||
?? throw new NotFoundException("Project", request.Id);
|
||||
db.Projects.Remove(entity);
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Exceptions;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Domain.Master;
|
||||
|
||||
namespace SolutionErp.Application.Master.Suppliers.Commands.CreateSupplier;
|
||||
|
||||
public record CreateSupplierCommand(
|
||||
string Code,
|
||||
string Name,
|
||||
SupplierType Type,
|
||||
string? TaxCode,
|
||||
string? Phone,
|
||||
string? Email,
|
||||
string? Address,
|
||||
string? ContactPerson,
|
||||
string? Note) : IRequest<Guid>;
|
||||
|
||||
public class CreateSupplierCommandValidator : AbstractValidator<CreateSupplierCommand>
|
||||
{
|
||||
public CreateSupplierCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||
RuleFor(x => x.Type).IsInEnum();
|
||||
RuleFor(x => x.TaxCode).MaximumLength(20);
|
||||
RuleFor(x => x.Phone).MaximumLength(30);
|
||||
RuleFor(x => x.Email).MaximumLength(100).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.Email));
|
||||
RuleFor(x => x.Address).MaximumLength(500);
|
||||
RuleFor(x => x.ContactPerson).MaximumLength(200);
|
||||
RuleFor(x => x.Note).MaximumLength(1000);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateSupplierCommandHandler : IRequestHandler<CreateSupplierCommand, Guid>
|
||||
{
|
||||
private readonly IApplicationDbContext _db;
|
||||
|
||||
public CreateSupplierCommandHandler(IApplicationDbContext db) => _db = db;
|
||||
|
||||
public async Task<Guid> Handle(CreateSupplierCommand request, CancellationToken ct)
|
||||
{
|
||||
if (await _db.Suppliers.AnyAsync(x => x.Code == request.Code, ct))
|
||||
throw new ConflictException($"Mã NCC '{request.Code}' đã tồn tại.");
|
||||
|
||||
var entity = new Supplier
|
||||
{
|
||||
Code = request.Code,
|
||||
Name = request.Name,
|
||||
Type = request.Type,
|
||||
TaxCode = request.TaxCode,
|
||||
Phone = request.Phone,
|
||||
Email = request.Email,
|
||||
Address = request.Address,
|
||||
ContactPerson = request.ContactPerson,
|
||||
Note = request.Note,
|
||||
};
|
||||
_db.Suppliers.Add(entity);
|
||||
await _db.SaveChangesAsync(ct);
|
||||
return entity.Id;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Exceptions;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
|
||||
namespace SolutionErp.Application.Master.Suppliers.Commands.DeleteSupplier;
|
||||
|
||||
public record DeleteSupplierCommand(Guid Id) : IRequest;
|
||||
|
||||
public class DeleteSupplierCommandHandler : IRequestHandler<DeleteSupplierCommand>
|
||||
{
|
||||
private readonly IApplicationDbContext _db;
|
||||
|
||||
public DeleteSupplierCommandHandler(IApplicationDbContext db) => _db = db;
|
||||
|
||||
public async Task Handle(DeleteSupplierCommand request, CancellationToken ct)
|
||||
{
|
||||
var entity = await _db.Suppliers.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
|
||||
?? throw new NotFoundException("Supplier", request.Id);
|
||||
_db.Suppliers.Remove(entity); // AuditingInterceptor convert sang soft delete
|
||||
await _db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Exceptions;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Domain.Master;
|
||||
|
||||
namespace SolutionErp.Application.Master.Suppliers.Commands.UpdateSupplier;
|
||||
|
||||
public record UpdateSupplierCommand(
|
||||
Guid Id,
|
||||
string Code,
|
||||
string Name,
|
||||
SupplierType Type,
|
||||
string? TaxCode,
|
||||
string? Phone,
|
||||
string? Email,
|
||||
string? Address,
|
||||
string? ContactPerson,
|
||||
string? Note) : IRequest;
|
||||
|
||||
public class UpdateSupplierCommandValidator : AbstractValidator<UpdateSupplierCommand>
|
||||
{
|
||||
public UpdateSupplierCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Id).NotEmpty();
|
||||
RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||
RuleFor(x => x.Type).IsInEnum();
|
||||
RuleFor(x => x.Email).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.Email));
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateSupplierCommandHandler : IRequestHandler<UpdateSupplierCommand>
|
||||
{
|
||||
private readonly IApplicationDbContext _db;
|
||||
|
||||
public UpdateSupplierCommandHandler(IApplicationDbContext db) => _db = db;
|
||||
|
||||
public async Task Handle(UpdateSupplierCommand request, CancellationToken ct)
|
||||
{
|
||||
var entity = await _db.Suppliers.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
|
||||
?? throw new NotFoundException("Supplier", request.Id);
|
||||
|
||||
if (entity.Code != request.Code &&
|
||||
await _db.Suppliers.AnyAsync(x => x.Code == request.Code && x.Id != request.Id, ct))
|
||||
throw new ConflictException($"Mã NCC '{request.Code}' đã tồn tại.");
|
||||
|
||||
entity.Code = request.Code;
|
||||
entity.Name = request.Name;
|
||||
entity.Type = request.Type;
|
||||
entity.TaxCode = request.TaxCode;
|
||||
entity.Phone = request.Phone;
|
||||
entity.Email = request.Email;
|
||||
entity.Address = request.Address;
|
||||
entity.ContactPerson = request.ContactPerson;
|
||||
entity.Note = request.Note;
|
||||
|
||||
await _db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
using SolutionErp.Domain.Master;
|
||||
|
||||
namespace SolutionErp.Application.Master.Suppliers.Dtos;
|
||||
|
||||
public record SupplierDto(
|
||||
Guid Id,
|
||||
string Code,
|
||||
string Name,
|
||||
SupplierType Type,
|
||||
string? TaxCode,
|
||||
string? Phone,
|
||||
string? Email,
|
||||
string? Address,
|
||||
string? ContactPerson,
|
||||
string? Note,
|
||||
DateTime CreatedAt,
|
||||
DateTime? UpdatedAt);
|
||||
@ -0,0 +1,23 @@
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Exceptions;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Application.Master.Suppliers.Dtos;
|
||||
|
||||
namespace SolutionErp.Application.Master.Suppliers.Queries.GetSupplier;
|
||||
|
||||
public record GetSupplierQuery(Guid Id) : IRequest<SupplierDto>;
|
||||
|
||||
public class GetSupplierQueryHandler : IRequestHandler<GetSupplierQuery, SupplierDto>
|
||||
{
|
||||
private readonly IApplicationDbContext _db;
|
||||
|
||||
public GetSupplierQueryHandler(IApplicationDbContext db) => _db = db;
|
||||
|
||||
public async Task<SupplierDto> Handle(GetSupplierQuery request, CancellationToken ct)
|
||||
{
|
||||
var x = await _db.Suppliers.AsNoTracking().FirstOrDefaultAsync(s => s.Id == request.Id, ct)
|
||||
?? throw new NotFoundException("Supplier", request.Id);
|
||||
return new SupplierDto(x.Id, x.Code, x.Name, x.Type, x.TaxCode, x.Phone, x.Email, x.Address, x.ContactPerson, x.Note, x.CreatedAt, x.UpdatedAt);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SolutionErp.Application.Common.Interfaces;
|
||||
using SolutionErp.Application.Common.Models;
|
||||
using SolutionErp.Application.Master.Suppliers.Dtos;
|
||||
using SolutionErp.Domain.Master;
|
||||
|
||||
namespace SolutionErp.Application.Master.Suppliers.Queries.ListSuppliers;
|
||||
|
||||
public record ListSuppliersQuery(SupplierType? Type = null) : PagedRequest, IRequest<PagedResult<SupplierDto>>;
|
||||
|
||||
public class ListSuppliersQueryHandler : IRequestHandler<ListSuppliersQuery, PagedResult<SupplierDto>>
|
||||
{
|
||||
private readonly IApplicationDbContext _db;
|
||||
|
||||
public ListSuppliersQueryHandler(IApplicationDbContext db) => _db = db;
|
||||
|
||||
public async Task<PagedResult<SupplierDto>> Handle(ListSuppliersQuery request, CancellationToken ct)
|
||||
{
|
||||
var query = _db.Suppliers.AsNoTracking();
|
||||
|
||||
if (request.Type is not null)
|
||||
query = query.Where(x => x.Type == request.Type);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Search))
|
||||
{
|
||||
var s = request.Search.Trim();
|
||||
query = query.Where(x =>
|
||||
x.Code.Contains(s) || x.Name.Contains(s) ||
|
||||
(x.TaxCode != null && x.TaxCode.Contains(s)));
|
||||
}
|
||||
|
||||
query = (request.SortBy, request.SortDesc) switch
|
||||
{
|
||||
("code", true) => query.OrderByDescending(x => x.Code),
|
||||
("code", false) => query.OrderBy(x => x.Code),
|
||||
("name", true) => query.OrderByDescending(x => x.Name),
|
||||
("name", false) => query.OrderBy(x => x.Name),
|
||||
("type", true) => query.OrderByDescending(x => x.Type),
|
||||
("type", false) => query.OrderBy(x => x.Type),
|
||||
_ => query.OrderByDescending(x => x.CreatedAt),
|
||||
};
|
||||
|
||||
var total = await query.CountAsync(ct);
|
||||
var items = await query
|
||||
.Skip((request.Page - 1) * request.PageSize)
|
||||
.Take(request.PageSize)
|
||||
.Select(x => new SupplierDto(
|
||||
x.Id, x.Code, x.Name, x.Type, x.TaxCode, x.Phone, x.Email, x.Address, x.ContactPerson, x.Note,
|
||||
x.CreatedAt, x.UpdatedAt))
|
||||
.ToListAsync(ct);
|
||||
|
||||
return new PagedResult<SupplierDto>(items, total, request.Page, request.PageSize);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user