[CLAUDE] Contract: K1 SP-002 danh muc loai HD - ContractCatalogEntries (Mig 70) + seed 86 + CQRS + FE kind-5 x2 app + 8 test
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m31s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 6m31s
Gate: reviewer PASS-WITH-FLAGS 9f (sub-reviewer-gate-k1.md). Xu ly: F4 IncludeInactive mac-dinh-loai (spec K1.c) + test T5b; F7 comment 5-kind; F1 OG-6 doi nhan soat-SAU @PAUSE-2 (seeder skip-if-exists giu sua-tay CRUD qua restart - update-branch co y KHONG co); F3 authz mirror khuon Catalogs, gioi-han khai: 4 policy ContractCatalog.* sinh ra nhung chua endpoint nao tieu thu (display-layer khac API-layer, #82); F6 ten index IX_ giu EF-default (NIT). Suite 598/0 (45D+553I). Mig 70 ap SQL Server that OK. Drift DELTA: MenuKeys.All 55->56, policies 220->224, bang 96->97. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,62 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SolutionErp.Application.Master.Catalogs;
|
||||
|
||||
namespace SolutionErp.Api.Controllers;
|
||||
|
||||
// [K1 — S164, Mig 70] API "Danh mục Hợp đồng" (SOL-CCM-SP-002, 86 dòng × 8 nhóm duyệt).
|
||||
//
|
||||
// Route nằm DƯỚI `api/catalogs/` để đồng bộ với 4 catalog cũ (FE dùng chung trang
|
||||
// KIND_CONFIG theo `:kind` → kind mới = `contract-catalog`), nhưng tách CONTROLLER
|
||||
// riêng cho gọn (CatalogsController đã 4 kind × 4 verb).
|
||||
//
|
||||
// AUTHZ (QUYẾT-1 S164 — mirror y CatalogsController.cs:12-14, KHÔNG policy key mới):
|
||||
// - class `[Authorize]` = mọi user đăng nhập ĐỌC được → picker chọn hạng mục ở
|
||||
// màn tạo phiếu KHKK (K2) không bị 403 vì thiếu grant leaf (gotcha #44/#85).
|
||||
// - mọi action GHI `[Authorize(Roles = "Admin")]` — sửa danh mục là việc quản trị,
|
||||
// KHÔNG phải quyền của drafter phiếu.
|
||||
|
||||
[ApiController]
|
||||
[Route("api/catalogs/contract-catalog")]
|
||||
[Authorize]
|
||||
public class ContractCatalogController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<List<ContractCatalogEntryDto>> List(
|
||||
[FromQuery] string? groupCode,
|
||||
[FromQuery] int? approvalGroup,
|
||||
[FromQuery] bool includeInactive,
|
||||
[FromQuery] string? q,
|
||||
CancellationToken ct)
|
||||
=> await mediator.Send(new ListContractCatalogQuery(groupCode, approvalGroup, includeInactive, q), ct);
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<ContractCatalogEntryDto> Get(Guid id, CancellationToken ct)
|
||||
=> await mediator.Send(new GetContractCatalogEntryQuery(id), ct);
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<IActionResult> Create([FromBody] CreateContractCatalogEntryCommand body, CancellationToken ct)
|
||||
{
|
||||
var id = await mediator.Send(body, ct);
|
||||
return CreatedAtAction(nameof(Get), new { id }, new { id });
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateContractCatalogEntryCommand body, CancellationToken ct)
|
||||
{
|
||||
if (id != body.Id) return BadRequest("Id mismatch");
|
||||
await mediator.Send(body, ct);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
await mediator.Send(new DeleteContractCatalogEntryCommand(id), ct);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,8 @@ public interface IApplicationDbContext
|
||||
DbSet<MaterialItem> MaterialItems { get; }
|
||||
DbSet<ServiceItem> ServiceItems { get; }
|
||||
DbSet<WorkItem> WorkItems { get; }
|
||||
// [K1 S164 — Mig 70] Danh mục Hợp đồng SOL-CCM-SP-002 (86 dòng × 8 nhóm duyệt)
|
||||
DbSet<ContractCatalogEntry> ContractCatalogEntries { get; }
|
||||
DbSet<MenuItem> MenuItems { get; }
|
||||
DbSet<Permission> Permissions { get; }
|
||||
DbSet<User> Users { get; }
|
||||
|
||||
@ -0,0 +1,226 @@
|
||||
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;
|
||||
|
||||
// [K1 — S164, Mig 70] CRUD CQRS cho "Danh mục Hợp đồng" (SOL-CCM-SP-002, 86 dòng).
|
||||
// File RIÊNG cạnh CatalogsFeatures.cs (mega-file 4 catalog cũ) vì shape khác hẳn:
|
||||
// có GroupCode + ApprovalGroup (1-8 = nhóm duyệt N1..N8) + 2 cột vai-ký dạng TEXT.
|
||||
//
|
||||
// Endpoints (ContractCatalogController, route `api/catalogs/contract-catalog`):
|
||||
// GET /api/catalogs/contract-catalog — list (filter groupCode/approvalGroup/q;
|
||||
// mặc định CHỈ trả IsActive=true — spec K1.c `IncludeInactive?` mặc-định-loại;
|
||||
// truyền includeInactive=true để thấy cả dòng ngừng hiệu lực — gate-K1 F4 @S165)
|
||||
// GET /api/catalogs/contract-catalog/{id} — get by id
|
||||
// POST /api/catalogs/contract-catalog — create (Admin)
|
||||
// PUT /api/catalogs/contract-catalog/{id} — update (Admin)
|
||||
// DELETE /api/catalogs/contract-catalog/{id} — soft delete (Admin)
|
||||
//
|
||||
// KHÔNG paging: 86 dòng, dùng chung cho trang quản trị + picker chọn hạng mục (K2).
|
||||
// Dup-Code check chạy trên ACTIVE-set (query filter !IsDeleted) — khớp UNIQUE index
|
||||
// filtered `[IsDeleted] = 0` ⇒ Code của row đã xoá mềm được tái sử dụng (gotcha #57).
|
||||
|
||||
// ===== DTO =====
|
||||
|
||||
public record ContractCatalogEntryDto(
|
||||
Guid Id,
|
||||
string Code,
|
||||
string TenVi,
|
||||
string? TenEn,
|
||||
string GroupCode,
|
||||
int ApprovalGroup,
|
||||
string SignerRole,
|
||||
string? InitialsChain,
|
||||
string? NduqNote,
|
||||
bool IsActive,
|
||||
int SortOrder);
|
||||
|
||||
// ===== List =====
|
||||
|
||||
public record ListContractCatalogQuery(
|
||||
string? GroupCode = null,
|
||||
int? ApprovalGroup = null,
|
||||
bool IncludeInactive = false,
|
||||
string? Q = null) : IRequest<List<ContractCatalogEntryDto>>;
|
||||
|
||||
public class ListContractCatalogHandler(IApplicationDbContext db)
|
||||
: IRequestHandler<ListContractCatalogQuery, List<ContractCatalogEntryDto>>
|
||||
{
|
||||
public async Task<List<ContractCatalogEntryDto>> Handle(ListContractCatalogQuery req, CancellationToken ct)
|
||||
{
|
||||
var q = db.ContractCatalogEntries.AsNoTracking().AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.GroupCode))
|
||||
q = q.Where(x => x.GroupCode == req.GroupCode);
|
||||
if (req.ApprovalGroup is int g)
|
||||
q = q.Where(x => x.ApprovalGroup == g);
|
||||
if (!req.IncludeInactive)
|
||||
q = q.Where(x => x.IsActive);
|
||||
if (!string.IsNullOrWhiteSpace(req.Q))
|
||||
{
|
||||
var s = req.Q.ToLower();
|
||||
q = q.Where(x => x.Code.ToLower().Contains(s)
|
||||
|| x.TenVi.ToLower().Contains(s)
|
||||
|| (x.TenEn != null && x.TenEn.ToLower().Contains(s)));
|
||||
}
|
||||
|
||||
return await q.OrderBy(x => x.GroupCode).ThenBy(x => x.SortOrder).ThenBy(x => x.Code)
|
||||
.Select(x => new ContractCatalogEntryDto(
|
||||
x.Id, x.Code, x.TenVi, x.TenEn, x.GroupCode, x.ApprovalGroup,
|
||||
x.SignerRole, x.InitialsChain, x.NduqNote, x.IsActive, x.SortOrder))
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Get by id =====
|
||||
|
||||
public record GetContractCatalogEntryQuery(Guid Id) : IRequest<ContractCatalogEntryDto>;
|
||||
|
||||
public class GetContractCatalogEntryHandler(IApplicationDbContext db)
|
||||
: IRequestHandler<GetContractCatalogEntryQuery, ContractCatalogEntryDto>
|
||||
{
|
||||
public async Task<ContractCatalogEntryDto> Handle(GetContractCatalogEntryQuery req, CancellationToken ct)
|
||||
=> await db.ContractCatalogEntries.AsNoTracking()
|
||||
.Where(x => x.Id == req.Id)
|
||||
.Select(x => new ContractCatalogEntryDto(
|
||||
x.Id, x.Code, x.TenVi, x.TenEn, x.GroupCode, x.ApprovalGroup,
|
||||
x.SignerRole, x.InitialsChain, x.NduqNote, x.IsActive, x.SortOrder))
|
||||
.FirstOrDefaultAsync(ct)
|
||||
?? throw new NotFoundException("ContractCatalogEntry", req.Id);
|
||||
}
|
||||
|
||||
// ===== Create =====
|
||||
|
||||
public record CreateContractCatalogEntryCommand(
|
||||
string Code,
|
||||
string TenVi,
|
||||
string? TenEn,
|
||||
string GroupCode,
|
||||
int ApprovalGroup,
|
||||
string SignerRole,
|
||||
string? InitialsChain,
|
||||
string? NduqNote,
|
||||
bool IsActive,
|
||||
int SortOrder) : IRequest<Guid>;
|
||||
|
||||
// MaxLength khớp ĐÚNG EF config (ContractCatalogEntryConfiguration.cs:18-24) —
|
||||
// EF là source of truth, KHÔNG lấy số từ spec.
|
||||
public class CreateContractCatalogEntryValidator : AbstractValidator<CreateContractCatalogEntryCommand>
|
||||
{
|
||||
public CreateContractCatalogEntryValidator()
|
||||
{
|
||||
RuleFor(x => x.Code).NotEmpty().MaximumLength(20);
|
||||
RuleFor(x => x.TenVi).NotEmpty().MaximumLength(500);
|
||||
RuleFor(x => x.TenEn).MaximumLength(500);
|
||||
RuleFor(x => x.GroupCode).NotEmpty().MaximumLength(10);
|
||||
RuleFor(x => x.ApprovalGroup).InclusiveBetween(1, 8)
|
||||
.WithMessage("Nhóm duyệt phải từ 1 đến 8 (N1..N8).");
|
||||
RuleFor(x => x.SignerRole).NotEmpty().MaximumLength(100);
|
||||
RuleFor(x => x.InitialsChain).MaximumLength(300);
|
||||
RuleFor(x => x.NduqNote).MaximumLength(500);
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateContractCatalogEntryHandler(IApplicationDbContext db)
|
||||
: IRequestHandler<CreateContractCatalogEntryCommand, Guid>
|
||||
{
|
||||
public async Task<Guid> Handle(CreateContractCatalogEntryCommand req, CancellationToken ct)
|
||||
{
|
||||
if (await db.ContractCatalogEntries.AnyAsync(x => x.Code == req.Code, ct))
|
||||
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||
|
||||
var entity = new ContractCatalogEntry
|
||||
{
|
||||
Code = req.Code,
|
||||
TenVi = req.TenVi,
|
||||
TenEn = req.TenEn,
|
||||
GroupCode = req.GroupCode,
|
||||
ApprovalGroup = req.ApprovalGroup,
|
||||
SignerRole = req.SignerRole,
|
||||
InitialsChain = req.InitialsChain,
|
||||
NduqNote = req.NduqNote,
|
||||
IsActive = req.IsActive,
|
||||
SortOrder = req.SortOrder,
|
||||
};
|
||||
db.ContractCatalogEntries.Add(entity);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return entity.Id;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Update =====
|
||||
|
||||
public record UpdateContractCatalogEntryCommand(
|
||||
Guid Id,
|
||||
string Code,
|
||||
string TenVi,
|
||||
string? TenEn,
|
||||
string GroupCode,
|
||||
int ApprovalGroup,
|
||||
string SignerRole,
|
||||
string? InitialsChain,
|
||||
string? NduqNote,
|
||||
bool IsActive,
|
||||
int SortOrder) : IRequest;
|
||||
|
||||
public class UpdateContractCatalogEntryValidator : AbstractValidator<UpdateContractCatalogEntryCommand>
|
||||
{
|
||||
public UpdateContractCatalogEntryValidator()
|
||||
{
|
||||
RuleFor(x => x.Id).NotEmpty();
|
||||
RuleFor(x => x.Code).NotEmpty().MaximumLength(20);
|
||||
RuleFor(x => x.TenVi).NotEmpty().MaximumLength(500);
|
||||
RuleFor(x => x.TenEn).MaximumLength(500);
|
||||
RuleFor(x => x.GroupCode).NotEmpty().MaximumLength(10);
|
||||
RuleFor(x => x.ApprovalGroup).InclusiveBetween(1, 8)
|
||||
.WithMessage("Nhóm duyệt phải từ 1 đến 8 (N1..N8).");
|
||||
RuleFor(x => x.SignerRole).NotEmpty().MaximumLength(100);
|
||||
RuleFor(x => x.InitialsChain).MaximumLength(300);
|
||||
RuleFor(x => x.NduqNote).MaximumLength(500);
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateContractCatalogEntryHandler(IApplicationDbContext db)
|
||||
: IRequestHandler<UpdateContractCatalogEntryCommand>
|
||||
{
|
||||
public async Task Handle(UpdateContractCatalogEntryCommand req, CancellationToken ct)
|
||||
{
|
||||
var entity = await db.ContractCatalogEntries.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||
?? throw new NotFoundException("ContractCatalogEntry", req.Id);
|
||||
|
||||
if (entity.Code != req.Code && await db.ContractCatalogEntries.AnyAsync(x => x.Code == req.Code, ct))
|
||||
throw new ConflictException($"Mã '{req.Code}' đã tồn tại.");
|
||||
|
||||
entity.Code = req.Code;
|
||||
entity.TenVi = req.TenVi;
|
||||
entity.TenEn = req.TenEn;
|
||||
entity.GroupCode = req.GroupCode;
|
||||
entity.ApprovalGroup = req.ApprovalGroup;
|
||||
entity.SignerRole = req.SignerRole;
|
||||
entity.InitialsChain = req.InitialsChain;
|
||||
entity.NduqNote = req.NduqNote;
|
||||
entity.IsActive = req.IsActive;
|
||||
entity.SortOrder = req.SortOrder;
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Delete (soft) =====
|
||||
|
||||
public record DeleteContractCatalogEntryCommand(Guid Id) : IRequest;
|
||||
|
||||
public class DeleteContractCatalogEntryHandler(IApplicationDbContext db)
|
||||
: IRequestHandler<DeleteContractCatalogEntryCommand>
|
||||
{
|
||||
public async Task Handle(DeleteContractCatalogEntryCommand req, CancellationToken ct)
|
||||
{
|
||||
var entity = await db.ContractCatalogEntries.FirstOrDefaultAsync(x => x.Id == req.Id, ct)
|
||||
?? throw new NotFoundException("ContractCatalogEntry", req.Id);
|
||||
db.ContractCatalogEntries.Remove(entity); // Soft delete via AuditingInterceptor
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
@ -25,6 +25,10 @@ public static class MenuKeys
|
||||
public const string CatalogMaterials = "CatalogMaterials";
|
||||
public const string CatalogServices = "CatalogServices";
|
||||
public const string CatalogWorkItems = "CatalogWorkItems";
|
||||
// [K1 — S164, Mig 70] Danh mục Hợp đồng SOL-CCM-SP-002 (86 dòng × 8 nhóm duyệt).
|
||||
// 🔴 VÀO `All` (lệnh lead S164) ⇒ sinh 4 policy ContractCatalog.{Read,Create,Update,Delete}
|
||||
// ⇒ 2 số canonical `docs/STATUS.md` đổi: Menu keys +1 · Policies +4 (DERIVED |All|×|Actions|).
|
||||
public const string ContractCatalog = "ContractCatalog";
|
||||
|
||||
// Per-contract-type menu groups + 3 action leaves each.
|
||||
// Key format: Ct_<TypeCode>[_<Action>] — prefix `Ct_` distinguishes from
|
||||
@ -166,6 +170,7 @@ public static class MenuKeys
|
||||
Dashboard,
|
||||
Master, Suppliers, Projects, Departments,
|
||||
Catalogs, CatalogUnits, CatalogMaterials, CatalogServices, CatalogWorkItems,
|
||||
ContractCatalog, // [K1 Mig 70 — S164] Danh mục Hợp đồng SP-002
|
||||
Contracts, Forms, Reports,
|
||||
PurchaseEvaluations,
|
||||
Hrm, HrmHoSo, // Mig 34 — Phase 10.1
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
using SolutionErp.Domain.Common;
|
||||
|
||||
namespace SolutionErp.Domain.Master.Catalogs;
|
||||
|
||||
// [K1 — S164, Mig 70] Master data "Danh mục Hợp đồng" theo quy trình SOL-CCM-SP-002.v01
|
||||
// (bảng 5.1 A1..C3 + mục 5.2 CEO = 86 dòng). Mỗi dòng = 1 loại hạng mục hợp đồng,
|
||||
// gắn 1 trong 8 NHÓM DUYỆT (N1..N8) quyết định workflow KHKK nào áp cho phiếu.
|
||||
//
|
||||
// Vai ký lưu dạng TEXT (SignerRole + InitialsChain) — 2 TẦNG cố ý (QĐ-4 spec S164):
|
||||
// TEXT ở đây = chuỗi in trên khối ký giai đoạn 4 (bản cứng)
|
||||
// ⟂ ApprovalWorkflowLevel.ApproverUserId = NGƯỜI THẬT duyệt trên hệ thống.
|
||||
// Limitation đã khai: TEXT KHÔNG ràng buộc máy với người-duyệt-thật.
|
||||
public class ContractCatalogEntry : AuditableEntity
|
||||
{
|
||||
public string Code { get; set; } = ""; // Mã dòng danh mục ("A1-01" … "CEO-01") — UNIQUE filtered [IsDeleted]=0
|
||||
public string TenVi { get; set; } = ""; // Tên tiếng Việt
|
||||
public string? TenEn { get; set; } // Tên tiếng Anh
|
||||
public string GroupCode { get; set; } = ""; // Section SP-002: A1..A4, B1..B3, C1..C3, CEO
|
||||
public int ApprovalGroup { get; set; } // 1..8 → nhóm duyệt N1..N8 (validator range, không CHECK constraint)
|
||||
public string SignerRole { get; set; } = ""; // Vai KÝ CHÍNH dạng TEXT (TP.CCM / TP.PRO / PD-PM / CEO)
|
||||
public string? InitialsChain { get; set; } // Chuỗi ký nháy Ô1→Ô4 ("TBP.CCM → NV.CCM → TBP.EQU → NV.EQU"), bỏ N/A
|
||||
public string? NduqNote { get; set; } // Tham khảo Nghị định ủy quyền (OG-3) — seed null, nhập tay sau
|
||||
public bool IsActive { get; set; } = true; // Ẩn khỏi picker mà không cần xoá
|
||||
public int SortOrder { get; set; } // Thứ tự trong section (= STT trên bảng gốc)
|
||||
}
|
||||
@ -28,6 +28,8 @@ public class ApplicationDbContext
|
||||
public DbSet<MaterialItem> MaterialItems => Set<MaterialItem>();
|
||||
public DbSet<ServiceItem> ServiceItems => Set<ServiceItem>();
|
||||
public DbSet<WorkItem> WorkItems => Set<WorkItem>();
|
||||
// [K1 S164 — Mig 70] Danh mục Hợp đồng SOL-CCM-SP-002 (86 dòng × 8 nhóm duyệt)
|
||||
public DbSet<ContractCatalogEntry> ContractCatalogEntries => Set<ContractCatalogEntry>();
|
||||
public DbSet<MenuItem> MenuItems => Set<MenuItem>();
|
||||
public DbSet<Permission> Permissions => Set<Permission>();
|
||||
public DbSet<ContractTemplate> ContractTemplates => Set<ContractTemplate>();
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using SolutionErp.Domain.Master.Catalogs;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||
|
||||
// [K1 — S164, Mig 70] EF config cho danh mục Hợp đồng SOL-CCM-SP-002 (86 dòng).
|
||||
// File RIÊNG (không nhét vào CatalogsConfiguration.cs) — mirror khuôn PE/KHKK
|
||||
// 1-entity-1-file cho entity mới. Pattern: Code unique FILTERED [IsDeleted]=0
|
||||
// (gotcha #57 — soft-delete phải filter) + query filter soft-delete + index
|
||||
// ApprovalGroup cho picker K2 lọc theo nhóm phiếu.
|
||||
public class ContractCatalogEntryConfiguration : IEntityTypeConfiguration<ContractCatalogEntry>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ContractCatalogEntry> b)
|
||||
{
|
||||
b.ToTable("ContractCatalogEntries");
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.Code).HasMaxLength(20).IsRequired();
|
||||
b.Property(x => x.TenVi).HasMaxLength(500).IsRequired();
|
||||
b.Property(x => x.TenEn).HasMaxLength(500);
|
||||
b.Property(x => x.GroupCode).HasMaxLength(10).IsRequired();
|
||||
b.Property(x => x.SignerRole).HasMaxLength(100).IsRequired();
|
||||
b.Property(x => x.InitialsChain).HasMaxLength(300);
|
||||
b.Property(x => x.NduqNote).HasMaxLength(500);
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
b.Property(x => x.SortOrder).HasDefaultValue(0);
|
||||
b.HasIndex(x => x.Code).IsUnique().HasFilter("[IsDeleted] = 0");
|
||||
b.HasIndex(x => x.ApprovalGroup);
|
||||
b.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@ -126,6 +126,9 @@ public static class DbInitializer
|
||||
// S55 (2026-06-09) — Master data thật của công ty (62 dự án + 71 hạng mục +
|
||||
// 3 NCC). NOT gated DemoSeed — coexist với demo data qua per-code idempotent.
|
||||
await SeedRealMasterDataAsync(db, logger);
|
||||
// [K1 — S164, Mig 70] Danh mục Hợp đồng SOL-CCM-SP-002 (86 dòng, 8 nhóm duyệt).
|
||||
// NOT gated DemoSeed — master data thật, prod cần ngay deploy đầu (gotcha #51).
|
||||
await SeedContractCatalogAsync(db, logger);
|
||||
|
||||
// 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.
|
||||
@ -1767,6 +1770,10 @@ public static class DbInitializer
|
||||
(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"),
|
||||
// [K1 Mig 70 — S164] Danh mục Hợp đồng SOL-CCM-SP-002 (86 dòng × 8 nhóm duyệt).
|
||||
// Leaf thứ 5 dưới nhóm "Danh mục chi tiết". Key CÓ trong MenuKeys.All ⇒ Admin
|
||||
// được grant tự động qua loop SeedAdminPermissionsAsync :2058.
|
||||
(MenuKeys.ContractCatalog, "Danh mục Hợp đồng", MenuKeys.Catalogs, 5, "FileSpreadsheet"),
|
||||
// [S57] Order 30→31: nhường slot 30 cho nhóm "Cá nhân" (đứng ngay sau Văn phòng số = 29, mirror Puro).
|
||||
(MenuKeys.Contracts, "Duyệt hợp đồng", null, 31, "FileText"), // [S159] rename theo 4-GĐ (owner tab strip)
|
||||
// [S159] 2 root PLACEHOLDER GĐ2/GĐ4 — menu = 4 giai đoạn (owner). FE staticMap trỏ
|
||||
@ -3137,6 +3144,185 @@ public static class DbInitializer
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// [K1 — S164, Mig 70] Danh mục Hợp đồng theo quy trình SOL-CCM-SP-002.v01
|
||||
// (bảng 5.1 mục A1..C3 = 85 dòng + mục 5.2 CEO 1 dòng = 86 row). Mỗi dòng gắn 1
|
||||
// trong 8 NHÓM DUYỆT N1..N8 — nhóm quyết định workflow KHKK nào áp cho phiếu (K2/K3).
|
||||
//
|
||||
// NOT gated DemoSeed:Disabled — đây là INFRASTRUCTURE master data thật (mirror
|
||||
// SeedRealMasterDataAsync, xem note :2893 gotcha #51), phải có mặt prod ngay deploy đầu.
|
||||
//
|
||||
// Idempotent PER-CODE + `IgnoreQueryFilters()` — né #75/#76 (resurrect): row đã
|
||||
// xoá-mềm VẪN tính là "đã tồn tại" ⇒ KHÔNG re-create mỗi restart. Cố ý LÀM ĐÚNG
|
||||
// HƠN khuôn :2970 (khuôn dò `db.Projects.Select(p => p.Code)` KHÔNG IgnoreQueryFilters
|
||||
// ⇒ row soft-deleted vô hình → hồi sinh mỗi lần boot).
|
||||
//
|
||||
// SignerRole + InitialsChain = TEXT denorm theo nhóm (QĐ-4 spec S164: TEXT in trên
|
||||
// khối ký bản cứng GĐ4 ⟂ ApprovalWorkflowLevel.ApproverUserId = người duyệt THẬT
|
||||
// trên hệ thống). Lấy từ lookup 8-nhóm rồi ghi vào TỪNG row (denorm per-row) —
|
||||
// chép tay cùng chuỗi 86 lần dễ lệch ký tự.
|
||||
private static async Task SeedContractCatalogAsync(ApplicationDbContext db, ILogger logger)
|
||||
{
|
||||
// 8 nhóm duyệt — (Ký chính, Ký nháy Ô1→Ô4). Ô "N/A" trong bảng gốc đã BỎ.
|
||||
var signByGroup = new Dictionary<int, (string SignerRole, string InitialsChain)>
|
||||
{
|
||||
[1] = ("TP.CCM", "TBP.CCM → NV.CCM → TBP.EQU → NV.EQU"),
|
||||
[2] = ("TP.CCM", "TBP.CCM → PD/PM → SM → QS"),
|
||||
[3] = ("TP.CCM", "TBP.CCM → TPB/TBP → TBP/NV.PB"),
|
||||
[4] = ("TP.PRO", "TBP.PRO → NV.PRO"),
|
||||
[5] = ("TP.PRO", "TP.CCM → TBP.PRO → PD/PM/SM → QS"),
|
||||
[6] = ("TP.PRO", "TP.CCM → TBP.PRO → PD/PM/SM → MEP SM"),
|
||||
[7] = ("PD/PM", "TP.CCM → SM → QS"),
|
||||
[8] = ("CEO", "TP.CCM → TP.PRO"),
|
||||
};
|
||||
|
||||
// (Code, TenVi, TenEn, GroupCode, ApprovalGroup 1-8, SortOrder = STT trong section)
|
||||
// Nguồn: transcribe 7 ảnh SOL-CCM-SP-002.v01 trang 5-9 (OG-6 owner soát).
|
||||
var entries = new (string Code, string TenVi, string TenEn, string GroupCode, int ApprovalGroup, int SortOrder)[]
|
||||
{
|
||||
// ----- A1 · Thiết bị, máy móc, vật tư phụ (5) → N1 -----
|
||||
("A1-01", "Thuê thiết bị/máy móc/dụng cụ thi công/thiết bị an toàn lao động/thiết bị văn phòng (máy photo, máy in)", "Rental of construction equipment, machinery, and tools; occupational safety equipment; and office equipment (photocopiers, printers)", "A1", 1, 1),
|
||||
("A1-02", "Thuê container văn phòng, văn phòng BCH CT, nhà vệ sinh, nhà bảo vệ", "Rental of office containers, site offices, toilets, and guardhouses", "A1", 1, 2),
|
||||
("A1-03", "Cung cấp vật tư phụ phục vụ công tác tạm/tiện ích công trường", "Supply of materials for temporary works and site utilities", "A1", 1, 3),
|
||||
("A1-04", "Cung cấp vật tư phụ thi công: nylon bảo dưỡng, Sika, khoan cấy, …", "Supply of auxiliary construction materials: curing nylon sheets, Sika products, chemical anchors, etc", "A1", 1, 4),
|
||||
("A1-05", "Cung cấp vật tư biện pháp: sika, lưới mắt cáo, thanh trương nở, …", "Supply of method-related materials: Sika products, wire mesh, expansion bars, etc", "A1", 1, 5),
|
||||
|
||||
// ----- A2 · Kết cấu, hạ tầng, phá dỡ (1) → N2 -----
|
||||
("A2-01", "Thầu phụ hạ tầng & phá dỡ", "Infrastructure and demolition subcontracting works", "A2", 2, 1),
|
||||
|
||||
// ----- A3 · Dịch vụ hỗ trợ thi công (9) → N2 -----
|
||||
("A3-01", "Dịch vụ ca cẩu", "Crane rental services", "A3", 2, 1),
|
||||
("A3-02", "Dịch vụ vận chuyển", "Transportation services", "A3", 2, 2),
|
||||
("A3-03", "Trắc đạc", "Surveying services", "A3", 2, 3),
|
||||
("A3-04", "Thí nghiệm", "Testing services", "A3", 2, 4),
|
||||
("A3-05", "Bảo vệ", "Security services", "A3", 2, 5),
|
||||
("A3-06", "Dịch vụ giấy phép, tư vấn, thiết kế (kể cả dịch vụ tư vấn MEP)", "Licensing, consultancy, and design services (including MEP consultancy services)", "A3", 2, 6),
|
||||
("A3-07", "Điện tạm", "Temporary power supply", "A3", 2, 7),
|
||||
("A3-08", "Cấp và thoát nước tạm", "Temporary water supply and drainage", "A3", 2, 8),
|
||||
("A3-09", "Cung cấp tiện ích công trường: tủ hồ sơ, bàn ghế, internet, văn phòng phẩm, thiết bị bảo hộ lao động (PPE), nước uống, dịch vụ y tế, bảng biểu an toàn, …", "Supply of site utilities: filing cabinets, desks and chairs, internet, stationery, personal protective equipment (PPE), drinking water, medical services, safety signage, etc.", "A3", 2, 9),
|
||||
|
||||
// ----- A4 · Hoạt động văn phòng (5) → N3 -----
|
||||
("A4-01", "Thuê văn phòng", "Office leasing", "A4", 3, 1),
|
||||
("A4-02", "Văn phòng phẩm", "Stationery", "A4", 3, 2),
|
||||
("A4-03", "Bảo hiểm cháy nổ văn phòng", "Office fire and explosion insurance", "A4", 3, 3),
|
||||
("A4-04", "Thiết bị máy tính, phần mềm", "Computer equipment and software", "A4", 3, 4),
|
||||
("A4-05", "Các hợp đồng khác liên quan đến hoạt động văn phòng", "Other contracts related to office operations", "A4", 3, 5),
|
||||
|
||||
// ----- B1 · Vật tư (23) → N4 -----
|
||||
("B1-01", "Đá", "Stone", "B1", 4, 1),
|
||||
("B1-02", "Cát", "Sand", "B1", 4, 2),
|
||||
("B1-03", "Cừ tràm", "Mangrove piles", "B1", 4, 3),
|
||||
("B1-04", "Bê tông", "Concrete", "B1", 4, 4),
|
||||
("B1-05", "Xi măng", "Cement", "B1", 4, 5),
|
||||
("B1-06", "Thép", "Steel", "B1", 4, 6),
|
||||
("B1-07", "Ván phủ phim", "Film-faced plywood", "B1", 4, 7),
|
||||
("B1-08", "Gạch ốp lát", "Floor and wall tiles", "B1", 4, 8),
|
||||
("B1-09", "Gạch xây", "Bricks", "B1", 4, 9),
|
||||
("B1-10", "Nylon", "Nylon sheets", "B1", 4, 10),
|
||||
("B1-11", "Vữa grouting", "Grouting", "B1", 4, 11),
|
||||
("B1-12", "Chất tăng cứng bề mặt", "Hardener", "B1", 4, 12),
|
||||
("B1-13", "Sơn nước", "Emulsion paint", "B1", 4, 13),
|
||||
("B1-14", "Sơn dầu", "Oil-based paint", "B1", 4, 14),
|
||||
("B1-15", "Bột matic", "Matic", "B1", 4, 15),
|
||||
("B1-16", "Vật liệu hoàn thiện sàn", "Floor finishing materials", "B1", 4, 16),
|
||||
("B1-17", "Thanh cản nước", "Waterbar", "B1", 4, 17),
|
||||
("B1-18", "Vật liệu chống thấm", "Waterproofing", "B1", 4, 18),
|
||||
("B1-19", "Đá granite", "Granite stone", "B1", 4, 19),
|
||||
("B1-20", "Thiết bị vệ sinh", "Sanitary equipment", "B1", 4, 20),
|
||||
("B1-21", "Vật liệu hoàn thiện", "Finishing materials", "B1", 4, 21),
|
||||
("B1-22", "Vật tư, thiết bị MEP (cáp điện, tủ điện, máy móc của hệ MEP)", "MEP materials and equipment (electrical cables, panels, machinery for MEP systems)", "B1", 4, 22),
|
||||
("B1-23", "Vật tư chính khác", "Other main materials", "B1", 4, 23),
|
||||
|
||||
// ----- B2 · Thầu phụ xây dựng (14) → N5 -----
|
||||
// ⚠️ Ảnh gốc đánh số 1-13 nhưng TRÙNG số "7" ×2 (kim loại + cảnh quan);
|
||||
// lead transcribe đánh lại 01-14 ⇒ B2 = 14 dòng (KHÔNG phải 13). Giữ AS-IS.
|
||||
("B2-01", "Ép cọc, cừ Larsen", "Pile works and sheet pile", "B2", 5, 1),
|
||||
("B2-02", "Trần thạch cao", "Gypsum ceiling works", "B2", 5, 2),
|
||||
("B2-03", "Đá granite", "Granite", "B2", 5, 3),
|
||||
("B2-04", "Kết cấu thép", "Steel structure", "B2", 5, 4),
|
||||
("B2-05", "Cửa đi, cửa sổ", "Doors and windows", "B2", 5, 5),
|
||||
("B2-06", "Cửa chống cháy, thoát hiểm", "Fire-rated and emergency exit doors", "B2", 5, 6),
|
||||
("B2-07", "Công tác kim loại", "Metal", "B2", 5, 7), // ảnh: số 7 thứ nhất
|
||||
("B2-08", "Cảnh quan", "Landscaping", "B2", 5, 8), // ảnh: số 7 thứ hai ⚠️
|
||||
("B2-09", "Công tác kính", "Glazing", "B2", 5, 9), // ảnh: số 8
|
||||
("B2-10", "Rèm cửa", "Curtain", "B2", 5, 10), // ảnh: 9
|
||||
("B2-11", "Nội thất", "Interior", "B2", 5, 11), // ảnh: 10
|
||||
("B2-12", "Chống thấm", "Waterproofing", "B2", 5, 12), // ảnh: 11
|
||||
("B2-13", "Hoàn thiện sàn nâng", "Raised floor finishing", "B2", 5, 13),// ảnh: 12
|
||||
("B2-14", "Thầu phụ xây dựng khác", "Other civil subcontractors", "B2", 5, 14), // ảnh: 13
|
||||
|
||||
// ----- B3 · Thầu phụ MEP (15) → N6 -----
|
||||
("B3-01", "Hệ thống điện trung thế", "Medium-voltage power systems", "B3", 6, 1),
|
||||
("B3-02", "Máy phát điện", "Generator systems", "B3", 6, 2),
|
||||
("B3-03", "Hệ thống điện hạ thế và chiếu sáng", "Low-voltage power and lighting systems", "B3", 6, 3),
|
||||
("B3-04", "Hệ thống điện nhẹ ELV", "Extra Low Voltage (ELV) systems", "B3", 6, 4),
|
||||
("B3-05", "Hệ thống lạnh (Máy lạnh, Chiller, Cooling Tower, Process cooling...)", "Cooling systems (air conditioning, chillers, cooling towers, process cooling, etc.)", "B3", 6, 5),
|
||||
("B3-06", "Hệ thống thông gió", "Ventilation systems", "B3", 6, 6),
|
||||
("B3-07", "Hệ thống cấp thoát nước", "Water supply and drainage systems", "B3", 6, 7),
|
||||
("B3-08", "Hệ thống xử lý nước", "Water treatment systems", "B3", 6, 8),
|
||||
("B3-09", "Hệ thống chữa cháy", "Firefighting systems", "B3", 6, 9),
|
||||
("B3-10", "Hệ thống báo cháy", "Fire alarm systems", "B3", 6, 10),
|
||||
("B3-11", "Hệ thống chống sét", "Lightning protection systems", "B3", 6, 11),
|
||||
("B3-12", "Hệ thống Gas", "Gas systems", "B3", 6, 12),
|
||||
("B3-13", "Hệ thống khí nén", "Compressed air systems", "B3", 6, 13),
|
||||
("B3-14", "Thiết bị bếp", "Kitchen equipment systems", "B3", 6, 14),
|
||||
("B3-15", "Thầu phụ MEP khác", "Other MEP subcontractors", "B3", 6, 15),
|
||||
|
||||
// ----- C1 · Kết cấu, công tác tạm (6) → N7 -----
|
||||
("C1-01", "Nhân công công tác tạm, vệ sinh công nghiệp", "Labor for temporary works and industrial cleaning", "C1", 7, 1),
|
||||
("C1-02", "Nhân công khoán đội, công nhật", "Construction teams and daily workers", "C1", 7, 2),
|
||||
("C1-03", "Nhân công thầu phụ", "Subcontractor labor", "C1", 7, 3),
|
||||
("C1-04", "Thầu phụ trọn gói", "Lump-sum subcontractors", "C1", 7, 4),
|
||||
("C1-05", "Thầu phụ giàn giáo", "Scaffolding subcontractors", "C1", 7, 5),
|
||||
("C1-06", "Cơ giới, ca máy", "Machinery and equipment shifts", "C1", 7, 6),
|
||||
|
||||
// ----- C2 · Xây tô, sơn nước, hoàn thiện, ốp lát (3) → N7 -----
|
||||
("C2-01", "Thi công sơn nước", "Painting", "C2", 7, 1),
|
||||
("C2-02", "Thi công công tác hoàn thiện (xây tô, ốp lát)", "Finishing works (masonry, plastering, tiling)", "C2", 7, 2),
|
||||
("C2-03", "Thi công defect, bó vỉa, xoa nền, …", "Defect rectification works, kerb works, floor finishing/polishing, etc.", "C2", 7, 3),
|
||||
|
||||
// ----- C3 · Khác (4) → N7 -----
|
||||
("C3-01", "Thuê nhà ở cho BCH CT", "House rental", "C3", 7, 1),
|
||||
("C3-02", "Bảo hiểm tai nạn 24/24", "24/7 accident insurance", "C3", 7, 2),
|
||||
("C3-03", "Ban quản lý khu công nghiệp", "Industrial Park Management Board", "C3", 7, 3),
|
||||
("C3-04", "Mua bán điện ngoài mục đích sinh hoạt với Công ty Điện lực", "Electricity supply agreement for non-residential use with EVN", "C3", 7, 4),
|
||||
|
||||
// ----- 5.2 · CEO (1) → N8 -----
|
||||
("CEO-01", "Tất cả Hợp đồng chưa Ủy quyền", "All Contracts Not Subject to Authorization", "CEO", 8, 1),
|
||||
};
|
||||
|
||||
var existingCodes = await db.ContractCatalogEntries
|
||||
.IgnoreQueryFilters() // né #75/#76 — row soft-deleted vẫn chặn re-create
|
||||
.Select(x => x.Code)
|
||||
.ToListAsync();
|
||||
|
||||
var added = 0;
|
||||
foreach (var (code, tenVi, tenEn, groupCode, approvalGroup, sortOrder) in entries)
|
||||
{
|
||||
if (existingCodes.Contains(code)) continue;
|
||||
var sign = signByGroup[approvalGroup];
|
||||
db.ContractCatalogEntries.Add(new ContractCatalogEntry
|
||||
{
|
||||
Code = code,
|
||||
TenVi = tenVi,
|
||||
TenEn = tenEn,
|
||||
GroupCode = groupCode,
|
||||
ApprovalGroup = approvalGroup,
|
||||
SignerRole = sign.SignerRole,
|
||||
InitialsChain = sign.InitialsChain,
|
||||
NduqNote = null, // [OG-3] ảnh KHÔNG có số NĐUQ per-dòng → nhập tay sau
|
||||
IsActive = true,
|
||||
SortOrder = sortOrder,
|
||||
});
|
||||
added++;
|
||||
}
|
||||
|
||||
if (added > 0)
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
logger.LogInformation("Seeded {Count} contract catalog entries (S164 SP-002)", added);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SeedContractTemplatesAsync(ApplicationDbContext db, ILogger logger)
|
||||
{
|
||||
// Chỉ IsActive=true nếu file thực tế tồn tại trong wwwroot/templates/.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddContractCatalogEntries : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContractCatalogEntries",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Code = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
|
||||
TenVi = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
TenEn = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
GroupCode = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
|
||||
ApprovalGroup = table.Column<int>(type: "int", nullable: false),
|
||||
SignerRole = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
InitialsChain = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: true),
|
||||
NduqNote = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
IsActive = table.Column<bool>(type: "bit", nullable: false, defaultValue: true),
|
||||
SortOrder = table.Column<int>(type: "int", nullable: false, defaultValue: 0),
|
||||
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_ContractCatalogEntries", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractCatalogEntries_ApprovalGroup",
|
||||
table: "ContractCatalogEntries",
|
||||
column: "ApprovalGroup");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ContractCatalogEntries_Code",
|
||||
table: "ContractCatalogEntries",
|
||||
column: "Code",
|
||||
unique: true,
|
||||
filter: "[IsDeleted] = 0");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContractCatalogEntries");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3287,6 +3287,89 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||
b.ToTable("Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.ContractCatalogEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("ApprovalGroup")
|
||||
.HasColumnType("int");
|
||||
|
||||
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>("GroupCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.Property<string>("InitialsChain")
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("nvarchar(300)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("NduqNote")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("SignerRole")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasDefaultValue(0);
|
||||
|
||||
b.Property<string>("TenEn")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("TenVi")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("UpdatedBy")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApprovalGroup");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.IsUnique()
|
||||
.HasFilter("[IsDeleted] = 0");
|
||||
|
||||
b.ToTable("ContractCatalogEntries", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SolutionErp.Domain.Master.Catalogs.MaterialItem", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
||||
Reference in New Issue
Block a user