[CLAUDE] Domain+App+Infra+Api+FE-Admin+FE-User: S37 Mig 37 enum + Plan G-O3 Đề xuất full-stack
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m53s

Phase 10.3 G-O3 Đề xuất (Proposal) — Mig 37 enum extend +5 values + Mig 38
Proposal schema + BE CQRS 8 endpoint + FE 2 app SHA256 IDENTICAL.

Mig 37 (em main solo): extend ApprovalWorkflowApplicableType enum +5 values
  ProposalGeneral=4 / LeaveRequest=5 / OtRequest=6 / VehicleBooking=7 / ItTicket=8
  cookie-cutter Mig 22 pattern (Up/Down empty — enum mức Domain).

Mig 38 (em main solo): 4 entity Proposal (Code DX/YYYY/NNN) + ProposalAttachment
  + ProposalLevelOpinion (UNIQUE composite PEId+LevelId mirror PE Mig 26) +
  ProposalCodeSequence (Prefix PK atomic seq). 4 EF Config + 2 DbContext mod.

BE CQRS (em main solo ~700 LOC ProposalFeatures.cs sau Implementer truncate phase
exploration gotcha #53 5th + 529 Overload):
  - 4 Header handler (List paged + GetById detail + Create + UpdateDraft owner-OR-admin)
  - 4 Workflow handler (Submit gen MaDeXuat atomic + Approve UPSERT LevelOpinion advance + Reject + Return)
  - SERIALIZABLE transaction CodeGen
  - DTOs nested LevelOpinion với Step+Level metadata JOIN

ProposalsController 8 endpoint /api/proposals (List/GetById/Create/Update/Submit/Approve/Reject/Return)
class-level [Authorize] + handler-level owner-OR-admin guard.

DbInitializer: SeedSampleProposalWorkflowV2Async ~40 LOC seed QT-DX-V2-001 IsUserSelectable=true
NOT gated DemoSeed per gotcha #51. SeedMenuTreeAsync +4 row (Off_DeXuat sub-group + 3 leaf).

FE 2 app (em main solo + Implementer 529 fail fallback):
  - types/proposal.ts × 2 SHA256 IDENTICAL 95607052ff1138f2
  - ProposalsListPage.tsx × 2 IDENTICAL 603f0d9cf74cd09a — table 6 cột + Status badge + filter
  - ProposalCreatePage.tsx × 2 IDENTICAL 6aed3a76563dd576 — Form Header card
  - ProposalDetailPage.tsx × 2 IDENTICAL 3dc229ea8dcc9bc0 — 3 Section + WorkflowActions
  - Pattern 16-bis 8× cumulative (App.tsx + menuKeys + Layout staticMap 3 entry)

Verify:
- dotnet build PASS 0 error 2 warning pre-existing DocxRenderer
- dotnet test 130/130 PASS baseline preserve
- npm build × 2 PASS (fe-admin 14.72s + fe-user 6.40s)
- SHA256 verify 4 file × 2 app all IDENTICAL

Pattern reinforced cumulative S37:
- Pattern 12-bis cross-module mirror 11× (PE V2 → Proposal V2 ApproveV2)
- Pattern 16-bis 4-place mirror cross-app 8×
- gotcha #53 5th occurrence Implementer mid-exploration truncation + 529 Overload 1× — em main solo fallback proven

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-28 15:51:14 +07:00
parent 37593f95b5
commit de1c378279
35 changed files with 13650 additions and 0 deletions

View File

@ -102,6 +102,12 @@ public class ApplicationDbContext
public DbSet<MeetingBooking> MeetingBookings => Set<MeetingBooking>();
public DbSet<MeetingBookingAttendee> MeetingBookingAttendees => Set<MeetingBookingAttendee>();
// Phase 10.3 G-O3 (Mig 38 — S37) — Đề xuất + Attachment + LevelOpinion + CodeSequence.
public DbSet<Proposal> Proposals => Set<Proposal>();
public DbSet<ProposalAttachment> ProposalAttachments => Set<ProposalAttachment>();
public DbSet<ProposalLevelOpinion> ProposalLevelOpinions => Set<ProposalLevelOpinion>();
public DbSet<ProposalCodeSequence> ProposalCodeSequences => Set<ProposalCodeSequence>();
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);

