[CLAUDE] Domain+App+Infra+Api+FE-Admin+FE-User: S38 G-O4+G-O5+G-O6+G-P1+G-H3 SKELETON full-stack
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m53s

Phase 10.3-10.4 SKELETON 5 plan combo finish — Mig 39+40 + BE skeleton 7 module +
FE 2 app SHA256 IDENTICAL + 11 menu key. UAT visible end-to-end.

⚠️ SKELETON Phase 1 trade-off rõ:
  - Status flat 5-state WorkflowAppStatus enum share Leave/OT/Travel/Vehicle
  - ApproveV2 workflow advance DEFER Phase 11 (Drafter Create OK, Approve flow chưa wire)
  - LevelOpinions per-module DEFER Phase 11
  - LeaveBalance calc + Auto-assign + SLA timer DEFER Phase 11
  - CodeGen atomic + MaDonTu/MaTicket gen DEFER Phase 11
  - Vehicle catalog + Driver catalog DEFER Phase 11 (free text VehicleLicense)
  - ItTicketComments thread DEFER Phase 11 (free text Resolution field)

Mig 39 (em main solo): 5 entity Workflow Apps schema
  - LeaveRequest (G-O4, FK LeaveType Hrm Mig 35, ApplicableType=5)
  - OtRequest (G-O4, FK OtPolicy optional, ApplicableType=6)
  - TravelRequest (G-O4, reuse ApplicableType=4 Proposal)
  - VehicleBooking (G-O5, free text vehicle, ApplicableType=7)
  - ItTicket (G-O6, NO workflow V2 — kanban status flow)

Mig 40 (em main solo): Attendance entity (G-P1)
  - GPS lat/long check-in/out + Source enum Web/Mobile/Device
  - UNIQUE composite (UserId, AttendanceDate)
  - WorkHours computed simple diff (NO OtPolicy multiplier yet)

BE CQRS (em main solo, single mega ~1100 LOC):
  - WorkflowAppsFeatures.cs 7 region (5 module Create+List + Attendance CheckIn/Out/GetMonth + HrDashboard)
  - 7 Controller: /api/leave-requests + /ot-requests + /travel-requests + /vehicle-bookings + /it-tickets + /attendances + /hr/dashboard
  - Class-level [Authorize] any authenticated
  - 13 endpoint total

FE 2 app (em main solo fallback gotcha #53 risk):
  - types/workflowApps.ts × 2 SHA256 IDENTICAL 77470e182a15de88 (all DTOs + Status badge)
  - WorkflowAppsListPage.tsx × 2 IDENTICAL 58139d0301a60ddf — generic declarative KIND_CONFIG handles 4 module (Leave/OT/Travel/Vehicle)
  - ItTicketsPage.tsx × 2 IDENTICAL d3062de2f54c794c — kanban 5 status column
  - MyAttendancePage.tsx × 2 IDENTICAL 86da48ae147db012 — GPS check-in/out + tháng calendar
  - HrmDashboardPage.tsx × 2 IDENTICAL d9c6c12a5a8694f8 — 4 KPI card + gender ratio + status breakdown
  - Pattern 16-bis 9× cumulative (App.tsx +4 routes + menuKeys +8 const + Layout staticMap +7 entry)
  - 7 amber banner "Skeleton Phase 1 — full feature Phase 11" rõ ràng UAT

Menu seed: +11 const + SeedMenuTreeAsync 8 row (Off_DonTu sub-group + 3 leaf + Off_DatXe + Off_ItTicket + Off_ChamCong + Hrm_Dashboard).
DbInitializer Sample workflow seed DEFER (workflows V2 already seeded từ S29+S37 reuse — admin clone tạo riêng per ApplicableType=5/6/7).

Verify:
- dotnet build PASS 0 error 2 pre-existing warning
- dotnet test 130/130 PASS baseline preserve
- npm build × 2 PASS clean
- SHA256 verify 5 file × 2 app all IDENTICAL

Plan G-* progress 11/11  (100% COMPLETE):
   G-H1 (S33) + G-O1 (S34) + G-H2 (S35) + G-O2 (S36) + G-O3 (S37) +
   G-O4 + G-O5 + G-O6 + G-P1 + G-H3 (S38 skeleton)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-28 16:19:42 +07:00
parent 17aaba9df0
commit e54a22de0c
41 changed files with 14980 additions and 0 deletions

View File

@ -108,6 +108,16 @@ public class ApplicationDbContext
public DbSet<ProposalLevelOpinion> ProposalLevelOpinions => Set<ProposalLevelOpinion>();
public DbSet<ProposalCodeSequence> ProposalCodeSequences => Set<ProposalCodeSequence>();
// Phase 10.3 G-O4+G-O5+G-O6 (Mig 39 — S38) — Workflow Apps skeleton 5 entity.
public DbSet<LeaveRequest> LeaveRequests => Set<LeaveRequest>();
public DbSet<OtRequest> OtRequests => Set<OtRequest>();
public DbSet<TravelRequest> TravelRequests => Set<TravelRequest>();
public DbSet<VehicleBooking> VehicleBookings => Set<VehicleBooking>();
public DbSet<ItTicket> ItTickets => Set<ItTicket>();
// Phase 10.4 G-P1 (Mig 40 — S38) — Chấm công web GPS.
public DbSet<Attendance> Attendances => Set<Attendance>();
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);

