[CLAUDE] Workflow: wire ApproveV2 + LevelOpinions cho 4 WorkflowApps module (Phase 11 P11-A)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m6s

Wire full approval workflow V2 cho Leave/OT/Travel/Vehicle — cookie-cutter
mirror Proposal (Mig 38). Trước đây skeleton Phase 1 (Create+List), giờ
ApproveV2 advance-level + UPSERT LevelOpinion + atomic codegen.

Schema (Mig 41 WireWorkflowAppsApprovalV2 — 84→89 tables, pure additive):
- 4 bảng {Leave,Ot,Travel,Vehicle}LevelOpinions (UNIQUE composite + Cascade
  parent + Restrict Level — mirror ProposalLevelOpinion)
- 1 bảng WorkflowAppCodeSequences (shared atomic MaDonTu, Prefix-keyed)
- 4 cột RejectedFromStatus (smart return tracking)
- enum ApprovalWorkflowApplicableType.TravelRequest = 9

Application (LeaveOt + TravelVehicle ApprovalFeatures.cs — 30 handler):
- GetById detail (Include LevelOpinions + JOIN Step/Level) · UpdateDraft
- Submit (gen MaDonTu + DaGuiDuyet + level=1, verify ApplicableType per module)
- Approve (verify actor==ApproverUserId OR Admin, UPSERT opinion latest-write-wins,
  advance level OR terminal DaDuyet, empty comment → placeholder)
- Reject (→TuChoi) · Return (→TraLai + RejectedFromStatus)

Api: 4 controller +6 route mỗi cái (GET/{id}, PUT/{id}, submit/approve/reject/return)
Infra: DbInitializer seed 4 workflow V2 mẫu (QT-NP/OT/CT/XE-V2-001) → UAT test ngay
FE: WorkflowAppDetailPage.tsx declarative 4-kind (fe-admin+fe-user SHA256 identical)
  — workflow status + opinion timeline + action buttons; gỡ banner skeleton + row nav

Tests: +11 WorkflowAppApproveV2Tests (130→141 PASS) — state machine + UPSERT
  invariant + guards + codegen + forbidden + placeholder (Leave full + Ot smoke)

Verify: build 0 error · 141 test PASS · FE build ×2 · reviewer checklist
  (ApplicableType per-module + cross-module DbSet + [Authorize] — no copy-paste bug)
Known-minor (unreachable): Reject/Return actor-check skip nếu CurrentApprovalLevelOrder
  null — nhưng DaGuiDuyet luôn có set (defer hardening).
