[CLAUDE] PE: section Bang so sanh + rename demo email @solutions.com.vn
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m10s

PART A: Section 'Bang so sanh' (file tong ho so so sanh)

User request: 'theo them cho thong tin ve Bang so sanh, cho dinh kem
file so sanh tong len'.

BE:
 - PurchaseEvaluationAttachmentPurpose.ComparisonTable = 4 (new enum value)
   Backend validator IsInEnum pass, khong can migration (int column).

FE types (2 app):
 - PeAttachmentPurpose.ComparisonTable + Label '4: Bang so sanh'.

FE PeDetailTabs:
 - Them section thu 4 'Bang so sanh (file tong)' sau 'Hang muc + Bao gia'.
 - Component GeneralAttachmentsSection: upload KHONG truyen supplierRowId
   (BE luu NULL) → purpose=ComparisonTable default. Filter attachments
   co supplierRowId===null de render.
 - Card layout khac SupplierAttachmentsCell: full-width card + brand color
   + purpose chip + date. Upload button to hon ([+ Tai len bang so sanh]).
 - readOnly hide upload + delete, giu download.

PART B: Demo email rebrand @solutionerp.local → @solutions.com.vn

User request: 'tao nguoi dung demo theo email cua ben nay'.

BE DbInitializer:
 - Rename 18 email in source: AdminEmail const + 17 demo users
   (bod/pm/ccm/pro/fin/act/equ/hra/qs/nv) — keep password + role unchanged.
 - Them BackfillUserEmailDomainAsync (idempotent): scan user co email
   @solutionerp.local, rename sang @solutions.com.vn, update Email +
   NormalizedEmail + UserName + NormalizedUserName. Skip neu co conflict
   user da ton tai voi email moi. Chay truoc SeedAdmin de tranh tao
   duplicate admin.

Admin permission tao user da co san qua /system/users page.
Comment input khi duyet da co san o PeWorkflowPanel (Ghi chu tuy chon
Textarea) + ContractDetailContent (Yeu cau sua / Duyet tiep dialog).
This commit is contained in:
pqhuy1987
2026-04-24 15:08:00 +07:00
parent eda9e84187
commit a336997cfe
6 changed files with 353 additions and 27 deletions

View File

@ -4,9 +4,10 @@ namespace SolutionErp.Domain.PurchaseEvaluations;
public enum PurchaseEvaluationAttachmentPurpose
{
QuoteDocument = 1, // File báo giá NCC gửi (PDF/xlsx)
QuoteDocument = 1, // File báo giá NCC gửi (PDF/xlsx) — gắn với NCC cụ thể
RequirementSpec = 2, // Bản vẽ/yêu cầu kỹ thuật kèm theo
DecisionExport = 3, // Bản phiếu duyệt đã export
ComparisonTable = 4, // Bảng so sánh tổng — không gắn với 1 NCC, file tổng hợp
Other = 99,
}

View File