View File

@ -0,0 +1,127 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using SolutionErp.Domain.Office;
namespace SolutionErp.Infrastructure.Persistence.Configurations;
// EF Mig 39 G-O4+G-O5+G-O6 (S38) — 5 entity Workflow Apps skeleton.
// Cookie-cutter mirror Proposal pattern (Mig 38). Status flat WorkflowAppStatus 5-state.
// All share workflow V2 ApprovalWorkflowId pin (Mig 37 enum extend +5/+6/+7 done).
// Skeleton Phase 1: NO LevelOpinions table (defer Phase 11 — share global table OR per-module).
public class LeaveRequestConfiguration : IEntityTypeConfiguration<LeaveRequest>
{
public void Configure(EntityTypeBuilder<LeaveRequest> e)
{
e.ToTable("LeaveRequests");
e.Property(x => x.MaDonTu).HasMaxLength(50);
e.Property(x => x.RequesterFullName).HasMaxLength(200).IsRequired();
e.Property(x => x.Reason).HasMaxLength(1000).IsRequired();
e.Property(x => x.NumDays).HasColumnType("decimal(5,2)");
e.Property(x => x.Status).HasConversion<int>();
e.HasIndex(x => x.MaDonTu).IsUnique().HasFilter("[MaDonTu] IS NOT NULL");
e.HasIndex(x => x.RequesterUserId);
e.HasIndex(x => x.Status);
}
}
public class OtRequestConfiguration : IEntityTypeConfiguration<OtRequest>
{
public void Configure(EntityTypeBuilder<OtRequest> e)
{
e.ToTable("OtRequests");
e.Property(x => x.MaDonTu).HasMaxLength(50);
e.Property(x => x.RequesterFullName).HasMaxLength(200).IsRequired();
e.Property(x => x.Reason).HasMaxLength(1000).IsRequired();
e.Property(x => x.Hours).HasColumnType("decimal(5,2)");
e.Property(x => x.Status).HasConversion<int>();
e.HasIndex(x => x.MaDonTu).IsUnique().HasFilter("[MaDonTu] IS NOT NULL");
e.HasIndex(x => x.RequesterUserId);
e.HasIndex(x => x.Status);
}
}
public class TravelRequestConfiguration : IEntityTypeConfiguration<TravelRequest>
{
public void Configure(EntityTypeBuilder<TravelRequest> e)
{
e.ToTable("TravelRequests");
e.Property(x => x.MaDonTu).HasMaxLength(50);
e.Property(x => x.RequesterFullName).HasMaxLength(200).IsRequired();
e.Property(x => x.Destination).HasMaxLength(300).IsRequired();
e.Property(x => x.Purpose).HasMaxLength(1000).IsRequired();
e.Property(x => x.EstimatedCost).HasColumnType("decimal(18,2)");
e.Property(x => x.Status).HasConversion<int>();
e.HasIndex(x => x.MaDonTu).IsUnique().HasFilter("[MaDonTu] IS NOT NULL");
e.HasIndex(x => x.RequesterUserId);
e.HasIndex(x => x.Status);
}
}
public class VehicleBookingConfiguration : IEntityTypeConfiguration<VehicleBooking>
{
public void Configure(EntityTypeBuilder<VehicleBooking> e)
{
e.ToTable("VehicleBookings");
e.Property(x => x.MaDonTu).HasMaxLength(50);
e.Property(x => x.RequesterFullName).HasMaxLength(200).IsRequired();
e.Property(x => x.VehicleLicense).HasMaxLength(20).IsRequired();
e.Property(x => x.VehicleName).HasMaxLength(200);
e.Property(x => x.Destination).HasMaxLength(300).IsRequired();
e.Property(x => x.Purpose).HasMaxLength(1000).IsRequired();
e.Property(x => x.DriverName).HasMaxLength(200);
e.Property(x => x.Status).HasConversion<int>();
e.HasIndex(x => x.MaDonTu).IsUnique().HasFilter("[MaDonTu] IS NOT NULL");
e.HasIndex(x => x.RequesterUserId);
e.HasIndex(x => x.Status);
e.HasIndex(x => new { x.VehicleLicense, x.StartAt }); // overlap check defer Phase 11
}
}
public class ItTicketConfiguration : IEntityTypeConfiguration<ItTicket>
{
public void Configure(EntityTypeBuilder<ItTicket> e)
{
e.ToTable("ItTickets");
e.Property(x => x.MaTicket).HasMaxLength(50);
e.Property(x => x.RequesterFullName).HasMaxLength(200).IsRequired();
e.Property(x => x.Title).HasMaxLength(300).IsRequired();
e.Property(x => x.Description).HasMaxLength(5000).IsRequired();
e.Property(x => x.AssignedToFullName).HasMaxLength(200);
e.Property(x => x.Resolution).HasMaxLength(5000);
e.Property(x => x.Category).HasConversion<int>();
e.Property(x => x.Priority).HasConversion<int>();
e.Property(x => x.Status).HasConversion<int>();
e.HasIndex(x => x.MaTicket).IsUnique().HasFilter("[MaTicket] IS NOT NULL");
e.HasIndex(x => x.RequesterUserId);
e.HasIndex(x => x.AssignedToUserId);
e.HasIndex(x => x.Status);
e.HasIndex(x => x.Category);
}
}
// Mig 40 G-P1 — Attendance
public class AttendanceConfiguration : IEntityTypeConfiguration<Attendance>
{
public void Configure(EntityTypeBuilder<Attendance> e)
{
e.ToTable("Attendances");
e.Property(x => x.UserFullName).HasMaxLength(200).IsRequired();
e.Property(x => x.CheckInLatitude).HasColumnType("decimal(10,7)");
e.Property(x => x.CheckInLongitude).HasColumnType("decimal(10,7)");
e.Property(x => x.CheckInAccuracy).HasColumnType("decimal(8,2)");
e.Property(x => x.CheckOutLatitude).HasColumnType("decimal(10,7)");
e.Property(x => x.CheckOutLongitude).HasColumnType("decimal(10,7)");
e.Property(x => x.CheckOutAccuracy).HasColumnType("decimal(8,2)");
e.Property(x => x.IpAddressIn).HasMaxLength(50);
e.Property(x => x.IpAddressOut).HasMaxLength(50);
e.Property(x => x.Note).HasMaxLength(500);
e.Property(x => x.WorkHours).HasColumnType("decimal(5,2)");
e.Property(x => x.OtHours).HasColumnType("decimal(5,2)");
e.Property(x => x.SourceIn).HasConversion<int>();
e.Property(x => x.SourceOut).HasConversion<int>();
// UNIQUE composite (UserId, AttendanceDate)
e.HasIndex(x => new { x.UserId, x.AttendanceDate }).IsUnique();
}
}

