[CLAUDE] Supplier: Mig 62 expand 30-field + seed 4 NCC + FE form 2-app
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m7s

Phase A of the NCC/TP Excel-import backlog (anh Kiet FDC). Supplier
entity 9 -> 27 fields to hold the 30-column supplier database.

Backend:
- Supplier +18 nullable cols; SupplierType +CaHai=6 (append-only);
  new nullable SupplierStatus enum.
- Mig 62 ExpandSupplierFields (3-file): AddColumn x18 + guarded un-cram
  Sql (clear legacy crammed Note on the 4 seeded rows, exact-match).
  No new table (89), reversible Down.
- Seed fill-nulls-if-exists: populate 30 fields for TRUONGGIANG/TANPHU/
  TGN/DONGDUONG from Excel; idempotent, never clobbers existing cols.
- DTO + Create/Update commands + validators (MaximumLength-only, no
  NotEmpty so open-auth Create minimal payload stays valid) + 2 query
  projections.

Frontend (fe-admin + fe-user, byte-identical SHA-mirror):
- types/master.ts +18 fields +SupplierStatus; SuppliersPage form 4
  section groups + Status select + 3 NAS links via new shared PathLink
  (extracted from PE HoSoLink render+Copy). All new fields optional.

Excel upload auto-map = Phase B (deferred). Tests test-after (UAT mode).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-06 16:06:11 +07:00
parent 6d217f77ed
commit 778fc98b2f
21 changed files with 7440 additions and 23 deletions

View File

@ -16,7 +16,27 @@ public record CreateSupplierCommand(
string? Email,
string? Address,
string? ContactPerson,
string? Note) : IRequest<Guid>;
string? Note,
// S? go-live expansion — 18 field bổ sung, tất cả TRAILING OPTIONAL (= null)
// để payload tối thiểu cũ + call-site positional 9-arg vẫn hợp lệ (mọi field optional).
string? PackageCategory = null,
string? OfficeAddress = null,
string? MailingAddress = null,
string? Fax = null,
string? BankAccount = null,
string? SecondaryBankAccount = null,
string? LegalRepresentative = null,
string? LegalRepTitle = null,
string? AuthorizationNote = null,
string? LinkGuq = null,
string? LinkGpkd = null,
string? LinkHsnl = null,
string? ContactTitle = null,
string? ContactPhone = null,
string? MailRecipient = null,
string? ReferralSource = null,
string? OwnerPmh = null,
SupplierStatus? Status = null) : IRequest<Guid>;
public class CreateSupplierCommandValidator : AbstractValidator<CreateSupplierCommand>
{
@ -31,6 +51,24 @@ public class CreateSupplierCommandValidator : AbstractValidator<CreateSupplierCo
RuleFor(x => x.Address).MaximumLength(500);
RuleFor(x => x.ContactPerson).MaximumLength(200);
RuleFor(x => x.Note).MaximumLength(1000);
// S? — new fields OPTIONAL: MaximumLength ONLY, KHÔNG NotEmpty (Create open-auth, minimal payload phải hợp lệ).
RuleFor(x => x.PackageCategory).MaximumLength(300);
RuleFor(x => x.OfficeAddress).MaximumLength(500);
RuleFor(x => x.MailingAddress).MaximumLength(500);
RuleFor(x => x.Fax).MaximumLength(50);
RuleFor(x => x.BankAccount).MaximumLength(500);
RuleFor(x => x.SecondaryBankAccount).MaximumLength(500);
RuleFor(x => x.LegalRepresentative).MaximumLength(200);
RuleFor(x => x.LegalRepTitle).MaximumLength(150);
RuleFor(x => x.AuthorizationNote).MaximumLength(1000);
RuleFor(x => x.LinkGuq).MaximumLength(1000);
RuleFor(x => x.LinkGpkd).MaximumLength(1000);
RuleFor(x => x.LinkHsnl).MaximumLength(1000);
RuleFor(x => x.ContactTitle).MaximumLength(150);
RuleFor(x => x.ContactPhone).MaximumLength(50);
RuleFor(x => x.MailRecipient).MaximumLength(200);
RuleFor(x => x.ReferralSource).MaximumLength(300);
RuleFor(x => x.OwnerPmh).MaximumLength(150);
}
}
@ -56,6 +94,24 @@ public class CreateSupplierCommandHandler : IRequestHandler<CreateSupplierComman
Address = request.Address,
ContactPerson = request.ContactPerson,
Note = request.Note,
PackageCategory = request.PackageCategory,
OfficeAddress = request.OfficeAddress,
MailingAddress = request.MailingAddress,
Fax = request.Fax,
BankAccount = request.BankAccount,
SecondaryBankAccount = request.SecondaryBankAccount,
LegalRepresentative = request.LegalRepresentative,
LegalRepTitle = request.LegalRepTitle,
AuthorizationNote = request.AuthorizationNote,
LinkGuq = request.LinkGuq,
LinkGpkd = request.LinkGpkd,
LinkHsnl = request.LinkHsnl,
ContactTitle = request.ContactTitle,
ContactPhone = request.ContactPhone,
MailRecipient = request.MailRecipient,
ReferralSource = request.ReferralSource,
OwnerPmh = request.OwnerPmh,
Status = request.Status,
};
_db.Suppliers.Add(entity);
await _db.SaveChangesAsync(ct);

