[CLAUDE] Domain+App+Infra: Plan B G-H1 Mig 34 EmployeeProfile + seed 30 demo
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m38s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m38s
Phase 10.1 G-H1 Hồ sơ Nhân sự Foundation — port từ NamGroup CT_NHANSU
(1675 NV, 10 bảng TblNhanVien*) sau anh main chốt S32 4 quyết định
(scope FULL 11 module + DB single schema dbo + reuse Workflow V2 +
chunk per-module Plan riêng). G-H1 CRITICAL FIRST vì depend by 8/11
module Phase 10 sau (Đề xuất/Đơn từ/OT/Đặt xe/Ticket/Dashboard NS/
Chấm công đều cần EmployeeProfile data).
Investigator pre-flight (a103d20) audit NamGroup confirm:
- Main TblNhanVien 105 cols (drop 35 cols duplicate User/UX legacy)
- 5 satellite Phase 10.1 (defer 3 HĐLĐ Plan H2): WorkHistory + Education
+ FamilyRelation + Skill polymorphic Kind + Document
- 6 enum thay catalog FK (Gender/MaritalStatus/EmployeeStatus/...)
- DiaChi dual-write FK + freetext lesson Plan C NamGroup 1675 NV drift
Em main 4 decision chốt:
1. 5 satellite Phase 10.1 (defer 3 HĐLĐ Plan H2)
2. Skill polymorphic Kind enum (gộp 3 NamGroup table)
3. DiaChi 6 FK Province/District/Ward declare nullable + freetext dual-write
ngày đầu (FK constraint defer G-H2 khi catalog scaffold — Implementer
smart decision documented EmployeeProfile.cs comment line 14-17)
4. MaNhanVien format NV/{YYYY}/{Seq:D4} atomic Serializable reset/year
Implementer Case 2 (a8f4567) Pattern 12-bis cross-module mirror PE → Hrm
cookie-cutter scaffold 17 file mới + 4 modified + 3-file mig rule:
Domain (8 file SolutionErp.Domain.Hrm):
- EmployeeProfile.cs (main ~70 cols inherit AuditableEntity, 1-1 UNIQUE User)
- EmployeeWorkHistory.cs + EmployeeEducation.cs + EmployeeFamilyRelation.cs
- EmployeeSkill.cs (polymorphic Kind=Computer/Language/Other)
- EmployeeDocument.cs (IdCard/Passport/Degree/Certificate/LaborContract/Other)
- EmployeeCodeSequence.cs (PK string Prefix, NOT BaseEntity Id Guid)
- Enums.cs (10 enum gọn 1 file)
Application (1 file):
- IEmployeeCodeGenerator.cs interface (mirror IContractCodeGenerator)
Infrastructure (8 file):
- EmployeeCodeGenerator.cs impl IsolationLevel.Serializable transaction
- 7 EF Configuration file (HasIndex UNIQUE UserId/EmployeeCode/Phone +
HasMaxLength + HasColumnType decimal(18,2) + FK Cascade satellite)
- DependencyInjection.cs (M): register IEmployeeCodeGenerator → impl
Persistence (3 file modified + 2 new mig + 1 snapshot):
- IApplicationDbContext.cs (M): +7 DbSet<EmployeeProfile/...>
- ApplicationDbContext.cs (M): +7 DbSet impl
- ApplicationDbContextModelSnapshot.cs (M): EF auto-update
- 20260526110207_AddEmployeeProfiles.cs (NEW, EF auto-gen)
- 20260526110207_AddEmployeeProfiles.Designer.cs (NEW, EF auto-gen)
DbInitializer.cs (M, em main solo Task 3b ~90 LOC):
- using SolutionErp.Domain.Hrm import added
- SeedDemoEmployeeProfilesAsync method appended (end of class)
- Register call after SeedDemoUsersAsync line 88 (depend user exist first)
- NOT gated DemoSeed:Disabled flag (infrastructure data per gotcha #51 lesson)
- 30 demo profile mirror 30 user @solutions.com.vn + sequential code
NV/{YYYY}/0001..0030 + placeholder masked CMND/BHXH/Bank (bro UAT update
qua FE Page Task 5) + EmployeeCodeSequence row LastSeq=30 → production
gen tiếp 0031+
Verify:
- dotnet build: 0 err 2 unrelated warn DocxRenderer (2.49s + 7.86s rebuild)
- dotnet ef database update _Dev: Mig 34 applied (top of __EFMigrationsHistory)
- dotnet test: **120/120 PASS** baseline preserved (no test add Phase 10
test-after per §7 UAT mode — test bundle defer Task 4+5+6 done)
Stats target Phase 10 end: 33→42 mig (+9 Mig 34-42), 60→85 tables (+25),
~148→250 endpoint (+100), 38→60 FE pages (+22). Current after this commit:
33→34 mig + 60→66 tables + endpoint/FE unchanged (G-H1 Task 4+5 next).
Pattern reusable cross-project:
- Pattern 12-bis cross-module entity cookie-cutter mirror reinforced 3×
(S29 Plan B Contract V2 + S33 Plan B G-H1 EmployeeProfile)
- Infrastructure seed OUT of DemoSeed gate (gotcha #51 lesson, mirror Mig 32
SeedSampleContractWorkflowV2)
- DiaChi dual-write FK + freetext từ ngày đầu (NamGroup 1675 NV drift lesson)
Pending Plan B G-H1 Phase 2 (chờ anh main signal kick off):
- Task 4 — Implementer BE CQRS handler + 6 endpoint controller
- Task 5 — Implementer FE 2 app EmployeesPage 3-panel + 6 section tabs
- Task 6 — Em main Permission menu Hrm_HoSo* seed
- Task 7 — Reviewer pre-commit + CICD post-deploy verify
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -4,6 +4,7 @@ using SolutionErp.Domain.Budgets;
|
|||||||
using SolutionErp.Domain.Contracts;
|
using SolutionErp.Domain.Contracts;
|
||||||
using SolutionErp.Domain.Contracts.Details;
|
using SolutionErp.Domain.Contracts.Details;
|
||||||
using SolutionErp.Domain.Forms;
|
using SolutionErp.Domain.Forms;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
using SolutionErp.Domain.Identity;
|
using SolutionErp.Domain.Identity;
|
||||||
using SolutionErp.Domain.Master;
|
using SolutionErp.Domain.Master;
|
||||||
using SolutionErp.Domain.Master.Catalogs;
|
using SolutionErp.Domain.Master.Catalogs;
|
||||||
@ -83,5 +84,16 @@ public interface IApplicationDbContext
|
|||||||
DbSet<BudgetChangelog> BudgetChangelogs { get; }
|
DbSet<BudgetChangelog> BudgetChangelogs { get; }
|
||||||
DbSet<BudgetDepartmentApproval> BudgetDepartmentApprovals { get; }
|
DbSet<BudgetDepartmentApproval> BudgetDepartmentApprovals { get; }
|
||||||
|
|
||||||
|
// Phase 10.1 G-H1 (Mig 34 — S33) — Hồ sơ Nhân sự port từ NamGroup.
|
||||||
|
// 1 main + 5 satellite + 1 sequence. 1-1 với User qua UserId UNIQUE.
|
||||||
|
// 3 HĐLĐ satellite defer Plan H2 sau.
|
||||||
|
DbSet<EmployeeProfile> EmployeeProfiles { get; }
|
||||||
|
DbSet<EmployeeWorkHistory> EmployeeWorkHistories { get; }
|
||||||
|
DbSet<EmployeeEducation> EmployeeEducations { get; }
|
||||||
|
DbSet<EmployeeFamilyRelation> EmployeeFamilyRelations { get; }
|
||||||
|
DbSet<EmployeeSkill> EmployeeSkills { get; }
|
||||||
|
DbSet<EmployeeDocument> EmployeeDocuments { get; }
|
||||||
|
DbSet<EmployeeCodeSequence> EmployeeCodeSequences { get; }
|
||||||
|
|
||||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
namespace SolutionErp.Application.Hrm.Services;
|
||||||
|
|
||||||
|
// Atomic sequence generator cho MaNhanVien — mirror IContractCodeGenerator
|
||||||
|
// + IPurchaseEvaluationCodeGenerator pattern.
|
||||||
|
//
|
||||||
|
// Format: "NV/{YYYY}/{Seq:D4}"
|
||||||
|
// - YYYY = năm hiện tại (UTC)
|
||||||
|
// - Seq = 4 chữ số tăng dần, reset per năm
|
||||||
|
//
|
||||||
|
// VD: NV/2026/0001, NV/2026/0002, ... ; sang 2027 reset NV/2027/0001.
|
||||||
|
//
|
||||||
|
// Transaction SERIALIZABLE để tránh race condition khi 2 admin tạo NV cùng lúc.
|
||||||
|
public interface IEmployeeCodeGenerator
|
||||||
|
{
|
||||||
|
Task<string> GenerateAsync(CancellationToken ct = default);
|
||||||
|
}
|
||||||
15
src/Backend/SolutionErp.Domain/Hrm/EmployeeCodeSequence.cs
Normal file
15
src/Backend/SolutionErp.Domain/Hrm/EmployeeCodeSequence.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
// Sequence generator cho MaNhanVien format "NV/{YYYY}/{Seq:D4}".
|
||||||
|
// Prefix = "NV/2026" (per year), LastSeq tăng dần.
|
||||||
|
// Reset per năm: prefix "NV/2027" sẽ start LastSeq=1 lại.
|
||||||
|
// Update atomic qua transaction SERIALIZABLE (mirror ContractCodeSequence).
|
||||||
|
//
|
||||||
|
// PK là Prefix string NOT Id Guid (mirror Contract/PE pattern). KHÔNG inherit
|
||||||
|
// BaseEntity vì không cần audit fields trên sequence table.
|
||||||
|
public class EmployeeCodeSequence
|
||||||
|
{
|
||||||
|
public string Prefix { get; set; } = string.Empty;
|
||||||
|
public int LastSeq { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
26
src/Backend/SolutionErp.Domain/Hrm/EmployeeDocument.cs
Normal file
26
src/Backend/SolutionErp.Domain/Hrm/EmployeeDocument.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
// Satellite — File scan CCCD/Bằng/Chứng chỉ/HĐLĐ (NamGroup `CT_TAILIEU`).
|
||||||
|
// FK Cascade từ EmployeeProfile.
|
||||||
|
//
|
||||||
|
// File lưu local storage qua IFileStorage (mirror PurchaseEvaluationAttachment
|
||||||
|
// + ContractAttachment pattern). FilePath relative root storage dir.
|
||||||
|
public class EmployeeDocument : AuditableEntity
|
||||||
|
{
|
||||||
|
public Guid EmployeeProfileId { get; set; }
|
||||||
|
|
||||||
|
public EmployeeDocumentType DocumentType { get; set; }
|
||||||
|
|
||||||
|
public string FileName { get; set; } = string.Empty; // Tên file gốc khi upload
|
||||||
|
public string FilePath { get; set; } = string.Empty; // Relative path trong storage
|
||||||
|
public long FileSize { get; set; } // Byte
|
||||||
|
public string ContentType { get; set; } = string.Empty; // MIME (vd "application/pdf")
|
||||||
|
|
||||||
|
public DateOnly? IssueDate { get; set; } // Ngày cấp
|
||||||
|
public DateOnly? ExpiryDate { get; set; } // Ngày hết hạn
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
|
||||||
|
public EmployeeProfile? EmployeeProfile { get; set; }
|
||||||
|
}
|
||||||
25
src/Backend/SolutionErp.Domain/Hrm/EmployeeEducation.cs
Normal file
25
src/Backend/SolutionErp.Domain/Hrm/EmployeeEducation.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
// Satellite — Quá trình học vấn (NamGroup `CT_QUATRINHHOCVAN`).
|
||||||
|
// FK Cascade từ EmployeeProfile.
|
||||||
|
public class EmployeeEducation : AuditableEntity
|
||||||
|
{
|
||||||
|
public Guid EmployeeProfileId { get; set; }
|
||||||
|
|
||||||
|
public string SchoolName { get; set; } = string.Empty;
|
||||||
|
public string? Major { get; set; } // Chuyên ngành
|
||||||
|
|
||||||
|
public DegreeLevel? DegreeLevel { get; set; } // Trình độ (CĐ/ĐH/Th.S/TS)
|
||||||
|
public EducationMode? EducationMode { get; set; } // Hình thức (Chính quy/Tại chức/Từ xa)
|
||||||
|
public GradeLevel? GradeLevel { get; set; } // Xếp loại (TB/Khá/Giỏi)
|
||||||
|
|
||||||
|
public DateOnly? FromDate { get; set; }
|
||||||
|
public DateOnly? ToDate { get; set; }
|
||||||
|
public DateOnly? CertificateIssueDate { get; set; } // Ngày cấp bằng
|
||||||
|
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
|
||||||
|
public EmployeeProfile? EmployeeProfile { get; set; }
|
||||||
|
}
|
||||||
20
src/Backend/SolutionErp.Domain/Hrm/EmployeeFamilyRelation.cs
Normal file
20
src/Backend/SolutionErp.Domain/Hrm/EmployeeFamilyRelation.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
// Satellite — Quan hệ gia đình (NamGroup `CT_QUANHEGIADINH`).
|
||||||
|
// FK Cascade từ EmployeeProfile.
|
||||||
|
public class EmployeeFamilyRelation : AuditableEntity
|
||||||
|
{
|
||||||
|
public Guid EmployeeProfileId { get; set; }
|
||||||
|
|
||||||
|
public string FullName { get; set; } = string.Empty;
|
||||||
|
public FamilyRelationKind Relationship { get; set; }
|
||||||
|
public int? BirthYear { get; set; } // Năm sinh (chỉ year, không cần DateOnly đủ)
|
||||||
|
|
||||||
|
public string? Occupation { get; set; } // Nghề nghiệp
|
||||||
|
public string? CurrentAddress { get; set; } // Địa chỉ hiện tại
|
||||||
|
public string? Phone { get; set; }
|
||||||
|
|
||||||
|
public EmployeeProfile? EmployeeProfile { get; set; }
|
||||||
|
}
|
||||||
137
src/Backend/SolutionErp.Domain/Hrm/EmployeeProfile.cs
Normal file
137
src/Backend/SolutionErp.Domain/Hrm/EmployeeProfile.cs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
using SolutionErp.Domain.Identity;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
// Phase 10.1 G-H1 — Hồ sơ Nhân sự main entity (Mig 34).
|
||||||
|
// 1-1 với User qua UserId UNIQUE FK. AuditableEntity inherit cho soft delete.
|
||||||
|
//
|
||||||
|
// Port từ NamGroup CT_NHANSU (1675 NV) — Investigator field map đã verified với
|
||||||
|
// 10 NamGroup table. 5 satellite entity Phase 10.1 (defer 3 HĐLĐ Plan H2 sau):
|
||||||
|
// EmployeeWorkHistory + EmployeeEducation + EmployeeFamilyRelation +
|
||||||
|
// EmployeeSkill (polymorphic Kind) + EmployeeDocument.
|
||||||
|
//
|
||||||
|
// DiaChi dual-write 6 FK + freetext: spec gốc declare FK Province/District/Ward
|
||||||
|
// nhưng catalog chưa scaffold trong Mig 34 — DEFER FK constraint sang G-H2 khi
|
||||||
|
// thêm catalog Province/District/Ward. Plain nullable Guid? lưu giá trị tham
|
||||||
|
// chiếu tương lai + freetext snapshot song hành ngày đầu (lesson NamGroup drift).
|
||||||
|
//
|
||||||
|
// MaNhanVien format "NV/{YYYY}/{Seq:D4}" gen atomic SERIALIZABLE qua
|
||||||
|
// IEmployeeCodeGenerator (mirror IContractCodeGenerator pattern).
|
||||||
|
public class EmployeeProfile : AuditableEntity
|
||||||
|
{
|
||||||
|
// ===== Identity link =====
|
||||||
|
// 1-1 với User.Id (UNIQUE index). NV phải có user account login system.
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
|
// Mã nhân viên format "NV/2026/0001" — gen atomic per-year reset sequence.
|
||||||
|
public string EmployeeCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// ===== Trạng thái + phân loại =====
|
||||||
|
public EmployeeStatus EmployeeStatus { get; set; } = EmployeeStatus.Active;
|
||||||
|
public Gender? Gender { get; set; }
|
||||||
|
public MaritalStatus? MaritalStatus { get; set; }
|
||||||
|
public EmployeeType? EmployeeType { get; set; }
|
||||||
|
|
||||||
|
// ===== Thông tin cá nhân cơ bản =====
|
||||||
|
public DateOnly? DateOfBirth { get; set; }
|
||||||
|
public string? BirthPlace { get; set; } // Nơi sinh
|
||||||
|
public string? Hometown { get; set; } // Nguyên quán
|
||||||
|
|
||||||
|
// ===== Liên hệ =====
|
||||||
|
public string? Phone { get; set; } // SDT cá nhân (INDEX)
|
||||||
|
public string? PersonalEmail { get; set; } // Email cá nhân (khác email login)
|
||||||
|
public string? InternalPhone { get; set; } // SDT nội bộ
|
||||||
|
|
||||||
|
// ===== Dân tộc + tôn giáo + quốc tịch =====
|
||||||
|
public string? Ethnicity { get; set; } // Dân tộc
|
||||||
|
public string? Religion { get; set; } // Tôn giáo
|
||||||
|
public string Nationality { get; set; } = "Việt Nam";
|
||||||
|
|
||||||
|
// ===== Giấy tờ tuỳ thân =====
|
||||||
|
public string? IdCardNumber { get; set; } // CCCD/CMND
|
||||||
|
public DateOnly? IdCardIssueDate { get; set; }
|
||||||
|
public string? IdCardIssuePlace { get; set; }
|
||||||
|
|
||||||
|
public string? TaxCode { get; set; } // MST cá nhân
|
||||||
|
public string? SocialInsuranceNumber { get; set; } // Số sổ BHXH
|
||||||
|
public string? PassportNumber { get; set; }
|
||||||
|
|
||||||
|
// ===== Địa chỉ HKTT (Hộ khẩu thường trú) — dual-write FK + freetext =====
|
||||||
|
// FK Province/District/Ward DEFER G-H2 khi catalog scaffold (plain Guid? ngày đầu).
|
||||||
|
public string? PermanentAddressText { get; set; } // Freetext snapshot full (vd "123 Nguyễn Văn A, P. Bến Nghé, Q.1, TP.HCM")
|
||||||
|
public Guid? PermanentProvinceId { get; set; } // FK→Provinces (defer)
|
||||||
|
public Guid? PermanentDistrictId { get; set; } // FK→Districts (defer)
|
||||||
|
public Guid? PermanentWardId { get; set; } // FK→Wards (defer)
|
||||||
|
public string? StreetAddressPermanent { get; set; } // Số nhà + tên đường
|
||||||
|
|
||||||
|
// ===== Địa chỉ Tạm trú — dual-write FK + freetext =====
|
||||||
|
public string? TemporaryAddressText { get; set; }
|
||||||
|
public Guid? TemporaryProvinceId { get; set; }
|
||||||
|
public Guid? TemporaryDistrictId { get; set; }
|
||||||
|
public Guid? TemporaryWardId { get; set; }
|
||||||
|
public string? StreetAddressTemporary { get; set; }
|
||||||
|
|
||||||
|
// ===== Tuyển dụng + nghỉ việc =====
|
||||||
|
public DateOnly? HireDate { get; set; } // Ngày vào làm
|
||||||
|
public DateOnly? ResignDate { get; set; } // Ngày nghỉ việc
|
||||||
|
|
||||||
|
// ===== Liên hệ khẩn cấp (inline NOT satellite — chỉ 1 contact) =====
|
||||||
|
public string? EmergencyContactName { get; set; }
|
||||||
|
public string? EmergencyContactPhone { get; set; }
|
||||||
|
public string? EmergencyContactAddress { get; set; }
|
||||||
|
|
||||||
|
// ===== Trình độ + chức danh =====
|
||||||
|
public string? Qualification { get; set; } // Trình độ chuyên môn cao nhất (vd "Thạc sĩ XD")
|
||||||
|
public string? AcademicTitle { get; set; } // Học hàm/học vị (vd "PGS.TS.")
|
||||||
|
|
||||||
|
// ===== Vị trí công tác =====
|
||||||
|
public string? WorkLocation { get; set; } // Nơi làm việc (công trường / VP)
|
||||||
|
public string? TimekeepingCode { get; set; } // Mã chấm công (sync máy chấm)
|
||||||
|
|
||||||
|
// ===== Tài khoản ngân hàng =====
|
||||||
|
public string? BankAccount { get; set; }
|
||||||
|
public string? BankName { get; set; }
|
||||||
|
public string? BankBranch { get; set; }
|
||||||
|
|
||||||
|
// ===== Sức khoẻ =====
|
||||||
|
public int? HeightCm { get; set; }
|
||||||
|
public int? WeightKg { get; set; }
|
||||||
|
public string? BloodType { get; set; } // "A+", "O-", "AB" ...
|
||||||
|
|
||||||
|
// ===== Lương =====
|
||||||
|
// decimal NOT double (tránh floating point error tài chính).
|
||||||
|
public decimal? BaseSalary { get; set; } // Lương cơ bản
|
||||||
|
public decimal? TotalSalary { get; set; } // Tổng lương (bao gồm phụ cấp)
|
||||||
|
|
||||||
|
// ===== Phép =====
|
||||||
|
// decimal(5,2) — vd 12.5 ngày phép.
|
||||||
|
public decimal? AnnualLeaveDays { get; set; } // Phép năm
|
||||||
|
public decimal? RemainingLeaveDays { get; set; } // Phép còn lại
|
||||||
|
public decimal? CompensatoryLeaveDays { get; set; } // Phép bù
|
||||||
|
public decimal? SeniorityLeaveDays { get; set; } // Phép thâm niên
|
||||||
|
|
||||||
|
// ===== BHXH + BHYT =====
|
||||||
|
public DateOnly? SocialInsuranceStartDate { get; set; } // Ngày bắt đầu đóng BHXH
|
||||||
|
public string? MedicalRegistrationPlace { get; set; } // Nơi đăng ký KCB ban đầu BHYT
|
||||||
|
|
||||||
|
// ===== Đoàn thể =====
|
||||||
|
public bool IsCommunistParty { get; set; }
|
||||||
|
public DateOnly? CommunistPartyJoinDate { get; set; }
|
||||||
|
public bool IsYouthUnion { get; set; }
|
||||||
|
public DateOnly? YouthUnionJoinDate { get; set; }
|
||||||
|
public bool IsTradeUnion { get; set; }
|
||||||
|
public DateOnly? TradeUnionJoinDate { get; set; }
|
||||||
|
|
||||||
|
// ===== Khác =====
|
||||||
|
public string? PhotoUrl { get; set; } // URL ảnh đại diện
|
||||||
|
public string? Notes { get; set; } // Ghi chú free text
|
||||||
|
|
||||||
|
// ===== Navigation =====
|
||||||
|
public User? User { get; set; }
|
||||||
|
public ICollection<EmployeeWorkHistory> WorkHistories { get; set; } = new List<EmployeeWorkHistory>();
|
||||||
|
public ICollection<EmployeeEducation> Educations { get; set; } = new List<EmployeeEducation>();
|
||||||
|
public ICollection<EmployeeFamilyRelation> FamilyRelations { get; set; } = new List<EmployeeFamilyRelation>();
|
||||||
|
public ICollection<EmployeeSkill> Skills { get; set; } = new List<EmployeeSkill>();
|
||||||
|
public ICollection<EmployeeDocument> Documents { get; set; } = new List<EmployeeDocument>();
|
||||||
|
}
|
||||||
30
src/Backend/SolutionErp.Domain/Hrm/EmployeeSkill.cs
Normal file
30
src/Backend/SolutionErp.Domain/Hrm/EmployeeSkill.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
// Satellite POLYMORPHIC — gộp 3 NamGroup table:
|
||||||
|
// CT_KYNANGVITINH (Kind=Computer)
|
||||||
|
// CT_NGOAINGU (Kind=Language)
|
||||||
|
// CT_KYNANGKHAC (Kind=Other)
|
||||||
|
// Decision em main: 1 entity polymorphic discriminator Kind enum để tránh
|
||||||
|
// 3 satellite riêng cho cùng pattern "skill".
|
||||||
|
//
|
||||||
|
// Field semantic mapping per Kind:
|
||||||
|
// Computer: Name=TenPhanMem (vd "AutoCAD"), Level=TrinhDo (vd "Thành thạo")
|
||||||
|
// Language: Name=TenNgoaiNgu (vd "Tiếng Anh"), LanguageId=ISO code "en"|"fr"|"zh"|"jp"|"vi",
|
||||||
|
// Level=BangCapChungChi (vd "IELTS 7.0", "TOEIC 800")
|
||||||
|
// Other: Name=TenKyNangKhac (vd "Lãnh đạo nhóm"), Level=free text
|
||||||
|
//
|
||||||
|
// FK Cascade từ EmployeeProfile.
|
||||||
|
public class EmployeeSkill : AuditableEntity
|
||||||
|
{
|
||||||
|
public Guid EmployeeProfileId { get; set; }
|
||||||
|
|
||||||
|
public SkillKind Kind { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty; // Required (semantic theo Kind)
|
||||||
|
public string? LanguageId { get; set; } // ISO code chỉ set khi Kind=Language ("en"/"fr"/...)
|
||||||
|
public string? Level { get; set; } // TrinhDo / BangCapChungChi / free text
|
||||||
|
|
||||||
|
public EmployeeProfile? EmployeeProfile { get; set; }
|
||||||
|
}
|
||||||
23
src/Backend/SolutionErp.Domain/Hrm/EmployeeWorkHistory.cs
Normal file
23
src/Backend/SolutionErp.Domain/Hrm/EmployeeWorkHistory.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using SolutionErp.Domain.Common;
|
||||||
|
|
||||||
|
namespace SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
// Satellite — Quá trình công tác (NamGroup `CT_QUATRINHCONGTAC`).
|
||||||
|
// Cookie-cutter port. FK Cascade từ EmployeeProfile (xoá NV → xoá history).
|
||||||
|
public class EmployeeWorkHistory : AuditableEntity
|
||||||
|
{
|
||||||
|
public Guid EmployeeProfileId { get; set; }
|
||||||
|
|
||||||
|
public string CompanyName { get; set; } = string.Empty;
|
||||||
|
public string? CompanyAddress { get; set; }
|
||||||
|
public string? Industry { get; set; } // Ngành nghề
|
||||||
|
|
||||||
|
public DateOnly? FromDate { get; set; }
|
||||||
|
public DateOnly? ToDate { get; set; }
|
||||||
|
|
||||||
|
public string? JobTitle { get; set; } // Chức danh
|
||||||
|
public string? JobDescription { get; set; } // Mô tả công việc
|
||||||
|
public string? ResignReason { get; set; } // Lý do nghỉ
|
||||||
|
|
||||||
|
public EmployeeProfile? EmployeeProfile { get; set; }
|
||||||
|
}
|
||||||
84
src/Backend/SolutionErp.Domain/Hrm/Enums.cs
Normal file
84
src/Backend/SolutionErp.Domain/Hrm/Enums.cs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
namespace SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
// Phase 10.1 G-H1 — Hồ sơ Nhân sự enum set. 10 enum gọn 1 file.
|
||||||
|
// Port từ NamGroup CT_NHANSU (1675 NV) catalog. Int storage (TS6 erasableSyntaxOnly
|
||||||
|
// FE mirror dùng const-object pattern, NOT enum).
|
||||||
|
|
||||||
|
public enum EmployeeStatus
|
||||||
|
{
|
||||||
|
Active = 1, // Đang làm việc
|
||||||
|
OnLeave = 2, // Nghỉ phép / Tạm hoãn HĐ
|
||||||
|
Resigned = 3, // Đã nghỉ việc
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Gender
|
||||||
|
{
|
||||||
|
Male = 1,
|
||||||
|
Female = 2,
|
||||||
|
Other = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MaritalStatus
|
||||||
|
{
|
||||||
|
Single = 1, // Độc thân
|
||||||
|
Married = 2, // Đã kết hôn
|
||||||
|
Divorced = 3, // Đã ly hôn
|
||||||
|
Widowed = 4, // Goá
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EmployeeType
|
||||||
|
{
|
||||||
|
FullTime = 1, // Chính thức
|
||||||
|
PartTime = 2, // Bán thời gian
|
||||||
|
Intern = 3, // Thực tập
|
||||||
|
Contractor = 4, // Khoán việc
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DegreeLevel
|
||||||
|
{
|
||||||
|
College = 1, // Cao đẳng
|
||||||
|
Bachelor = 2, // Đại học
|
||||||
|
Master = 3, // Thạc sĩ
|
||||||
|
PhD = 4, // Tiến sĩ
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EducationMode
|
||||||
|
{
|
||||||
|
FullTime = 1, // Chính quy
|
||||||
|
PartTime = 2, // Tại chức
|
||||||
|
Distance = 3, // Từ xa
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GradeLevel
|
||||||
|
{
|
||||||
|
Average = 1, // Trung bình
|
||||||
|
Good = 2, // Khá
|
||||||
|
Excellent = 3, // Giỏi
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FamilyRelationKind
|
||||||
|
{
|
||||||
|
Father = 1,
|
||||||
|
Mother = 2,
|
||||||
|
Spouse = 3, // Vợ/Chồng
|
||||||
|
Child = 4, // Con
|
||||||
|
Sibling = 5, // Anh/Chị/Em ruột
|
||||||
|
Other = 99, // Khác
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SkillKind
|
||||||
|
{
|
||||||
|
Computer = 1, // Kỹ năng vi tính (TenPhanMem + TrinhDo)
|
||||||
|
Language = 2, // Ngoại ngữ (LanguageId + BangCapChungChi)
|
||||||
|
Other = 3, // Kỹ năng khác (TenKyNangKhac + Level free text)
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EmployeeDocumentType
|
||||||
|
{
|
||||||
|
IdCard = 1, // CMND/CCCD scan
|
||||||
|
Passport = 2, // Hộ chiếu
|
||||||
|
Degree = 3, // Bằng cấp
|
||||||
|
Certificate = 4, // Chứng chỉ
|
||||||
|
LaborContract = 5, // HĐLĐ
|
||||||
|
Other = 99,
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using SolutionErp.Application.Common.Interfaces;
|
using SolutionErp.Application.Common.Interfaces;
|
||||||
using SolutionErp.Application.Contracts.Services;
|
using SolutionErp.Application.Contracts.Services;
|
||||||
using SolutionErp.Application.Forms.Services;
|
using SolutionErp.Application.Forms.Services;
|
||||||
|
using SolutionErp.Application.Hrm.Services;
|
||||||
using SolutionErp.Application.Notifications;
|
using SolutionErp.Application.Notifications;
|
||||||
using SolutionErp.Application.PurchaseEvaluations.Services;
|
using SolutionErp.Application.PurchaseEvaluations.Services;
|
||||||
using SolutionErp.Application.Reports.Services;
|
using SolutionErp.Application.Reports.Services;
|
||||||
@ -35,6 +36,7 @@ public static class DependencyInjection
|
|||||||
services.AddScoped<IContractWorkflowService, ContractWorkflowService>();
|
services.AddScoped<IContractWorkflowService, ContractWorkflowService>();
|
||||||
services.AddScoped<IPurchaseEvaluationWorkflowService, PurchaseEvaluationWorkflowService>();
|
services.AddScoped<IPurchaseEvaluationWorkflowService, PurchaseEvaluationWorkflowService>();
|
||||||
services.AddScoped<IPurchaseEvaluationCodeGenerator, PurchaseEvaluationCodeGenerator>();
|
services.AddScoped<IPurchaseEvaluationCodeGenerator, PurchaseEvaluationCodeGenerator>();
|
||||||
|
services.AddScoped<IEmployeeCodeGenerator, EmployeeCodeGenerator>();
|
||||||
services.AddScoped<IContractExcelExporter, ContractExcelExporter>();
|
services.AddScoped<IContractExcelExporter, ContractExcelExporter>();
|
||||||
services.AddScoped<INotificationService, NotificationService>();
|
services.AddScoped<INotificationService, NotificationService>();
|
||||||
services.AddScoped<IChangelogService, ChangelogService>();
|
services.AddScoped<IChangelogService, ChangelogService>();
|
||||||
|
|||||||
@ -6,6 +6,7 @@ using SolutionErp.Domain.Budgets;
|
|||||||
using SolutionErp.Domain.Contracts;
|
using SolutionErp.Domain.Contracts;
|
||||||
using SolutionErp.Domain.Contracts.Details;
|
using SolutionErp.Domain.Contracts.Details;
|
||||||
using SolutionErp.Domain.Forms;
|
using SolutionErp.Domain.Forms;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
using SolutionErp.Domain.Identity;
|
using SolutionErp.Domain.Identity;
|
||||||
using SolutionErp.Domain.Master;
|
using SolutionErp.Domain.Master;
|
||||||
using SolutionErp.Domain.Master.Catalogs;
|
using SolutionErp.Domain.Master.Catalogs;
|
||||||
@ -80,6 +81,15 @@ public class ApplicationDbContext
|
|||||||
public DbSet<BudgetChangelog> BudgetChangelogs => Set<BudgetChangelog>();
|
public DbSet<BudgetChangelog> BudgetChangelogs => Set<BudgetChangelog>();
|
||||||
public DbSet<BudgetDepartmentApproval> BudgetDepartmentApprovals => Set<BudgetDepartmentApproval>();
|
public DbSet<BudgetDepartmentApproval> BudgetDepartmentApprovals => Set<BudgetDepartmentApproval>();
|
||||||
|
|
||||||
|
// Phase 10.1 G-H1 (Mig 34 — S33) — Hồ sơ Nhân sự port từ NamGroup.
|
||||||
|
public DbSet<EmployeeProfile> EmployeeProfiles => Set<EmployeeProfile>();
|
||||||
|
public DbSet<EmployeeWorkHistory> EmployeeWorkHistories => Set<EmployeeWorkHistory>();
|
||||||
|
public DbSet<EmployeeEducation> EmployeeEducations => Set<EmployeeEducation>();
|
||||||
|
public DbSet<EmployeeFamilyRelation> EmployeeFamilyRelations => Set<EmployeeFamilyRelation>();
|
||||||
|
public DbSet<EmployeeSkill> EmployeeSkills => Set<EmployeeSkill>();
|
||||||
|
public DbSet<EmployeeDocument> EmployeeDocuments => Set<EmployeeDocument>();
|
||||||
|
public DbSet<EmployeeCodeSequence> EmployeeCodeSequences => Set<EmployeeCodeSequence>();
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
base.OnModelCreating(builder);
|
base.OnModelCreating(builder);
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
// EF Mig 34 — Sequence table cho MaNhanVien. PK Prefix string.
|
||||||
|
// Mirror ContractCodeSequenceConfiguration pattern.
|
||||||
|
public class EmployeeCodeSequenceConfiguration : IEntityTypeConfiguration<EmployeeCodeSequence>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<EmployeeCodeSequence> e)
|
||||||
|
{
|
||||||
|
e.ToTable("EmployeeCodeSequences");
|
||||||
|
|
||||||
|
e.HasKey(x => x.Prefix);
|
||||||
|
e.Property(x => x.Prefix).HasMaxLength(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
// EF Mig 34 — File scan attachment (satellite). FK Cascade EmployeeProfile.
|
||||||
|
// INDEX DocumentType cho filter "tất cả bằng cấp của NV X".
|
||||||
|
public class EmployeeDocumentConfiguration : IEntityTypeConfiguration<EmployeeDocument>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<EmployeeDocument> e)
|
||||||
|
{
|
||||||
|
e.ToTable("EmployeeDocuments");
|
||||||
|
|
||||||
|
e.Property(x => x.FileName).HasMaxLength(255).IsRequired();
|
||||||
|
e.Property(x => x.FilePath).HasMaxLength(500).IsRequired();
|
||||||
|
e.Property(x => x.ContentType).HasMaxLength(100).IsRequired();
|
||||||
|
e.Property(x => x.Notes).HasMaxLength(500);
|
||||||
|
|
||||||
|
e.HasOne(x => x.EmployeeProfile)
|
||||||
|
.WithMany(p => p.Documents)
|
||||||
|
.HasForeignKey(x => x.EmployeeProfileId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
e.HasIndex(x => x.EmployeeProfileId);
|
||||||
|
e.HasIndex(x => x.DocumentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
// EF Mig 34 — Quá trình học vấn (satellite). FK Cascade EmployeeProfile.
|
||||||
|
public class EmployeeEducationConfiguration : IEntityTypeConfiguration<EmployeeEducation>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<EmployeeEducation> e)
|
||||||
|
{
|
||||||
|
e.ToTable("EmployeeEducations");
|
||||||
|
|
||||||
|
e.Property(x => x.SchoolName).HasMaxLength(200).IsRequired();
|
||||||
|
e.Property(x => x.Major).HasMaxLength(200);
|
||||||
|
e.Property(x => x.Notes).HasMaxLength(500);
|
||||||
|
|
||||||
|
e.HasOne(x => x.EmployeeProfile)
|
||||||
|
.WithMany(p => p.Educations)
|
||||||
|
.HasForeignKey(x => x.EmployeeProfileId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
e.HasIndex(x => x.EmployeeProfileId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
// EF Mig 34 — Quan hệ gia đình (satellite). FK Cascade EmployeeProfile.
|
||||||
|
public class EmployeeFamilyRelationConfiguration : IEntityTypeConfiguration<EmployeeFamilyRelation>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<EmployeeFamilyRelation> e)
|
||||||
|
{
|
||||||
|
e.ToTable("EmployeeFamilyRelations");
|
||||||
|
|
||||||
|
e.Property(x => x.FullName).HasMaxLength(200).IsRequired();
|
||||||
|
e.Property(x => x.Occupation).HasMaxLength(200);
|
||||||
|
e.Property(x => x.CurrentAddress).HasMaxLength(500);
|
||||||
|
e.Property(x => x.Phone).HasMaxLength(20);
|
||||||
|
|
||||||
|
e.HasOne(x => x.EmployeeProfile)
|
||||||
|
.WithMany(p => p.FamilyRelations)
|
||||||
|
.HasForeignKey(x => x.EmployeeProfileId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
e.HasIndex(x => x.EmployeeProfileId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
|
using SolutionErp.Domain.Identity;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
// EF Mig 34 — Hồ sơ Nhân sự main entity.
|
||||||
|
// 1-1 User qua UNIQUE UserId. FK Cascade Users (xoá user → wipe profile).
|
||||||
|
// EmployeeCode UNIQUE. Phone INDEX (lookup nhanh).
|
||||||
|
// IsDeleted INDEX (soft delete query filter).
|
||||||
|
//
|
||||||
|
// Province/District/Ward 6 cột nullable plain Guid? — KHÔNG declare FK
|
||||||
|
// constraint (catalog chưa scaffold trong Mig 34, defer G-H2 khi thêm catalog
|
||||||
|
// thì alter table add FK).
|
||||||
|
public class EmployeeProfileConfiguration : IEntityTypeConfiguration<EmployeeProfile>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<EmployeeProfile> e)
|
||||||
|
{
|
||||||
|
e.ToTable("EmployeeProfiles");
|
||||||
|
|
||||||
|
// ===== EmployeeCode + UserId UNIQUE =====
|
||||||
|
e.Property(x => x.EmployeeCode).HasMaxLength(50).IsRequired();
|
||||||
|
e.HasIndex(x => x.EmployeeCode).IsUnique();
|
||||||
|
|
||||||
|
e.HasIndex(x => x.UserId).IsUnique();
|
||||||
|
|
||||||
|
// ===== 1-1 User FK Cascade =====
|
||||||
|
e.HasOne(x => x.User)
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey<EmployeeProfile>(x => x.UserId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
// ===== Cá nhân =====
|
||||||
|
e.Property(x => x.BirthPlace).HasMaxLength(200);
|
||||||
|
e.Property(x => x.Hometown).HasMaxLength(200);
|
||||||
|
|
||||||
|
e.Property(x => x.Phone).HasMaxLength(20);
|
||||||
|
e.HasIndex(x => x.Phone); // Non-unique (nhiều NV cùng nhà có thể chung SDT cố định)
|
||||||
|
e.Property(x => x.PersonalEmail).HasMaxLength(200);
|
||||||
|
e.Property(x => x.InternalPhone).HasMaxLength(20);
|
||||||
|
|
||||||
|
e.Property(x => x.Ethnicity).HasMaxLength(50);
|
||||||
|
e.Property(x => x.Religion).HasMaxLength(50);
|
||||||
|
e.Property(x => x.Nationality).HasMaxLength(50).IsRequired();
|
||||||
|
|
||||||
|
// ===== Giấy tờ =====
|
||||||
|
e.Property(x => x.IdCardNumber).HasMaxLength(20);
|
||||||
|
e.Property(x => x.IdCardIssuePlace).HasMaxLength(200);
|
||||||
|
e.Property(x => x.TaxCode).HasMaxLength(20);
|
||||||
|
e.Property(x => x.SocialInsuranceNumber).HasMaxLength(20);
|
||||||
|
e.Property(x => x.PassportNumber).HasMaxLength(20);
|
||||||
|
|
||||||
|
// ===== Địa chỉ HKTT (no FK — defer G-H2) =====
|
||||||
|
e.Property(x => x.PermanentAddressText).HasMaxLength(500);
|
||||||
|
e.Property(x => x.StreetAddressPermanent).HasMaxLength(200);
|
||||||
|
|
||||||
|
// ===== Địa chỉ Tạm trú (no FK — defer G-H2) =====
|
||||||
|
e.Property(x => x.TemporaryAddressText).HasMaxLength(500);
|
||||||
|
e.Property(x => x.StreetAddressTemporary).HasMaxLength(200);
|
||||||
|
|
||||||
|
// ===== Khẩn cấp =====
|
||||||
|
e.Property(x => x.EmergencyContactName).HasMaxLength(200);
|
||||||
|
e.Property(x => x.EmergencyContactPhone).HasMaxLength(20);
|
||||||
|
e.Property(x => x.EmergencyContactAddress).HasMaxLength(500);
|
||||||
|
|
||||||
|
// ===== Trình độ + chức danh =====
|
||||||
|
e.Property(x => x.Qualification).HasMaxLength(200);
|
||||||
|
e.Property(x => x.AcademicTitle).HasMaxLength(200);
|
||||||
|
|
||||||
|
// ===== Vị trí =====
|
||||||
|
e.Property(x => x.WorkLocation).HasMaxLength(200);
|
||||||
|
e.Property(x => x.TimekeepingCode).HasMaxLength(50);
|
||||||
|
|
||||||
|
// ===== Ngân hàng =====
|
||||||
|
e.Property(x => x.BankAccount).HasMaxLength(50);
|
||||||
|
e.Property(x => x.BankName).HasMaxLength(200);
|
||||||
|
e.Property(x => x.BankBranch).HasMaxLength(200);
|
||||||
|
|
||||||
|
// ===== Sức khoẻ =====
|
||||||
|
e.Property(x => x.BloodType).HasMaxLength(5);
|
||||||
|
|
||||||
|
// ===== Lương + Phép — decimal precision =====
|
||||||
|
e.Property(x => x.BaseSalary).HasColumnType("decimal(18,2)");
|
||||||
|
e.Property(x => x.TotalSalary).HasColumnType("decimal(18,2)");
|
||||||
|
e.Property(x => x.AnnualLeaveDays).HasColumnType("decimal(5,2)");
|
||||||
|
e.Property(x => x.RemainingLeaveDays).HasColumnType("decimal(5,2)");
|
||||||
|
e.Property(x => x.CompensatoryLeaveDays).HasColumnType("decimal(5,2)");
|
||||||
|
e.Property(x => x.SeniorityLeaveDays).HasColumnType("decimal(5,2)");
|
||||||
|
|
||||||
|
// ===== BHXH =====
|
||||||
|
e.Property(x => x.MedicalRegistrationPlace).HasMaxLength(200);
|
||||||
|
|
||||||
|
// ===== Khác =====
|
||||||
|
e.Property(x => x.PhotoUrl).HasMaxLength(500);
|
||||||
|
e.Property(x => x.Notes).HasMaxLength(2000);
|
||||||
|
|
||||||
|
// ===== Soft delete index =====
|
||||||
|
e.HasIndex(x => x.IsDeleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
// EF Mig 34 — Skill polymorphic Kind=Computer/Language/Other.
|
||||||
|
// FK Cascade EmployeeProfile. INDEX Kind cho filter "tất cả ngoại ngữ của NV X".
|
||||||
|
public class EmployeeSkillConfiguration : IEntityTypeConfiguration<EmployeeSkill>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<EmployeeSkill> e)
|
||||||
|
{
|
||||||
|
e.ToTable("EmployeeSkills");
|
||||||
|
|
||||||
|
e.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
||||||
|
e.Property(x => x.LanguageId).HasMaxLength(20);
|
||||||
|
e.Property(x => x.Level).HasMaxLength(200);
|
||||||
|
|
||||||
|
e.HasOne(x => x.EmployeeProfile)
|
||||||
|
.WithMany(p => p.Skills)
|
||||||
|
.HasForeignKey(x => x.EmployeeProfileId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
e.HasIndex(x => x.EmployeeProfileId);
|
||||||
|
e.HasIndex(x => x.Kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
// EF Mig 34 — Quá trình công tác (satellite). FK Cascade EmployeeProfile.
|
||||||
|
public class EmployeeWorkHistoryConfiguration : IEntityTypeConfiguration<EmployeeWorkHistory>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<EmployeeWorkHistory> e)
|
||||||
|
{
|
||||||
|
e.ToTable("EmployeeWorkHistories");
|
||||||
|
|
||||||
|
e.Property(x => x.CompanyName).HasMaxLength(200).IsRequired();
|
||||||
|
e.Property(x => x.CompanyAddress).HasMaxLength(500);
|
||||||
|
e.Property(x => x.Industry).HasMaxLength(200);
|
||||||
|
|
||||||
|
e.Property(x => x.JobTitle).HasMaxLength(200);
|
||||||
|
e.Property(x => x.JobDescription).HasMaxLength(2000);
|
||||||
|
e.Property(x => x.ResignReason).HasMaxLength(500);
|
||||||
|
|
||||||
|
e.HasOne(x => x.EmployeeProfile)
|
||||||
|
.WithMany(p => p.WorkHistories)
|
||||||
|
.HasForeignKey(x => x.EmployeeProfileId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
e.HasIndex(x => x.EmployeeProfileId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ using SolutionErp.Application.Contracts.Services;
|
|||||||
using SolutionErp.Domain.ApprovalWorkflowsV2;
|
using SolutionErp.Domain.ApprovalWorkflowsV2;
|
||||||
using SolutionErp.Domain.Contracts;
|
using SolutionErp.Domain.Contracts;
|
||||||
using SolutionErp.Domain.Forms;
|
using SolutionErp.Domain.Forms;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
using SolutionErp.Domain.Identity;
|
using SolutionErp.Domain.Identity;
|
||||||
using SolutionErp.Domain.Master;
|
using SolutionErp.Domain.Master;
|
||||||
using SolutionErp.Domain.Master.Catalogs;
|
using SolutionErp.Domain.Master.Catalogs;
|
||||||
@ -86,6 +87,11 @@ public static class DbInitializer
|
|||||||
await SeedAdminAsync(userManager, logger);
|
await SeedAdminAsync(userManager, logger);
|
||||||
await SeedDepartmentsAsync(db, logger);
|
await SeedDepartmentsAsync(db, logger);
|
||||||
await SeedDemoUsersAsync(db, userManager, logger);
|
await SeedDemoUsersAsync(db, userManager, logger);
|
||||||
|
// Plan B G-H1 (Mig 34 S33 2026-05-26) — seed EmployeeProfile 1-1 với
|
||||||
|
// mọi user @solutions.com.vn. Idempotent. NOT gated DemoSeed flag
|
||||||
|
// (infrastructure data, mirror Mig 32 SeedSampleContractWorkflowV2
|
||||||
|
// gotcha #51 lesson). Bro UAT update field nhạy cảm qua FE Hồ sơ NS.
|
||||||
|
await SeedDemoEmployeeProfilesAsync(db, userManager, logger);
|
||||||
await SeedMenuTreeAsync(db, logger);
|
await SeedMenuTreeAsync(db, logger);
|
||||||
await SeedAdminPermissionsAsync(db, roleManager, logger);
|
await SeedAdminPermissionsAsync(db, roleManager, logger);
|
||||||
await SeedDemoMasterDataAsync(db, logger);
|
await SeedDemoMasterDataAsync(db, logger);
|
||||||
@ -1919,4 +1925,99 @@ public static class DbInitializer
|
|||||||
logger.LogInformation("Seeded {Count} contract templates (active file check)", added);
|
logger.LogInformation("Seeded {Count} contract templates (active file check)", added);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plan B G-H1 (Mig 34 — S33 2026-05-26) — seed EmployeeProfile cho mọi
|
||||||
|
// user @solutions.com.vn (30 demo + admin). Idempotent — skip nếu đã có
|
||||||
|
// EmployeeProfile. Sequential code NV/{Year}/0001..N. Field nhạy cảm
|
||||||
|
// (CMND/BHXH/Bank) PLACEHOLDER masked — bro UAT update real qua FE Hồ sơ
|
||||||
|
// NS Page (Task 5).
|
||||||
|
//
|
||||||
|
// Cũng seed EmployeeCodeSequence "NV/{Year}" với LastSeq=N → production
|
||||||
|
// gen tiếp từ N+1 (mirror PE/Contract CodeGen pattern).
|
||||||
|
//
|
||||||
|
// NOT gated DemoSeed:Disabled flag vì EmployeeProfile là INFRASTRUCTURE
|
||||||
|
// data (1-1 với User), không phải DEMO content như sample HĐ/PE. Gotcha
|
||||||
|
// #51 lesson (S29 Plan B): infra seed phải OUT of flag gate.
|
||||||
|
private static async Task SeedDemoEmployeeProfilesAsync(
|
||||||
|
ApplicationDbContext db, UserManager<User> userManager, ILogger logger)
|
||||||
|
{
|
||||||
|
if (await db.EmployeeProfiles.AnyAsync())
|
||||||
|
{
|
||||||
|
logger.LogInformation("SeedDemoEmployeeProfilesAsync: skip — đã có EmployeeProfile.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var users = await userManager.Users
|
||||||
|
.Where(u => u.Email != null && u.Email.EndsWith("@solutions.com.vn"))
|
||||||
|
.OrderBy(u => u.Email)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (users.Count == 0)
|
||||||
|
{
|
||||||
|
logger.LogWarning("SeedDemoEmployeeProfilesAsync: no users @solutions.com.vn found — run SeedDemoUsersAsync first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rng = new Random(20260526); // Deterministic seed for reproducibility
|
||||||
|
var year = DateTime.UtcNow.Year;
|
||||||
|
var seq = 0;
|
||||||
|
foreach (var u in users)
|
||||||
|
{
|
||||||
|
seq++;
|
||||||
|
var birthYear = 1970 + rng.Next(0, 26); // 1970-1995 → 30-55 tuổi
|
||||||
|
var hireYear = 2020 + (seq % 6); // 2020-2025
|
||||||
|
var profile = new EmployeeProfile
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
UserId = u.Id,
|
||||||
|
EmployeeCode = $"NV/{year}/{seq:D4}",
|
||||||
|
EmployeeStatus = SolutionErp.Domain.Hrm.EmployeeStatus.Active,
|
||||||
|
Gender = (seq % 2 == 0) ? SolutionErp.Domain.Hrm.Gender.Female : SolutionErp.Domain.Hrm.Gender.Male,
|
||||||
|
MaritalStatus = (seq % 3 == 0) ? SolutionErp.Domain.Hrm.MaritalStatus.Single : SolutionErp.Domain.Hrm.MaritalStatus.Married,
|
||||||
|
EmployeeType = SolutionErp.Domain.Hrm.EmployeeType.FullTime,
|
||||||
|
DateOfBirth = new DateOnly(birthYear, rng.Next(1, 13), rng.Next(1, 28)),
|
||||||
|
BirthPlace = "Hà Nội",
|
||||||
|
Hometown = "Hà Nội",
|
||||||
|
Phone = $"09{rng.Next(10000000, 99999999)}",
|
||||||
|
PersonalEmail = u.Email,
|
||||||
|
Nationality = "Việt Nam",
|
||||||
|
Ethnicity = "Kinh",
|
||||||
|
IdCardNumber = $"001099{seq:D6}", // PLACEHOLDER masked
|
||||||
|
IdCardIssueDate = new DateOnly(2020, 1, 15),
|
||||||
|
IdCardIssuePlace = "Cục CS QLHC về TTXH",
|
||||||
|
TaxCode = $"8{seq:D9}",
|
||||||
|
SocialInsuranceNumber = $"BHXH{seq:D8}",
|
||||||
|
PermanentAddressText = "Hà Nội (cập nhật qua Hồ sơ NS)",
|
||||||
|
HireDate = new DateOnly(hireYear, ((seq % 12) + 1), 1),
|
||||||
|
EmergencyContactName = "Người thân — cập nhật sau",
|
||||||
|
EmergencyContactPhone = $"09{rng.Next(10000000, 99999999)}",
|
||||||
|
Qualification = (seq % 4 == 0) ? "Thạc sĩ" : "Đại học",
|
||||||
|
WorkLocation = (seq % 3 == 0) ? "Công trường" : "Văn phòng",
|
||||||
|
BankAccount = $"19030000{seq:D4}",
|
||||||
|
BankName = "Techcombank",
|
||||||
|
BankBranch = "Hà Nội",
|
||||||
|
BaseSalary = 15_000_000m + (seq % 10) * 1_000_000m,
|
||||||
|
AnnualLeaveDays = 12m,
|
||||||
|
RemainingLeaveDays = 12m - (seq % 5),
|
||||||
|
SocialInsuranceStartDate = new DateOnly(hireYear, ((seq % 12) + 1), 1),
|
||||||
|
IsCommunistParty = false,
|
||||||
|
IsYouthUnion = (seq % 4 == 0),
|
||||||
|
IsTradeUnion = true,
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
};
|
||||||
|
db.EmployeeProfiles.Add(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.EmployeeCodeSequences.Add(new EmployeeCodeSequence
|
||||||
|
{
|
||||||
|
Prefix = $"NV/{year}",
|
||||||
|
LastSeq = seq,
|
||||||
|
UpdatedAt = DateTime.UtcNow,
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
logger.LogInformation(
|
||||||
|
"SeedDemoEmployeeProfilesAsync: seeded {Count} profiles + 1 sequence row NV/{Year} LastSeq={Seq}",
|
||||||
|
seq, year, seq);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4712
src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260526110207_AddEmployeeProfiles.Designer.cs
generated
Normal file
4712
src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260526110207_AddEmployeeProfiles.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,356 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddEmployeeProfiles : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EmployeeCodeSequences",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Prefix = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
LastSeq = table.Column<int>(type: "int", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_EmployeeCodeSequences", x => x.Prefix);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EmployeeProfiles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
EmployeeCode = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
EmployeeStatus = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Gender = table.Column<int>(type: "int", nullable: true),
|
||||||
|
MaritalStatus = table.Column<int>(type: "int", nullable: true),
|
||||||
|
EmployeeType = table.Column<int>(type: "int", nullable: true),
|
||||||
|
DateOfBirth = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
BirthPlace = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
Hometown = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
Phone = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||||
|
PersonalEmail = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
InternalPhone = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||||
|
Ethnicity = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||||
|
Religion = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||||
|
Nationality = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
|
IdCardNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||||
|
IdCardIssueDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
IdCardIssuePlace = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
TaxCode = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||||
|
SocialInsuranceNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||||
|
PassportNumber = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||||
|
PermanentAddressText = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||||
|
PermanentProvinceId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
PermanentDistrictId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
PermanentWardId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
StreetAddressPermanent = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
TemporaryAddressText = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||||
|
TemporaryProvinceId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
TemporaryDistrictId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
TemporaryWardId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
|
StreetAddressTemporary = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
HireDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
ResignDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
EmergencyContactName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
EmergencyContactPhone = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||||
|
EmergencyContactAddress = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||||
|
Qualification = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
AcademicTitle = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
WorkLocation = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
TimekeepingCode = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||||
|
BankAccount = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||||
|
BankName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
BankBranch = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
HeightCm = table.Column<int>(type: "int", nullable: true),
|
||||||
|
WeightKg = table.Column<int>(type: "int", nullable: true),
|
||||||
|
BloodType = table.Column<string>(type: "nvarchar(5)", maxLength: 5, nullable: true),
|
||||||
|
BaseSalary = table.Column<decimal>(type: "decimal(18,2)", nullable: true),
|
||||||
|
TotalSalary = table.Column<decimal>(type: "decimal(18,2)", nullable: true),
|
||||||
|
AnnualLeaveDays = table.Column<decimal>(type: "decimal(5,2)", nullable: true),
|
||||||
|
RemainingLeaveDays = table.Column<decimal>(type: "decimal(5,2)", nullable: true),
|
||||||
|
CompensatoryLeaveDays = table.Column<decimal>(type: "decimal(5,2)", nullable: true),
|
||||||
|
SeniorityLeaveDays = table.Column<decimal>(type: "decimal(5,2)", nullable: true),
|
||||||
|
SocialInsuranceStartDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
MedicalRegistrationPlace = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
IsCommunistParty = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
CommunistPartyJoinDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
IsYouthUnion = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
YouthUnionJoinDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
IsTradeUnion = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
TradeUnionJoinDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
PhotoUrl = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||||
|
Notes = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, 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_EmployeeProfiles", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EmployeeProfiles_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EmployeeDocuments",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
EmployeeProfileId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
DocumentType = table.Column<int>(type: "int", nullable: false),
|
||||||
|
FileName = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: false),
|
||||||
|
FilePath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||||
|
FileSize = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ContentType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
|
IssueDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
ExpiryDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
Notes = table.Column<string>(type: "nvarchar(500)", maxLength: 500, 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_EmployeeDocuments", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EmployeeDocuments_EmployeeProfiles_EmployeeProfileId",
|
||||||
|
column: x => x.EmployeeProfileId,
|
||||||
|
principalTable: "EmployeeProfiles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EmployeeEducations",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
EmployeeProfileId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
SchoolName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||||
|
Major = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
DegreeLevel = table.Column<int>(type: "int", nullable: true),
|
||||||
|
EducationMode = table.Column<int>(type: "int", nullable: true),
|
||||||
|
GradeLevel = table.Column<int>(type: "int", nullable: true),
|
||||||
|
FromDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
ToDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
CertificateIssueDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
Notes = table.Column<string>(type: "nvarchar(500)", maxLength: 500, 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_EmployeeEducations", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EmployeeEducations_EmployeeProfiles_EmployeeProfileId",
|
||||||
|
column: x => x.EmployeeProfileId,
|
||||||
|
principalTable: "EmployeeProfiles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EmployeeFamilyRelations",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
EmployeeProfileId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
FullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||||
|
Relationship = table.Column<int>(type: "int", nullable: false),
|
||||||
|
BirthYear = table.Column<int>(type: "int", nullable: true),
|
||||||
|
Occupation = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
CurrentAddress = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||||
|
Phone = table.Column<string>(type: "nvarchar(20)", maxLength: 20, 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_EmployeeFamilyRelations", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EmployeeFamilyRelations_EmployeeProfiles_EmployeeProfileId",
|
||||||
|
column: x => x.EmployeeProfileId,
|
||||||
|
principalTable: "EmployeeProfiles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EmployeeSkills",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
EmployeeProfileId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
Kind = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||||
|
LanguageId = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: true),
|
||||||
|
Level = table.Column<string>(type: "nvarchar(200)", maxLength: 200, 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_EmployeeSkills", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EmployeeSkills_EmployeeProfiles_EmployeeProfileId",
|
||||||
|
column: x => x.EmployeeProfileId,
|
||||||
|
principalTable: "EmployeeProfiles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EmployeeWorkHistories",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
EmployeeProfileId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
CompanyName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||||
|
CompanyAddress = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||||
|
Industry = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
FromDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
ToDate = table.Column<DateOnly>(type: "date", nullable: true),
|
||||||
|
JobTitle = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||||
|
JobDescription = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
||||||
|
ResignReason = table.Column<string>(type: "nvarchar(500)", maxLength: 500, 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_EmployeeWorkHistories", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EmployeeWorkHistories_EmployeeProfiles_EmployeeProfileId",
|
||||||
|
column: x => x.EmployeeProfileId,
|
||||||
|
principalTable: "EmployeeProfiles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeDocuments_DocumentType",
|
||||||
|
table: "EmployeeDocuments",
|
||||||
|
column: "DocumentType");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeDocuments_EmployeeProfileId",
|
||||||
|
table: "EmployeeDocuments",
|
||||||
|
column: "EmployeeProfileId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeEducations_EmployeeProfileId",
|
||||||
|
table: "EmployeeEducations",
|
||||||
|
column: "EmployeeProfileId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeFamilyRelations_EmployeeProfileId",
|
||||||
|
table: "EmployeeFamilyRelations",
|
||||||
|
column: "EmployeeProfileId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeProfiles_EmployeeCode",
|
||||||
|
table: "EmployeeProfiles",
|
||||||
|
column: "EmployeeCode",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeProfiles_IsDeleted",
|
||||||
|
table: "EmployeeProfiles",
|
||||||
|
column: "IsDeleted");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeProfiles_Phone",
|
||||||
|
table: "EmployeeProfiles",
|
||||||
|
column: "Phone");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeProfiles_UserId",
|
||||||
|
table: "EmployeeProfiles",
|
||||||
|
column: "UserId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeSkills_EmployeeProfileId",
|
||||||
|
table: "EmployeeSkills",
|
||||||
|
column: "EmployeeProfileId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeSkills_Kind",
|
||||||
|
table: "EmployeeSkills",
|
||||||
|
column: "Kind");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EmployeeWorkHistories_EmployeeProfileId",
|
||||||
|
table: "EmployeeWorkHistories",
|
||||||
|
column: "EmployeeProfileId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EmployeeCodeSequences");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EmployeeDocuments");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EmployeeEducations");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EmployeeFamilyRelations");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EmployeeSkills");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EmployeeWorkHistories");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "EmployeeProfiles");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1894,6 +1894,606 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("ContractTemplates", (string)null);
|
b.ToTable("ContractTemplates", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeCodeSequence", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Prefix")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<int>("LastSeq")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("Prefix");
|
||||||
|
|
||||||
|
b.ToTable("EmployeeCodeSequences", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeDocument", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
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<int>("DocumentType")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<Guid>("EmployeeProfileId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("ExpiryDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<string>("FileName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
|
b.Property<string>("FilePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<long>("FileSize")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("IssueDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DocumentType");
|
||||||
|
|
||||||
|
b.HasIndex("EmployeeProfileId");
|
||||||
|
|
||||||
|
b.ToTable("EmployeeDocuments", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeEducation", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("CertificateIssueDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<int?>("DegreeLevel")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeletedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<int?>("EducationMode")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<Guid>("EmployeeProfileId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("FromDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<int?>("GradeLevel")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Major")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("SchoolName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("ToDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EmployeeProfileId");
|
||||||
|
|
||||||
|
b.ToTable("EmployeeEducations", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeFamilyRelation", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<int?>("BirthYear")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("CurrentAddress")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeletedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid>("EmployeeProfileId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("FullName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Occupation")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<int>("Relationship")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EmployeeProfileId");
|
||||||
|
|
||||||
|
b.ToTable("EmployeeFamilyRelations", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeProfile", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("AcademicTitle")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("AnnualLeaveDays")
|
||||||
|
.HasColumnType("decimal(5,2)");
|
||||||
|
|
||||||
|
b.Property<string>("BankAccount")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("BankBranch")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("BankName")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("BaseSalary")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<string>("BirthPlace")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("BloodType")
|
||||||
|
.HasMaxLength(5)
|
||||||
|
.HasColumnType("nvarchar(5)");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("CommunistPartyJoinDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<decimal?>("CompensatoryLeaveDays")
|
||||||
|
.HasColumnType("decimal(5,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("DateOfBirth")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("DeletedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("EmergencyContactAddress")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("EmergencyContactName")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("EmergencyContactPhone")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("EmployeeCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<int>("EmployeeStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("EmployeeType")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Ethnicity")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<int?>("Gender")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("HeightCm")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("HireDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<string>("Hometown")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("IdCardIssueDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<string>("IdCardIssuePlace")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("IdCardNumber")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("InternalPhone")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsCommunistParty")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsTradeUnion")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsYouthUnion")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<int?>("MaritalStatus")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("MedicalRegistrationPlace")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Nationality")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("nvarchar(2000)");
|
||||||
|
|
||||||
|
b.Property<string>("PassportNumber")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("PermanentAddressText")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PermanentDistrictId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PermanentProvinceId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PermanentWardId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("PersonalEmail")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("PhotoUrl")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("Qualification")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Religion")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("RemainingLeaveDays")
|
||||||
|
.HasColumnType("decimal(5,2)");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("ResignDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<decimal?>("SeniorityLeaveDays")
|
||||||
|
.HasColumnType("decimal(5,2)");
|
||||||
|
|
||||||
|
b.Property<string>("SocialInsuranceNumber")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("SocialInsuranceStartDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<string>("StreetAddressPermanent")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("StreetAddressTemporary")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("TaxCode")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("TemporaryAddressText")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("TemporaryDistrictId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid?>("TemporaryProvinceId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid?>("TemporaryWardId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("TimekeepingCode")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<decimal?>("TotalSalary")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("TradeUnionJoinDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<int?>("WeightKg")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("WorkLocation")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("YouthUnionJoinDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EmployeeCode")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("IsDeleted");
|
||||||
|
|
||||||
|
b.HasIndex("Phone");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("EmployeeProfiles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeSkill", 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<Guid>("EmployeeProfileId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<int>("Kind")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("LanguageId")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
|
b.Property<string>("Level")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EmployeeProfileId");
|
||||||
|
|
||||||
|
b.HasIndex("Kind");
|
||||||
|
|
||||||
|
b.ToTable("EmployeeSkills", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeWorkHistory", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("CompanyAddress")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<string>("CompanyName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
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<Guid>("EmployeeProfileId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("FromDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<string>("Industry")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("JobDescription")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("nvarchar(2000)");
|
||||||
|
|
||||||
|
b.Property<string>("JobTitle")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<string>("ResignReason")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("ToDate")
|
||||||
|
.HasColumnType("date");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedBy")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EmployeeProfileId");
|
||||||
|
|
||||||
|
b.ToTable("EmployeeWorkHistories", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Key")
|
b.Property<string>("Key")
|
||||||
@ -3731,6 +4331,72 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Step");
|
b.Navigation("Step");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeDocument", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SolutionErp.Domain.Hrm.EmployeeProfile", "EmployeeProfile")
|
||||||
|
.WithMany("Documents")
|
||||||
|
.HasForeignKey("EmployeeProfileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("EmployeeProfile");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeEducation", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SolutionErp.Domain.Hrm.EmployeeProfile", "EmployeeProfile")
|
||||||
|
.WithMany("Educations")
|
||||||
|
.HasForeignKey("EmployeeProfileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("EmployeeProfile");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeFamilyRelation", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SolutionErp.Domain.Hrm.EmployeeProfile", "EmployeeProfile")
|
||||||
|
.WithMany("FamilyRelations")
|
||||||
|
.HasForeignKey("EmployeeProfileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("EmployeeProfile");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeProfile", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SolutionErp.Domain.Identity.User", "User")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("SolutionErp.Domain.Hrm.EmployeeProfile", "UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeSkill", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SolutionErp.Domain.Hrm.EmployeeProfile", "EmployeeProfile")
|
||||||
|
.WithMany("Skills")
|
||||||
|
.HasForeignKey("EmployeeProfileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("EmployeeProfile");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeWorkHistory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("SolutionErp.Domain.Hrm.EmployeeProfile", "EmployeeProfile")
|
||||||
|
.WithMany("WorkHistories")
|
||||||
|
.HasForeignKey("EmployeeProfileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("EmployeeProfile");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("SolutionErp.Domain.Identity.MenuItem", "Parent")
|
b.HasOne("SolutionErp.Domain.Identity.MenuItem", "Parent")
|
||||||
@ -3982,6 +4648,19 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
|
|||||||
b.Navigation("Approvers");
|
b.Navigation("Approvers");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("SolutionErp.Domain.Hrm.EmployeeProfile", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Documents");
|
||||||
|
|
||||||
|
b.Navigation("Educations");
|
||||||
|
|
||||||
|
b.Navigation("FamilyRelations");
|
||||||
|
|
||||||
|
b.Navigation("Skills");
|
||||||
|
|
||||||
|
b.Navigation("WorkHistories");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
modelBuilder.Entity("SolutionErp.Domain.Identity.MenuItem", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Children");
|
b.Navigation("Children");
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
using System.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SolutionErp.Application.Common.Interfaces;
|
||||||
|
using SolutionErp.Application.Hrm.Services;
|
||||||
|
using SolutionErp.Domain.Hrm;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Services;
|
||||||
|
|
||||||
|
// Mirror ContractCodeGenerator + PurchaseEvaluationCodeGenerator pattern.
|
||||||
|
// Format: "NV/{YYYY}/{Seq:D4}", reset per năm.
|
||||||
|
//
|
||||||
|
// SERIALIZABLE transaction tránh race khi 2 admin tạo NV đồng thời.
|
||||||
|
public class EmployeeCodeGenerator(IApplicationDbContext db, IDateTime dateTime)
|
||||||
|
: IEmployeeCodeGenerator
|
||||||
|
{
|
||||||
|
public async Task<string> GenerateAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var year = dateTime.UtcNow.Year;
|
||||||
|
var prefix = $"NV/{year}";
|
||||||
|
|
||||||
|
var context = (DbContext)db;
|
||||||
|
await using var tx = await context.Database
|
||||||
|
.BeginTransactionAsync(IsolationLevel.Serializable, ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var seq = await db.EmployeeCodeSequences
|
||||||
|
.FirstOrDefaultAsync(s => s.Prefix == prefix, ct);
|
||||||
|
if (seq is null)
|
||||||
|
{
|
||||||
|
seq = new EmployeeCodeSequence
|
||||||
|
{
|
||||||
|
Prefix = prefix,
|
||||||
|
LastSeq = 1,
|
||||||
|
UpdatedAt = dateTime.UtcNow,
|
||||||
|
};
|
||||||
|
db.EmployeeCodeSequences.Add(seq);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
seq.LastSeq += 1;
|
||||||
|
seq.UpdatedAt = dateTime.UtcNow;
|
||||||
|
}
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
await tx.CommitAsync(ct);
|
||||||
|
return $"{prefix}/{seq.LastSeq:D4}";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await tx.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user