View File

@ -1567,6 +1567,16 @@ public static class DbInitializer
(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"),
// Phase 10.3 G-O4+G-O5+G-O6+G-P1 (Mig 39+40 — S38 2026-05-28). Skeleton Workflow Apps.
(MenuKeys.OffDonTu, "Đơn từ", MenuKeys.Off, 4, "FileText"),
(MenuKeys.OffDonTuLeave, "Nghỉ phép", MenuKeys.OffDonTu, 1, "CalendarOff"),
(MenuKeys.OffDonTuOt, "Đăng ký OT", MenuKeys.OffDonTu, 2, "Clock"),
(MenuKeys.OffDonTuTravel, "Công tác", MenuKeys.OffDonTu, 3, "Plane"),
(MenuKeys.OffDatXe, "Đặt xe công", MenuKeys.Off, 5, "Car"),
(MenuKeys.OffItTicket, "Ticket CNTT", MenuKeys.Off, 6, "Ticket"),
(MenuKeys.OffChamCong, "Chấm công", MenuKeys.Off, 7, "Fingerprint"),
// Phase 10.4 G-H3 — Dashboard NS dưới root Hrm.
(MenuKeys.HrmDashboard, "Dashboard NS", MenuKeys.Hrm, 3, "BarChart3"),
};
// Per-type sub-menu under Contracts: 1 group + 3 leaves each