View File

@ -17,7 +17,26 @@ public record UpdateSupplierCommand(
string? Email,
string? Address,
string? ContactPerson,
string? Note) : IRequest;
string? Note,
// S? go-live expansion — 18 field bổ sung, tất cả TRAILING OPTIONAL (= null). FE sửa tay gửi đủ field.
string? PackageCategory = null,
string? OfficeAddress = null,
string? MailingAddress = null,
string? Fax = null,
string? BankAccount = null,
string? SecondaryBankAccount = null,
string? LegalRepresentative = null,
string? LegalRepTitle = null,
string? AuthorizationNote = null,
string? LinkGuq = null,
string? LinkGpkd = null,
string? LinkHsnl = null,
string? ContactTitle = null,
string? ContactPhone = null,
string? MailRecipient = null,
string? ReferralSource = null,
string? OwnerPmh = null,
SupplierStatus? Status = null) : IRequest;
public class UpdateSupplierCommandValidator : AbstractValidator<UpdateSupplierCommand>
{
@ -28,6 +47,24 @@ public class UpdateSupplierCommandValidator : AbstractValidator<UpdateSupplierCo
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
RuleFor(x => x.Type).IsInEnum();
RuleFor(x => x.Email).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.Email));
// S? — new fields OPTIONAL: MaximumLength ONLY, KHÔNG NotEmpty.
RuleFor(x => x.PackageCategory).MaximumLength(300);
RuleFor(x => x.OfficeAddress).MaximumLength(500);
RuleFor(x => x.MailingAddress).MaximumLength(500);
RuleFor(x => x.Fax).MaximumLength(50);
RuleFor(x => x.BankAccount).MaximumLength(500);
RuleFor(x => x.SecondaryBankAccount).MaximumLength(500);
RuleFor(x => x.LegalRepresentative).MaximumLength(200);
RuleFor(x => x.LegalRepTitle).MaximumLength(150);
RuleFor(x => x.AuthorizationNote).MaximumLength(1000);
RuleFor(x => x.LinkGuq).MaximumLength(1000);
RuleFor(x => x.LinkGpkd).MaximumLength(1000);
RuleFor(x => x.LinkHsnl).MaximumLength(1000);
RuleFor(x => x.ContactTitle).MaximumLength(150);
RuleFor(x => x.ContactPhone).MaximumLength(50);
RuleFor(x => x.MailRecipient).MaximumLength(200);
RuleFor(x => x.ReferralSource).MaximumLength(300);
RuleFor(x => x.OwnerPmh).MaximumLength(150);
}
}
@ -55,6 +92,24 @@ public class UpdateSupplierCommandHandler : IRequestHandler<UpdateSupplierComman
entity.Address = request.Address;
entity.ContactPerson = request.ContactPerson;
entity.Note = request.Note;
entity.PackageCategory = request.PackageCategory;
entity.OfficeAddress = request.OfficeAddress;
entity.MailingAddress = request.MailingAddress;
entity.Fax = request.Fax;
entity.BankAccount = request.BankAccount;
entity.SecondaryBankAccount = request.SecondaryBankAccount;
entity.LegalRepresentative = request.LegalRepresentative;
entity.LegalRepTitle = request.LegalRepTitle;
entity.AuthorizationNote = request.AuthorizationNote;
entity.LinkGuq = request.LinkGuq;
entity.LinkGpkd = request.LinkGpkd;
entity.LinkHsnl = request.LinkHsnl;
entity.ContactTitle = request.ContactTitle;
entity.ContactPhone = request.ContactPhone;
entity.MailRecipient = request.MailRecipient;
entity.ReferralSource = request.ReferralSource;
entity.OwnerPmh = request.OwnerPmh;
entity.Status = request.Status;
await _db.SaveChangesAsync(ct);
}

