# sub-invest-codebase-A-mapping

**HEADLINE:** 30 cột Excel map GỌN vào 27 field Supplier: 1 ignore (STT) + 27 direct-field (cột 2–28, mỗi field dùng đúng 1 lần) + 2 cột audit-provenance (NGÀY/NGƯỜI CẬP NHẬT) KHÔNG có field khớp trực-tiếp → cần owner quyết. Seed 4 NCC (DbInitializer.cs:2775-2782) đã là precedent transform sống cho 27 mapping.

## LANE A — COLUMN→FIELD MAPPING (verified vs source thật)

**Nguồn verify:** `src/Backend/SolutionErp.Domain/Master/Supplier.cs:5-36` (27 field), `SupplierType.cs:3-11`, `SupplierStatus.cs:3-8`, precedent transform `DbInitializer.cs:2775-2782` (seed 4 NCC thật đã map 30→27), audit `AuditingInterceptor.cs:44-49` + `BaseEntity.cs:6-9`.

### Bảng mapping đủ 30 dòng

| # | Excel col (r4) | → target field | kind | transform / note |
|---|---|---|---|---|
| 1 | STT | — | **ignore** | Số dòng, không persist |
| 2 | GÓI THẦU | `PackageCategory` | direct-field | Free-text. Seed vd `"10-Nhôm kính+Face Mặt dựng"` |
| 3 | PHÂN LOẠI (NTP/NCC/Cả hai) | `Type` (`SupplierType`, **non-null**) | **enum-map** | Trim+ci: `NCC`→NhaCungCap(1) · `NTP`→NhaThauPhu(2) · `Cả hai`→CaHai(6). (Enum còn TĐ=3/ĐVDV=4/CĐT=5 nếu source có). Non-nullable → giá trị lạ phải reject-row hoặc default (owner) |
| 4 | TÊN VIẾT TẮT | `Code` (**required**) | direct-field | = Mã NCC dùng trong mã HĐ. Trim+UPPER khuyến nghị (seed: `TRUONGGIANG`). Là **khóa idempotent/dedup** |
| 5 | TÊN CÔNG TY | `Name` (**required**) | direct-field | Tên đầy đủ. KHÔNG tạo cột `companyName` mới — reuse `Name` gốc |
| 6 | ĐỊA CHỈ XUẤT HÓA ĐƠN | `Address` | direct-field | = "địa chỉ xuất HĐ/đăng ký" (comment field khớp). Reuse cột gốc, KHÔNG tạo `invoiceAddress` |
| 7 | ĐỊA CHỈ VĂN PHÒNG | `OfficeAddress` | direct-field | 4 NCC seed để trống → field vẫn là target đúng |
| 8 | SỐ ĐT CÔNG TY | `Phone` | direct-field | Phone công-ty (phân biệt cột 21) |
| 9 | FAX | `Fax` | direct-field | Chỉ TGN có (seed `"0251.2814663"`) |
| 10 | SỐ TÀI KHOẢN+TÊN+CN.NH | `BankAccount` | direct-field · **COMPOSITE** | **Giữ RAW 1 ô** (precedent: `"100212127 Ngân hàng Eximbank - Chi Nhánh Hòa Bình"`). Entity KHÔNG có AccountName/BankBranch → không tách |
| 11 | SỐ TK PHỤ | `SecondaryBankAccount` | direct-field | 4 NCC seed để trống, field đúng target |
| 12 | MÃ SỐ THUẾ | `TaxCode` | direct-field | Giữ raw kể cả có space (`"0312 251 859"`). Có thể null (DONGDUONG) |
| 13 | NGƯỜI ĐẠI DIỆN PHÁP LUẬT | `LegalRepresentative` | direct-field | Giữ prefix `"Ông/Bà"` như seed |
| 14 | CHỨC VỤ ĐẠI DIỆN | `LegalRepTitle` | direct-field | vd `"GIÁM ĐỐC ĐIỀU HÀNH"` |
| 15 | GIẤY ỦY QUYỀN | `AuthorizationNote` | direct-field | Free-text dài (số GUQ+ngày+người ủy quyền) |
| 16 | Link GUQ | `LinkGuq` | direct-field · **NAS path** | **Giữ RAW backslash** `@"01. GPKD_HSNL_GUQ\02.Trường giang\...pdf"` — KHÔNG normalize `\`→`/` |
| 17 | Link GPKD | `LinkGpkd` | direct-field · NAS path | như trên |
| 18 | Link HSNL | `LinkHsnl` | direct-field · NAS path | như trên |
| 19 | NGƯỜI LH CHÍNH | `ContactPerson` | direct-field | Người liên hệ chính (seed `"Mr. Việt"`) |
| 20 | CHỨC VỤ NGƯỜI LH | `ContactTitle` | direct-field | vd `"Kinh doanh"` |
| 21 | SĐT CHÍNH | `ContactPhone` | direct-field | Phone người-LH (KHÁC cột 8 phone công-ty) |
| 22 | EMAIL | `Email` | direct-field | |
| 23 | ĐỊA CHỈ GỬI THƯ | `MailingAddress` | direct-field | Địa chỉ thứ-3 (khác cột 6 & 7) |
| 24 | NGƯỜI NHẬN THƯ/SĐT | `MailRecipient` | direct-field · **COMPOSITE** | **Giữ RAW** `"MS Thủy / 036 405 0016"` (name+phone gộp). Entity không có field tách |
| 25 | NGUỒN GIỚI THIỆU | `ReferralSource` | direct-field | vd `"Tái sử dụng DA trước"` |
| 26 | NGƯỜI PHỤ TRÁCH(PMH) | `OwnerPmh` | direct-field | vd `"Bình"` |
| 27 | TÌNH TRẠNG HIỆN TẠI | `Status` (`SupplierStatus?`, **nullable**) | **enum-map** | **Strip emoji + trim**: `✅ Đang hoạt động`→DangHoatDong(1) · `Blacklist`/`đen`→Blacklist(2) · `Ngừng hợp tác`→NgungHopTac(3). Nullable → giá trị lạ = null OK |
| 28 | GHI CHÚ/LÝ DO BLACKLIST | `Note` | direct-field | Target ĐÚNG cho Note. (Seed để `Note=null` vì đã un-cram legacy — import thì col 28 mới là Note thật; coherence: khi Status=Blacklist thì lý-do nằm ở đây) |
| 29 | NGÀY CẬP NHẬT CUỐI | **(audit)** `UpdatedAt`? | **KHÔNG có field khớp** | ⚠️ Map `UpdatedAt` = **futile**: `AuditingInterceptor.cs:48` GHI ĐÈ `UpdatedAt=now` mỗi save. → ignore / gộp-Note / đề-xuất-field-mới (xem openQuestions) |
| 30 | NGƯỜI CẬP NHẬT | **(audit)** `UpdatedBy`? | **KHÔNG có field khớp** | ⚠️ `UpdatedBy` là `Guid?` (userId), Excel là **tên người** (text) → type mismatch + bị `AuditingInterceptor.cs:49` ghi đè. Không direct-map được |

### Kiểm chứng độ-phủ (0 sót)
- **27/27 field entity** được map đúng 1 lần (9 core + 18 expansion) — cột 2–28 (27 cột).
- **Cột 1** STT = ignore.
- **Cột 29–30** = audit-provenance, KHÔNG field khớp trực tiếp → quyết định owner.
- Tổng: 1 ignore + 27 direct + 2 no-field = **30 ✓**.

### 3 nhóm cần transform đặc-biệt (đã đánh dấu trong bảng)
- **COMPOSITE (2):** cột 10 BankAccount + cột 24 MailRecipient → **giữ RAW** (precedent seed đã giữ raw, entity thiếu sub-field để tách).
- **ENUM-MAP (2):** cột 3 Type (trim+ci, non-null → cần rule giá-trị-lạ) + cột 27 Status (strip emoji, nullable → lạ=null).
- **NAS path (3):** cột 16/17/18 → giữ raw backslash, KHÔNG đổi `\`→`/`.
- **Chung mọi cột:** normalize `\n`/whitespace trong header r4 (multi-line) + trim cell; skip `#REF!` ở r2 (chỉ parse tên r4).