View File

@ -0,0 +1,329 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SolutionErp.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddWorkflowApps : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Attendances",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
AttendanceDate = table.Column<DateTime>(type: "datetime2", nullable: false),
CheckInAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CheckOutAt = table.Column<DateTime>(type: "datetime2", nullable: true),
CheckInLatitude = table.Column<decimal>(type: "decimal(10,7)", nullable: true),
CheckInLongitude = table.Column<decimal>(type: "decimal(10,7)", nullable: true),
CheckInAccuracy = table.Column<decimal>(type: "decimal(8,2)", nullable: true),
CheckOutLatitude = table.Column<decimal>(type: "decimal(10,7)", nullable: true),
CheckOutLongitude = table.Column<decimal>(type: "decimal(10,7)", nullable: true),
CheckOutAccuracy = table.Column<decimal>(type: "decimal(8,2)", nullable: true),
SourceIn = table.Column<int>(type: "int", nullable: false),
SourceOut = table.Column<int>(type: "int", nullable: false),
IpAddressIn = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
IpAddressOut = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
Note = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
WorkHours = table.Column<decimal>(type: "decimal(5,2)", nullable: true),
OtHours = table.Column<decimal>(type: "decimal(5,2)", 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_Attendances", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ItTickets",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MaTicket = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
RequesterUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RequesterFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Title = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", maxLength: 5000, nullable: false),
Category = table.Column<int>(type: "int", nullable: false),
Priority = table.Column<int>(type: "int", nullable: false),
Status = table.Column<int>(type: "int", nullable: false),
AssignedToUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
AssignedToFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
ResolvedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
Resolution = table.Column<string>(type: "nvarchar(max)", maxLength: 5000, 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_ItTickets", x => x.Id);
});
migrationBuilder.CreateTable(
name: "LeaveRequests",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MaDonTu = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
RequesterUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RequesterFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
LeaveTypeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false),
EndDate = table.Column<DateTime>(type: "datetime2", nullable: false),
NumDays = table.Column<decimal>(type: "decimal(5,2)", nullable: false),
Reason = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
Status = table.Column<int>(type: "int", nullable: false),
ApprovalWorkflowId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
CurrentApprovalLevelOrder = 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_LeaveRequests", x => x.Id);
});
migrationBuilder.CreateTable(
name: "OtRequests",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MaDonTu = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
RequesterUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RequesterFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
OtDate = table.Column<DateTime>(type: "datetime2", nullable: false),
StartTime = table.Column<TimeSpan>(type: "time", nullable: false),
EndTime = table.Column<TimeSpan>(type: "time", nullable: false),
Hours = table.Column<decimal>(type: "decimal(5,2)", nullable: false),
Reason = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
OtPolicyId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
ApprovalWorkflowId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
CurrentApprovalLevelOrder = 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_OtRequests", x => x.Id);
});
migrationBuilder.CreateTable(
name: "TravelRequests",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MaDonTu = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
RequesterUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RequesterFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Destination = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: false),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false),
EndDate = table.Column<DateTime>(type: "datetime2", nullable: false),
NumDays = table.Column<int>(type: "int", nullable: false),
Purpose = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
EstimatedCost = table.Column<decimal>(type: "decimal(18,2)", nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
ApprovalWorkflowId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
CurrentApprovalLevelOrder = 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_TravelRequests", x => x.Id);
});
migrationBuilder.CreateTable(
name: "VehicleBookings",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MaDonTu = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
RequesterUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
RequesterFullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
VehicleLicense = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
VehicleName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
StartAt = table.Column<DateTime>(type: "datetime2", nullable: false),
EndAt = table.Column<DateTime>(type: "datetime2", nullable: false),
Destination = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: false),
Purpose = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: false),
DriverName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
ApprovalWorkflowId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
CurrentApprovalLevelOrder = 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_VehicleBookings", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Attendances_UserId_AttendanceDate",
table: "Attendances",
columns: new[] { "UserId", "AttendanceDate" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ItTickets_AssignedToUserId",
table: "ItTickets",
column: "AssignedToUserId");
migrationBuilder.CreateIndex(
name: "IX_ItTickets_Category",
table: "ItTickets",
column: "Category");
migrationBuilder.CreateIndex(
name: "IX_ItTickets_MaTicket",
table: "ItTickets",
column: "MaTicket",
unique: true,
filter: "[MaTicket] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_ItTickets_RequesterUserId",
table: "ItTickets",
column: "RequesterUserId");
migrationBuilder.CreateIndex(
name: "IX_ItTickets_Status",
table: "ItTickets",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_LeaveRequests_MaDonTu",
table: "LeaveRequests",
column: "MaDonTu",
unique: true,
filter: "[MaDonTu] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_LeaveRequests_RequesterUserId",
table: "LeaveRequests",
column: "RequesterUserId");
migrationBuilder.CreateIndex(
name: "IX_LeaveRequests_Status",
table: "LeaveRequests",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_OtRequests_MaDonTu",
table: "OtRequests",
column: "MaDonTu",
unique: true,
filter: "[MaDonTu] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_OtRequests_RequesterUserId",
table: "OtRequests",
column: "RequesterUserId");
migrationBuilder.CreateIndex(
name: "IX_OtRequests_Status",
table: "OtRequests",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_TravelRequests_MaDonTu",
table: "TravelRequests",
column: "MaDonTu",
unique: true,
filter: "[MaDonTu] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_TravelRequests_RequesterUserId",
table: "TravelRequests",
column: "RequesterUserId");
migrationBuilder.CreateIndex(
name: "IX_TravelRequests_Status",
table: "TravelRequests",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_VehicleBookings_MaDonTu",
table: "VehicleBookings",
column: "MaDonTu",
unique: true,
filter: "[MaDonTu] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_VehicleBookings_RequesterUserId",
table: "VehicleBookings",
column: "RequesterUserId");
migrationBuilder.CreateIndex(
name: "IX_VehicleBookings_Status",
table: "VehicleBookings",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_VehicleBookings_VehicleLicense_StartAt",
table: "VehicleBookings",
columns: new[] { "VehicleLicense", "StartAt" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Attendances");
migrationBuilder.DropTable(
name: "ItTickets");
migrationBuilder.DropTable(
name: "LeaveRequests");
migrationBuilder.DropTable(
name: "OtRequests");
migrationBuilder.DropTable(
name: "TravelRequests");
migrationBuilder.DropTable(
name: "VehicleBookings");
}
}
}