View File

@ -13,5 +13,23 @@ public record SupplierDto(
string? Address,
string? ContactPerson,
string? Note,
string? PackageCategory,
string? OfficeAddress,
string? MailingAddress,
string? Fax,
string? BankAccount,
string? SecondaryBankAccount,
string? LegalRepresentative,
string? LegalRepTitle,
string? AuthorizationNote,
string? LinkGuq,
string? LinkGpkd,
string? LinkHsnl,
string? ContactTitle,
string? ContactPhone,
string? MailRecipient,
string? ReferralSource,
string? OwnerPmh,
SupplierStatus? Status,
DateTime CreatedAt,
DateTime? UpdatedAt);

View File

@ -18,6 +18,10 @@ public class GetSupplierQueryHandler : IRequestHandler<GetSupplierQuery, Supplie
{
var x = await _db.Suppliers.AsNoTracking().FirstOrDefaultAsync(s => s.Id == request.Id, ct)
?? throw new NotFoundException("Supplier", request.Id);
return new SupplierDto(x.Id, x.Code, x.Name, x.Type, x.TaxCode, x.Phone, x.Email, x.Address, x.ContactPerson, x.Note, x.CreatedAt, x.UpdatedAt);
return new SupplierDto(x.Id, x.Code, x.Name, x.Type, x.TaxCode, x.Phone, x.Email, x.Address, x.ContactPerson, x.Note,
x.PackageCategory, x.OfficeAddress, x.MailingAddress, x.Fax, x.BankAccount, x.SecondaryBankAccount,
x.LegalRepresentative, x.LegalRepTitle, x.AuthorizationNote, x.LinkGuq, x.LinkGpkd, x.LinkHsnl,
x.ContactTitle, x.ContactPhone, x.MailRecipient, x.ReferralSource, x.OwnerPmh, x.Status,
x.CreatedAt, x.UpdatedAt);
}
}

View File

@ -47,6 +47,9 @@ public class ListSuppliersQueryHandler : IRequestHandler<ListSuppliersQuery, Pag
.Take(request.PageSize)
.Select(x => new SupplierDto(
x.Id, x.Code, x.Name, x.Type, x.TaxCode, x.Phone, x.Email, x.Address, x.ContactPerson, x.Note,
x.PackageCategory, x.OfficeAddress, x.MailingAddress, x.Fax, x.BankAccount, x.SecondaryBankAccount,
x.LegalRepresentative, x.LegalRepTitle, x.AuthorizationNote, x.LinkGuq, x.LinkGpkd, x.LinkHsnl,
x.ContactTitle, x.ContactPhone, x.MailRecipient, x.ReferralSource, x.OwnerPmh, x.Status,
x.CreatedAt, x.UpdatedAt))
.ToListAsync(ct);

View File

@ -10,7 +10,27 @@ public class Supplier : AuditableEntity
public string? TaxCode { get; set; } // Mã số thuế
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Address { get; set; }
public string? ContactPerson { get; set; } // Người liên hệ
public string? Note { get; set; }
public string? Address { get; set; } // Địa chỉ xuất HĐ (đăng ký)
public string? ContactPerson { get; set; } // Người liên hệ chính
public string? Note { get; set; } // Ghi chú / lý do blacklist
// ---- S? go-live expansion (18 field bổ sung, tất cả nullable — import Excel "Database NCC") ----
public string? PackageCategory { get; set; } // Gói thầu (free-text)
public string? OfficeAddress { get; set; } // Địa chỉ văn phòng
public string? MailingAddress { get; set; } // Địa chỉ gửi thư
public string? Fax { get; set; }
public string? BankAccount { get; set; } // Số TK + tên + CN ngân hàng (composite raw)
public string? SecondaryBankAccount { get; set; } // Số TK phụ
public string? LegalRepresentative { get; set; } // Người đại diện pháp luật
public string? LegalRepTitle { get; set; } // Chức vụ đại diện
public string? AuthorizationNote { get; set; } // Giấy ủy quyền (free text)
public string? LinkGuq { get; set; } // Link GUQ (NAS path)
public string? LinkGpkd { get; set; } // Link GPKD (NAS path)
public string? LinkHsnl { get; set; } // Link HSNL (NAS path)
public string? ContactTitle { get; set; } // Chức vụ người liên hệ
public string? ContactPhone { get; set; } // SĐT chính (người liên hệ)
public string? MailRecipient { get; set; } // Người nhận thư / SĐT (composite raw)
public string? ReferralSource { get; set; } // Nguồn giới thiệu
public string? OwnerPmh { get; set; } // Người phụ trách (PMH)
public SupplierStatus? Status { get; set; } // Tình trạng hiện tại (nullable = chưa phân loại)
}

