[CLAUDE] Master: nạp master data thật từ Excel (62 dự án + 71 hạng mục + 3 NCC) + Project +4 cột (Mig 48)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m33s

Nạp master data công ty từ file Excel 'HẠNG MỤC CÔNG VIỆC DỰ ÁN':
- 62 Projects (Mã + Năm; tên/CĐT/địa điểm/gói thầu cho ~6 dự án có chi tiết)
- 71 WorkItems: Vật tư 16 · Thầu phụ 30 · MEP 9 · Thiết bị 16
- 3 Suppliers (TRUONGGIANG/TANPHU/TGN)

Mig 48 AddProjectMasterFields: Project +4 cột nullable (Year/Investor/Location/Package) + ProjectFeatures DTO/Create/Update + ProjectsPage form ×2 app (SHA256 mirror).
SeedRealMasterDataAsync per-code idempotent, UNGATED → reaches prod (coexist demo). FLOCK01 collision → skip (demo wins).

Verify: build 0-err · test 216 PASS · runtime Dev proof (data landed, Investor col populates). Provenance: scripts/master-import-data.generated.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-09 09:27:04 +07:00
parent f8640d6f18
commit 69cb3937bb
17 changed files with 7069 additions and 9 deletions

View File

@ -18,7 +18,11 @@ public record ProjectDto(
decimal? BudgetTotal,
string? Note,
DateTime CreatedAt,
DateTime? UpdatedAt);
DateTime? UpdatedAt,
int? Year,
string? Investor,
string? Location,
string? Package);
// ===================== LIST =====================
public record ListProjectsQuery : PagedRequest, IRequest<PagedResult<ProjectDto>>;
@ -44,7 +48,7 @@ public class ListProjectsQueryHandler(IApplicationDbContext db) : IRequestHandle
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))
.Select(x => new ProjectDto(x.Id, x.Code, x.Name, x.StartDate, x.EndDate, x.ManagerUserId, x.BudgetTotal, x.Note, x.CreatedAt, x.UpdatedAt, x.Year, x.Investor, x.Location, x.Package))
.ToListAsync(ct);
return new PagedResult<ProjectDto>(items, total, request.Page, request.PageSize);
}
@ -59,14 +63,15 @@ public class GetProjectQueryHandler(IApplicationDbContext db) : IRequestHandler<
{
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);
return new ProjectDto(x.Id, x.Code, x.Name, x.StartDate, x.EndDate, x.ManagerUserId, x.BudgetTotal, x.Note, x.CreatedAt, x.UpdatedAt, x.Year, x.Investor, x.Location, x.Package);
}
}
// ===================== CREATE =====================
public record CreateProjectCommand(
string Code, string Name, DateTime? StartDate, DateTime? EndDate,
Guid? ManagerUserId, decimal? BudgetTotal, string? Note) : IRequest<Guid>;
Guid? ManagerUserId, decimal? BudgetTotal, string? Note,
int? Year, string? Investor, string? Location, string? Package) : IRequest<Guid>;
public class CreateProjectCommandValidator : AbstractValidator<CreateProjectCommand>
{
@ -75,6 +80,9 @@ public class CreateProjectCommandValidator : AbstractValidator<CreateProjectComm
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.Investor).MaximumLength(250);
RuleFor(x => x.Location).MaximumLength(500);
RuleFor(x => x.Package).MaximumLength(300);
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");
}
@ -91,6 +99,7 @@ public class CreateProjectCommandHandler(IApplicationDbContext db) : IRequestHan
Code = request.Code, Name = request.Name,
StartDate = request.StartDate, EndDate = request.EndDate,
ManagerUserId = request.ManagerUserId, BudgetTotal = request.BudgetTotal, Note = request.Note,
Year = request.Year, Investor = request.Investor, Location = request.Location, Package = request.Package,
};
db.Projects.Add(entity);
await db.SaveChangesAsync(ct);
@ -101,7 +110,8 @@ public class CreateProjectCommandHandler(IApplicationDbContext db) : IRequestHan
// ===================== UPDATE =====================
public record UpdateProjectCommand(
Guid Id, string Code, string Name, DateTime? StartDate, DateTime? EndDate,
Guid? ManagerUserId, decimal? BudgetTotal, string? Note) : IRequest;
Guid? ManagerUserId, decimal? BudgetTotal, string? Note,
int? Year, string? Investor, string? Location, string? Package) : IRequest;
public class UpdateProjectCommandValidator : AbstractValidator<UpdateProjectCommand>
{
@ -111,6 +121,9 @@ public class UpdateProjectCommandValidator : AbstractValidator<UpdateProjectComm
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.Investor).MaximumLength(250);
RuleFor(x => x.Location).MaximumLength(500);
RuleFor(x => x.Package).MaximumLength(300);
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");
}
@ -131,6 +144,10 @@ public class UpdateProjectCommandHandler(IApplicationDbContext db) : IRequestHan
entity.ManagerUserId = request.ManagerUserId;
entity.BudgetTotal = request.BudgetTotal;
entity.Note = request.Note;
entity.Year = request.Year;
entity.Investor = request.Investor;
entity.Location = request.Location;
entity.Package = request.Package;
await db.SaveChangesAsync(ct);
}
}