ItTicket KHÔNG đụng (kanban, no workflow V2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-30 09:44:00 +07:00
parent ad1dea9349
commit e7b66cd52b
39 changed files with 10604 additions and 22 deletions

View File

@ -115,6 +115,13 @@ public class ApplicationDbContext
public DbSet<VehicleBooking> VehicleBookings => Set<VehicleBooking>();
public DbSet<ItTicket> ItTickets => Set<ItTicket>();
// Phase 11 P11-A (Mig 41) — LevelOpinions 4 module + shared CodeSequence.
public DbSet<LeaveRequestLevelOpinion> LeaveRequestLevelOpinions => Set<LeaveRequestLevelOpinion>();
public DbSet<OtRequestLevelOpinion> OtRequestLevelOpinions => Set<OtRequestLevelOpinion>();
public DbSet<TravelRequestLevelOpinion> TravelRequestLevelOpinions => Set<TravelRequestLevelOpinion>();
public DbSet<VehicleBookingLevelOpinion> VehicleBookingLevelOpinions => Set<VehicleBookingLevelOpinion>();
public DbSet<WorkflowAppCodeSequence> WorkflowAppCodeSequences => Set<WorkflowAppCodeSequence>();
// Phase 10.4 G-P1 (Mig 40 — S38) — Chấm công web GPS.
public DbSet<Attendance> Attendances => Set<Attendance>();

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 41 P11-A (Phase 11) — Ý kiến cấp duyệt V2 dynamic cho LeaveRequest.
// Cookie-cutter mirror ProposalLevelOpinionConfiguration (Mig 38).
public class LeaveRequestLevelOpinionConfiguration : IEntityTypeConfiguration<LeaveRequestLevelOpinion>
{
public void Configure(EntityTypeBuilder<LeaveRequestLevelOpinion> e)
{
e.ToTable("LeaveRequestLevelOpinions");
e.Property(x => x.Comment).HasMaxLength(2000);
e.Property(x => x.SignedByFullName).HasMaxLength(200).IsRequired();
e.HasOne(x => x.LeaveRequest)
.WithMany(p => p.LevelOpinions)
.HasForeignKey(x => x.LeaveRequestId)
.OnDelete(DeleteBehavior.Cascade);
e.HasOne(x => x.Level)
.WithMany()
.HasForeignKey(x => x.ApprovalWorkflowLevelId)
.OnDelete(DeleteBehavior.Restrict);
e.HasIndex(x => new { x.LeaveRequestId, x.ApprovalWorkflowLevelId }).IsUnique();
e.HasIndex(x => x.ApprovalWorkflowLevelId);
}
}

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 41 P11-A (Phase 11) — Ý kiến cấp duyệt V2 dynamic cho OtRequest.
// Cookie-cutter mirror ProposalLevelOpinionConfiguration (Mig 38).
public class OtRequestLevelOpinionConfiguration : IEntityTypeConfiguration<OtRequestLevelOpinion>
{
public void Configure(EntityTypeBuilder<OtRequestLevelOpinion> e)
{
e.ToTable("OtRequestLevelOpinions");
e.Property(x => x.Comment).HasMaxLength(2000);
e.Property(x => x.SignedByFullName).HasMaxLength(200).IsRequired();
e.HasOne(x => x.OtRequest)
.WithMany(p => p.LevelOpinions)
.HasForeignKey(x => x.OtRequestId)
.OnDelete(DeleteBehavior.Cascade);
e.HasOne(x => x.Level)
.WithMany()
.HasForeignKey(x => x.ApprovalWorkflowLevelId)
.OnDelete(DeleteBehavior.Restrict);
e.HasIndex(x => new { x.OtRequestId, x.ApprovalWorkflowLevelId }).IsUnique();
e.HasIndex(x => x.ApprovalWorkflowLevelId);
}
}

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 41 P11-A (Phase 11) — Ý kiến cấp duyệt V2 dynamic cho TravelRequest.
// Cookie-cutter mirror ProposalLevelOpinionConfiguration (Mig 38).
public class TravelRequestLevelOpinionConfiguration : IEntityTypeConfiguration<TravelRequestLevelOpinion>
{
public void Configure(EntityTypeBuilder<TravelRequestLevelOpinion> e)
{
e.ToTable("TravelRequestLevelOpinions");
e.Property(x => x.Comment).HasMaxLength(2000);
e.Property(x => x.SignedByFullName).HasMaxLength(200).IsRequired();
e.HasOne(x => x.TravelRequest)
.WithMany(p => p.LevelOpinions)
.HasForeignKey(x => x.TravelRequestId)
.OnDelete(DeleteBehavior.Cascade);
e.HasOne(x => x.Level)
.WithMany()
.HasForeignKey(x => x.ApprovalWorkflowLevelId)
.OnDelete(DeleteBehavior.Restrict);
e.HasIndex(x => new { x.TravelRequestId, x.ApprovalWorkflowLevelId }).IsUnique();
e.HasIndex(x => x.ApprovalWorkflowLevelId);
}
}

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 41 P11-A (Phase 11) — Ý kiến cấp duyệt V2 dynamic cho VehicleBooking.
// Cookie-cutter mirror ProposalLevelOpinionConfiguration (Mig 38).
public class VehicleBookingLevelOpinionConfiguration : IEntityTypeConfiguration<VehicleBookingLevelOpinion>
{
public void Configure(EntityTypeBuilder<VehicleBookingLevelOpinion> e)
{
e.ToTable("VehicleBookingLevelOpinions");
e.Property(x => x.Comment).HasMaxLength(2000);
e.Property(x => x.SignedByFullName).HasMaxLength(200).IsRequired();
e.HasOne(x => x.VehicleBooking)
.WithMany(p => p.LevelOpinions)
.HasForeignKey(x => x.VehicleBookingId)
.OnDelete(DeleteBehavior.Cascade);
e.HasOne(x => x.Level)
.WithMany()
.HasForeignKey(x => x.ApprovalWorkflowLevelId)
.OnDelete(DeleteBehavior.Restrict);
e.HasIndex(x => new { x.VehicleBookingId, x.ApprovalWorkflowLevelId }).IsUnique();
e.HasIndex(x => x.ApprovalWorkflowLevelId);
}
}

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 41 P11-A (Phase 11) — Sequence generator dùng chung cho mã đơn từ 4
// WorkflowApps module (Leave/OT/Travel/VehicleBooking). Mirror
// ProposalCodeSequenceConfiguration pattern. PK = Prefix (string), LastSeq atomic.
public class WorkflowAppCodeSequenceConfiguration : IEntityTypeConfiguration<WorkflowAppCodeSequence>
{
public void Configure(EntityTypeBuilder<WorkflowAppCodeSequence> e)
{
e.ToTable("WorkflowAppCodeSequences");
e.HasKey(x => x.Prefix);
e.Property(x => x.Prefix).HasMaxLength(20); // "DT/LR/2026" max ~10 chars OK
}
}

View File

@ -137,6 +137,13 @@ public static class DbInitializer
// NOT gated DemoSeed per gotcha #51 INFRASTRUCTURE seed.
await SeedSampleProposalWorkflowV2Async(db, userManager, logger);
// Phase 11 P11-A (S42) — Sample workflow V2 cho 4 WorkflowApps module.
// INFRASTRUCTURE seed NOT gated DemoSeed (gotcha #51) — mirror Proposal.
await SeedSampleLeaveRequestWorkflowV2Async(db, userManager, logger);
await SeedSampleOtRequestWorkflowV2Async(db, userManager, logger);
await SeedSampleTravelRequestWorkflowV2Async(db, userManager, logger);
await SeedSampleVehicleBookingWorkflowV2Async(db, userManager, logger);
await WarnDefaultAdminPasswordAsync(userManager, logger);
}
@ -294,6 +301,202 @@ public static class DbInitializer
logger.LogInformation("Seeded sample ApprovalWorkflow V2 for Proposal: QT-DX-V2-001 v01");
}
// Phase 11 P11-A (S42) — Sample workflow V2 cho 4 WorkflowApps module để UAT
// test approval ngay (Leave/OT/Travel/Vehicle). Cookie-cutter mirror
// SeedSampleProposalWorkflowV2Async CHÍNH XÁC. INFRASTRUCTURE seed NOT gated
// DemoSeed (gotcha #51). Idempotent — skip nếu đã có ANY workflow cùng type.
private static async Task SeedSampleLeaveRequestWorkflowV2Async(
ApplicationDbContext db, UserManager<User> userManager, ILogger logger)
{
var hasAny = await db.ApprovalWorkflows
.AnyAsync(w => w.ApplicableType == ApprovalWorkflowApplicableType.LeaveRequest);
if (hasAny) return;
var approver = await userManager.FindByEmailAsync("binh.le@solutions.com.vn");
if (approver is null)
{
logger.LogWarning("SeedSampleLeaveRequestWorkflowV2Async: 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-NP-V2-001",
Version = 1,
ApplicableType = ApprovalWorkflowApplicableType.LeaveRequest,
Name = "Quy trình duyệt đơn nghỉ phép (mẫu)",
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 = "Cấp duyệt",
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 LeaveRequest: QT-NP-V2-001 v01");
}
private static async Task SeedSampleOtRequestWorkflowV2Async(
ApplicationDbContext db, UserManager<User> userManager, ILogger logger)
{
var hasAny = await db.ApprovalWorkflows
.AnyAsync(w => w.ApplicableType == ApprovalWorkflowApplicableType.OtRequest);
if (hasAny) return;
var approver = await userManager.FindByEmailAsync("binh.le@solutions.com.vn");
if (approver is null)
{
logger.LogWarning("SeedSampleOtRequestWorkflowV2Async: 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-OT-V2-001",
Version = 1,
ApplicableType = ApprovalWorkflowApplicableType.OtRequest,
Name = "Quy trình duyệt đơn OT (mẫu)",
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 = "Cấp duyệt",
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 OtRequest: QT-OT-V2-001 v01");
}
private static async Task SeedSampleTravelRequestWorkflowV2Async(
ApplicationDbContext db, UserManager<User> userManager, ILogger logger)
{
var hasAny = await db.ApprovalWorkflows
.AnyAsync(w => w.ApplicableType == ApprovalWorkflowApplicableType.TravelRequest);
if (hasAny) return;
var approver = await userManager.FindByEmailAsync("binh.le@solutions.com.vn");
if (approver is null)
{
logger.LogWarning("SeedSampleTravelRequestWorkflowV2Async: 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-CT-V2-001",
Version = 1,
ApplicableType = ApprovalWorkflowApplicableType.TravelRequest,
Name = "Quy trình duyệt đơn công tác (mẫu)",
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 = "Cấp duyệt",
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 TravelRequest: QT-CT-V2-001 v01");
}
private static async Task SeedSampleVehicleBookingWorkflowV2Async(
ApplicationDbContext db, UserManager<User> userManager, ILogger logger)
{
var hasAny = await db.ApprovalWorkflows
.AnyAsync(w => w.ApplicableType == ApprovalWorkflowApplicableType.VehicleBooking);
if (hasAny) return;
var approver = await userManager.FindByEmailAsync("binh.le@solutions.com.vn");
if (approver is null)
{
logger.LogWarning("SeedSampleVehicleBookingWorkflowV2Async: 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-XE-V2-001",
Version = 1,
ApplicableType = ApprovalWorkflowApplicableType.VehicleBooking,
Name = "Quy trình duyệt đặt xe (mẫu)",
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 = "Cấp duyệt",
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 VehicleBooking: QT-XE-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)

View File

@ -0,0 +1,275 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SolutionErp.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class WireWorkflowAppsApprovalV2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "RejectedFromStatus",
table: "VehicleBookings",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "RejectedFromStatus",
table: "TravelRequests",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "RejectedFromStatus",
table: "OtRequests",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "RejectedFromStatus",
table: "LeaveRequests",
type: "int",
nullable: true);
migrationBuilder.CreateTable(
name: "LeaveRequestLevelOpinions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
LeaveRequestId = 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_LeaveRequestLevelOpinions", x => x.Id);
table.ForeignKey(
name: "FK_LeaveRequestLevelOpinions_ApprovalWorkflowLevels_ApprovalWorkflowLevelId",
column: x => x.ApprovalWorkflowLevelId,
principalTable: "ApprovalWorkflowLevels",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_LeaveRequestLevelOpinions_LeaveRequests_LeaveRequestId",
column: x => x.LeaveRequestId,
principalTable: "LeaveRequests",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "OtRequestLevelOpinions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
OtRequestId = 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_OtRequestLevelOpinions", x => x.Id);
table.ForeignKey(
name: "FK_OtRequestLevelOpinions_ApprovalWorkflowLevels_ApprovalWorkflowLevelId",
column: x => x.ApprovalWorkflowLevelId,
principalTable: "ApprovalWorkflowLevels",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_OtRequestLevelOpinions_OtRequests_OtRequestId",
column: x => x.OtRequestId,
principalTable: "OtRequests",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TravelRequestLevelOpinions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TravelRequestId = 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_TravelRequestLevelOpinions", x => x.Id);
table.ForeignKey(
name: "FK_TravelRequestLevelOpinions_ApprovalWorkflowLevels_ApprovalWorkflowLevelId",
column: x => x.ApprovalWorkflowLevelId,
principalTable: "ApprovalWorkflowLevels",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_TravelRequestLevelOpinions_TravelRequests_TravelRequestId",
column: x => x.TravelRequestId,
principalTable: "TravelRequests",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "VehicleBookingLevelOpinions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
VehicleBookingId = 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_VehicleBookingLevelOpinions", x => x.Id);
table.ForeignKey(
name: "FK_VehicleBookingLevelOpinions_ApprovalWorkflowLevels_ApprovalWorkflowLevelId",
column: x => x.ApprovalWorkflowLevelId,
principalTable: "ApprovalWorkflowLevels",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_VehicleBookingLevelOpinions_VehicleBookings_VehicleBookingId",
column: x => x.VehicleBookingId,
principalTable: "VehicleBookings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "WorkflowAppCodeSequences",
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_WorkflowAppCodeSequences", x => x.Prefix);
});
migrationBuilder.CreateIndex(
name: "IX_LeaveRequestLevelOpinions_ApprovalWorkflowLevelId",
table: "LeaveRequestLevelOpinions",
column: "ApprovalWorkflowLevelId");
migrationBuilder.CreateIndex(
name: "IX_LeaveRequestLevelOpinions_LeaveRequestId_ApprovalWorkflowLevelId",
table: "LeaveRequestLevelOpinions",
columns: new[] { "LeaveRequestId", "ApprovalWorkflowLevelId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OtRequestLevelOpinions_ApprovalWorkflowLevelId",
table: "OtRequestLevelOpinions",
column: "ApprovalWorkflowLevelId");
migrationBuilder.CreateIndex(
name: "IX_OtRequestLevelOpinions_OtRequestId_ApprovalWorkflowLevelId",
table: "OtRequestLevelOpinions",
columns: new[] { "OtRequestId", "ApprovalWorkflowLevelId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_TravelRequestLevelOpinions_ApprovalWorkflowLevelId",
table: "TravelRequestLevelOpinions",
column: "ApprovalWorkflowLevelId");
migrationBuilder.CreateIndex(
name: "IX_TravelRequestLevelOpinions_TravelRequestId_ApprovalWorkflowLevelId",
table: "TravelRequestLevelOpinions",
columns: new[] { "TravelRequestId", "ApprovalWorkflowLevelId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_VehicleBookingLevelOpinions_ApprovalWorkflowLevelId",
table: "VehicleBookingLevelOpinions",
column: "ApprovalWorkflowLevelId");
migrationBuilder.CreateIndex(
name: "IX_VehicleBookingLevelOpinions_VehicleBookingId_ApprovalWorkflowLevelId",
table: "VehicleBookingLevelOpinions",
columns: new[] { "VehicleBookingId", "ApprovalWorkflowLevelId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "LeaveRequestLevelOpinions");
migrationBuilder.DropTable(
name: "OtRequestLevelOpinions");
migrationBuilder.DropTable(
name: "TravelRequestLevelOpinions");
migrationBuilder.DropTable(
name: "VehicleBookingLevelOpinions");
migrationBuilder.DropTable(
name: "WorkflowAppCodeSequences");
migrationBuilder.DropColumn(
name: "RejectedFromStatus",
table: "VehicleBookings");
migrationBuilder.DropColumn(
name: "RejectedFromStatus",
table: "TravelRequests");
migrationBuilder.DropColumn(
name: "RejectedFromStatus",
table: "OtRequests");
migrationBuilder.DropColumn(
name: "RejectedFromStatus",
table: "LeaveRequests");
}
}
}

View File

@ -3676,6 +3676,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<int?>("RejectedFromStatus")
.HasColumnType("int");
b.Property<string>("RequesterFullName")
.IsRequired()
.HasMaxLength(200)
@ -3709,6 +3712,64 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("LeaveRequests", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.LeaveRequestLevelOpinion", 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>("LeaveRequestId")
.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("LeaveRequestId", "ApprovalWorkflowLevelId")
.IsUnique();
b.ToTable("LeaveRequestLevelOpinions", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.MeetingBooking", b =>
{
b.Property<Guid>("Id")
@ -3932,6 +3993,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<int?>("RejectedFromStatus")
.HasColumnType("int");
b.Property<string>("RequesterFullName")
.IsRequired()
.HasMaxLength(200)
@ -3965,6 +4029,64 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("OtRequests", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.OtRequestLevelOpinion", 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>("OtRequestId")
.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("OtRequestId", "ApprovalWorkflowLevelId")
.IsUnique();
b.ToTable("OtRequestLevelOpinions", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.Proposal", b =>
{
b.Property<Guid>("Id")
@ -4236,6 +4358,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<int?>("RejectedFromStatus")
.HasColumnType("int");
b.Property<string>("RequesterFullName")
.IsRequired()
.HasMaxLength(200)
@ -4269,6 +4394,64 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("TravelRequests", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.TravelRequestLevelOpinion", 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<DateTime>("SignedAt")
.HasColumnType("datetime2");
b.Property<string>("SignedByFullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("SignedByUserId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("TravelRequestId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("ApprovalWorkflowLevelId");
b.HasIndex("TravelRequestId", "ApprovalWorkflowLevelId")
.IsUnique();
b.ToTable("TravelRequestLevelOpinions", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.VehicleBooking", b =>
{
b.Property<Guid>("Id")
@ -4317,6 +4500,9 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<int?>("RejectedFromStatus")
.HasColumnType("int");
b.Property<string>("RequesterFullName")
.IsRequired()
.HasMaxLength(200)
@ -4361,6 +4547,81 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("VehicleBookings", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.VehicleBookingLevelOpinion", 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<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.Property<Guid>("VehicleBookingId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("ApprovalWorkflowLevelId");
b.HasIndex("VehicleBookingId", "ApprovalWorkflowLevelId")
.IsUnique();
b.ToTable("VehicleBookingLevelOpinions", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.WorkflowAppCodeSequence", 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("WorkflowAppCodeSequences", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b =>
{
b.Property<Guid>("Id")
@ -5603,6 +5864,25 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SolutionErp.Domain.Office.LeaveRequestLevelOpinion", b =>
{
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflowLevel", "Level")
.WithMany()
.HasForeignKey("ApprovalWorkflowLevelId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("SolutionErp.Domain.Office.LeaveRequest", "LeaveRequest")
.WithMany("LevelOpinions")
.HasForeignKey("LeaveRequestId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("LeaveRequest");
b.Navigation("Level");
});
modelBuilder.Entity("SolutionErp.Domain.Office.MeetingBooking", b =>
{
b.HasOne("SolutionErp.Domain.Office.MeetingRoom", "Room")
@ -5625,6 +5905,25 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Booking");
});
modelBuilder.Entity("SolutionErp.Domain.Office.OtRequestLevelOpinion", b =>
{
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflowLevel", "Level")
.WithMany()
.HasForeignKey("ApprovalWorkflowLevelId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("SolutionErp.Domain.Office.OtRequest", "OtRequest")
.WithMany("LevelOpinions")
.HasForeignKey("OtRequestId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Level");
b.Navigation("OtRequest");
});
modelBuilder.Entity("SolutionErp.Domain.Office.ProposalAttachment", b =>
{
b.HasOne("SolutionErp.Domain.Office.Proposal", "Proposal")
@ -5655,6 +5954,44 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Proposal");
});
modelBuilder.Entity("SolutionErp.Domain.Office.TravelRequestLevelOpinion", b =>
{
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflowLevel", "Level")
.WithMany()
.HasForeignKey("ApprovalWorkflowLevelId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("SolutionErp.Domain.Office.TravelRequest", "TravelRequest")
.WithMany("LevelOpinions")
.HasForeignKey("TravelRequestId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Level");
b.Navigation("TravelRequest");
});
modelBuilder.Entity("SolutionErp.Domain.Office.VehicleBookingLevelOpinion", b =>
{
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflowLevel", "Level")
.WithMany()
.HasForeignKey("ApprovalWorkflowLevelId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("SolutionErp.Domain.Office.VehicleBooking", "VehicleBooking")
.WithMany("LevelOpinions")
.HasForeignKey("VehicleBookingId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Level");
b.Navigation("VehicleBooking");
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b =>
{
b.HasOne("SolutionErp.Domain.ApprovalWorkflowsV2.ApprovalWorkflow", null)
@ -5889,11 +6226,21 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("Permissions");
});
modelBuilder.Entity("SolutionErp.Domain.Office.LeaveRequest", b =>
{
b.Navigation("LevelOpinions");
});
modelBuilder.Entity("SolutionErp.Domain.Office.MeetingBooking", b =>
{
b.Navigation("Attendees");
});
modelBuilder.Entity("SolutionErp.Domain.Office.OtRequest", b =>
{
b.Navigation("LevelOpinions");
});
modelBuilder.Entity("SolutionErp.Domain.Office.Proposal", b =>
{
b.Navigation("Attachments");
@ -5901,6 +6248,16 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.Navigation("LevelOpinions");
});
modelBuilder.Entity("SolutionErp.Domain.Office.TravelRequest", b =>
{
b.Navigation("LevelOpinions");
});
modelBuilder.Entity("SolutionErp.Domain.Office.VehicleBooking", b =>
{
b.Navigation("LevelOpinions");
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b =>
{
b.Navigation("Approvals");