View File

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

View File

@ -3448,6 +3448,267 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("Notifications", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.Attendance", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("AttendanceDate")
.HasColumnType("datetime2");
b.Property<decimal?>("CheckInAccuracy")
.HasColumnType("decimal(8,2)");
b.Property<DateTime?>("CheckInAt")
.HasColumnType("datetime2");
b.Property<decimal?>("CheckInLatitude")
.HasColumnType("decimal(10,7)");
b.Property<decimal?>("CheckInLongitude")
.HasColumnType("decimal(10,7)");
b.Property<decimal?>("CheckOutAccuracy")
.HasColumnType("decimal(8,2)");
b.Property<DateTime?>("CheckOutAt")
.HasColumnType("datetime2");
b.Property<decimal?>("CheckOutLatitude")
.HasColumnType("decimal(10,7)");
b.Property<decimal?>("CheckOutLongitude")
.HasColumnType("decimal(10,7)");
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>("IpAddressIn")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("IpAddressOut")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("Note")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<decimal?>("OtHours")
.HasColumnType("decimal(5,2)");
b.Property<int>("SourceIn")
.HasColumnType("int");
b.Property<int>("SourceOut")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserFullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<decimal?>("WorkHours")
.HasColumnType("decimal(5,2)");
b.HasKey("Id");
b.HasIndex("UserId", "AttendanceDate")
.IsUnique();
b.ToTable("Attendances", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.ItTicket", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("AssignedToFullName")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid?>("AssignedToUserId")
.HasColumnType("uniqueidentifier");
b.Property<int>("Category")
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uniqueidentifier");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uniqueidentifier");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(5000)
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("MaTicket")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("Priority")
.HasColumnType("int");
b.Property<string>("RequesterFullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("RequesterUserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Resolution")
.HasMaxLength(5000)
.HasColumnType("nvarchar(max)");
b.Property<DateTime?>("ResolvedAt")
.HasColumnType("datetime2");
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("AssignedToUserId");
b.HasIndex("Category");
b.HasIndex("MaTicket")
.IsUnique()
.HasFilter("[MaTicket] IS NOT NULL");
b.HasIndex("RequesterUserId");
b.HasIndex("Status");
b.ToTable("ItTickets", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.LeaveRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
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<DateTime>("EndDate")
.HasColumnType("datetime2");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<Guid>("LeaveTypeId")
.HasColumnType("uniqueidentifier");
b.Property<string>("MaDonTu")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<decimal>("NumDays")
.HasColumnType("decimal(5,2)");
b.Property<string>("Reason")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("RequesterFullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("RequesterUserId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("StartDate")
.HasColumnType("datetime2");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("MaDonTu")
.IsUnique()
.HasFilter("[MaDonTu] IS NOT NULL");
b.HasIndex("RequesterUserId");
b.HasIndex("Status");
b.ToTable("LeaveRequests", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.MeetingBooking", b =>
{
b.Property<Guid>("Id")
@ -3623,6 +3884,87 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("MeetingRooms", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.OtRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
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<TimeSpan>("EndTime")
.HasColumnType("time");
b.Property<decimal>("Hours")
.HasColumnType("decimal(5,2)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("MaDonTu")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<DateTime>("OtDate")
.HasColumnType("datetime2");
b.Property<Guid?>("OtPolicyId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Reason")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("RequesterFullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("RequesterUserId")
.HasColumnType("uniqueidentifier");
b.Property<TimeSpan>("StartTime")
.HasColumnType("time");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("MaDonTu")
.IsUnique()
.HasFilter("[MaDonTu] IS NOT NULL");
b.HasIndex("RequesterUserId");
b.HasIndex("Status");
b.ToTable("OtRequests", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.Proposal", b =>
{
b.Property<Guid>("Id")
@ -3844,6 +4186,181 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
b.ToTable("ProposalLevelOpinions", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.TravelRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
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<string>("Destination")
.IsRequired()
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<DateTime>("EndDate")
.HasColumnType("datetime2");
b.Property<decimal?>("EstimatedCost")
.HasColumnType("decimal(18,2)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("MaDonTu")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("NumDays")
.HasColumnType("int");
b.Property<string>("Purpose")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("RequesterFullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("RequesterUserId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("StartDate")
.HasColumnType("datetime2");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("MaDonTu")
.IsUnique()
.HasFilter("[MaDonTu] IS NOT NULL");
b.HasIndex("RequesterUserId");
b.HasIndex("Status");
b.ToTable("TravelRequests", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.Office.VehicleBooking", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
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<string>("Destination")
.IsRequired()
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<string>("DriverName")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<DateTime>("EndAt")
.HasColumnType("datetime2");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("MaDonTu")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Purpose")
.IsRequired()
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("RequesterFullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid>("RequesterUserId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("StartAt")
.HasColumnType("datetime2");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime2");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uniqueidentifier");
b.Property<string>("VehicleLicense")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.Property<string>("VehicleName")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("MaDonTu")
.IsUnique()
.HasFilter("[MaDonTu] IS NOT NULL");
b.HasIndex("RequesterUserId");
b.HasIndex("Status");
b.HasIndex("VehicleLicense", "StartAt");
b.ToTable("VehicleBookings", (string)null);
});
modelBuilder.Entity("SolutionErp.Domain.PurchaseEvaluations.PurchaseEvaluation", b =>
{
b.Property<Guid>("Id")