### Lưu ý reuse-cột (tránh orphan)
Cột 5/6 map thẳng `Name`/`Address` gốc — KHÔNG tạo `companyName`/`invoiceAddress` mới (sẽ orphan cột seed live). 18 expansion field vốn purpose-built đúng cho Excel này (Mig 62), khớp 1-1 cột 2,7,9,10,11,13-18,20,21,23-27.

## Open Questions
**1. Cột 29-30 (NGÀY/NGƯỜI CẬP NHẬT) xử lý sao?** — KHÔNG có field khớp (audit fields `UpdatedAt`/`UpdatedBy` bị `AuditingInterceptor` ghi đè + `UpdatedBy` là Guid≠tên). 3 lựa chọn: (a) **ignore** — audit tự stamp import-time (mất provenance nguồn); (b) **gộp-Note** — nối `"Cập nhật cuối 01/07/2027 bởi Bình"` vào `Note` (giữ provenance, không thêm schema); (c) **đề-xuất-field-mới** `SourceUpdatedAt DateTime?` + `SourceUpdatedBy string?` (+2 cột Mig 63, sạch nhất nhưng thêm migration).

**2. PHÂN LOẠI (col 3, non-nullable Type) khi gặp giá-trị-lạ** ngoài NTP/NCC/Cả hai → reject-row hay default về NhaCungCap? (Status nullable nên không vướng — lạ=null).

**3. Composite cột 10 & 24 — xác nhận giữ RAW** (theo precedent seed) hay owner muốn tách sub-field (cần thêm cột AccountName/BankBranch/RecipientPhone → Mig mới)?

**4. Code (col 4) là khóa idempotent** — import gặp Code đã tồn tại thì skip / fill-nulls / overwrite? (Ảnh hưởng Lane B robustness).