View File

@ -0,0 +1,8 @@
namespace SolutionErp.Domain.Master;
public enum SupplierStatus
{
DangHoatDong = 1, // Đang hoạt động
Blacklist = 2, // Blacklist
NgungHopTac = 3, // Ngừng hợp tác
}

View File

@ -7,4 +7,5 @@ public enum SupplierType
ToDoi = 3, // TĐ — Tổ đội
DonViDichVu = 4, // ĐVDV — Đơn vị dịch vụ
ChuDauTu = 5, // CĐT — Chủ đầu tư (đặc biệt, bypass quy trình CCM)
CaHai = 6, // Cả hai — vừa NCC vừa NTP (append S? go-live; int 1-5 giữ nguyên trên phiếu cũ)
}

View File

@ -21,6 +21,26 @@ public class SupplierConfiguration : IEntityTypeConfiguration<Supplier>
b.Property(x => x.ContactPerson).HasMaxLength(200);
b.Property(x => x.Note).HasMaxLength(1000);
// S? go-live expansion — 18 field bổ sung (all nullable). MaxLength khớp spec §3.
b.Property(x => x.PackageCategory).HasMaxLength(300);
b.Property(x => x.OfficeAddress).HasMaxLength(500);
b.Property(x => x.MailingAddress).HasMaxLength(500);
b.Property(x => x.Fax).HasMaxLength(50);
b.Property(x => x.BankAccount).HasMaxLength(500);
b.Property(x => x.SecondaryBankAccount).HasMaxLength(500);
b.Property(x => x.LegalRepresentative).HasMaxLength(200);
b.Property(x => x.LegalRepTitle).HasMaxLength(150);
b.Property(x => x.AuthorizationNote).HasMaxLength(1000);
b.Property(x => x.LinkGuq).HasMaxLength(1000);
b.Property(x => x.LinkGpkd).HasMaxLength(1000);
b.Property(x => x.LinkHsnl).HasMaxLength(1000);
b.Property(x => x.ContactTitle).HasMaxLength(150);
b.Property(x => x.ContactPhone).HasMaxLength(50);
b.Property(x => x.MailRecipient).HasMaxLength(200);
b.Property(x => x.ReferralSource).HasMaxLength(300);
b.Property(x => x.OwnerPmh).HasMaxLength(150);
b.Property(x => x.Status).HasConversion<int>(); // nullable int enum
b.HasIndex(x => x.Code).IsUnique().HasFilter("[IsDeleted] = 0"); // Mig 47 (gotcha #57 EXT) — soft-deleted slot reusable, khớp HasQueryFilter !IsDeleted app-check
b.HasIndex(x => x.Type);

View File