View File

@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SolutionErp.Domain.Office;
namespace SolutionErp.Infrastructure.Persistence.Configurations;
// EF Mig 38 G-O3 (S37) — Attachment cho Đề xuất.
// FK Cascade Proposal (wipe khi xoá Đề xuất).
public class ProposalAttachmentConfiguration : IEntityTypeConfiguration<ProposalAttachment>
{
public void Configure(EntityTypeBuilder<ProposalAttachment> e)
{
e.ToTable("ProposalAttachments");
e.Property(x => x.FileName).HasMaxLength(500).IsRequired();
e.Property(x => x.FilePath).HasMaxLength(1000).IsRequired();
e.Property(x => x.MimeType).HasMaxLength(200);
e.Property(x => x.UploadedByFullName).HasMaxLength(200).IsRequired();
e.HasOne(x => x.Proposal)
.WithMany(p => p.Attachments)
.HasForeignKey(x => x.ProposalId)
.OnDelete(DeleteBehavior.Cascade);
e.HasIndex(x => x.ProposalId);
}
}

View File

@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SolutionErp.Domain.Office;
namespace SolutionErp.Infrastructure.Persistence.Configurations;
// EF Mig 38 G-O3 (S37) — Sequence generator cho mã Đề xuất.
// Mirror PurchaseEvaluationCodeSequenceConfiguration pattern.
// PK = Prefix (string) — "DX/2026". LastSeq tăng atomic.
public class ProposalCodeSequenceConfiguration : IEntityTypeConfiguration<ProposalCodeSequence>
{
public void Configure(EntityTypeBuilder<ProposalCodeSequence> e)
{
e.ToTable("ProposalCodeSequences");
e.HasKey(x => x.Prefix);
e.Property(x => x.Prefix).HasMaxLength(20); // "DX/2026" max 7 chars OK
}
}

View File

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SolutionErp.Domain.Office;
namespace SolutionErp.Infrastructure.Persistence.Configurations;
// EF Mig 38 G-O3 (S37) — Đề xuất aggregate root.
// Cookie-cutter mirror PurchaseEvaluationConfiguration pattern.
public class ProposalConfiguration : IEntityTypeConfiguration<Proposal>
{
public void Configure(EntityTypeBuilder<Proposal> e)
{
e.ToTable("Proposals");
e.Property(x => x.MaDeXuat).HasMaxLength(50); // "DX/2026/001"
e.Property(x => x.Title).HasMaxLength(300).IsRequired();
e.Property(x => x.Description).HasMaxLength(5000);
e.Property(x => x.AmountEstimate).HasColumnType("decimal(18,2)");
e.Property(x => x.Status).HasConversion<int>();
e.Property(x => x.RejectedFromStatus).HasConversion<int>();
// MaDeXuat optional UNIQUE (gen sau Submit lần đầu)
e.HasIndex(x => x.MaDeXuat).IsUnique().HasFilter("[MaDeXuat] IS NOT NULL");
// Index Status + Drafter cho list filter/inbox
e.HasIndex(x => x.Status);
e.HasIndex(x => x.DrafterUserId);
e.HasIndex(x => x.DepartmentId);
e.HasIndex(x => x.ApprovalWorkflowId);
}
}

