[CLAUDE] Domain+App+Api: 4 master catalogs cho Details (migration 10)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
User feedback: thêm Master Data cho phần Chi tiết (line items) — autocomplete khi user nhập field thay vì gõ tay free text. ## 4 entities mới (Domain/Master/Catalogs/) | Entity | Bảng | Dùng cho HĐ Detail | |---|---|---| | UnitOfMeasure | UnitsOfMeasure | Tất cả 7 type (DonViTinh) | | MaterialItem | MaterialItems | NCC + Mua bán + Nguyên tắc NCC (MaSP/TenSP) | | ServiceItem | ServiceItems | Dịch vụ + Nguyên tắc DV (MaDichVu/TenDichVu) | | WorkItem | WorkItems | Thầu phụ + Giao khoán (HangMuc/MaCongViec) | Common pattern: Code unique (filter IsDeleted=0) + Name + Category + DefaultUnit + AuditableEntity (soft delete) + IsActive flag. ## Migration 10: AddMasterCatalogs 3-file rule (gotcha #17). Total DB: 32 → 36 tables. Apply LocalDB OK. ## Seed defaults (idempotent — skip per-table nếu có row) - 20 UnitsOfMeasure: m2, m3, kg, tấn, lít, ngc (ngày công), giờ, gói, ... - 15 MaterialItems demo: xi măng PCB40, cát vàng, đá 1x2, thép D10, gạch 4 lỗ, sơn lót, ống PVC... - 10 ServiceItems demo: vận chuyển, bảo trì, tư vấn, kiểm định, vệ sinh... - 15 WorkItems demo: đào móng, đổ bê tông, xây tường, trát, lát gạch, sơn nước, lắp điện, lắp nước, thấm chống... ## CQRS (Application/Master/Catalogs/CatalogsFeatures.cs ~290 dòng) Mỗi catalog 5 handlers (List filter q+category, Create với unique code guard, Update, Delete soft). FluentValidation max length per spec EF. ## Controller (Api/Controllers/CatalogsController.cs) 13 endpoints: - GET /api/catalogs/{units|materials|services|work-items} - POST /api/catalogs/{kind} (Admin role) - PUT /api/catalogs/{kind}/{id} (Admin role) - DELETE /api/catalogs/{kind}/{id} (Admin role) Read open cho mọi role (FE Details add form autocomplete cần list). ## Menu (5 mới) - Catalogs (group, Master parent, order 24, "Library" icon) - CatalogUnits "Đơn vị tính" (Ruler) - CatalogMaterials "Vật tư / SP" (Package) - CatalogServices "Dịch vụ" (Wrench) - CatalogWorkItems "Hạng mục công việc" (ListChecks) Admin auto-grant tất cả CRUD action (qua SeedAdminPermissionsAsync loop MenuKeys.All). ## Build dotnet build BE pass (0 error) ## Note FE admin page CatalogsPage + datalist autocomplete trong Details form sẽ ở commit kế tiếp. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
136
src/Backend/SolutionErp.Api/Controllers/CatalogsController.cs
Normal file
136
src/Backend/SolutionErp.Api/Controllers/CatalogsController.cs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using SolutionErp.Application.Master.Catalogs;
|
||||||
|
|
||||||
|
namespace SolutionErp.Api.Controllers;
|
||||||
|
|
||||||
|
// Master catalogs API — 4 sub-resource (units, materials, services, work-items)
|
||||||
|
// dùng Mediator dispatch theo command/query type. Authorize = đăng nhập đủ
|
||||||
|
// (mọi role được đọc cho autocomplete; ghi cần Admin).
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/catalogs")]
|
||||||
|
[Authorize]
|
||||||
|
public class CatalogsController(IMediator mediator) : ControllerBase
|
||||||
|
{
|
||||||
|
// ===== Units =====
|
||||||
|
[HttpGet("units")]
|
||||||
|
public async Task<List<UnitOfMeasureDto>> ListUnits([FromQuery] string? q, CancellationToken ct)
|
||||||
|
=> await mediator.Send(new ListUnitsQuery(q), ct);
|
||||||
|
|
||||||
|
[HttpPost("units")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> CreateUnit([FromBody] CreateUnitCommand body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var id = await mediator.Send(body, ct);
|
||||||
|
return CreatedAtAction(nameof(ListUnits), new { id }, new { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("units/{id:guid}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> UpdateUnit(Guid id, [FromBody] UpdateUnitCommand body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (id != body.Id) return BadRequest("Id mismatch");
|
||||||
|
await mediator.Send(body, ct);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("units/{id:guid}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> DeleteUnit(Guid id, CancellationToken ct)
|
||||||
|
{
|
||||||
|
await mediator.Send(new DeleteUnitCommand(id), ct);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Materials =====
|
||||||
|
[HttpGet("materials")]
|
||||||
|
public async Task<List<MaterialItemDto>> ListMaterials([FromQuery] string? q, [FromQuery] string? category, CancellationToken ct)
|
||||||
|
=> await mediator.Send(new ListMaterialsQuery(q, category), ct);
|
||||||
|
|
||||||
|
[HttpPost("materials")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> CreateMaterial([FromBody] CreateMaterialCommand body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var id = await mediator.Send(body, ct);
|
||||||
|
return CreatedAtAction(nameof(ListMaterials), new { id }, new { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("materials/{id:guid}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> UpdateMaterial(Guid id, [FromBody] UpdateMaterialCommand body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (id != body.Id) return BadRequest("Id mismatch");
|
||||||
|
await mediator.Send(body, ct);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("materials/{id:guid}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> DeleteMaterial(Guid id, CancellationToken ct)
|
||||||
|
{
|
||||||
|
await mediator.Send(new DeleteMaterialCommand(id), ct);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Services =====
|
||||||
|
[HttpGet("services")]
|
||||||
|
public async Task<List<ServiceItemDto>> ListServices([FromQuery] string? q, [FromQuery] string? category, CancellationToken ct)
|
||||||
|
=> await mediator.Send(new ListServicesQuery(q, category), ct);
|
||||||
|
|
||||||
|
[HttpPost("services")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> CreateService([FromBody] CreateServiceCommand body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var id = await mediator.Send(body, ct);
|
||||||
|
return CreatedAtAction(nameof(ListServices), new { id }, new { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("services/{id:guid}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> UpdateService(Guid id, [FromBody] UpdateServiceCommand body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (id != body.Id) return BadRequest("Id mismatch");
|
||||||
|
await mediator.Send(body, ct);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("services/{id:guid}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> DeleteService(Guid id, CancellationToken ct)
|
||||||
|
{
|
||||||
|
await mediator.Send(new DeleteServiceCommand(id), ct);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Work Items =====
|
||||||
|
[HttpGet("work-items")]
|
||||||
|
public async Task<List<WorkItemDto>> ListWorkItems([FromQuery] string? q, [FromQuery] string? category, CancellationToken ct)
|
||||||
|
=> await mediator.Send(new ListWorkItemsQuery(q, category), ct);
|
||||||
|
|
||||||
|
[HttpPost("work-items")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> CreateWorkItem([FromBody] CreateWorkItemCommand body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var id = await mediator.Send(body, ct);
|
||||||
|
return CreatedAtAction(nameof(ListWorkItems), new { id }, new { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("work-items/{id:guid}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> UpdateWorkItem(Guid id, [FromBody] UpdateWorkItemCommand body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (id != body.Id) return BadRequest("Id mismatch");
|
||||||
|
await mediator.Send(body, ct);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("work-items/{id:guid}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<IActionResult> DeleteWorkItem(Guid id, CancellationToken ct)
|
||||||
|
{
|
||||||
|
await mediator.Send(new DeleteWorkItemCommand(id), ct);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ using SolutionErp.Domain.Contracts.Details;
|
|||||||
using SolutionErp.Domain.Forms;
|
using SolutionErp.Domain.Forms;
|
||||||
using SolutionErp.Domain.Identity;
|
using SolutionErp.Domain.Identity;
|
||||||
using SolutionErp.Domain.Master;
|
using SolutionErp.Domain.Master;
|
||||||
|
using SolutionErp.Domain.Master.Catalogs;
|
||||||
using SolutionErp.Domain.Notifications;
|
using SolutionErp.Domain.Notifications;
|
||||||
|
|
||||||
namespace SolutionErp.Application.Common.Interfaces;
|
namespace SolutionErp.Application.Common.Interfaces;
|
||||||
@ -13,6 +14,11 @@ public interface IApplicationDbContext
|
|||||||
DbSet<Supplier> Suppliers { get; }
|
DbSet<Supplier> Suppliers { get; }
|
||||||
DbSet<Project> Projects { get; }
|
DbSet<Project> Projects { get; }
|
||||||
DbSet<Department> Departments { get; }
|
DbSet<Department> Departments { get; }
|
||||||
|
// 4 master catalogs cho Details add form (autocomplete)
|
||||||
|
DbSet<UnitOfMeasure> UnitsOfMeasure { get; }
|
||||||
|
DbSet<MaterialItem> MaterialItems { get; }
|
||||||
|
DbSet<ServiceItem> ServiceItems { get; }
|
||||||
|
DbSet<WorkItem> WorkItems { get; }
|
||||||
DbSet<MenuItem> MenuItems { get; }
|
DbSet<MenuItem> MenuItems { get; }
|
||||||
DbSet<Permission> Permissions { get; }
|
DbSet<Permission> Permissions { get; }
|
||||||
DbSet<ContractTemplate> ContractTemplates { get; }
|
DbSet<ContractTemplate> ContractTemplates { get; }
|
||||||
|
|||||||
@ -0,0 +1,334 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SolutionErp.Application.Common.Exceptions;
|
||||||
|
using SolutionErp.Application.Common.Interfaces;
|
||||||
|
using SolutionErp.Domain.Master.Catalogs;
|
||||||
|
|
||||||
|
namespace SolutionErp.Application.Master.Catalogs;
|
||||||
|
|
||||||
|
// CRUD CQRS cho 4 master catalogs (Units, Materials, Services, WorkItems).
|
||||||
|
// Pattern lặp đi lặp lại với mỗi catalog — gộp 1 file để dễ maintain, mỗi
|
||||||
|
// catalog 5 handlers (List + GetById + Create + Update + Delete).
|
||||||
|
//
|
||||||
|
// Endpoints expose qua CatalogsController:
|
||||||
|
// GET /api/catalogs/{kind} — list (filter q + category)
|
||||||
|
// GET /api/catalogs/{kind}/{id} — get by id
|
||||||
|
// POST /api/catalogs/{kind} — create
|
||||||
|
// PUT /api/catalogs/{kind}/{id} — update
|
||||||
|
// DELETE /api/catalogs/{kind}/{id} — soft delete
|
||||||
|
// kind ∈ {units, materials, services, work-items}
|
||||||
|
|
||||||
|
// ===== DTOs =====
|
||||||
|
|
||||||
|
public record UnitOfMeasureDto(Guid Id, string Code, string Name, string? Description);
|
||||||
|
public record MaterialItemDto(Guid Id, string Code, string Name, string? Category, string? DefaultUnit, string? Specification, string? OriginCountry, bool IsActive);
|
||||||
|
public record ServiceItemDto(Guid Id, string Code, string Name, string? Category, string? DefaultUnit, string? Description, bool IsActive);
|
||||||
|
public record WorkItemDto(Guid Id, string Code, string Name, string? Category, string? DefaultUnit, string? Description, bool IsActive);
|
||||||
|
|
||||||
|
// ===== UnitOfMeasure =====
|
||||||
|
|
||||||
|
public record ListUnitsQuery(string? Q = null) : IRequest<List<UnitOfMeasureDto>>;
|
||||||
|
public class ListUnitsHandler(IApplicationDbContext db) : IRequestHandler<ListUnitsQuery, List<UnitOfMeasureDto>>
|
||||||
|
{
|
||||||
|
public async Task<List<UnitOfMeasureDto>> Handle(ListUnitsQuery req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var q = db.UnitsOfMeasure.AsNoTracking().AsQueryable();
|
||||||
|
if (!string.IsNullOrWhiteSpace(req.Q))
|
||||||
|
{
|
||||||
|
var s = req.Q.ToLower();
|
||||||
|
q = q.Where(x => x.Code.ToLower().Contains(s) || x.Name.ToLower().Contains(s));
|
||||||
|
}
|
||||||
|
return await q.OrderBy(x => x.Code)
|
||||||
|
.Select(x => new UnitOfMeasureDto(x.Id, x.Code, x.Name, x.Description))
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CreateUnitCommand(string Code, string Name, string? Description) : IRequest<Guid>;
|
||||||
|
public class CreateUnitValidator : AbstractValidator<CreateUnitCommand>
|
||||||
|
{
|
||||||
|
public CreateUnitValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Code).NotEmpty().MaximumLength(20);
|
||||||
|
RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class CreateUnitHandler(IApplicationDbContext db) : IRequestHandler<CreateUnitCommand, Guid>
|
||||||
|
{
|
||||||
|
public async Task<Guid> Handle(CreateUnitCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (await db.UnitsOfMeasure.AnyAsync(x => x.Code == req.Code, ct))
|
||||||
|
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||||
|
var entity = new UnitOfMeasure { Code = req.Code, Name = req.Name, Description = req.Description };
|
||||||
|
db.UnitsOfMeasure.Add(entity);
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
return entity.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record UpdateUnitCommand(Guid Id, string Code, string Name, string? Description) : IRequest;
|
||||||
|
public class UpdateUnitHandler(IApplicationDbContext db) : IRequestHandler<UpdateUnitCommand>
|
||||||
|
{
|
||||||
|
public async Task Handle(UpdateUnitCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var entity = await db.UnitsOfMeasure.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||||
|
?? throw new NotFoundException("UnitOfMeasure", req.Id);
|
||||||
|
if (entity.Code != req.Code && await db.UnitsOfMeasure.AnyAsync(x => x.Code == req.Code, ct))
|
||||||
|
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||||
|
entity.Code = req.Code;
|
||||||
|
entity.Name = req.Name;
|
||||||
|
entity.Description = req.Description;
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DeleteUnitCommand(Guid Id) : IRequest;
|
||||||
|
public class DeleteUnitHandler(IApplicationDbContext db) : IRequestHandler<DeleteUnitCommand>
|
||||||
|
{
|
||||||
|
public async Task Handle(DeleteUnitCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var entity = await db.UnitsOfMeasure.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||||
|
?? throw new NotFoundException("UnitOfMeasure", req.Id);
|
||||||
|
db.UnitsOfMeasure.Remove(entity); // Soft delete via AuditingInterceptor
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== MaterialItem =====
|
||||||
|
|
||||||
|
public record ListMaterialsQuery(string? Q = null, string? Category = null) : IRequest<List<MaterialItemDto>>;
|
||||||
|
public class ListMaterialsHandler(IApplicationDbContext db) : IRequestHandler<ListMaterialsQuery, List<MaterialItemDto>>
|
||||||
|
{
|
||||||
|
public async Task<List<MaterialItemDto>> Handle(ListMaterialsQuery req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var q = db.MaterialItems.AsNoTracking().AsQueryable();
|
||||||
|
if (!string.IsNullOrWhiteSpace(req.Q))
|
||||||
|
{
|
||||||
|
var s = req.Q.ToLower();
|
||||||
|
q = q.Where(x => x.Code.ToLower().Contains(s) || x.Name.ToLower().Contains(s));
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(req.Category))
|
||||||
|
q = q.Where(x => x.Category == req.Category);
|
||||||
|
return await q.OrderBy(x => x.Category).ThenBy(x => x.Code)
|
||||||
|
.Select(x => new MaterialItemDto(x.Id, x.Code, x.Name, x.Category, x.DefaultUnit, x.Specification, x.OriginCountry, x.IsActive))
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CreateMaterialCommand(string Code, string Name, string? Category, string? DefaultUnit, string? Specification, string? OriginCountry, bool IsActive) : IRequest<Guid>;
|
||||||
|
public class CreateMaterialValidator : AbstractValidator<CreateMaterialCommand>
|
||||||
|
{
|
||||||
|
public CreateMaterialValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
|
||||||
|
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class CreateMaterialHandler(IApplicationDbContext db) : IRequestHandler<CreateMaterialCommand, Guid>
|
||||||
|
{
|
||||||
|
public async Task<Guid> Handle(CreateMaterialCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (await db.MaterialItems.AnyAsync(x => x.Code == req.Code, ct))
|
||||||
|
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||||
|
var entity = new MaterialItem
|
||||||
|
{
|
||||||
|
Code = req.Code, Name = req.Name, Category = req.Category,
|
||||||
|
DefaultUnit = req.DefaultUnit, Specification = req.Specification,
|
||||||
|
OriginCountry = req.OriginCountry, IsActive = req.IsActive,
|
||||||
|
};
|
||||||
|
db.MaterialItems.Add(entity);
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
return entity.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record UpdateMaterialCommand(Guid Id, string Code, string Name, string? Category, string? DefaultUnit, string? Specification, string? OriginCountry, bool IsActive) : IRequest;
|
||||||
|
public class UpdateMaterialHandler(IApplicationDbContext db) : IRequestHandler<UpdateMaterialCommand>
|
||||||
|
{
|
||||||
|
public async Task Handle(UpdateMaterialCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var entity = await db.MaterialItems.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||||
|
?? throw new NotFoundException("MaterialItem", req.Id);
|
||||||
|
if (entity.Code != req.Code && await db.MaterialItems.AnyAsync(x => x.Code == req.Code, ct))
|
||||||
|
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||||
|
entity.Code = req.Code;
|
||||||
|
entity.Name = req.Name;
|
||||||
|
entity.Category = req.Category;
|
||||||
|
entity.DefaultUnit = req.DefaultUnit;
|
||||||
|
entity.Specification = req.Specification;
|
||||||
|
entity.OriginCountry = req.OriginCountry;
|
||||||
|
entity.IsActive = req.IsActive;
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DeleteMaterialCommand(Guid Id) : IRequest;
|
||||||
|
public class DeleteMaterialHandler(IApplicationDbContext db) : IRequestHandler<DeleteMaterialCommand>
|
||||||
|
{
|
||||||
|
public async Task Handle(DeleteMaterialCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var entity = await db.MaterialItems.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||||
|
?? throw new NotFoundException("MaterialItem", req.Id);
|
||||||
|
db.MaterialItems.Remove(entity);
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== ServiceItem =====
|
||||||
|
|
||||||
|
public record ListServicesQuery(string? Q = null, string? Category = null) : IRequest<List<ServiceItemDto>>;
|
||||||
|
public class ListServicesHandler(IApplicationDbContext db) : IRequestHandler<ListServicesQuery, List<ServiceItemDto>>
|
||||||
|
{
|
||||||
|
public async Task<List<ServiceItemDto>> Handle(ListServicesQuery req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var q = db.ServiceItems.AsNoTracking().AsQueryable();
|
||||||
|
if (!string.IsNullOrWhiteSpace(req.Q))
|
||||||
|
{
|
||||||
|
var s = req.Q.ToLower();
|
||||||
|
q = q.Where(x => x.Code.ToLower().Contains(s) || x.Name.ToLower().Contains(s))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(req.Category))
|
||||||
|
q = q.Where(x => x.Category == req.Category)
|
||||||
|
;
|
||||||
|
return await q.OrderBy(x => x.Category).ThenBy(x => x.Code)
|
||||||
|
.Select(x => new ServiceItemDto(x.Id, x.Code, x.Name, x.Category, x.DefaultUnit, x.Description, x.IsActive))
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CreateServiceCommand(string Code, string Name, string? Category, string? DefaultUnit, string? Description, bool IsActive) : IRequest<Guid>;
|
||||||
|
public class CreateServiceValidator : AbstractValidator<CreateServiceCommand>
|
||||||
|
{
|
||||||
|
public CreateServiceValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
|
||||||
|
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class CreateServiceHandler(IApplicationDbContext db) : IRequestHandler<CreateServiceCommand, Guid>
|
||||||
|
{
|
||||||
|
public async Task<Guid> Handle(CreateServiceCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (await db.ServiceItems.AnyAsync(x => x.Code == req.Code, ct))
|
||||||
|
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||||
|
var entity = new ServiceItem
|
||||||
|
{
|
||||||
|
Code = req.Code, Name = req.Name, Category = req.Category,
|
||||||
|
DefaultUnit = req.DefaultUnit, Description = req.Description, IsActive = req.IsActive,
|
||||||
|
};
|
||||||
|
db.ServiceItems.Add(entity);
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
return entity.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record UpdateServiceCommand(Guid Id, string Code, string Name, string? Category, string? DefaultUnit, string? Description, bool IsActive) : IRequest;
|
||||||
|
public class UpdateServiceHandler(IApplicationDbContext db) : IRequestHandler<UpdateServiceCommand>
|
||||||
|
{
|
||||||
|
public async Task Handle(UpdateServiceCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var entity = await db.ServiceItems.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||||
|
?? throw new NotFoundException("ServiceItem", req.Id);
|
||||||
|
if (entity.Code != req.Code && await db.ServiceItems.AnyAsync(x => x.Code == req.Code, ct))
|
||||||
|
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||||
|
entity.Code = req.Code;
|
||||||
|
entity.Name = req.Name;
|
||||||
|
entity.Category = req.Category;
|
||||||
|
entity.DefaultUnit = req.DefaultUnit;
|
||||||
|
entity.Description = req.Description;
|
||||||
|
entity.IsActive = req.IsActive;
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DeleteServiceCommand(Guid Id) : IRequest;
|
||||||
|
public class DeleteServiceHandler(IApplicationDbContext db) : IRequestHandler<DeleteServiceCommand>
|
||||||
|
{
|
||||||
|
public async Task Handle(DeleteServiceCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var entity = await db.ServiceItems.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||||
|
?? throw new NotFoundException("ServiceItem", req.Id);
|
||||||
|
db.ServiceItems.Remove(entity);
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== WorkItem =====
|
||||||
|
|
||||||
|
public record ListWorkItemsQuery(string? Q = null, string? Category = null) : IRequest<List<WorkItemDto>>;
|
||||||
|
public class ListWorkItemsHandler(IApplicationDbContext db) : IRequestHandler<ListWorkItemsQuery, List<WorkItemDto>>
|
||||||
|
{
|
||||||
|
public async Task<List<WorkItemDto>> Handle(ListWorkItemsQuery req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var q = db.WorkItems.AsNoTracking().AsQueryable();
|
||||||
|
if (!string.IsNullOrWhiteSpace(req.Q))
|
||||||
|
{
|
||||||
|
var s = req.Q.ToLower();
|
||||||
|
q = q.Where(x => x.Code.ToLower().Contains(s) || x.Name.ToLower().Contains(s));
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(req.Category))
|
||||||
|
q = q.Where(x => x.Category == req.Category);
|
||||||
|
return await q.OrderBy(x => x.Category).ThenBy(x => x.Code)
|
||||||
|
.Select(x => new WorkItemDto(x.Id, x.Code, x.Name, x.Category, x.DefaultUnit, x.Description, x.IsActive))
|
||||||
|
.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CreateWorkItemCommand(string Code, string Name, string? Category, string? DefaultUnit, string? Description, bool IsActive) : IRequest<Guid>;
|
||||||
|
public class CreateWorkItemValidator : AbstractValidator<CreateWorkItemCommand>
|
||||||
|
{
|
||||||
|
public CreateWorkItemValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
|
||||||
|
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class CreateWorkItemHandler(IApplicationDbContext db) : IRequestHandler<CreateWorkItemCommand, Guid>
|
||||||
|
{
|
||||||
|
public async Task<Guid> Handle(CreateWorkItemCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (await db.WorkItems.AnyAsync(x => x.Code == req.Code, ct))
|
||||||
|
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||||
|
var entity = new WorkItem
|
||||||
|
{
|
||||||
|
Code = req.Code, Name = req.Name, Category = req.Category,
|
||||||
|
DefaultUnit = req.DefaultUnit, Description = req.Description, IsActive = req.IsActive,
|
||||||
|
};
|
||||||
|
db.WorkItems.Add(entity);
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
return entity.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record UpdateWorkItemCommand(Guid Id, string Code, string Name, string? Category, string? DefaultUnit, string? Description, bool IsActive) : IRequest;
|
||||||
|
public class UpdateWorkItemHandler(IApplicationDbContext db) : IRequestHandler<UpdateWorkItemCommand>
|
||||||
|
{
|
||||||
|
public async Task Handle(UpdateWorkItemCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var entity = await db.WorkItems.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||||
|
?? throw new NotFoundException("WorkItem", req.Id);
|
||||||
|
if (entity.Code != req.Code && await db.WorkItems.AnyAsync(x => x.Code == req.Code, ct))
|
||||||
|
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||||
|
entity.Code = req.Code;
|
||||||
|
entity.Name = req.Name;
|
||||||
|
entity.Category = req.Category;
|
||||||
|
entity.DefaultUnit = req.DefaultUnit;
|
||||||
|
entity.Description = req.Description;
|
||||||
|
entity.IsActive = req.IsActive;
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DeleteWorkItemCommand(Guid Id) : IRequest;
|
||||||
|
public class DeleteWorkItemHandler(IApplicationDbContext db) : IRequestHandler<DeleteWorkItemCommand>
|
||||||
|
{
|
||||||
|
public async Task Handle(DeleteWorkItemCommand req, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var entity = await db.WorkItems.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||||
|
?? throw new NotFoundException("WorkItem", req.Id);
|
||||||
|
db.WorkItems.Remove(entity);
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,13 @@ public static class MenuKeys
|
|||||||
public const string Permissions = "Permissions";
|
public const string Permissions = "Permissions";
|
||||||
public const string Workflows = "Workflows";
|
public const string Workflows = "Workflows";
|
||||||
|
|
||||||
|
// 4 master catalogs cho Details add form (autocomplete) — admin CRUD
|
||||||
|
public const string Catalogs = "Catalogs"; // group
|
||||||
|
public const string CatalogUnits = "CatalogUnits";
|
||||||
|
public const string CatalogMaterials = "CatalogMaterials";
|
||||||
|
public const string CatalogServices = "CatalogServices";
|
||||||
|
public const string CatalogWorkItems = "CatalogWorkItems";
|
||||||
|
|
||||||
// Per-contract-type menu groups + 3 action leaves each.
|
// Per-contract-type menu groups + 3 action leaves each.
|
||||||
// Key format: Ct_<TypeCode>[_<Action>] — prefix `Ct_` distinguishes from
|
// Key format: Ct_<TypeCode>[_<Action>] — prefix `Ct_` distinguishes from
|
||||||
// top-level. Menu tree endpoint (GetMyMenuTreeQuery) treats descendants of
|
// top-level. Menu tree endpoint (GetMyMenuTreeQuery) treats descendants of
|
||||||
@ -39,6 +46,7 @@ public static class MenuKeys
|
|||||||
[
|
[
|
||||||
Dashboard,
|
Dashboard,
|
||||||
Master, Suppliers, Projects, Departments,
|
Master, Suppliers, Projects, Departments,
|
||||||
|
Catalogs, CatalogUnits, CatalogMaterials, CatalogServices, CatalogWorkItems,
|
||||||
Contracts, Forms, Reports,
|
Contracts, Forms, Reports,
|
||||||
System, Users, Roles, Permissions, Workflows,
|
System, Users, Roles, Permissions, Workflows,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Master.Catalogs;
|
||||||
|
|
||||||
|
// Master danh mục vật tư / sản phẩm — dùng cho HĐ NCC, Mua bán, Nguyên tắc NCC.
|
||||||
|
// Khi user nhập Details: autocomplete từ danh mục này để autofill Mã/Tên/ĐVT/Spec.
|
||||||
|
public class MaterialItem : AuditableEntity
|
||||||
|
{
|
||||||
|
public string Code { get; set; } = ""; // Mã SP (unique)
|
||||||
|
public string Name { get; set; } = ""; // Tên SP
|
||||||
|
public string? Category { get; set; } // Nhóm SP (vd "Vật liệu xây dựng", "Sắt thép")
|
||||||
|
public string? DefaultUnit { get; set; } // ĐVT mặc định (autofill khi chọn)
|
||||||
|
public string? Specification { get; set; } // Thông số kỹ thuật mẫu
|
||||||
|
public string? OriginCountry { get; set; } // Xuất xứ
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Master.Catalogs;
|
||||||
|
|
||||||
|
// Master danh mục dịch vụ — dùng cho HĐ Dịch vụ + Nguyên tắc Dịch vụ.
|
||||||
|
public class ServiceItem : AuditableEntity
|
||||||
|
{
|
||||||
|
public string Code { get; set; } = ""; // Mã DV
|
||||||
|
public string Name { get; set; } = ""; // Tên DV
|
||||||
|
public string? Category { get; set; } // Loại DV (vd "Vận chuyển", "Bảo trì", "Tư vấn")
|
||||||
|
public string? DefaultUnit { get; set; } // ĐVT mặc định (giờ, ngày, gói, lần...)
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Master.Catalogs;
|
||||||
|
|
||||||
|
// Master danh mục đơn vị tính dùng cho mọi loại HĐ Details (m2, kg, ngày
|
||||||
|
// công, gói, bộ, ...). Code = unique short identifier; Name = label tiếng Việt.
|
||||||
|
public class UnitOfMeasure : AuditableEntity
|
||||||
|
{
|
||||||
|
public string Code { get; set; } = ""; // vd "m2", "kg", "ngc"
|
||||||
|
public string Name { get; set; } = ""; // vd "Mét vuông", "Kilogram", "Ngày công"
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
14
src/Backend/SolutionErp.Domain/Master/Catalogs/WorkItem.cs
Normal file
14
src/Backend/SolutionErp.Domain/Master/Catalogs/WorkItem.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Master.Catalogs;
|
||||||
|
|
||||||
|
// Master danh mục hạng mục công việc — dùng cho HĐ Thầu phụ + Giao khoán.
|
||||||
|
public class WorkItem : AuditableEntity
|
||||||
|
{
|
||||||
|
public string Code { get; set; } = ""; // Mã hạng mục (vd "DAO_MONG", "DO_BTONG")
|
||||||
|
public string Name { get; set; } = ""; // Tên hạng mục
|
||||||
|
public string? Category { get; set; } // Nhóm (vd "Phần thô", "Hoàn thiện", "Cơ điện")
|
||||||
|
public string? DefaultUnit { get; set; } // ĐVT mặc định
|
||||||
|
public string? Description { get; set; } // Mô tả + spec yêu cầu
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ using SolutionErp.Domain.Contracts.Details;
|
|||||||
using SolutionErp.Domain.Forms;
|
using SolutionErp.Domain.Forms;
|
||||||
using SolutionErp.Domain.Identity;
|
using SolutionErp.Domain.Identity;
|
||||||
using SolutionErp.Domain.Master;
|
using SolutionErp.Domain.Master;
|
||||||
|
using SolutionErp.Domain.Master.Catalogs;
|
||||||
using SolutionErp.Domain.Notifications;
|
using SolutionErp.Domain.Notifications;
|
||||||
|
|
||||||
namespace SolutionErp.Infrastructure.Persistence;
|
namespace SolutionErp.Infrastructure.Persistence;
|
||||||
@ -18,6 +19,10 @@ public class ApplicationDbContext
|
|||||||
public DbSet<Supplier> Suppliers => Set<Supplier>();
|
public DbSet<Supplier> Suppliers => Set<Supplier>();
|
||||||
public DbSet<Project> Projects => Set<Project>();
|
public DbSet<Project> Projects => Set<Project>();
|
||||||
public DbSet<Department> Departments => Set<Department>();
|
public DbSet<Department> Departments => Set<Department>();
|
||||||
|
public DbSet<UnitOfMeasure> UnitsOfMeasure => Set<UnitOfMeasure>();
|
||||||
|
public DbSet<MaterialItem> MaterialItems => Set<MaterialItem>();
|
||||||
|
public DbSet<ServiceItem> ServiceItems => Set<ServiceItem>();
|
||||||
|
public DbSet<WorkItem> WorkItems => Set<WorkItem>();
|
||||||
public DbSet<MenuItem> MenuItems => Set<MenuItem>();
|
public DbSet<MenuItem> MenuItems => Set<MenuItem>();
|
||||||
public DbSet<Permission> Permissions => Set<Permission>();
|
public DbSet<Permission> Permissions => Set<Permission>();
|
||||||
public DbSet<ContractTemplate> ContractTemplates => Set<ContractTemplate>();
|
public DbSet<ContractTemplate> ContractTemplates => Set<ContractTemplate>();
|
||||||
|
|||||||
@ -0,0 +1,75 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using SolutionErp.Domain.Master.Catalogs;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
// EF config cho 4 master catalogs (UnitsOfMeasure, MaterialItems,
|
||||||
|
// ServiceItems, WorkItems) — phục vụ autocomplete trong Details add form.
|
||||||
|
// Pattern: Code unique + IsDeleted query filter + IsActive flag soft-archive.
|
||||||
|
|
||||||
|
public class UnitOfMeasureConfiguration : IEntityTypeConfiguration<UnitOfMeasure>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<UnitOfMeasure> b)
|
||||||
|
{
|
||||||
|
b.ToTable("UnitsOfMeasure");
|
||||||
|
b.HasKey(x => x.Id);
|
||||||
|
b.Property(x => x.Code).HasMaxLength(20).IsRequired();
|
||||||
|
b.Property(x => x.Name).HasMaxLength(100).IsRequired();
|
||||||
|
b.Property(x => x.Description).HasMaxLength(500);
|
||||||
|
b.HasIndex(x => x.Code).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
|
b.HasQueryFilter(x => !x.IsDeleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MaterialItemConfiguration : IEntityTypeConfiguration<MaterialItem>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<MaterialItem> b)
|
||||||
|
{
|
||||||
|
b.ToTable("MaterialItems");
|
||||||
|
b.HasKey(x => x.Id);
|
||||||
|
b.Property(x => x.Code).HasMaxLength(50).IsRequired();
|
||||||
|
b.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||||
|
b.Property(x => x.Category).HasMaxLength(100);
|
||||||
|
b.Property(x => x.DefaultUnit).HasMaxLength(50);
|
||||||
|
b.Property(x => x.Specification).HasMaxLength(1000);
|
||||||
|
b.Property(x => x.OriginCountry).HasMaxLength(100);
|
||||||
|
b.HasIndex(x => x.Code).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
|
b.HasIndex(x => x.Category);
|
||||||
|
b.HasQueryFilter(x => !x.IsDeleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServiceItemConfiguration : IEntityTypeConfiguration<ServiceItem>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ServiceItem> b)
|
||||||
|
{
|
||||||
|
b.ToTable("ServiceItems");
|
||||||
|
b.HasKey(x => x.Id);
|
||||||
|
b.Property(x => x.Code).HasMaxLength(50).IsRequired();
|
||||||
|
b.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||||
|
b.Property(x => x.Category).HasMaxLength(100);
|
||||||
|
b.Property(x => x.DefaultUnit).HasMaxLength(50);
|
||||||
|
b.Property(x => x.Description).HasMaxLength(1000);
|
||||||
|
b.HasIndex(x => x.Code).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
|
b.HasIndex(x => x.Category);
|
||||||
|
b.HasQueryFilter(x => !x.IsDeleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WorkItemConfiguration : IEntityTypeConfiguration<WorkItem>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<WorkItem> b)
|
||||||
|
{
|
||||||
|
b.ToTable("WorkItems");
|
||||||
|
b.HasKey(x => x.Id);
|
||||||
|
b.Property(x => x.Code).HasMaxLength(50).IsRequired();
|
||||||
|
b.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||||
|
b.Property(x => x.Category).HasMaxLength(100);
|
||||||
|
b.Property(x => x.DefaultUnit).HasMaxLength(50);
|
||||||
|
b.Property(x => x.Description).HasMaxLength(1000);
|
||||||
|
b.HasIndex(x => x.Code).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||||
|
b.HasIndex(x => x.Category);
|
||||||
|
b.HasQueryFilter(x => !x.IsDeleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ using SolutionErp.Domain.Contracts;
|
|||||||
using SolutionErp.Domain.Forms;
|
using SolutionErp.Domain.Forms;
|
||||||
using SolutionErp.Domain.Identity;
|
using SolutionErp.Domain.Identity;
|
||||||
using SolutionErp.Domain.Master;
|
using SolutionErp.Domain.Master;
|
||||||
|
using SolutionErp.Domain.Master.Catalogs;
|
||||||
|
|
||||||
namespace SolutionErp.Infrastructure.Persistence;
|
namespace SolutionErp.Infrastructure.Persistence;
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ public static class DbInitializer
|
|||||||
await SeedDemoMasterDataAsync(db, logger);
|
await SeedDemoMasterDataAsync(db, logger);
|
||||||
await SeedContractTemplatesAsync(db, logger);
|
await SeedContractTemplatesAsync(db, logger);
|
||||||
await SeedWorkflowDefinitionsAsync(db, logger);
|
await SeedWorkflowDefinitionsAsync(db, logger);
|
||||||
|
await SeedCatalogsAsync(db, logger);
|
||||||
|
|
||||||
// Backfill mã HĐ cho HĐ legacy chưa có (sau khi đổi policy gen-tại-create).
|
// Backfill mã HĐ cho HĐ legacy chưa có (sau khi đổi policy gen-tại-create).
|
||||||
// Idempotent: chỉ HĐ MaHopDong IS NULL được gen.
|
// Idempotent: chỉ HĐ MaHopDong IS NULL được gen.
|
||||||
@ -44,6 +46,105 @@ public static class DbInitializer
|
|||||||
await WarnDefaultAdminPasswordAsync(userManager, logger);
|
await WarnDefaultAdminPasswordAsync(userManager, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seed 4 master catalogs với defaults cho user nhập liệu Details. Idempotent:
|
||||||
|
// skip per-table nếu đã có row (admin có thể đã thêm/sửa — không clobber).
|
||||||
|
private static async Task SeedCatalogsAsync(ApplicationDbContext db, ILogger logger)
|
||||||
|
{
|
||||||
|
// 1. UnitsOfMeasure (~17)
|
||||||
|
if (!await db.UnitsOfMeasure.AnyAsync())
|
||||||
|
{
|
||||||
|
var units = new[]
|
||||||
|
{
|
||||||
|
("m2", "Mét vuông"), ("m3", "Mét khối"), ("m", "Mét"), ("md", "Mét dài"),
|
||||||
|
("kg", "Kilogram"), ("tan", "Tấn"), ("g", "Gram"),
|
||||||
|
("l", "Lít"), ("ml", "Mililít"),
|
||||||
|
("cai", "Cái"), ("chiec", "Chiếc"), ("bo", "Bộ"), ("goi", "Gói"), ("hop", "Hộp"),
|
||||||
|
("ngc", "Ngày công"), ("h", "Giờ"), ("ca", "Ca"),
|
||||||
|
("nguoi", "Người"), ("luot", "Lượt"), ("lan", "Lần"),
|
||||||
|
};
|
||||||
|
foreach (var (code, name) in units)
|
||||||
|
db.UnitsOfMeasure.Add(new UnitOfMeasure { Code = code, Name = name });
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
logger.LogInformation("Seed {Count} UnitsOfMeasure", units.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. MaterialItems (~15 demo)
|
||||||
|
if (!await db.MaterialItems.AnyAsync())
|
||||||
|
{
|
||||||
|
var materials = new[]
|
||||||
|
{
|
||||||
|
("XM-PCB40", "Xi măng PCB40", "Xi măng", "kg"),
|
||||||
|
("XM-PCB30", "Xi măng PCB30", "Xi măng", "kg"),
|
||||||
|
("CAT-VANG", "Cát vàng đổ bê tông", "Cát đá", "m3"),
|
||||||
|
("CAT-DEN", "Cát đen xây trát", "Cát đá", "m3"),
|
||||||
|
("DA-1x2", "Đá 1x2", "Cát đá", "m3"),
|
||||||
|
("DA-4x6", "Đá 4x6", "Cát đá", "m3"),
|
||||||
|
("THEP-D6", "Thép cuộn phi 6", "Sắt thép", "kg"),
|
||||||
|
("THEP-D10", "Thép cây phi 10", "Sắt thép", "kg"),
|
||||||
|
("THEP-D14", "Thép cây phi 14", "Sắt thép", "kg"),
|
||||||
|
("THEP-D18", "Thép cây phi 18", "Sắt thép", "kg"),
|
||||||
|
("GACH-XANH", "Gạch xây 4 lỗ", "Gạch ngói", "cai"),
|
||||||
|
("GACH-DAT-NUNG", "Gạch đặc đất nung", "Gạch ngói", "cai"),
|
||||||
|
("SON-LOT", "Sơn lót chống kiềm", "Sơn", "l"),
|
||||||
|
("SON-NGOAI", "Sơn ngoại thất", "Sơn", "l"),
|
||||||
|
("ONG-PVC-90", "Ống PVC phi 90", "Vật tư cấp thoát nước", "m"),
|
||||||
|
};
|
||||||
|
foreach (var (code, name, cat, unit) in materials)
|
||||||
|
db.MaterialItems.Add(new MaterialItem { Code = code, Name = name, Category = cat, DefaultUnit = unit });
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
logger.LogInformation("Seed {Count} MaterialItems", materials.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. ServiceItems (~10 demo)
|
||||||
|
if (!await db.ServiceItems.AnyAsync())
|
||||||
|
{
|
||||||
|
var services = new[]
|
||||||
|
{
|
||||||
|
("VC-OTO", "Vận chuyển ô tô tải", "Vận chuyển", "ca"),
|
||||||
|
("VC-CAN-TRUC", "Cẩu tháp / cẩu tự hành", "Vận chuyển", "h"),
|
||||||
|
("BT-MAY", "Bảo trì máy móc thiết bị", "Bảo trì", "lan"),
|
||||||
|
("BT-DIEN", "Bảo trì hệ thống điện", "Bảo trì", "lan"),
|
||||||
|
("TV-THIET-KE", "Tư vấn thiết kế", "Tư vấn", "goi"),
|
||||||
|
("TV-GIAM-SAT", "Giám sát thi công", "Tư vấn", "ngc"),
|
||||||
|
("KIEM-DINH", "Kiểm định an toàn", "Kiểm định", "lan"),
|
||||||
|
("VS-CT", "Vệ sinh công trường", "Vệ sinh", "ca"),
|
||||||
|
("BV-CT", "Bảo vệ công trường", "Bảo vệ", "ngc"),
|
||||||
|
("THUE-MAY", "Thuê máy móc thiết bị", "Thuê thiết bị", "ca"),
|
||||||
|
};
|
||||||
|
foreach (var (code, name, cat, unit) in services)
|
||||||
|
db.ServiceItems.Add(new ServiceItem { Code = code, Name = name, Category = cat, DefaultUnit = unit });
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
logger.LogInformation("Seed {Count} ServiceItems", services.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. WorkItems (~15 demo)
|
||||||
|
if (!await db.WorkItems.AnyAsync())
|
||||||
|
{
|
||||||
|
var works = new[]
|
||||||
|
{
|
||||||
|
("DAO-MONG", "Đào móng công trình", "Phần thô", "m3"),
|
||||||
|
("DO-BTONG", "Đổ bê tông móng/cột/dầm", "Phần thô", "m3"),
|
||||||
|
("LAP-COT-THEP", "Lắp dựng cốt thép", "Phần thô", "kg"),
|
||||||
|
("LAP-COPPHA", "Lắp dựng cốp pha", "Phần thô", "m2"),
|
||||||
|
("XAY-TUONG", "Xây tường gạch", "Phần thô", "m3"),
|
||||||
|
("TRAT-TUONG", "Trát tường", "Hoàn thiện", "m2"),
|
||||||
|
("LAT-GACH-NEN", "Lát gạch nền", "Hoàn thiện", "m2"),
|
||||||
|
("OP-GACH-TUONG", "Ốp gạch tường", "Hoàn thiện", "m2"),
|
||||||
|
("SON-NUOC", "Sơn nước", "Hoàn thiện", "m2"),
|
||||||
|
("LAP-CUA", "Lắp cửa", "Hoàn thiện", "cai"),
|
||||||
|
("LAP-DIEN", "Lắp đặt hệ thống điện", "Cơ điện", "goi"),
|
||||||
|
("LAP-NUOC", "Lắp đặt hệ thống cấp thoát nước", "Cơ điện", "goi"),
|
||||||
|
("LAP-DIEU-HOA", "Lắp điều hòa", "Cơ điện", "cai"),
|
||||||
|
("THAM-CHONG", "Thấm chống thấm", "Hoàn thiện", "m2"),
|
||||||
|
("VC-PHE-THAI", "Vận chuyển phế thải", "Khác", "m3"),
|
||||||
|
};
|
||||||
|
foreach (var (code, name, cat, unit) in works)
|
||||||
|
db.WorkItems.Add(new WorkItem { Code = code, Name = name, Category = cat, DefaultUnit = unit });
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
logger.LogInformation("Seed {Count} WorkItems", works.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task BackfillContractCodesAsync(
|
private static async Task BackfillContractCodesAsync(
|
||||||
ApplicationDbContext db, IContractCodeGenerator codeGen, ILogger logger)
|
ApplicationDbContext db, IContractCodeGenerator codeGen, ILogger logger)
|
||||||
{
|
{
|
||||||
@ -236,6 +337,12 @@ public static class DbInitializer
|
|||||||
(MenuKeys.Suppliers, "Nhà cung cấp", MenuKeys.Master, 21, "Building2"),
|
(MenuKeys.Suppliers, "Nhà cung cấp", MenuKeys.Master, 21, "Building2"),
|
||||||
(MenuKeys.Projects, "Dự án", MenuKeys.Master, 22, "FolderKanban"),
|
(MenuKeys.Projects, "Dự án", MenuKeys.Master, 22, "FolderKanban"),
|
||||||
(MenuKeys.Departments, "Phòng ban", MenuKeys.Master, 23, "Users"),
|
(MenuKeys.Departments, "Phòng ban", MenuKeys.Master, 23, "Users"),
|
||||||
|
// 4 master catalog cho Details add form (autocomplete suggestion)
|
||||||
|
(MenuKeys.Catalogs, "Danh mục chi tiết", MenuKeys.Master, 24, "Library"),
|
||||||
|
(MenuKeys.CatalogUnits, "Đơn vị tính", MenuKeys.Catalogs, 1, "Ruler"),
|
||||||
|
(MenuKeys.CatalogMaterials,"Vật tư / SP", MenuKeys.Catalogs, 2, "Package"),
|
||||||
|
(MenuKeys.CatalogServices, "Dịch vụ", MenuKeys.Catalogs, 3, "Wrench"),
|
||||||
|
(MenuKeys.CatalogWorkItems,"Hạng mục công việc", MenuKeys.Catalogs, 4, "ListChecks"),
|
||||||
(MenuKeys.Contracts, "Hợp đồng", null, 30, "FileText"),
|
(MenuKeys.Contracts, "Hợp đồng", null, 30, "FileText"),
|
||||||
(MenuKeys.Forms, "Biểu mẫu", null, 40, "FileSpreadsheet"),
|
(MenuKeys.Forms, "Biểu mẫu", null, 40, "FileSpreadsheet"),
|
||||||
(MenuKeys.Reports, "Báo cáo", null, 50, "BarChart3"),
|
(MenuKeys.Reports, "Báo cáo", null, 50, "BarChart3"),
|
||||||
|
|||||||
2164
src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423051650_AddMasterCatalogs.Designer.cs
generated
Normal file
2164
src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260423051650_AddMasterCatalogs.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,168 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddMasterCatalogs : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "MaterialItems",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
Code = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||||
|
Category = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||||
|
DefaultUnit = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||||
|
Specification = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||||
|
OriginCountry = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||||
|
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_MaterialItems", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ServiceItems",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
Code = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||||
|
Category = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||||
|
DefaultUnit = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||||
|
Description = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||||
|
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ServiceItems", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "UnitsOfMeasure",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
Code = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
|
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_UnitsOfMeasure", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "WorkItems",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
Code = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||||
|
Category = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||||
|
DefaultUnit = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||||
|
Description = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||||
|
IsActive = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
CreatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
UpdatedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
DeletedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||||
|
DeletedBy = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_WorkItems", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_MaterialItems_Category",
|
||||||
|
table: "MaterialItems",
|
||||||
|
column: "Category");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_MaterialItems_Code",
|
||||||
|
table: "MaterialItems",
|
||||||
|
column: "Code",
|
||||||
|
unique: true,
|
||||||
|
filter: "[IsDeleted] = 0");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ServiceItems_Category",
|
||||||
|
table: "ServiceItems",
|
||||||
|
column: "Category");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ServiceItems_Code",
|
||||||
|
table: "ServiceItems",
|
||||||
|
column: "Code",
|
||||||
|
unique: true,
|
||||||
|
filter: "[IsDeleted] = 0");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_UnitsOfMeasure_Code",
|
||||||
|
table: "UnitsOfMeasure",
|
||||||
|
column: "Code",
|
||||||
|
unique: true,
|
||||||
|
filter: "[IsDeleted] = 0");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_WorkItems_Category",
|
||||||
|
table: "WorkItems",
|
||||||
|
column: "Category");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_WorkItems_Code",
|
||||||
|
table: "WorkItems",
|
||||||
|
column: "Code",
|
||||||
|
unique: true,
|
||||||
|
filter: "[IsDeleted] = 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "MaterialItems");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ServiceItems");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "UnitsOfMeasure");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "WorkItems");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1408,6 +1408,249 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("Users", (string)null);
|
b.ToTable("Users", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.MaterialItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Code")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultUnit")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeletedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("OriginCountry")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Specification")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("nvarchar(1000)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Category");
|
||||||
|
|
||||||
|
b.HasIndex("Code")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.ToTable("MaterialItems", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.ServiceItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Code")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultUnit")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeletedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("nvarchar(1000)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Category");
|
||||||
|
|
||||||
|
b.HasIndex("Code")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.ToTable("ServiceItems", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.UnitOfMeasure", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("Code")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeletedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Code")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.ToTable("UnitsOfMeasure", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.WorkItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("Category")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Code")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("DefaultUnit")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeletedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(1000)
|
||||||
|
.HasColumnType("nvarchar(1000)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Category");
|
||||||
|
|
||||||
|
b.HasIndex("Code")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[IsDeleted] = 0");
|
||||||
|
|
||||||
|
b.ToTable("WorkItems", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("SolutionErp.Domain.Master.Department", b =>
|
modelBuilder.Entity("SolutionErp.Domain.Master.Department", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|||||||
Reference in New Issue
Block a user