@ -2765,26 +2765,62 @@ public static class DbInitializer
}
if (addedWorkItems > 0) logger.LogInformation("Seeded {Count} real work items (S55 import)", addedWorkItems);
// ---------- SUPPLIERS (4) — (Code, Name, Type, TaxCode, Phone, Email, Address, ContactPerson, Note) ---------- [S96 2026-07-01: +DONGDUONG import từ Excel "Database NCC" anh Kiệt FDC; 15-18 field form (bank chi tiết/địa chỉ VP/link GPKD-HSNL-GUQ/PMH/nguồn GT...) NOTE-lại-từ-từ-tính = backlog schema Supplier mở rộng, xem docs/STATUS Next-up]
// ---------- SUPPLIERS (4) — 30-field expansion (S? go-live, Mig 62) ----------
// Import Excel "Database NCC" anh Kiệt FDC. 9 field cũ (reuse) + 18 field mới +Status.
// Note = null cho cả 4 (đã tách legacy-cram sang các cột riêng; un-cram Sql() trong Mig 62).
// Idempotency = insert-if-missing + FILL-NULLS-if-exists: row đã tồn tại → CHỈ set các
// cột MỚI đang null (KHÔNG đè non-null) → go-live-safe, không clobber, idempotent.
var suppliers = new[]
{
new Supplier { Code = "TRUONGGIANG", Name = "CÔNG TY CỔ PHẦN NHÔM KÍNH VÀ ĐẦU TƯ TRƯỜNG GIANG", Type = SupplierType.NhaThauPhu, TaxCode = "0312 251 859", Phone = "08 2253 1381", Email = "viethq1985@gmail.com", Address = "Số 77/46A Đường Chuyên Dùng Chính, Phường Phú Thuận, TP.HCM", ContactPerson = "Mr. Việt", Note = "Loại: Nhôm kính, alu; NĐD: Ông: HOÀNG QUỐC VIỆT - GIÁM ĐỐC ĐIỀU HÀNH; TK: 100212127 Ngân hàng Eximbank - Chi Nhánh Hòa Bình; Trạng thái: ✅ Đang hoạt động" },
new Supplier { Code = "TANPHU", Name = "CÔNG TY TNHH SẢN XUẤT THƯƠNG MẠI SƠN TÂN PHÚ", Type = SupplierType.NhaThauPhu, TaxCode = "0305497393", Phone = "0902 686468", Email = "thu@tanphupaint.com", Address = "Số 49/5A Đường số 7, Khu phố 53, P. Linh Xuân, Thành phố Hồ Chí Minh, Việt Nam", ContactPerson = "Ms Thư", Note = "Loại: Sơn PU, Epoxy, liquid; NĐD: Ông ĐINH PHƯỚC THỌ - GIÁM ĐỐC; TK: 8683107979 tại Ngân hàng BIDV - CN Nam Bình Dương; Trạng thái: ✅ Đang hoạt động" },
new Supplier { Code = "TGN", Name = "CÔNG TY CỔ PHẦN SIÊU THỊ VLXD THẾ GIỚI NHÀ", Type = SupplierType.NhaCungCap, TaxCode = "3603497972", Phone = "028 36206000", Email = "duong.ltt@tgngroup.vn", Address = "46-48 Nguyễn Cơ Thạch, Phường An Khánh, Thành phố Hồ Chí Minh,Việt Nam", ContactPerson = "Ms. Dương", Note = "Loại: Bê tông; NĐD: Ông TRẦN ANH ĐIỀN - TỔNG GIÁM ĐỐC; TK: 114002641307 tại VietinBank CN KCN Biên Hòa; Trạng thái: ✅ Đang hoạt động" },
new Supplier { Code = "DONGDUONG", Name = "CÔNG TY CỔ PHẦN DỊCH VỤ CÔNG NGHỆ ĐÔNG DƯƠNG", Type = SupplierType.NhaThauPhu, Phone = "0913 912 094", Email = "hoangmi.tl@dongduongelevator.com", Address = "Số 11 Đường 27 Khu đô thị Vạn Phúc, Phường Hiệp Bình, Thành phố Hồ Chí Minh, Việt Nam", ContactPerson = "Ms.Mi", Note = "Loại: Thang máy, bàn nâng hạ; NĐD: Ông ĐOÀN MẬU QUẾ - GIÁM ĐỐC; TK: 110616498868 tại Ngân hàng TMCP Công thương (Vietinbank) CN Thủ Thiêm; GUQ: Số 02/2024/QĐ-ĐD ký 17/06/2024 (Bà Đoàn Phan Ngọc Bích - CT HĐQT); Trạng thái: ✅ Đang hoạt động" },
new Supplier { Code = "TRUONGGIANG", Name = "CÔNG TY CỔ PHẦN NHÔM KÍNH VÀ ĐẦU TƯ TRƯỜNG GIANG", Type = SupplierType.NhaThauPhu, TaxCode = "0312 251 859", Phone = "08 2253 1381", Email = "viethq1985@gmail.com", Address = "77/46A Đường Chuyên Dùng Chính, Phường Phú Thuận, Thành phố Hồ Chí Minh, Việt Nam", ContactPerson = "Mr. Việt", Note = null, Status = SupplierStatus.DangHoatDong,
PackageCategory = "10-Nhôm kính+Face Mặt dựng", BankAccount = "100212127 Ngân hàng Eximbank - Chi Nhánh Hòa Bình", LegalRepresentative = "Ông: HOÀNG QUỐC VIỆT", LegalRepTitle = "GIÁM ĐỐC ĐIỀU HÀNH", AuthorizationNote = "Theo giấy ủy quyền Số 01UQTGD-2022 ký ngày 01/04/2022 từ đại diện pháp luật của công ty là Ông Nguyễn Đăng Khoa, Chức Vụ: Tổng Giám Đốc", LinkGuq = @"01. GPKD_HSNL_GUQ\02.Trường giang\GIẤY ỦY QUYỀN ANH VIỆT.pdf", LinkGpkd = @"01. GPKD_HSNL_GUQ\02.Trường giang\GP TRƯỜNG GIANG 14.11.2024 THAY DOI L6.pdf", ContactTitle = "Giám đốc ĐH", ContactPhone = "0966 801 456", MailingAddress = "D32, ấp 1B, Xã Rạch Kiến, Tỉnh Tây Ninh (địa chỉ cũ: D32 ấp 1B, xã Long Hòa, Huyện Cần Đước, Tỉnh Long An)", MailRecipient = "MS Thủy / 036 405 0016", ReferralSource = "Tái sử dụng DA trước", OwnerPmh = "Bình" },
new Supplier { Code = "TANPHU", Name = "CÔNG TY TNHH SẢN XUẤT THƯƠNG MẠI SƠN TÂN PHÚ", Type = SupplierType.NhaThauPhu, TaxCode = "0305497393", Phone = "0902 686468", Email = "thu@tanphupaint.com", Address = "Số 49/5A Đường số 7, Khu phố 53, P. Linh Xuân, Thành phố Hồ Chí Minh, Việt Nam", ContactPerson = "Ms Thư", Note = null, Status = SupplierStatus.DangHoatDong,
PackageCategory = "15-Sơn PU/Epoxy/liquid", BankAccount = "8683107979 tại Ngân hàng BIDV - CN Nam Bình Dương", LegalRepresentative = "Ông ĐINH PHƯỚC THỌ", LegalRepTitle = "GIÁM ĐỐC", LinkGpkd = @"01. GPKD_HSNL_GUQ\01.Sơn Tân Phú\BUSINESS LICENSE-TAN PHU PAINT CO LTD(1).pdf", LinkHsnl = @"01. GPKD_HSNL_GUQ\01.Sơn Tân Phú\HỒ SƠ NĂNG LỰC CÔNG TY SƠN TÂN PHÚ-04.2026.pdf", ContactTitle = "Kinh doanh", ContactPhone = "0908355222", ReferralSource = "Qua đối tác", OwnerPmh = "Bình" },
new Supplier { Code = "TGN", Name = "CÔNG TY CỔ PHẦN SIÊU THỊ VLXD THẾ GIỚI NHÀ", Type = SupplierType.NhaCungCap, TaxCode = "3603497972", Phone = "028 36206000", Email = "duong.ltt@tgngroup.vn", Address = "46-48 Nguyễn Cơ Thạch, Phường An Khánh, Thành phố Hồ Chí Minh,Việt Nam", ContactPerson = "Ms. Dương", Note = null, Status = SupplierStatus.DangHoatDong,
PackageCategory = "2-Bê tông", Fax = "0251.2814663", BankAccount = "114002641307 tại VietinBank CN KCN Biên Hòa", LegalRepresentative = "Ông TRẦN ANH ĐIỀN", LegalRepTitle = "TỔNG GIÁM ĐỐC", LinkGpkd = @"01. GPKD_HSNL_GUQ\03.TGN\GPKD 2026.pdf", LinkHsnl = @"01. GPKD_HSNL_GUQ\03.TGN\HỒ SƠ NĂNG LỰC TGN 2022.pdf", ContactTitle = "Kinh doanh", ContactPhone = "0906.303. 087", MailingAddress = "643 xa lộ hà nội, kp2, p. long bình, đồng nai", MailRecipient = "Ms Dương/ 0967 019 386", ReferralSource = "Tái sử dụng DA trước", OwnerPmh = "Bình" },
new Supplier { Code = "DONGDUONG", Name = "CÔNG TY CỔ PHẦN DỊCH VỤ CÔNG NGHỆ ĐÔNG DƯƠNG", Type = SupplierType.NhaThauPhu, TaxCode = null, Phone = "0913 912 094", Email = "hoangmi.tl@dongduongelevator.com", Address = "Số 11 Đường 27 Khu đô thị Vạn Phúc, Phường Hiệp Bình, Thành phố Hồ Chí Minh, Việt Nam", ContactPerson = "Ms.Mi", Note = null, Status = SupplierStatus.DangHoatDong,
PackageCategory = "6-Thang máy/ bàn nâng hạ", BankAccount = "110616498868 tại Ngân hàng TMCP Công thương (Vietinbank) CN Thủ Thiêm", LegalRepresentative = "Ông ĐOÀN MẬU QUẾ", LegalRepTitle = "GIÁM ĐỐC", AuthorizationNote = "Theo giấy ủy quyền Số 02/2024/QĐ-ĐD ký ngày 17/06/2024 từ đại diện pháp luật của công ty là Bà Đoàn Phan Ngọc Bích, Chức Vụ: Chủ Tịch Hồi Đồng Quản Trị", LinkGuq = @"01. GPKD_HSNL_GUQ\04. Đông Dương\GIẤY ỦY QUYỀN GIÁM ĐỐC_MR.QUẾ_NĂM 2026.pdf", LinkGpkd = @"01. GPKD_HSNL_GUQ\04. Đông Dương\4. LẦN THỨ 3_GIẤY ĐKKD_CÔNG NGHỆ ĐÔNG DƯƠNG_02.04.2026.pdf", LinkHsnl = @"01. GPKD_HSNL_GUQ\04. Đông Dương\HỒ SƠ NĂNG LỰC 2025.pdf", ContactTitle = "Kinh doanh", ContactPhone = "0903.632.625", MailRecipient = "Ms Mi/0903.632.625", ReferralSource = "Tái sử dụng DA trước", OwnerPmh = "Bình" },
};
var existingSupplierCodes = await db.Suppliers.Select(s => s.Code).ToListAsync();
// Fetch existing rows FULL (không chỉ Code) để fill-nulls trên các cột mới.
var existingSuppliers = await db.Suppliers.ToListAsync();
var existingByCode = existingSuppliers.ToDictionary(s => s.Code, StringComparer.Ordinal);
int addedSuppliers = 0;
int filledSuppliers = 0;
foreach (var s in suppliers)
{
if (existingSupplierCodes.Contains(s.Code)) continue;
if (existingByCode.TryGetValue(s.Code, out var e))
{
// FILL-NULLS: chỉ set cột MỚI đang null (KHÔNG đè non-null; KHÔNG đụng 9 cột cũ).
bool changed = false;
if (e.PackageCategory is null && s.PackageCategory is not null) { e.PackageCategory = s.PackageCategory; changed = true; }
if (e.OfficeAddress is null && s.OfficeAddress is not null) { e.OfficeAddress = s.OfficeAddress; changed = true; }
if (e.MailingAddress is null && s.MailingAddress is not null) { e.MailingAddress = s.MailingAddress; changed = true; }
if (e.Fax is null && s.Fax is not null) { e.Fax = s.Fax; changed = true; }
if (e.BankAccount is null && s.BankAccount is not null) { e.BankAccount = s.BankAccount; changed = true; }
if (e.SecondaryBankAccount is null && s.SecondaryBankAccount is not null) { e.SecondaryBankAccount = s.SecondaryBankAccount; changed = true; }
if (e.LegalRepresentative is null && s.LegalRepresentative is not null) { e.LegalRepresentative = s.LegalRepresentative; changed = true; }
if (e.LegalRepTitle is null && s.LegalRepTitle is not null) { e.LegalRepTitle = s.LegalRepTitle; changed = true; }
if (e.AuthorizationNote is null && s.AuthorizationNote is not null) { e.AuthorizationNote = s.AuthorizationNote; changed = true; }
if (e.LinkGuq is null && s.LinkGuq is not null) { e.LinkGuq = s.LinkGuq; changed = true; }
if (e.LinkGpkd is null && s.LinkGpkd is not null) { e.LinkGpkd = s.LinkGpkd; changed = true; }
if (e.LinkHsnl is null && s.LinkHsnl is not null) { e.LinkHsnl = s.LinkHsnl; changed = true; }
if (e.ContactTitle is null && s.ContactTitle is not null) { e.ContactTitle = s.ContactTitle; changed = true; }
if (e.ContactPhone is null && s.ContactPhone is not null) { e.ContactPhone = s.ContactPhone; changed = true; }
if (e.MailRecipient is null && s.MailRecipient is not null) { e.MailRecipient = s.MailRecipient; changed = true; }
if (e.ReferralSource is null && s.ReferralSource is not null) { e.ReferralSource = s.ReferralSource; changed = true; }
if (e.OwnerPmh is null && s.OwnerPmh is not null) { e.OwnerPmh = s.OwnerPmh; changed = true; }
if (e.Status is null && s.Status is not null) { e.Status = s.Status; changed = true; }
if (changed) filledSuppliers++;
continue;
}
db.Suppliers.Add(s);
addedSuppliers++;
}
if (addedSuppliers > 0) logger.LogInformation("Seeded {Count} real suppliers (S55 import)", addedSuppliers);
if (filledSuppliers > 0) logger.LogInformation("Filled new columns on {Count} existing suppliers (S? 30-field)", filledSuppliers);
if (addedProjects + addedWorkItems + addedSuppliers > 0)
if (addedProjects + addedWorkItems + addedSuppliers + filledSuppliers > 0)
await db.SaveChangesAsync();
}