View File

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SolutionErp.Domain.Office;
namespace SolutionErp.Infrastructure.Persistence.Configurations;
// EF Mig 38 G-O3 (S37) — Ý kiến cấp duyệt V2 dynamic. UPSERT auto từ ApproveV2Async.
// Cookie-cutter mirror PurchaseEvaluationLevelOpinionConfiguration (Mig 26).
public class ProposalLevelOpinionConfiguration : IEntityTypeConfiguration<ProposalLevelOpinion>
{
public void Configure(EntityTypeBuilder<ProposalLevelOpinion> e)
{
e.ToTable("ProposalLevelOpinions");
e.Property(x => x.Comment).HasMaxLength(2000);
e.Property(x => x.SignedByFullName).HasMaxLength(200).IsRequired();
e.HasOne(x => x.Proposal)
.WithMany(p => p.LevelOpinions)
.HasForeignKey(x => x.ProposalId)
.OnDelete(DeleteBehavior.Cascade);
e.HasOne(x => x.Level)
.WithMany()
.HasForeignKey(x => x.ApprovalWorkflowLevelId)
.OnDelete(DeleteBehavior.Restrict);
e.HasIndex(x => new { x.ProposalId, x.ApprovalWorkflowLevelId }).IsUnique();
e.HasIndex(x => x.ApprovalWorkflowLevelId);
}
}

View File