@ -14,9 +14,39 @@ namespace SolutionErp.Infrastructure.Persistence;
public static class DbInitializer
{
public const string AdminEmail = "admin@solutionerp.local";
public const string AdminEmail = "admin@solutions.com.vn";
public const string AdminPassword = "Admin@123456";
// Migration helper (Phase 6 rebrand): rename user email
// @solutionerp.local → @solutions.com.vn. Idempotent — skip user đã rename.
// Password + role + refresh token giữ nguyên. Chạy trước SeedAdmin.
private static async Task BackfillUserEmailDomainAsync(
UserManager<User> userManager, ILogger logger)
{
const string OldDomain = "@solutionerp.local";
const string NewDomain = "@solutions.com.vn";
var oldUsers = userManager.Users
.Where(u => u.Email != null && u.Email.EndsWith(OldDomain))
.ToList();
if (oldUsers.Count == 0) return;
var renamed = 0;
foreach (var u in oldUsers)
{
var newEmail = u.Email!.Replace(OldDomain, NewDomain);
var conflict = await userManager.FindByEmailAsync(newEmail);
if (conflict != null && conflict.Id != u.Id) continue;
u.Email = newEmail;
u.NormalizedEmail = newEmail.ToUpperInvariant();
u.UserName = newEmail;
u.NormalizedUserName = newEmail.ToUpperInvariant();
var res = await userManager.UpdateAsync(u);
if (res.Succeeded) renamed++;
}
if (renamed > 0)
logger.LogInformation("Backfilled {Count} user emails -> @solutions.com.vn", renamed);
}
public static async Task InitializeAsync(IServiceProvider services)
{
using var scope = services.CreateScope();
@ -30,6 +60,9 @@ public static class DbInitializer
await db.Database.MigrateAsync();
await SeedRolesAsync(roleManager, logger);
// Phase 6 rebrand: rename user email @solutionerp.local → @solutions.com.vn
// TRƯỚC SeedAdmin để SeedAdmin tìm theo new email thấy user đã rename → skip create.
await BackfillUserEmailDomainAsync(userManager, logger);
await SeedAdminAsync(userManager, logger);
await SeedDepartmentsAsync(db, logger);
await SeedDemoUsersAsync(db, userManager, logger);
@ -452,10 +485,10 @@ public static class DbInitializer
var fallbackSupplier = suppliersByCode.Values.First();
var fallbackProject = projectsByCode.Values.First();
var qsHoang = await userManager.FindByEmailAsync("qs.hoang@solutionerp.local");
var ccmTran = await userManager.FindByEmailAsync("ccm.tran@solutionerp.local");
var bodHuynh = await userManager.FindByEmailAsync("bod.huynh@solutionerp.local");
var hraDang = await userManager.FindByEmailAsync("hra.dang@solutionerp.local");
var qsHoang = await userManager.FindByEmailAsync("qs.hoang@solutions.com.vn");
var ccmTran = await userManager.FindByEmailAsync("ccm.tran@solutions.com.vn");
var bodHuynh = await userManager.FindByEmailAsync("bod.huynh@solutions.com.vn");
var hraDang = await userManager.FindByEmailAsync("hra.dang@solutions.com.vn");
var qsDeptId = (await db.Departments.FirstOrDefaultAsync(d => d.Code == "QS"))?.Id;
var nowUtc = DateTime.UtcNow;
@ -704,11 +737,11 @@ public static class DbInitializer
}
// Lookup actor users (per role)
var qsHoang = await userManager.FindByEmailAsync("qs.hoang@solutionerp.local");
var proPham = await userManager.FindByEmailAsync("pro.pham@solutionerp.local");
var ccmTran = await userManager.FindByEmailAsync("ccm.tran@solutionerp.local");
var pmNguyen = await userManager.FindByEmailAsync("pm.nguyen@solutionerp.local");
var bodHuynh = await userManager.FindByEmailAsync("bod.huynh@solutionerp.local");
var qsHoang = await userManager.FindByEmailAsync("qs.hoang@solutions.com.vn");
var proPham = await userManager.FindByEmailAsync("pro.pham@solutions.com.vn");
var ccmTran = await userManager.FindByEmailAsync("ccm.tran@solutions.com.vn");
var pmNguyen = await userManager.FindByEmailAsync("pm.nguyen@solutions.com.vn");
var bodHuynh = await userManager.FindByEmailAsync("bod.huynh@solutions.com.vn");
var qsDeptId = (await db.Departments.FirstOrDefaultAsync(d => d.Code == "QS"))?.Id;
var nowUtc = DateTime.UtcNow;
@ -1101,28 +1134,28 @@ public static class DbInitializer
{
// (Email, FullName, DeptCode, Position, RoleNames[])
// BOD (3) — director + signer + admin assistant
("bod.huynh@solutionerp.local", "Huỳnh Văn Hùng", "BOD", "Tổng Giám đốc", new[] { AppRoles.Director }),
("bod.le@solutionerp.local", "Lê Thị Mai", "BOD", "Phó Giám đốc (NĐUQ)", new[] { AppRoles.AuthorizedSigner }),
("bod.tran@solutionerp.local", "Trần Quốc Bảo", "BOD", "Phó Giám đốc Kỹ thuật (NĐUQ)", new[] { AppRoles.AuthorizedSigner }),
("bod.huynh@solutions.com.vn", "Huỳnh Văn Hùng", "BOD", "Tổng Giám đốc", new[] { AppRoles.Director }),
("bod.le@solutions.com.vn", "Lê Thị Mai", "BOD", "Phó Giám đốc (NĐUQ)", new[] { AppRoles.AuthorizedSigner }),
("bod.tran@solutions.com.vn", "Trần Quốc Bảo", "BOD", "Phó Giám đốc Kỹ thuật (NĐUQ)", new[] { AppRoles.AuthorizedSigner }),
// PM (2) — giám đốc dự án
("pm.nguyen@solutionerp.local", "Nguyễn Quốc Cường", "PM", "Giám đốc Dự án FLOCK 01", new[] { AppRoles.ProjectManager }),
("pm.le@solutionerp.local", "Lê Thanh Tùng", "PM", "Giám đốc Dự án Vinhomes Ocean Park", new[] { AppRoles.ProjectManager }),
("pm.nguyen@solutions.com.vn", "Nguyễn Quốc Cường", "PM", "Giám đốc Dự án FLOCK 01", new[] { AppRoles.ProjectManager }),
("pm.le@solutions.com.vn", "Lê Thanh Tùng", "PM", "Giám đốc Dự án Vinhomes Ocean Park", new[] { AppRoles.ProjectManager }),
// CCM/PRO/FIN/ACT/EQU/HRA — Trưởng phòng các phòng ban (1 mỗi)
("ccm.tran@solutionerp.local", "Trần Văn Bình", "CCM", "Trưởng phòng Kiểm soát chi phí", new[] { AppRoles.CostControl, AppRoles.DeptManager }),
("pro.pham@solutionerp.local", "Phạm Thị Hồng", "PRO", "Trưởng phòng Cung ứng", new[] { AppRoles.Procurement, AppRoles.DeptManager }),
("fin.do@solutionerp.local", "Đỗ Minh Tuấn", "FIN", "Trưởng phòng Tài chính", new[] { AppRoles.Finance, AppRoles.DeptManager }),
("act.vu@solutionerp.local", "Vũ Thị Lan", "ACT", "Kế toán trưởng", new[] { AppRoles.Accounting, AppRoles.DeptManager }),
("equ.bui@solutionerp.local", "Bùi Văn Khánh", "EQU", "Trưởng phòng Thiết bị", new[] { AppRoles.Equipment, AppRoles.DeptManager }),
("hra.dang@solutionerp.local", "Đặng Thị Thanh", "HRA", "Trưởng phòng Nhân sự - Hành chính", new[] { AppRoles.HrAdmin, AppRoles.DeptManager }),
("ccm.tran@solutions.com.vn", "Trần Văn Bình", "CCM", "Trưởng phòng Kiểm soát chi phí", new[] { AppRoles.CostControl, AppRoles.DeptManager }),
("pro.pham@solutions.com.vn", "Phạm Thị Hồng", "PRO", "Trưởng phòng Cung ứng", new[] { AppRoles.Procurement, AppRoles.DeptManager }),
("fin.do@solutions.com.vn", "Đỗ Minh Tuấn", "FIN", "Trưởng phòng Tài chính", new[] { AppRoles.Finance, AppRoles.DeptManager }),
("act.vu@solutions.com.vn", "Vũ Thị Lan", "ACT", "Kế toán trưởng", new[] { AppRoles.Accounting, AppRoles.DeptManager }),
("equ.bui@solutions.com.vn", "Bùi Văn Khánh", "EQU", "Trưởng phòng Thiết bị", new[] { AppRoles.Equipment, AppRoles.DeptManager }),
("hra.dang@solutions.com.vn", "Đặng Thị Thanh", "HRA", "Trưởng phòng Nhân sự - Hành chính", new[] { AppRoles.HrAdmin, AppRoles.DeptManager }),
// Drafter (5) — soạn thảo HĐ ở các phòng khác nhau
("qs.hoang@solutionerp.local", "Hoàng Văn Đức", "QS", "QS công trường — soạn thảo HĐ", new[] { AppRoles.Drafter }),
("qs.ngo@solutionerp.local", "Ngô Thị Hà", "QS", "QS dự án FLOCK 01", new[] { AppRoles.Drafter }),
("nv.cao@solutionerp.local", "Cao Văn Long", "PRO", "Nhân viên Cung ứng", new[] { AppRoles.Drafter }),
("nv.dinh@solutionerp.local", "Đinh Thị Yến", "FIN", "Nhân viên Tài chính", new[] { AppRoles.Drafter }),
("nv.truong@solutionerp.local", "Trương Minh Quân", "CCM", "Nhân viên Kiểm soát chi phí", new[] { AppRoles.Drafter }),
("qs.hoang@solutions.com.vn", "Hoàng Văn Đức", "QS", "QS công trường — soạn thảo HĐ", new[] { AppRoles.Drafter }),
("qs.ngo@solutions.com.vn", "Ngô Thị Hà", "QS", "QS dự án FLOCK 01", new[] { AppRoles.Drafter }),
("nv.cao@solutions.com.vn", "Cao Văn Long", "PRO", "Nhân viên Cung ứng", new[] { AppRoles.Drafter }),
("nv.dinh@solutions.com.vn", "Đinh Thị Yến", "FIN", "Nhân viên Tài chính", new[] { AppRoles.Drafter }),
("nv.truong@solutions.com.vn", "Trương Minh Quân", "CCM", "Nhân viên Kiểm soát chi phí", new[] { AppRoles.Drafter }),
};
int created = 0, fixedExisting = 0, failed = 0;