View File

@ -0,0 +1,223 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SolutionErp.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class ExpandSupplierFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "AuthorizationNote",
table: "Suppliers",
type: "nvarchar(1000)",
maxLength: 1000,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "BankAccount",
table: "Suppliers",
type: "nvarchar(500)",
maxLength: 500,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ContactPhone",
table: "Suppliers",
type: "nvarchar(50)",
maxLength: 50,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ContactTitle",
table: "Suppliers",
type: "nvarchar(150)",
maxLength: 150,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Fax",
table: "Suppliers",
type: "nvarchar(50)",
maxLength: 50,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LegalRepTitle",
table: "Suppliers",
type: "nvarchar(150)",
maxLength: 150,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LegalRepresentative",
table: "Suppliers",
type: "nvarchar(200)",
maxLength: 200,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LinkGpkd",
table: "Suppliers",
type: "nvarchar(1000)",
maxLength: 1000,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LinkGuq",
table: "Suppliers",
type: "nvarchar(1000)",
maxLength: 1000,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LinkHsnl",
table: "Suppliers",
type: "nvarchar(1000)",
maxLength: 1000,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "MailRecipient",
table: "Suppliers",
type: "nvarchar(200)",
maxLength: 200,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "MailingAddress",
table: "Suppliers",
type: "nvarchar(500)",
maxLength: 500,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "OfficeAddress",
table: "Suppliers",
type: "nvarchar(500)",
maxLength: 500,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "OwnerPmh",
table: "Suppliers",
type: "nvarchar(150)",
maxLength: 150,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "PackageCategory",
table: "Suppliers",
type: "nvarchar(300)",
maxLength: 300,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ReferralSource",
table: "Suppliers",
type: "nvarchar(300)",
maxLength: 300,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SecondaryBankAccount",
table: "Suppliers",
type: "nvarchar(500)",
maxLength: 500,
nullable: true);
migrationBuilder.AddColumn<int>(
name: "Status",
table: "Suppliers",
type: "int",
nullable: true);
// One-time un-cram: clear the legacy concatenated Note on the 4 seeded suppliers now that
// their data lives in structured columns. Guarded exact-match prefix (Note LIKE N'Loại:%')
// so manually-edited Notes are never touched (spec §4; safe on existing prod rows only).
migrationBuilder.Sql(
"UPDATE Suppliers SET Note = NULL " +
"WHERE Code IN (N'TRUONGGIANG', N'TANPHU', N'TGN', N'DONGDUONG') " +
"AND Note LIKE N'Loại:%';");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AuthorizationNote",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "BankAccount",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "ContactPhone",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "ContactTitle",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "Fax",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "LegalRepTitle",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "LegalRepresentative",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "LinkGpkd",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "LinkGuq",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "LinkHsnl",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "MailRecipient",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "MailingAddress",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "OfficeAddress",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "OwnerPmh",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "PackageCategory",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "ReferralSource",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "SecondaryBankAccount",
table: "Suppliers");
migrationBuilder.DropColumn(
name: "Status",
table: "Suppliers");
}
}
}

View File

@ -3218,6 +3218,14 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("AuthorizationNote")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("BankAccount")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(50)
@ -3227,6 +3235,14 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("ContactPhone")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("ContactTitle")
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
@ -3243,9 +3259,41 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Fax")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("LegalRepTitle")
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.Property<string>("LegalRepresentative")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("LinkGpkd")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("LinkGuq")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("LinkHsnl")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("MailRecipient")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<string>("MailingAddress")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
@ -3255,10 +3303,33 @@ namespace SolutionErp.Infrastructure.Persistence.Migrations
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<string>("OfficeAddress")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("OwnerPmh")
.HasMaxLength(150)
.HasColumnType("nvarchar(150)");
b.Property<string>("PackageCategory")
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<string>("Phone")
.HasMaxLength(30)
.HasColumnType("nvarchar(30)");
b.Property<string>("ReferralSource")
.HasMaxLength(300)
.HasColumnType("nvarchar(300)");
b.Property<string>("SecondaryBankAccount")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<int?>("Status")
.HasColumnType("int");
b.Property<string>("TaxCode")
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");