@ -132,6 +132,11 @@ public static class DbInitializer
// Idempotent: skip nếu workflow QT-HD-V2-001 đã tồn tại.
await SeedSampleContractWorkflowV2Async(db, userManager, logger);
// Phase 10.3 G-O3 (Mig 38 — S37 2026-05-28). Infrastructure seed sample workflow
// ApplicableType=ProposalGeneral=4 cho Drafter Workspace dropdown.
// NOT gated DemoSeed per gotcha #51 INFRASTRUCTURE seed.
await SeedSampleProposalWorkflowV2Async(db, userManager, logger);
await WarnDefaultAdminPasswordAsync(userManager, logger);
}
@ -239,6 +244,56 @@ public static class DbInitializer
logger.LogInformation("Seeded sample ApprovalWorkflow V2 for Contract: QT-HD-V2-001 v01");
}
// Phase 10.3 G-O3 (Mig 38 — S37) — Sample workflow Proposal cho UAT.
// Mirror SeedSampleContractWorkflowV2Async pattern. INFRASTRUCTURE seed NOT gated.
private static async Task SeedSampleProposalWorkflowV2Async(
ApplicationDbContext db, UserManager<User> userManager, ILogger logger)
{
var hasAnyProposal = await db.ApprovalWorkflows
.AnyAsync(w => w.ApplicableType == ApprovalWorkflowApplicableType.ProposalGeneral);
if (hasAnyProposal) return;
var approver = await userManager.FindByEmailAsync("binh.le@solutions.com.vn");
if (approver is null)
{
logger.LogWarning("SeedSampleProposalWorkflowV2Async: skip — approver binh.le@solutions.com.vn not found");
return;
}
var ccmDept = await db.Departments.FirstOrDefaultAsync(d => d.Code == "CCM");
var wf = new ApprovalWorkflow
{
Code = "QT-DX-V2-001",
Version = 1,
ApplicableType = ApprovalWorkflowApplicableType.ProposalGeneral,
Name = "Quy trình duyệt Đề xuất mẫu V2",
Description = "Sample seed UAT — 1 Bước Phòng CCM × 1 Cấp (Lê Văn Bình). Admin có thể clone tạo version mới qua Designer.",
IsActive = true,
IsUserSelectable = true,
ActivatedAt = DateTime.UtcNow,
};
var step = new ApprovalWorkflowStep
{
ApprovalWorkflow = wf,
Order = 1,
Name = "Bước 1 - Phòng CCM",
DepartmentId = ccmDept?.Id,
};
var level = new ApprovalWorkflowLevel
{
Step = step,
Order = 1,
Name = "Cấp 1",
ApproverUserId = approver.Id,
};
wf.Steps.Add(step);
step.Levels.Add(level);
db.ApprovalWorkflows.Add(wf);
await db.SaveChangesAsync();
logger.LogInformation("Seeded sample ApprovalWorkflow V2 for Proposal: QT-DX-V2-001 v01");
}
// 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)
@ -1507,6 +1562,11 @@ public static class DbInitializer
(MenuKeys.OffPhongHopView, "Xem lịch", MenuKeys.OffPhongHop, 1, "CalendarDays"),
(MenuKeys.OffPhongHopManage, "Quản lý phòng", MenuKeys.OffPhongHop, 2, "Building2"),
(MenuKeys.OffPhongHopBook, "Đặt phòng", MenuKeys.OffPhongHop, 3, "CalendarPlus"),
// Phase 10.3 G-O3 (Mig 38 — S37 2026-05-28). Sub-group "Đề xuất" + 3 leaf.
(MenuKeys.OffDeXuat, "Đề xuất", MenuKeys.Off, 3, "FileSignature"),
(MenuKeys.OffDeXuatList, "Danh sách", MenuKeys.OffDeXuat, 1, "List"),
(MenuKeys.OffDeXuatCreate, "Tạo mới", MenuKeys.OffDeXuat, 2, "Plus"),
(MenuKeys.OffDeXuatInbox, "Inbox duyệt", MenuKeys.OffDeXuat, 3, "Inbox"),
};
// Per-type sub-menu under Contracts: 1 group + 3 leaves each

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SolutionErp.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class ExtendApplicableTypeForWorkflowApps : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@ -0,0 +1,184 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SolutionErp.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddProposals : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ProposalCodeSequences",
columns: table => new
{
Prefix = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
LastSeq = table.Column<int>(type: "int", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProposalCodeSequences", x => x.Prefix);
});
migrationBuilder.CreateTable(
name: "Proposals",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MaDeXuat = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
Title = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", maxLength: 5000, nullable: true),
AmountEstimate = table.Column<decimal>(type: "decimal(18,2)", nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
DepartmentId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
DrafterUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ApprovalWorkflowId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
CurrentApprovalLevelOrder = table.Column<int>(type: "int", nullable: true),
SlaDeadline = table.Column<DateTime>(type: "datetime2", nullable: true),
SlaWarningSent = table.Column<bool>(type: "bit", nullable: false),
RejectedFromStatus = table.Column<int>(type: "int", 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_Proposals", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ProposalAttachments",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ProposalId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
FileName = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
FilePath = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
FileSize = table.Column<long>(type: "bigint", nullable: false),
MimeType = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
UploadedByUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UploadedByFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, 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_ProposalAttachments", x => x.Id);
table.ForeignKey(
name: "FK_ProposalAttachments_Proposals_ProposalId",
column: x => x.ProposalId,
principalTable: "Proposals",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ProposalLevelOpinions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ProposalId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ApprovalWorkflowLevelId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Comment = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
SignedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
SignedByUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
SignedByFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, 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_ProposalLevelOpinions", x => x.Id);
table.ForeignKey(
name: "FK_ProposalLevelOpinions_ApprovalWorkflowLevels_ApprovalWorkflowLevelId",
column: x => x.ApprovalWorkflowLevelId,
principalTable: "ApprovalWorkflowLevels",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_ProposalLevelOpinions_Proposals_ProposalId",
column: x => x.ProposalId,
principalTable: "Proposals",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ProposalAttachments_ProposalId",
table: "ProposalAttachments",
column: "ProposalId");
migrationBuilder.CreateIndex(
name: "IX_ProposalLevelOpinions_ApprovalWorkflowLevelId",
table: "ProposalLevelOpinions",
column: "ApprovalWorkflowLevelId");
migrationBuilder.CreateIndex(
name: "IX_ProposalLevelOpinions_ProposalId_ApprovalWorkflowLevelId",
table: "ProposalLevelOpinions",
columns: new[] { "ProposalId", "ApprovalWorkflowLevelId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Proposals_ApprovalWorkflowId",
table: "Proposals",
column: "ApprovalWorkflowId");
migrationBuilder.CreateIndex(
name: "IX_Proposals_DepartmentId",
table: "Proposals",
column: "DepartmentId");
migrationBuilder.CreateIndex(
name: "IX_Proposals_DrafterUserId",
table: "Proposals",
column: "DrafterUserId");
migrationBuilder.CreateIndex(
name: "IX_Proposals_MaDeXuat",
table: "Proposals",
column: "MaDeXuat",
unique: true,
filter: "[MaDeXuat] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_Proposals_Status",
table: "Proposals",
column: "Status");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ProposalAttachments");
migrationBuilder.DropTable(
name: "ProposalCodeSequences");
migrationBuilder.DropTable(
name: "ProposalLevelOpinions");
migrationBuilder.DropTable(
name: "Proposals");
}
}
}

View File

@ -3623,6 +3623,227 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("MeetingRooms", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.Proposal", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<decimal?>("AmountEstimate")
.HasColumnType("decimal(18,2)");
b.Property<Guid?>("ApprovalWorkflowId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uniqueidentifier");
b.Property<int?>("CurrentApprovalLevelOrder")
.HasColumnType("int");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("DepartmentId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Description")
.HasMaxLength(5000)
.HasColumnType("nvarchar(max)");
b.Property<Guid>("DrafterUserId")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("MaDeXuat")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int?>("RejectedFromStatus")
.HasColumnType("int");
b.Property<DateTime?>("SlaDeadline")
.HasColumnType("datetime2");
b.Property<bool>("SlaWarningSent")
.HasColumnType("bit");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("ApprovalWorkflowId");
b.HasIndex("DepartmentId");
b.HasIndex("DrafterUserId");
b.HasIndex("MaDeXuat")
.IsUnique()
.HasFilter("[MaDeXuat] IS NOT NULL");
b.HasIndex("Status");
b.ToTable("Proposals", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.ProposalAttachment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
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>("FileName")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("FilePath")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<long>("FileSize")
.HasColumnType("bigint");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("MimeType")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("ProposalId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.Property<string>("UploadedByFullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("UploadedByUserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("ProposalId");
b.ToTable("ProposalAttachments", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.ProposalCodeSequence", b =>
{
b.Property<string>("Prefix")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<int>("LastSeq")
.HasColumnType("int");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Prefix");
b.ToTable("ProposalCodeSequences", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.ProposalLevelOpinion", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<Guid>("ApprovalWorkflowLevelId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Comment")
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
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<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<Guid>("ProposalId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("SignedAt")
.HasColumnType("datetime2");
b.Property<string>("SignedByFullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("SignedByUserId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("ApprovalWorkflowLevelId");
b.HasIndex("ProposalId", "ApprovalWorkflowLevelId")
.IsUnique();
b.ToTable("ProposalLevelOpinions", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b =>
{
b.Property<Guid>("Id")
@ -4887,6 +5108,36 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Booking");
});
modelBuilder.Entity("SolutionErp.Domain.Office.ProposalAttachment", b =>
{
b.HasOne("SolutionErp.Domain.Office.Proposal", "Proposal")
.WithMany("Attachments")
.HasForeignKey("ProposalId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Proposal");
});
modelBuilder.Entity("SolutionErp.Domain.Office.ProposalLevelOpinion", b =>
{
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflowLevel", "Level")
.WithMany()
.HasForeignKey("ApprovalWorkflowLevelId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("SolutionErp.Domain.Office.Proposal", "Proposal")
.WithMany("LevelOpinions")
.HasForeignKey("ProposalId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Level");
b.Navigation("Proposal");
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b =>
{
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflow", null)
@ -5126,6 +5377,13 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Attendees");
});
modelBuilder.Entity("SolutionErp.Domain.Office.Proposal", b =>
{
b.Navigation("Attachments");
b.Navigation("LevelOpinions");
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b =>
{
b.Navigation("Approvals");