[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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user