[CLAUDE] Docs: S51 P11-C Vehicle+Driver + gotcha #57 (3 HRM catalog) closeout

STATUS/HANDOFF S51 (Mig 43->45, tables 91->92, test 181->186, bundle Cg9mvltU/YgqDvsqr, P11-C DONE) + gotchas #57 ext (2->3 HRM catalog Mig 45 + Master ext backlog Mig 46 worktree) + session log 2026-06-08-S51. Agent-memory flush (impl-be/fe + test + cicd + investigator self-write; reviewer em-main proxy [return truncated gotcha #53]). CI-skip (docs/.md only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-08 10:52:25 +07:00
parent 30a99aa03f
commit 68c6bfea77
10 changed files with 106 additions and 34 deletions

View File

@ -47,10 +47,10 @@ Read-only CI/CD + post-deploy verifier SOLUTION_ERP. Polls Gitea Actions API, ve
- **Gitea:** `git.baocaogiaoduc.vn/vietreport-admin/solution-erp` · workflow `.gitea/workflows/deploy.yml` · paths-ignore `['docs/**','**/*.md','.claude/skills/**']`
- **Prod:** api/admin/eoffice `.solutions.com.vn` · SSH `ssh vietreport-vps` (Administrator, id_ed25519) · IIS site phys paths (S42 verified): API `C:\inetpub\solution-erp\api` · admin `\fe-admin` · user `\fe-user` (3 sites Started). DB `.\SQLEXPRESS`/`SolutionErp`/`vrapp` SQL-auth. **Conn string key = `ConnectionStrings.Default` (NOT `DefaultConnection`!)** read pw from prod appsettings.Production.json when `$env:PROD_DB_PASSWORD` empty.
- **SSHPS quoting (S42 lesson):** nested bashsshpowershell mangles `$var`/`\"`. Use `iconv UTF-16LE | base64` `powershell -EncodedCommand $B64`. Single-quote literal paths.
- **Tests baseline:** **181 PASS** (S45 Run #368 sha 0c5a014; Domain 58 + Infra 123 = +27 HRM coverage gaps: HrmConfigHolidayTests + EmployeeSatelliteTests + AuthorizePolicyRegressionTests-ext vs prev 154). CI gate runs both test projects BEFORE build/deploy status=success test gate passed (`tasks` endpoint reports terminal as `status:success`, `conclusion` field NOT populated). Local grep undercounts (Theory/InlineData) trust CI conclusion. Phase 9 UAT mode skip per chunk OK.
- **Mig latest repo:** **Mig 43 `20260601064128_FilterHolidayUniqueIndexByIsDeleted`** (S45; index-only change, prod tables stay 90-by-sys.tables / 91-by-doc NO new table). Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`. Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5`. Table-count drift: `sys.tables` count = 90 (verified S42 #364 + S45 #368), CLAUDE.md narrative = 91 counting-convention diff, NOT missing table. Don't FAIL on 90.
- **Tests baseline:** **186 PASS** (S50 Run #371 sha 30a99aa; Domain 58 + Infra 128 = +5 `HrmConfigFilteredUniqueTests` soft-deleted-slot-reuse for Vehicle/Driver/LeaveType/Shift/OtPolicy vs prev 181). CI gate runs both test projects BEFORE build/deploy status=success test gate passed (`tasks` endpoint reports terminal as `status:success`, `conclusion` field NOT populated). Local grep undercounts (Theory/InlineData) trust CI conclusion. Phase 9 UAT mode skip per chunk OK.
- **Mig latest repo:** **Mig 45 `20260608031611_FilterHrmCatalogUniqueIndexesByIsDeleted`** (S50; index-only drop+recreate IX_{ShiftPatterns,OtPolicies,LeaveTypes}_Code filtered `[IsDeleted]=0`) + **Mig 44 `20260608030149_AddVehicleAndDriverCatalogs`** (+Vehicles +Drivers tables, both IX_*_Code filtered-from-birth). Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/`. Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC TOP 5`. Table-count drift: `sys.tables` count = 92 post-Mig44 (was 90 S42/S45 + 2 new), CLAUDE.md narrative = 93 same counting-convention diff, NOT missing table. Don't FAIL on 92.
- **Bearer:** admin `admin@solutions.com.vn/Admin@123456` (full) · UAT `nv.test@solutions.com.vn/TestUser@123456` (Drafter CCM, gotcha #44 check)
- **Bundle hash live S48:** admin `DPPTx2Kw` · user `CjoUEsoV` (Run #369 sha 350b2bf, login subtitle a11y). Prev admin `Krjvg_3j` · user `6sNStgxa` (#368/0c5a014 unchanged BE-only). Bundle size ~800KB/750KB gz.
- **Bundle hash live S50:** admin `Cg9mvltU` · user `YgqDvsqr` (Run #371 sha 30a99aa, P11-C HrmConfigsPage). Prev admin `DPPTx2Kw` · user `CjoUEsoV` (#369/350b2bf). Bundle size ~800KB/750KB gz. S50 mid-deploy transient: pre-success snapshot showed `CVbyotwa`/`BBlyMlJH` (intermediate FE copy in-flight), final post-success = `Cg9mvltU`/`YgqDvsqr` re-confirm hash AFTER status=success ALWAYS (anti-pattern #3).
- **DB pw (S42, when `$PROD_DB_PASSWORD` empty):** `vrapp/buKL3TGBkD0wDDbYVw65QeX9` read from `C:\inetpub\solution-erp\api\appsettings.Production.json``ConnectionStrings.Default`. Skill-doc path `C:\inetpub\apps\SolutionErp\Api` is STALE real path `C:\inetpub\solution-erp\api`. sqlcmd over SSH works direct (no UTF-16 encode needed). sys-catalog string-concat queries hit collation conflict (`Latin1_General_CI_AS_KS_WS` vs `SQL_Latin1_General_CP1_CI_AS`) add `COLLATE DATABASE_DEFAULT` per concatenated column.
## 🔑 Critical config (flag commit nếu tái xuất)
@ -68,14 +68,14 @@ BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code /
## 📅 Recent runs (FIFO — older → archive/git)
- **2026-06-08 Run #371 (run_number 257) sha=`30a99aa` PASS ~4m18s (S50 HMW-Wave2 P11-C Vehicle+Driver catalogs Mig 44 + gotcha #57 filtered-unique 3 HRM catalog Mig 45 — BE+FE×2+2Mig+tests):** Push `f8179c5..30a99aa` 1 commit 28 files: BE Domain `Vehicle.cs`/`Driver.cs` + App `HrmConfigFeatures.cs`+IApplicationDbContext + `HrmConfigsController` + 5 Config (Driver/Vehicle/LeaveType/OtPolicy/ShiftPattern) + DbContext + DbInitializer + MenuKeys + Mig44/45 (6 files) + FE×2 (HrmConfigsPage/Layout/menuKeys/hrm-config.ts) + `HrmConfigFilteredUniqueTests.cs` (+5 test → 181→**186**). All BE/FE/Mig, none in paths-ignore → CI ran. Poll iter3 status=success (started 10:32:58 → 10:37:16). **Bundle ROTATE admin `DPPTx2Kw→Cg9mvltU` + user `CjoUEsoV→YgqDvsqr`** (BOTH changed ✓ FE shipped, verified AFTER status=success). **NEW LESSON (timing trap):** pre-success snapshot showed transient `CVbyotwa`/`BBlyMlJH` (intermediate FE copy mid-deploy, NOT final) → re-verify post-success gave real `Cg9mvltU`/`YgqDvsqr`. Confirms anti-pattern #3 + Run #242 lesson: NEVER trust bundle hash until status=success; mid-deploy can show a 3rd transient hash. **Mig 44+45 auto-applied prod** (`__EFMigrationsHistory` top2 = FilterHrmCatalog... + AddVehicleAndDriver...). **Vehicles+Drivers tables EXIST**; sys.tables=**92** (was 90 +2, narrative-93 = convention diff, NOT missing). **gotcha #57 LIVE — all 5 idx filtered:** IX_{Vehicles,Drivers,LeaveTypes,ShiftPatterns,OtPolicies}_Code ALL `is_unique=1 filter=([IsDeleted]=(0))` (3 HRM ones LeaveType/Shift/OtPolicy were NULL pre-Mig45 → now filtered = proof applied). Health live+ready 200 + admin/eoffice index 200. New endpoint `GET /api/hrm-configs/{vehicles,drivers}` unauth=**401** (route wired, no crash) + admin auth=**200** seed 2/catalog (vehicles XE-01/XE-02, drivers TX-01/TX-02 ✓ DbInitializer infra seed ran). 0 regression. Tag `[s50, run371, pass, p11c-vehicle-driver, mig44-45, gotcha57-filtered-5idx]`.
- **2026-06-03 Run #369 (run_number 255) sha=`350b2bf` PASS ~4m13s (S48 FE-only login subtitle a11y `text-slate-500→600`, ZERO BE/Mig):** Push range `7bbfa5a..350b2bf` 2 commits: `009dd94` DOCS/GOVERNANCE-only (9 files: STATUS/HANDOFF + 3 adap-reports + error-ledger + session-log + frontend-designer MEMORY + session-end.md cmd — ALL `.md`/`.claude/**`) + `350b2bf` CODE 2 files `fe-{admin,user}/src/pages/LoginPage.tsx` (1-line each, slate-500→600 subtitle contrast). Mixed push: `.tsx` present → **NOT path-filter skipped, full pipeline RAN** (gotcha #41 Discovery #3 — ≥1 non-ignored file in range ⟹ whole range builds; docs commit alone would skip but `.tsx` overrides). Poll iter5 status=success (started 00:06:33 → 00:10:46). **Bundle ROTATE admin `Krjvg_3j→DPPTx2Kw` + user `6sNStgxa→CjoUEsoV`** (BOTH changed ✓ FE shipped — verified AFTER status=success; pre-deploy snapshot iter0 still showed OLD `Krjvg_3j`/`6sNStgxa`, correct timing per anti-pattern #3). **NO migration** — repo 43 == prod `__EFMigrationsHistory` 43, latest both `...FilterHolidayUniqueIndexByIsDeleted` (Mig 43 unchanged, BE/Domain untouched ✓). Health live+ready 200 + admin/eoffice index 200. Test gate 181 (CI both proj pre-deploy ⟹ success=passed). 0 regression. NEW LESSON: smallest possible FE change (1-line className) still rotates bundle hash — Vite content-hash sensitive to any source byte; mixed docs+tsx push is the canonical case where docs-only-skip does NOT apply. Tag `[s48, run369, pass, fe-only-a11y, mixed-push-not-skipped]`.
- **2026-06-01 Run #368 (run_number 254) sha=`0c5a014` PASS ~4m20s (S45 Mig 43 filter Holiday UNIQUE by IsDeleted + 3 HRM test gaps — BE+tests ONLY, ZERO FE):** Push range `dbbed15..0c5a014` 2 commits: `051b62b` Tests +27 (HrmConfigHolidayTests + EmployeeSatelliteTests + AuthorizePolicyRegressionTests-ext → baseline 154→**181**) + `0c5a014` Mig 43 `20260601064128_FilterHolidayUniqueIndexByIsDeleted` (drops+recreates `IX_Holidays_Year_Date` as filtered UNIQUE `WHERE [IsDeleted]=0`, was unfiltered) + HolidayConfiguration.cs edit + Case-7 test flip. 7 files, all BE+tests, none in paths-ignore → CI ran. Poll iter4 status=success (started 13:43:47 → 13:48:07). **Bundle hashes UNCHANGED admin `Krjvg_3j` + user `6sNStgxa`** (= #367) — CORRECT for BE-only push, NOT ship-fail (Run #243 precedent; ship-proof = Mig 43 applied, not bundle rotate). **Mig 43 auto-applied prod** (history top = `...FilterHolidayUniqueIndexByIsDeleted` ✓). **THE FIX VERIFIED prod:** `IX_Holidays_Year_Date | unique=1 | filter=([IsDeleted]=(0))` — filter_definition non-NULL = filtered UNIQUE live (soft-deleted holidays no longer collide on UNIQUE). Health live+ready 200 Healthy. `Holidays` table exists, 10 rows, 2 named idx (PK + filtered UNIQUE). Prod tables=90-by-sys.tables (index-only change, NO new table — consistent #364 delta). NEW LESSON: filtered-index migration verify = check `sys.indexes.filter_definition` non-NULL (NOT just mig-history row); index-only mig = bundle unchanged + table-count unchanged both EXPECTED. Tag `[s45, run368, pass, mig43-filtered-index, be-only-bundle-unchanged]`.
- **2026-05-30 Run #367 (run_number 253) sha=`82d7fcf` PASS ~4m08s (S42 P11-B LeaveBalance business logic, Mig 42):** Code commit 22 files (4 BE: Domain `LeaveBalance.cs` + App `LeaveBalanceFeatures.cs`/`LeaveOtApprovalFeatures` deduction hook + `LeaveBalancesController` + IApplicationDbContext + DbContext + Config + Mig42 3-file + 2 FE `WorkflowAppDetailPage`×2 +`workflowApps.ts`×2 + 2 tests + 4 agent-memory .md). Started 11:11:40 → success iter4 11:15:48. **Bundle rotate admin `BU8FTBRi→Krjvg_3j` + user `tepE4jvR→6sNStgxa`** (both changed ✓ FE shipped, verified AFTER status=success — pre-deploy snapshot still showed old hash, correct timing). **Mig 42 `20260530034336_AddLeaveBalances` auto-applied prod** (tables 90→**91**, `LeaveBalances` EXISTS). Schema ✓: UserId/LeaveTypeId/Year/EntitledDays/UsedDays/AdjustmentDays decimal + AuditableEntity soft-delete. **UNIQUE `IX_LeaveBalances_UserId_LeaveTypeId_Year`** + **FK→LeaveTypes del=NO_ACTION** (=Restrict) ✓. New endpoint smoke: `GET /api/leave-balances/my` unauth=**401** (route live not 404) + admin auth=**200** lazy-default 5 LeaveTypes (ANNUAL12/COMPASSIONATE3/MATERNITY180/SICK30/UNPAID0, all Used=0, `remainingDays`=entitled ✓ DTO shape has remainingDays/entitledDays) + `?year=2026` admin route 401 unauth + `PUT /adjust`=411 (route reg). health live/ready 200 Healthy. **NO seed gate concern** (plain table, lazy DTO — Stage 4.6 N/A). 0 regression. Note: prev run #366 (ffb2062 docs STATUS update) was a CODE-path push w/ status=success — NOT docs-only-skipped (commit touched only .md but Gitea still ran since prior range?); actually #366 display_title is Docs but ran full → confirms agent-memory .md NOT in paths-ignore (`.claude/skills/**` ignored, `.claude/agent-memory/**` NOT). Tag `[s42, run367, pass, p11b-leavebalance, mig42]`.
- **2026-05-30 Run #365 sha=`75df04e` PASS ~4m05s (S42 P11-A fix workflow picker 2-bug + SetWorkflow endpoint, NO migration):** Code commit 11 files (4 BE controllers + 2 App features `LeaveOtApprovalFeatures`/`TravelVehicleApprovalFeatures` +125 lines + 2 FE `WorkflowAppDetailPage` ×2 + 1 test +79 lines). Status=success iter5 (started 10:15:45). **Bundle rotate admin `BLA09-qv→6D4k-aRi` + user `CXvejOE-→DkME-974`** (both changed ✓ FE fix shipped, verified AFTER status=success). +4 endpoint `PUT /api/{leave,ot,travel,vehicle-bookings}/{id}/workflow` (`Set{Module}WorkflowCommand`, route `[HttpPut("{id:guid}/workflow")]` body record `SetWorkflowBody(Guid ApprovalWorkflowId)`). Unauth smoke leave+ot/workflow → **401** (route exists, NOT 404 ✓). health live+ready 200 Healthy. Test gate **144** (CI both proj pre-deploy; grep undercounts InlineData=14 Fact at WorkflowAppApproveV2Tests). **NO migration** → skipped Stage 4.6 seed (verified #250). **NAMING RECONCILE:** Gitea task IDs are real #364 (e7b66cd, mem-labeled "#250") + #365 (this). Going forward use actual Gitea task id. **HEADS-UP em main:** follow-up commit `e47ef1d` (FE-User ProposalCreatePage workflow dropdown shape, latent S37 bug) pushed 10:19:17 DURING poll — NOT yet triggered CI run, will redeploy FE shortly (bundle may re-rotate). Out of scope this verdict. Tag `[s42, run365, pass, p11a-setworkflow]`.
- **2026-05-30 Run #364 (mem #250) sha=`e7b66cd` PASS ~4m07s (S42 P11-A wire ApproveV2+LevelOpinions 4 WorkflowApps):** 1 commit BE+FE×2+Mig41+Tests. Status=success iter3. Bundle rotate admin `cWAXid0q→BLA09-qv` + user `CX79e2kZ→CXvejOE-`. **Mig 41 auto-applied prod** (latest=`20260530021936_WireWorkflowAppsApprovalV2`). Tables 84→**90** (+5: Leave/Ot/Travel/VehicleRequest LevelOpinions + WorkflowAppCodeSequences — ALL EXIST). 4 new endpoint smoke 200 auth (leave/ot/travel/vehicle-requests) + unauth 401 (route exists) + POST .../approve=411 (route reg). health live/ready 200. **Stage 4.6 seed gate PASS** (gotcha #51): 4 WF seeded prod despite DemoSeed:Disabled — QT-NP/OT/CT/XE-V2-001 AppType=5/6/7/9, verified call-site L142-145 OUTSIDE `if(!demoSeedDisabled)` gate. Test gate 141 (CI runs both proj pre-deploy). Note: table count 90 vs spec-expected 89 = baseline-count diff, NOT missing table (all 5 present). Stale doc drift deploy.yml comments "54/17 test" (cosmetic, flag em main). Tag `[s42, run250, pass, p11a-approvev2-workflowapps]`.
- **2026-05-28 Run #247 sha=`e54a22d` PASS 3m25s (S38 SKELETON 5-plan combo Mig 39+40 dual):** Push 1 commit mega `Domain+App+Infra+Api+FE×2`. ALL PASS. Bundle rotate admin `CGueDk22→cWAXid0q` + user `CEt0QRgX→CX79e2kZ`. Mig 39+40 dual auto-applied startup (90830→90839). 6 endpoint smoke 200 (leave/ot/travel/vehicle/it-tickets/hr-dashboard `totalEmployees=33 male=17 female=16`). 6 new tables + 8 menu seeded. 0 regression. Fastest S38 deploy. Tag `[s38, run247, pass, skeleton-combo]`.
- **2026-05-28 Run #246 sha=`de1c378` PASS 3m53s (S37 Proposal Mig 37+38):** Bundle admin `C9kzTTmq→CGueDk22` + user `CC4DQ-Tr→CEt0QRgX`. Mig 38 AddProposals + 37 ExtendApplicableType. `/api/proposals` 200 empty + workflow `QT-DX-V2-001` ApplicableType=4 seed + 4 Off_DeXuat menu. Stage 4.6 sample seed INFRASTRUCTURE-gated correct (gotcha #51). Tag `[s37, run246, pass, proposal-v2]`.
- **Archived Run #359/#243/#242/#241/#240 + S35/S36 startup → `archive/2026-05-q4.md` + git d2f52ba (S40 curate):** Run #359 G-O2 Meeting Mig 36 · #243 HrmConfig BE 16 endpoint (BE-only bundle unchanged anti-pattern verify) · #242 FE inline forms 5 satellite · #241 Mig 35 HRM foundation · #240 satellite CRUD. Discovery #7 path-filter eval/** + #8 collection `proj_*`. KEY absorbed in essentials/Stage sections above.
- **Archived Run #246 (S37 Proposal Mig 37+38 — `/api/proposals` 200 + QT-DX-V2-001 AppType=4 seed + Stage 4.6 INFRASTRUCTURE-gated correct gotcha #51) + #359/#243/#242/#241/#240 + S35/S36 startup → `archive/2026-05-q4.md` + git d2f52ba (S40 curate):** Run #359 G-O2 Meeting Mig 36 · #243 HrmConfig BE 16 endpoint (BE-only bundle unchanged anti-pattern verify) · #242 FE inline forms 5 satellite · #241 Mig 35 HRM foundation · #240 satellite CRUD. Discovery #7 path-filter eval/** + #8 collection `proj_*`. KEY absorbed in essentials/Stage sections above.
- **Archived Run #232 (S29 gotcha #51 catch — SeedSampleContractWorkflowV2 nested in demoSeedDisabled → empty V2 dropdown, hoist fix) → `archive/2026-05-q4.md` + git. Smart Friend ROI 4× cumulative (S22 #44 + S25 #48 + S29 ApplicableType + S29 DemoSeed).**
---

View File

@ -66,7 +66,7 @@ UI `disabled={!canX}` + BE helper `EnsureCanXAsync(id, userId)` throw 403 (NOT i
## 🧠 SOLUTION_ERP BE conventions (S40)
- **BE .NET 10:** PascalCase entities + DTO records + command names. CQRS+MediatR+FluentValidation+AutoMapper. Repository qua `IApplicationDbContext`. `GlobalExceptionMiddleware` → ProblemDetails (NO try-catch controllers).
- **State S43:** 42 mig (last `AddLeaveBalances`) · 90 SQL tables · ~214 endpoints · 130 test baseline (test-specialist owns). Phase 9 UAT skip per chunk (`feedback_uat_skip_verify`).
- **State S51:** 44 mig (last `AddVehicleAndDriverCatalogs` Mig 44) · 93 SQL tables · ~222 endpoints · 181 test baseline (test-specialist owns). Phase 9 UAT skip per chunk (`feedback_uat_skip_verify`).
- **Build:** `dotnet build SolutionErp.slnx` clean 0 err. Commit `[CLAUDE] <scope>: <msg>` + Co-Authored-By Claude Opus 4.8 (1M context).
- **Pin (KHÔNG `*`/latest):** MediatR `12.4.1` (14 fail DI) · Swashbuckle `6.9.0` · Node CI `20.x` · LibreOffice `25.8.6` · @microsoft/signalr `8.0.7`.
@ -74,6 +74,7 @@ UI `disabled={!canX}` + BE helper `EnsureCanXAsync(id, userId)` throw 403 (NOT i
## 📅 Recent activity (FIFO — older → archive/git)
- **S51 P11-C HMW W1 — Vehicle+Driver catalogs HrmConfigs (Mig 44 `AddVehicleAndDriverCatalogs`, 9 add-point ~12 file/edit):** Pattern 12-bis catalog-mega 4th cumulative. 2 entity (`Vehicle`/`Driver`:AuditableEntity Domain/Hrm) + 2 EF config (mirror `HolidayConfiguration` filtered, NOT buggy bare LeaveType) + 2 DbSet (IAppDbContext+ApplicationDbContext) + Mig 3-file + HrmConfigFeatures Region5/6 (DTO+List/Create/Update/Delete CQRS mirror Region1 EXACT) + Controller +2 route group (8 endpoint, GET public + POST/PUT/DELETE `[Authorize(Roles=Admin)]`) + MenuKeys +2 const +All array + DbInitializer (menu 2 leaf + SeedHrmConfigsAsync guard&seed 2 veh+2 drv). **gotcha #57 KEY:** Code UNIQUE `.HasFilter("[IsDeleted]=0")` — Mig diff verified CLEAN 2 CreateTable + 2 filtered IX no drift. Validator MaxLength = em main schema (Code50/Name200/Plate20/Phone20/LicNum50/LicClass20/Desc500), SeatCount GreaterThanOrEqualTo(0). Admin perm AUTO-grant: `SeedAdminPermissionsAsync` + `Program.cs:78` both iterate `MenuKeys.All` → +All array = 8 policy + Admin row auto (no manual grant code). HRM no HasQueryFilter → `.Where(!IsDeleted)` manual. Applied BOTH DB. Build 0 err (2 pre-existing DocxRenderer warn). RAG/Qdrant DOWN → all Read/Grep on-disk. Spec deterministic ~98% em main → ACCEPT Case 1. KHÔNG touch FE/test/commit. Tag `[s51, p11-c, mig44, vehicle-driver, catalog-mega]`.
- **S43 P11-B Wave 1 — LeaveBalance business logic (Mig 42 `AddLeaveBalances`, 7 file: 1 entity + 1 config + 2 DbSet edit + Mig 3-file + 1 hook edit + 1 Features + 1 Controller):** Case 1/3 deterministic ~98% em main spec. Pattern 12-ter-adjacent single-entity: entity `LeaveBalance:AuditableEntity` (UserId/LeaveTypeId/Year + EntitledDays/UsedDays/AdjustmentDays decimal(5,2), nav LeaveType). Config FK LeaveType WithMany() **Restrict** (catalog no cascade) + UNIQUE composite (UserId,LeaveTypeId,Year) + IX UserId. Mig diff CLEAN: 1 CreateTable + 3 IX, no drift. Applied BOTH DB (Dev `SolutionErp_Dev` + Design default). **Deduction hook:** insert in `ApproveLeaveRequestHandler` terminal else (DaDuyet branch) ONLY — UPSERT LeaveBalance, `bal.UsedDays += p.NumDays`, exactly-once guaranteed by early guard `Status != DaGuiDuyet throw`. OtRequest/Travel/Vehicle UNTOUCHED (only Leave has balance). CQRS `Application.Hrm`: DTO RemainingDays=Entitled+AdjustmentUsed COMPUTED (not stored) + GetMy/GetUser lazy-merge (load active LeaveTypes + balances → in-memory merge, synth default when no row — KHÔNG EF LEFT JOIN translate) + AdjustLeaveBalanceCommand admin upsert (HasValue-gated). **Policy resolved:** HRM admin convention = `[Authorize(Roles="Admin")]` NOT menu policy (verified HrmConfigsController write endpoints) — used on GET-by-user + PUT /adjust; /my = `[Authorize]`. Controller injects IDateTime for year default `clock.Now.Year` (thin, no DateTime.Now hardcode). HRM no HasQueryFilter → `.Where(!IsDeleted)` manual everywhere. KHÔNG touch FE/test/commit. Build 0 err (2 pre-existing DocxRenderer warn). Tag `[s43, p11-b-w1, mig42, leave-balance, single-entity]`.
- **S42 P11-A SEED — 4 sample ApprovalWorkflow V2 for WorkflowApps (DbInitializer.cs only, +4 method ~210 LOC + 4 call):** Case 1 mechanical mirror `SeedSampleProposalWorkflowV2Async` EXACT × 4 (Leave5/Ot6/Travel9/Vehicle7). Each: idempotent `AnyAsync(ApplicableType==X)` guard → resolve approver `binh.le@solutions.com.vn` (SAME user as Proposal/Contract seed, null→LogWarning+return) → 1 ApprovalWorkflow (Version=1, IsActive+IsUserSelectable=true, ActivatedAt=UtcNow) + 1 Step (Order=1, Name="Cấp duyệt", DepartmentId=CCM?.Id) + 1 Level (Order=1, ApproverUserId). Codes QT-NP/OT/CT/XE-V2-001. Wired 4 calls after SeedSampleProposalWorkflowV2Async (NOT gated DemoSeed, gotcha #51 infra seed). Enum verified Grep. Build 0 err 0 warn. Bash tool = bash NOT PowerShell despite env hint (use `cd && cmd | grep`). Spec deterministic 100% → ACCEPT Case 1. NOT touched App/Controller/FE/test/Mig. Tag `[s42, p11-a, seed, mirror-proposal-exact]`.
- **S42 P11-A Wave 2b APP — wire ApproveV2 CQRS Travel+Vehicle (`TravelVehicleApprovalFeatures.cs` ~830 LOC + 2 controller edit):** Cookie-cutter mirror Wave 2a / ProposalFeatures Region 2. 1 new file ns `Application.Office`: 2 module × (DetailDto + LevelOpinionDto + GetById JOIN Step/Level metadata + UpdateDraft + Submit + Approve UPSERT+advance + Reject TuChoi + Return TraLai+RejectedFromStatus) + 1 shared `internal static TravelVehicleCodeGen.GenerateMaDonTuAsync` (Serializable tx, `WorkflowAppCodeSequences` Prefix-keyed, prefix `DT/CT/{year}` Travel & `DX/XE/{year}` Vehicle, format `{prefix}/{seq:D3}` — D3 no year segment per spec). KEY gotcha: WorkflowAppStatus enum DIFFERS from ProposalStatus int values (DaGuiDuyet=2 not 1, TraLai=3) → mirror by SEMANTIC enum member not literal. Owner = `RequesterUserId` (not DrafterUserId). Submit verify wf.ApplicableType==Travel9/Vehicle7 else Conflict. 2 controller +6 route each (GET{id}/PUT/submit/approve/reject/return) nested body records, CreatedAtAction. KHÔNG sửa WorkflowAppsFeatures.cs/Leave/Ot/FE/test/seed. Build 0 err. Spec deterministic ~98% em main → ACCEPT Case 1/2. Tag `[s42, p11-a, wave-2b, mirror-proposal-region2]`.

View File

@ -25,8 +25,8 @@ Verified clean S33/S34/S35/S36/S37/S38 = 9× cumulative. Spec MUST list 4 places
### SHA256 IDENTICAL × 2 app
Viết fe-admin → `cp` fe-user → `sha256sum` verify. Khác UX (admin full sidebar vs user filter) → mirror tay + `diff`. Proven S36 (3 pair) + S37 (4 pair) + S38 (5 pair).
### Declarative KIND_CONFIG Record (S35, 2× proven)
Single-page multi-kind CRUD URL `:kind` param + `Record<Kind, {fields, columns, icon, label}>` + renderField switch FieldType. Reuse: HrmConfigs (S35) + WorkflowApps (S38).
### Declarative KIND_CONFIG Record (S35, 3× proven)
Single-page multi-kind CRUD URL `:kind` param + `Record<Kind, {fields, columns, icon, label}>` + renderField switch FieldType. Reuse: HrmConfigs (S35) + WorkflowApps (S38) + HrmConfigs +2 kind (S51). ADD-KIND playbook: union type + KIND_CONFIG entry + KINDS array + renderCells branch (before fallback) + import icon — `:kind`-driven page = NO new App.tsx route.
### Pattern 14 Tailwind JIT palette
Dynamic class purged. PALETTE array full literal `as const` cycle `index % length`.
@ -42,6 +42,7 @@ Dynamic class purged. PALETTE array full literal `as const` cycle `index % lengt
## 📅 Recent activity (last 10 FIFO)
- **2026-06-08 (S51 P11-C — vehicles+drivers kind → HrmConfigsPage):** Declarative KIND_CONFIG +2 entry (10th proof). 4-place adapt (`:kind`-driven page → NO App.tsx route): types/hrm-config.ts (union +'vehicles'+'drivers' + VehicleDto/DriverDto + Create/Update inputs) · HrmConfigsPage.tsx (KIND_CONFIG +2, KINDS array +2, renderCells +2 branch before ot-policies fallback, import Car+IdCard) · menuKeys.ts (+Hrm_Config_Vehicles/Drivers — BE string exact) · Layout.tsx staticMap +2 BOTH app. Field keys: vehicles{code,name,licensePlate,seatCount,description} drivers{code,name,phoneNumber,licenseNumber,licenseClass,description}. cp admin→user 3 file SHA256 identical (page a3afd724, type 2c0775b3, menuKeys d650c086). Layout mirror tay (structural diff OK, 2 entry verified both). Build PASS ×2 (admin 1944mod, user 1934mod, 0 TS err). lucide IdCard EXISTS (no UserRound fallback). AMBIGUITY: BE catalog vehicles/drivers chưa tồn tại on-disk (Wave 1 parallel — implementer-backend đang/sẽ làm) → FE scaffold theo contract spec cấp; runtime cần BE `/hrm-configs/vehicles`+`/drivers` endpoint + Hrm_Config_Vehicles/Drivers const trong BE MenuKeys.cs + seed.
- **2026-05-30 (S42 P11-B Wave 2 — leave balance display):** WorkflowAppDetailPage.tsx + workflowApps.ts (2 app SHA256 identical). +3 optional `leaveBalance{Entitled,Used,Remaining}?: number|null` trong `// leave` block (BE `decimal?` → camelCase). Block "Số dư phép" sau Section 1 IIFE `kind==='leave' && d.leaveBalanceRemaining != null`: year từ StartDate, banner amber/red khi `remaining<0 || (status!==DaDuyet && remaining<numDays)`. Case 1, KHÔNG 4-place (enrich existing page). cp fe-admin→fe-user. Build PASS ×2 (page 8ef83e4b, type 1c4f167a). Lesson reuse: IIFE inline `(() => {...})()` cho conditional block có derived vars — sạch hơn tách helper.
- **2026-05-29 (S39 agent split setup):** NEW agent từ split implementer. Seeded FE patterns (16-bis 9× + SHA256 mirror + KIND_CONFIG + Tailwind palette + PageHeader S37). Prior FE work absorbed: S33 EmployeesListPage + S34 Directory + S35 HrmConfigs declarative + S36 MeetingCalendar + S37 Proposal + S38 WorkflowApps generic.

View File

@ -70,6 +70,10 @@ Bearer từ `POST api.solutions.com.vn/api/auth/login` → status matrix expecte
## 📅 Recent activity (FIFO — older → archive/git)
- **2026-06-08 (S51 gotcha #57 EXTENSION reachability audit — 6 candidate, RAG down, on-disk only):** ⭐ Bug class = soft-delete + bare `.IsUnique()` on Code → recreate-after-delete throws DbUpdateException 500. Verdict 6 cand: **FIX 3 (Master)** Department/Supplier/Project (`Department/Supplier/ProjectConfiguration.cs:18/24/19` bare unique). ALL = AuditableEntity + **GLOBAL `HasQueryFilter(!IsDeleted)`** + Delete via `.Remove()``AuditingInterceptor.cs` (State Deleted→Modified, IsDeleted=true) + Create `AnyAsync(x=>x.Code==req.Code)` NO `!IsDeleted` BUT global filter auto-hides soft-deleted → check passes → unfiltered index 500. **CONFIRMED-reachable** (`DepartmentFeatures.cs:76+125`, `ProjectFeatures.cs:87+147`, `CreateSupplierCommand.cs:45`+`DeleteSupplierCommand.cs:20`). **SKIP 3:** (a) **ContractClause** (`ContractClauseConfiguration.cs:18`) — NO Create/Update/Delete handler ANYWHERE (only `IApplicationDbContext.cs:32` DbSet; FormsController = templates only) → not CRUD-reachable. (b) **MeetingRoom** (`MeetingRoomConfiguration.cs:20`) — Delete sets `IsActive=false` NOT IsDeleted (`MeetingFeatures.cs:178`, comment :175 "FK Restrict → NOT soft delete") → index never gets soft-deleted row; Create also checks `&& !IsDeleted` :113. (c) **EmployeeProfile** (`EmployeeProfileConfiguration.cs:24/26` EmployeeCode+UserId) — Delete soft (`EmployeeFeatures.cs:437`) BUT Create BLOCKS reuse by design: UserId check `AsNoTracking().FirstOrDefault(UserId==)` (no HRM global filter) sees soft-deleted → throws ConflictException "Cần khôi phục" :160-163; EmployeeCode auto-gen atomic (never user-supplied/reused) → no collision. **Completeness (grep ALL `.IsUnique()`):** beyond 3 Master + 6 HRM-fixed (LeaveType/Holiday/Shift/OtPolicy/Vehicle/Driver all `.HasFilter([IsDeleted]=0)`), every OTHER bare-unique is either composite junction (Permission RoleId+MenuKey, *LevelOpinion, MeetingBookingAttendee, LeaveBalance, Attendance UserId+Date), nullable-code already filtered (`[Ma*] IS NOT NULL`: Contract/PE/Proposal/Budget/WorkflowApps), or no-soft-delete (WorkflowDefinition/ApprovalWorkflow Code+Version, ContractTemplate FormCode, WorkflowTypeAssignment, DepartmentApprovals). **Mig 46 = exactly 3 indexes (Departments/Suppliers/Projects Code).** Surprise: Master GLOBAL query filter MAKES the bug (auto-hides soft-deleted from check) — opposite of HRM where bug needs manual `!IsDeleted`; either way unfiltered index = 500. Tag `[gotcha57-ext, reachability-audit, master-global-filter, s51]`.
- **2026-06-08 (S50 P11-C Vehicle+Driver — HrmConfigs add-kind pattern VERIFIED on-disk, RAG down):** ⭐ **HrmConfigs KHÔNG có "kind enum/registry" backend** — 4 entity RIÊNG (LeaveType/Holiday/ShiftPattern/OtPolicy), NOT discriminated table. "kind" chỉ FE: `HrmConfigKind` union `fe-admin/src/types/hrm-config.ts:4` + route param. **Add 1 kind = mirror FULL entity stack 11 chỗ:** BE (1) Domain `Hrm/{X}.cs` AuditableEntity soft-delete (2) `Configurations/{X}Configuration.cs` `.ToTable+.HasIndex(Code).IsUnique()` (3) `ApplicationDbContext.cs:95-98` DbSet (4) `IApplicationDbContext.cs:102-105` DbSet (5) `HrmConfigFeatures.cs` +Region N (DTO+List/Create/Update/Delete handler+validator, mega 4-region :30/125/222/328) (6) `HrmConfigsController.cs` +4 route hardcode `[HttpGet/Post/Put/Delete("{kind}")]` (Post/Put/Del `[Authorize(Roles="Admin")]`, Get chỉ `[Authorize]`) (7) `DbInitializer.cs:2329 SeedHrmConfigsAsync` +if-block + skip-guard :2331 phải +`&& OtPoliciesNew.AnyAsync()` (8) `MenuKeys.cs:88-92` +const + `:149 All[]` (Admin auto-grant `SeedAdminPermissionsAsync` loop idempotent). FE (9) `HrmConfigsPage.tsx:45 KIND_CONFIG` +entry + `:114 KINDS[]` + `:379 renderCells` branch + `:166 smart-defaults` + types/hrm-config.ts DTO (10) `App.tsx:90` route `/hrm/configs/:kind` SẴN catch-all → KHÔNG cần sửa, chỉ +menuKeys (11) `menuKeys.ts:38-42` + `Layout.tsx:60-63 staticMap`. **gotcha #57 CONFIRMED còn trần:** `LeaveTypeConfiguration.cs:19` + `ShiftPatternConfiguration.cs:19` + `OtPolicyConfiguration.cs:22` `.IsUnique()` CHƯA `.HasFilter("[IsDeleted]=0")` (chỉ `HolidayConfiguration.cs:18` đã fix Mig 43). → Vehicle/Driver Code UNIQUE PHẢI add filter ngay từ đầu. **Mig 44 BẮT BUỘC CREATE TABLE** (mỗi kind = bảng riêng, NOT discriminated → +2 bảng Vehicles+Drivers, không phải seed-only). **VehicleBooking** (`Office/VehicleBooking.cs:13-19`) pure free-text `VehicleLicense/VehicleName/DriverName` string, NO `VehicleId/DriverId` FK (grep empty) → P11-C catalog-only, FK link defer Mig sau. Latest Mig=43 `FilterHolidayUniqueIndexByIsDeleted` (`20260601064128`), next=44. Tag `[p11-c, hrmconfig-add-kind, gotcha57, on-disk-verify]`.
- **2026-06-07 (S50 wave `h2-verify` — B6 guardrail audit, read-only) [em main scribe from findings + H2 harvest]:** Verified B6 wave-isolation **3/3 PASS**. **B6 = TWO complementary rules:** (a) transient `wave-*/` + `agent-teams/` gitignored (`.gitignore:93-94`) → audit-noise=0; (b) canonical `agent-memory/**/MEMORY.md` TRACKED → rogue sub-write surfaces in `git status`. `git check-ignore -v` = ground-truth verifier BOTH directions (matched rule:line for ignored; empty for tracked). ⚠️ **Ordering gotcha:** wave/team patterns MUST sit AFTER `!.claude/**` (`.gitignore:82-83`) to win via last-match (`:91` documents intent) — else `!.claude/**` un-ignores everything. All 10 MEMORY.md tracked (roster 8→10). **Surprise (cross-cutting, both wave subs):** Bash tool = `/usr/bin/bash` NOT PowerShell despite env=PowerShell → `Get-ChildItem`/`Select-String`/`Test-Path` fail (exit 2/127); read-only Bash-only subs MUST use POSIX (`git ls-files`/`grep`/`ls`). Tag [wave-h2, b6-isolation, posix-not-pwsh].
- **2026-06-07 (Harness 1/2/3 adap-apply recon — 3 slice, HMW wave):** Governance recon AI_INFRA broadcast harness-1/2/3. **H1/H2 (Harness 1):** roster 8→10 — CREATE 2 sub TÁCH BIỆT `tooling-auditor` (H1 freshness 4-mặt skill/sub-role/plugin/docs) + `harvest-curator` (H2 integrity 5-trục). H2 PARTIAL sẵn: `session-end.md` Phase 1.5 §L.b(d) spawn-record 4-field + (f) double-check moved-not-cut + (c) 0-byte AS-8 = Coverage+Completeness+Corruption (3/5); THIẾU Fidelity-escalate + Placement. RE-REPORT @session-start = 0 (chỉ generic Phase 2.7). 2 sub mirror inv-codebase read-set + store_memory strip + NO Write/Edit; color brown+teal (8 màu cũ hết). **H2 wave (Harness 2):** SE `hmw.js` = OLD pre-wave (no subMdPath/writeGuard/wave-block); AI_INFRA `hmw.js` = canonical template. ⭐ `git check-ignore -v` = ground-truth B6: `.claude/workflows/wave-test/wave.md` HIỆN match `.gitignore:83 !.claude/**` = TRACKED → wave pattern PHẢI đặt AFTER `!.claude/**` (last-match-wins, mẫu `hmw-mode.on` :87). Read-only sub (4)=inv-cb/inv-api/reviewer/cicd; Write sub (4)=impl×2/test/fe-designer. B5 depends H2 harvest-curator. **H3 email (Harness 3):** broadcasts/ absent; id authoritative = `se` (NOT solution_erp), 6 others short `{ai_infra,vipix,dyd,namgroup,ashico,bvaau}` từ `AI_INFRA/broadcasts/sister-commands/send-email.md:13-22` (folder name = 2nd source-truth); `adap-apply.md:14` base-path STALE flat → `outbox/all/*.md` (latent bug). broadcasts/ ở root → commit OK (no gitignore rule). **Containment post-P2:** git-diff bắt 1 file-write (inv-api self-MEMORY), chunk-count 2414=2414 (0 RAG-write) = defense-in-depth proven. Tag [harness-recon, governance, hmw-wave, 2026-06-07].

View File

@ -57,6 +57,8 @@ Adversarial pre-commit reviewer SOLUTION_ERP. Read-only verify + live curl prod
## 📅 Recent activity (FIFO — older → archive/git)
- **2026-06-08 (S51 P11-C Vehicle+Driver + gotcha #57 pre-commit — PASS, 1 MAJOR caught) [em main proxy — reviewer return truncated gotcha #53]:** Reviewed Mig 44 (Vehicle/Driver catalog) + Mig 45 (filter 3 HRM unique) + FE KIND_CONFIG +2 + 5 tests (186 PASS). Independent build+test re-verify GREEN. **CAUGHT 1 MAJOR (Cat 3 cross-stack contract):** Driver FE↔BE required-field mismatch — FE render phoneNumber/licenseNumber/licenseClass OPTIONAL nhưng BE validator `NotEmpty()` + EF `.IsRequired()` NOT NULL → empty submit = 400/500. Root = inconsistent em-main brief (BE "mirror Vehicle"=required vs FE spec quên required). Fix: FE +`required:true` (align BE all-required như Vehicle). Cats khác clean (Mig diff clean, Authorize Roles=Admin writes, gotcha #57 grep-complete 3 HRM, DbInitializer idempotent + #51 infra-gated, SHA256 mirror, no copy-paste Driver↔Vehicle). **Learned:** parallel fan-out (BE∥FE file-disjoint) → bất kỳ inconsistency trong SHARED em-main contract chỉ lộ lúc integration; green tests ≠ correct contract (no test chạm empty-optional path). reviewer = the net. **surprise:** transient mid-deploy bundle hash (cicd lesson) + reviewer self-truncate trước khi ghi MEMORY → em main proxy. Verdict PASS post-fix. Tag [s51, p11-c, gotcha57, contract-mismatch-catch].
- **2026-06-07 (S49 Harness 1/2/3 adopt pre-commit — PASS all 3, no blocker):** Governance/infra adopt (no product code, no test impact). VERIFIED: H1/H2 = 2 sub scope-DISJOINT + tools `[Read,Grep,Glob,Bash+4RAG]` NO store_memory/Write (INFORM-only); genuinely **TAILORED not copy-paste** (SE 4-RAG vs AI_INFRA 2-RAG · dropped effort:max + agent-ops-monitor/sister · Fidelity→SE `reviewer`). H2 5-trục in harvest-curator.md + session-end §L.b(f). H2 wave-mode hmw.js mirror AI_INFRA + **B6 `git check-ignore` VERIFIED** (wave-*/+agent-teams/ ignored · hmw.js/README tracked). H3 self=`se` complete substitution · **SHA256 canonical formula byte-identical send==check** · 13 .gitkeep exact · adap-apply base-path `outbox\all\`. honest nấc executed-file/verified-runtime-PENDING. G-015 scan = 6 hits ALL negating ("KHÔNG enforced") = correct honesty. **1 MINOR (non-block):** README:11/18 "7-agent" ASCII diagram = **PRE-EXISTING** drift (git diff proved work này chỉ touch load-bearing title/decision-tree/tool-grant/matrix; diagram predates S47 frontend-designer) → tooling-auditor H1 designed-to-catch = self-validating adoption. **learned:** `git diff base..head` = discriminator introduced-defect vs pre-existing-drift (đừng đổ lỗi work mới cho drift cũ); name-collision tailor-verify = diff frontmatter AI_INFRA-canonical vs SE-instance. **surprise:** mojibake scan false-pos trên "ĐÃ" (U+00C3 = valid VN uppercase, KHÔNG double-encode → verify codepoint in-context trước flag); broadcast floor "12 .gitkeep" UNDERCOUNT (correct=13 incl `all/` adap-channel — em main đúng). Verdict PASS, safe commit + restart. Tag [s49, harness-adopt, governance, max-clean].
- **2026-05-30 (S43 P11-B LeaveBalance pre-commit — PASS, Max no-truncate):** 14 file (LeaveBalance entity+config+Mig42 + Features + Controller + deduction hook + Create/Update LeaveType guard + embed balance + FE×4 + tests). 154 PASS (130→154). **Deduction exactly-once VERIFIED** (terminal else only, guard Status!=DaGuiDuyet chặn re-approve; advance/reject/return no-deduct). **FK invariant fully closed** — grep 2 write site LeaveTypeId (Create + UpdateDraft) cả 2 guard AnyAsync→Conflict, bogus type không thể tới terminal FK insert. Embed balance = RequesterUserId (approver thấy đúng người tạo). admin `[Authorize(Roles=Admin)]`. **2 MINOR defer:** concurrency lost-update UsedDays (no RowVersion — human-sequential accept) · stale line-num comment. Verdict PASS. Tag `[s43, p11b-leavebalance, max-clean]`.

View File

@ -15,8 +15,8 @@ WRITE specialist độc quyền `tests/**`. xUnit + FluentAssertions 7.2 + EF SQ
- ❌ NOT: production code `src/Backend/**` + `fe-*/**` → test reveal bug → REPORT em main, KHÔNG fix
- ❌ NOT: decide WHAT to test (test plan) → em main + reviewer chốt priority
## 📊 Baseline 181 PASS (58 Domain + 123 Infra) ← S45 +27 HRM coverage (Gap1/2/3) · S43 +8 LeaveBalance
> ⚠️ Entry S45 dưới = **em main proxy** (agent final msg truncated mid-MEMORY-update, gotcha #53 recurrence).
## 📊 Baseline 185 tests = 183 PASS + 2 RED (58 Domain + 127 Infra) ← S51 +4 filtered-unique (2 GREEN Vehicle/Driver + 2 RED gotcha #57)
> ⚠️ 2 RED = **production bug intentional** (LeaveType+ShiftPattern bare `.IsUnique()` chưa filter) — em main fix Mig 45 → GREEN. KHÔNG phải test lỗi. Pre-S51 baseline 181 PASS (S45 +27 / S43 +8).
Run: `dotnet test SolutionErp.slnx --nologo --verbosity minimal`
### ⚠️ Pattern: deduction hook FK → seed LeaveType cho terminal test (S43)
@ -50,11 +50,11 @@ Test theo CODE (single source truth), document mismatch header comment + report.
2. **DONE S45** EmployeeSatellite FK invariant + soft-delete + cascade: 10 test (`EmployeeSatelliteTests.cs`)
3. **DONE S45** gotcha #44 authz regression EmployeesController + HrmConfigsController: 10 test (extend `AuthorizePolicyRegressionTests.cs`)
4. Phase 10.3 Proposal ApproveV2 (S37) + Workflow Apps skeleton (S38) test-after khi UAT confirm
5. **NEW (gotcha #57):** LeaveType.Code + ShiftPattern.Code UNIQUE chưa filter `[IsDeleted]=0` (cùng bug class Holiday đã fix Mig 43) recreate-on-soft-deleted-slot 500. Test-before khi fix.
5. **gotcha #57 (S51 REPRODUCED — RED live):** LeaveType.Code + ShiftPattern.Code bare `.IsUnique()` chưa filter `[IsDeleted]=0` (cùng class Holiday Mig 43). Test `HrmConfigFilteredUniqueTests.cs` 2 RED (`SQLite Error 19 UNIQUE constraint failed`). Em main fix Mig 45 `.HasFilter` 2 GREEN. Vehicle+Driver (Mig 44) ĐÃ filtered 2 GREEN baseline.
## 📅 Recent activity (last 10 FIFO)
- **2026-05-29 (S40 baseline audit smoke):** CONFIRMED 130 PASS (Domain 58 + Infra 72), 0 fail/skip, ~15s. Runner count authoritative; raw `[Fact]/[Theory]` attr = 48+70 (TheoryInlineData expand). Infra spread 15 files. Gap re-verified vs prod: EmployeesController+HrmConfigsController EXIST, authz regression ch ApprovalWorkflowsV2Controller (gotcha #44 gap real). Proposal = Domain entity + EF config only, CHƯA ApproveV2Async service (S37 skeleton, defer đúng). Agent load OK. AUDIT-only, no write.
- **2026-06-08 (S51 P11-C HMW Wave2 filtered-unique gotcha #57):** +4 test `tests/.../Application/HrmConfigFilteredUniqueTests.cs` **185 total = 183 PASS + 2 RED** (Infra 123127). Mirror HolidayTests Case 7 (seed soft-deleted Code-slot Create same Code assert success + active==1 + all==2). **2 GREEN** Vehicle+Driver (Mig 44 config ĐÃ filtered 2 catalog mới đúng). **2 RED INTENTIONAL = gotcha #57 REPRODUCED** (test-before): `CreateLeaveType_OnSoftDeletedCodeSlot...` `SQLite Error 19 UNIQUE constraint failed: LeaveTypes.Code` + `CreateShift_OnSoftDeletedCodeSlot...` `ShiftPatterns.Code` (bare `.IsUnique()` đếm cả row soft-deleted; handler app-check `!IsDeleted` PASS Add+SaveChanges DbUpdateException). NOT test lỗi REPORTED em main fix Mig 45 `.HasFilter("[IsDeleted]=0")` cho 2 config flip GREEN. ** Soft-delete trong test (giống Holiday):** AuditingInterceptor (prod soft-delete DeletedModified+IsDeleted=true) KHÔNG wire trong SqliteDbFixture `Remove+SaveChanges` = HARD delete (không test được). PHẢI seed row `IsDeleted=true` thủ công để phỏng slot bị chiếm. Handlers chỉ cần IApplicationDbContext `new CreateXxxHandler(db)`. Tag [s51, p11-c, gotcha-57, filtered-unique, test-before].
- **2026-05-30 (S42 P11-A Wave4):** +11 test `tests/.../Application/WorkflowAppApproveV2Tests.cs` **141 PASS** (Infra 7283). LeaveRequest 8 case full (Submit happy/guard×2, Approve advance/terminal/UPSERT-invariant/forbidden/empty-comment-placeholder, RejectTuChoi, ReturnTraLai+RejectedFromStatus) + OtRequest smoke (submitapprove single-levelDaDuyet). **No prod bug** LeaveOt ApproveV2 wire correct, all PASS first run. **NEW Pattern:** WorkflowApps handlers = CQRS MediatR (KHÔNG service) instantiate handler trực tiếp `new ApproveLeaveRequestHandler(db, AsUser(u), clock).Handle(cmd,ct)`, chỉ 3 dep (IApplicationDbContext + TestCurrentUser + FixedDateTime) nhẹ hơn 6-dep Contract service. MaDonTu format "DT/LR/2026/001". Gap #4 (Workflow Apps) PARTIAL done Travel/Vehicle mirror pending. Lesson: CWD drift (fe-user) ghi MEMORY nhầm path, em main relocate. Verify CWD root trước Write memory.
- **2026-05-30 (S43 P11-B Wave3 LeaveBalance):** +8 test `tests/.../Application/LeaveBalanceTests.cs` **152 PASS** (Infra 8694). Deduction hook (ApproveLeaveRequestHandler terminal) full: deduct single-level (create row from DaysPerYear), only-at-terminal multi-level (advance no-deduct + 1× terminal), accumulate UPSERT (5+2=7 no new row), negative allowed (Used20>Entitled12 → Remaining8 no throw), Reject+Return no-deduct (split 5a/5b), GetMyLeaveBalances lazy synth (2 active type filter inactive), AdjustLeaveBalance upsert. **⚠️ FOUND + FIXED 2 pre-existing RED** in S42 template (`Approve_LastLevel_TransitionsToDaDuyet` + `Approve_EmptyComment_StoresPlaceholder`): Wave 1 deduction hook (uncommitted, prod) làm terminal insert LeaveBalance FK→LeaveTypes Restrict FAIL vì BuildLeave dùng `LeaveTypeId=Guid.NewGuid()`. **NOT prod bug** (prod đơn luôn pin LeaveType thật) — fix tại test: BuildLeave +optional leaveTypeId, seed LeaveType ở 2 test đó. Baseline thật trước S43 = 142-pass/2-RED (KHÔNG phải 144-green). REPORTED em main.
- **2026-06-01 (S45 HRM coverage gaps + Holiday drift) [em main proxy]:** +27 test → **181 PASS** (Infra 96→123). 3 file: HrmConfigHolidayTests (7 — composite UNIQUE Create/Update, ⭐self-update giữ key đổi Name no-false-positive, soft-delete exclusion) + EmployeeSatelliteTests (10 — 5× FK-invariant parent `AnyAsync(!IsDeleted)` guard + soft-delete + cascade-non-behavior Case5 + EF model `DeleteBehavior.Cascade` config assertion) + AuthorizePolicyRegressionTests extend (10 — HrmConfigs bare-`[Authorize]`+writes `Roles=Admin`; Employees class-`Policy=Hrm_HoSo.Read`+per-action). **FOUND drift** (test theo CODE = single source): Holiday DB UNIQUE (Year,Date) unfiltered vs handler `!IsDeleted` → recreate-on-soft-deleted-slot `DbUpdateException(500)`. REPORTED → em main fixed Mig 43 `.HasFilter("[IsDeleted]=0")` (Case 7 flipped assert SUCCESS). New pattern: EF model-metadata assertion `db.Model.FindEntityType(typeof(X)).GetForeignKeys()...DeleteBehavior` lock schema intent. ⚠️ gotcha #57 backlog: LeaveType.Code + ShiftPattern.Code vẫn unfiltered.

View File

@ -2,7 +2,26 @@
> **Tiering rule (S40):** giữ **2-3 session gần nhất**. Cũ hơn → `docs/changelog/sessions/`. Full brief history pre-S40 → `docs/_archive/HANDOFF-preS40-fullhistory.md`.
**Last updated:** 2026-06-07 (Session 50**S49 Harness 1·2·3 verified-runtime closeout** (all 3 parts) · S49 adopt context: H1+H2 = 2 monitor sub TÁCH BIỆT (tooling-auditor + harvest-curator → roster 8→10) + wire session-start/end · H2 wave-folder isolation (hmw.js wave-mode + .gitignore B6) · H3 email channel `broadcasts/` (self=se). reviewer PASS all 3. **executed-file → S50 (2026-06-07) FULLY VERIFIED: (a) 2 monitor sub loaded+ran @session-start; (b) H2 wave-mode B6 isolation HELD (Run wf_b7e4d6ef-787); (c) H3 email send-path verified (AI_INFRA pull pending their step).** Prev: S48 adap verify closure; S47 adap channel install.)
**Last updated:** 2026-06-08 (Session 51**P11-C Vehicle+Driver catalogs (Mig 44) + gotcha #57 ext 3 HRM catalog (Mig 45)**, HMW-mode ON product feature, deployed prod Run #371. 7-agent fan-out, reviewer caught Driver FE↔BE required-field contract mismatch → fixed. test 181→186, tables 91→92, bundle admin `Cg9mvltU`/user `YgqDvsqr`. ⚠️ RAG/Qdrant DOWN cả session (file-based fallback). gotcha #57 EXT Master (Department/Supplier/Project, Mig 46) → separate worktree session. Prev S50: Harness 1·2·3 verified-runtime.)
---
## S51 (2026-06-08) — P11-C Vehicle+Driver catalogs + gotcha #57 ext (HMW-mode ON · product feature · deployed prod)
**User: `/session-start` → "P11-C Vehicle+Driver" → "làm đi / chạy luôn cho nhanh". HMW fan-out, ship to prod.**
**Done (commit `30a99aa` → Gitea Run #371 PASS ~4m18s, verified prod):**
- **Mig 44 `AddVehicleAndDriverCatalogs`** — extend HrmConfigs +2 declarative kind (Vehicle/Driver), 2 catalog table filtered-unique day-1. BE Region 5/6 CRUD + Controller +2 route (GET public/write Admin) + MenuKeys +All (auto perm) + DbInitializer idempotent seed. FE KIND_CONFIG +2 ×2 app SHA256 mirror + 4-place (`:kind`-driven, no new route). Tables 91→**92** (sys.tables ground-truth — narrative "93" was +1 drift).
- **Mig 45 `FilterHrmCatalogUniqueIndexesByIsDeleted`** (bundled gotcha #57) — LeaveType+ShiftPattern+**OtPolicy** filtered (3 HRM catalog; OtPolicy missed in "2 catalog" backlog, caught via grep). +5 test-before (181→**186** RED→GREEN).
- **reviewer caught 1 MAJOR** → fixed: Driver FE↔BE required-field mismatch (root = inconsistent em-main brief BE-required vs FE-optional).
- **cicd Run #371:** bundle admin `Cg9mvltU`/user `YgqDvsqr`, 5 idx filtered `([IsDeleted]=(0))` live, smoke 200, /hrm-configs/{vehicles,drivers} wired.
**🔴 NEXT SESSION:**
- **gotcha #57 EXT (worktree session đang chạy):** Department/Supplier/Project (Master) → Mig 46 (3 idx, confirmed-reachable). Merge khi xong — nó tạo Mig 46; main KHÔNG tạo migration nào nữa → no conflict, chỉ cần sequence đúng.
- **RAG/Qdrant DOWN:** restart `rag-infra.ps1 -Action ensure` (AI_INFRA repo) → verify `list_projects`. RAG re-index S42-S51 stale (AI_INFRA op).
- **AI_INFRA-side carry-over:** `/check-email se` pull S50 handshake.
- **Product (anh pick):** P11-D ItTicket SLA / P11-E AttendanceReport+OtPolicy multiplier / P11-F MaTicket / Phase 9 Ops.
- **Doc backlog (low):** STATUS/HANDOFF over-tiering (re-tier) · schema-diagram §16+ Mig 27-45 gap · agent baselines stale (reviewer 130-test/investigator counts → monthly 2026-07-01).
---

View File

@ -3,7 +3,7 @@
> **Update rule:** trước khi bắt đầu 1 task → ghi row `🔥 In Progress`. Xong → `✅ Recently Done`.
> **Tiering rule (S40):** chỉ giữ **state hiện tại + 3 session gần nhất** ở file này. Session cũ hơn → `docs/changelog/sessions/`. Full history pre-S40 → `docs/_archive/STATUS-preS40-fullhistory.md`. (Tránh over-context — xóa double, không cắt nội dung.)
**Last updated:** 2026-06-07 (Session 49**AI_INFRA Harness 1·2·3 adopt** (HMW-mode ON — recon fan-out 4 read-only agent @P2 + em main single-writer WRITE ~25 file, governance/infra no product code): H1+H2 = **2 monitor sub TÁCH BIỆT** (tooling-auditor + harvest-curator → roster 8→10) + session-start/end wire · H2 **wave-folder isolation** (hmw.js wave-mode + .gitignore B6 VERIFIED git-check-ignore) · H3 **email channel** `broadcasts/` (self=se, 6+6 folder + 2 cmd). **S50 (2026-06-07) verified-runtime: 2 monitor sub (tooling-auditor H1 + harvest-curator H2) CONFIRMED loaded+ran @session-start RE-REPORT** (test gate 181 PASS). **H2 wave-mode VERIFIED** (Run wf_b7e4d6ef-787: 2-agent wave, B6 isolation HELD — git status 6-baseline/chunk 2415 unchanged, B4 both-paths exercised) + **H3 email send-path VERIFIED** (handshake → outbox/ai_infra, body-hash self-verified; AI_INFRA /check-email se pull = their step). Prev S48: adap verify closure; S47 adap channel install.)
**Last updated:** 2026-06-08 (Session 51**P11-C Vehicle+Driver catalogs + gotcha #57 ext**, HMW-mode ON, product feature): Mig 44 `AddVehicleAndDriverCatalogs` (2 catalog table, filtered-unique day-1) + Mig 45 `FilterHrmCatalogUniqueIndexesByIsDeleted` (3 HRM catalog LeaveType/ShiftPattern/**OtPolicy** — OtPolicy missed in "2 catalog" backlog, caught via grep). 7-agent fan-out: investigator recon → BE∥FE parallel → test-before (RED→GREEN) → reviewer (caught Driver FE↔BE required-field contract mismatch → fixed) → cicd. **Commit `30a99aa` → Gitea Run #371 PASS, deployed prod** (test 181→186, tables 91→92, bundle admin `Cg9mvltU`/user `YgqDvsqr`, 5 idx filtered live). gotcha #57 EXT (Department/Supplier/Project Master, Mig 46) → separate worktree session. ⚠️ RAG/Qdrant DOWN cả session (file-based fallback). Prev S50: Harness 1·2·3 verified-runtime.)
---
@ -11,30 +11,30 @@
| Metric | Value | Note |
|---|---|---|
| Migrations | **43** | last `FilterHolidayUniqueIndexByIsDeleted` (20260601064128, S45) |
| SQL tables | **91** | +1 S43 LeaveBalances (verified prod, UNIQUE composite + FK LeaveTypes Restrict) |
| API endpoints | **~241** | +3 S43 (leave-balances my/admin/adjust) |
| FE pages | **67** | WorkflowAppDetailPage (admin+user SHA256 identical) |
| Menu keys | **~53** | BE `MenuKeys` const (FE menuKeys.ts mirror 54) |
| Tests | **181 PASS** | 58 Domain + 123 Infra · 0 fail / 0 skip · +27 HRM coverage S45 (Holiday/EmployeeSatellite/authz) |
| Gotchas | **57** | `docs/gotchas.md` (latest #57 soft-delete UNIQUE phải filter [IsDeleted]=0, S45) |
| User memory | **17** | +S50 monitor-residual-write-containment (E-006 lesson); H2 S50 cross-check 0 orphan/0 byte |
| Migrations | **45** | +S51 Mig 44 `AddVehicleAndDriverCatalogs` + Mig 45 `FilterHrmCatalogUniqueIndexesByIsDeleted` |
| SQL tables | **92** | +2 S51 Vehicles + Drivers (cicd `sys.tables` ground-truth; narrative "91→93" = +1 drift, real 90+2=92) |
| API endpoints | **~249** | +8 S51 (hrm-configs vehicles/drivers × GET/POST/PUT/DELETE) |
| FE pages | **67** | HrmConfigsPage `:kind`-driven (P11-C +2 kind, no new page) |
| Menu keys | **~55** | +2 S51 `Hrm_Config_Vehicles`/`Drivers` (BE `MenuKeys` const + FE mirror) |
| Tests | **186 PASS** | 58 Domain + 128 Infra · 0 fail / 0 skip · +5 S51 `HrmConfigFilteredUniqueTests` (Vehicle/Driver/LeaveType/Shift/OtPolicy) |
| Gotchas | **57** | #57 soft-delete UNIQUE filter — S51 EXTENDED 3 HRM catalog (Mig 45) + ext backlog 3 Master (worktree) |
| User memory | **17** | (unchanged S51 — product feature session) |
| Skills | 6 | 3 domain + 3 ops |
| Sub-agents | **10** | Opus 4.8 1M · 8 product/quality (7 core + frontend-designer) + **2 monitor INFORM-only** (tooling-auditor H1 + harvest-curator H2, S49 2026-06-07 Harness 1 — **verified-runtime CONFIRMED S50** both spawned+ran clean) |
| RAG chunks | **2415** | live (S50 verified ×2 via `list_projects`) · S41 baseline 2406 (3080→2406 674 junk) +9 store_memory since · last_indexed 2026-05-29 stale ~9d (AI_INFRA re-index op) |
| Sub-agents | **10** | Opus 4.8 1M · 8 product/quality (7 core + frontend-designer) + 2 monitor INFORM-only (tooling-auditor H1 + harvest-curator H2) |
| RAG chunks | **DOWN S51** | Qdrant 127.0.0.1:6333 refused cả session (`search_memory` ConnectError; `rag-infra.ps1 -Action ensure` = AI_INFRA op). S50 baseline 2415. SE file-based fallback. |
**Bundle hash live (prod):** admin `DPPTx2Kw` · user `CjoUEsoV` (Gitea Run #369, S48 — rotated from `Krjvg_3j`/`6sNStgxa` by login a11y fix). cicd-monitor PASS: test 181 · Mig stays 43 · health/smoke 200 ×4.
**Phase:** ✅ Phase 10 COMPLETE · 🔄 **Phase 11 IN PROGRESS** — P11-A + P11-B DONE (deployed prod) · ⬜ P11-C..F pending (P11-C Vehicle+Driver recon ready S45) · 🧪 S45 test-gap stabilization + Mig 43 Holiday fix shipped · 🚫 Phase 9 Ops blocked (anh main coordinate).
**Bundle hash live (prod):** admin `Cg9mvltU` · user `YgqDvsqr` (Gitea Run #371, S51 — rotated from `DPPTx2Kw`/`CjoUEsoV` by P11-C). cicd-monitor PASS: test 186 · Mig 44+45 applied prod (5 `IX_*_Code` filtered `[IsDeleted]=0`) · health/smoke 200 ×4 · /hrm-configs/{vehicles,drivers} wired.
**Phase:** ✅ Phase 10 COMPLETE · 🔄 **Phase 11 IN PROGRESS** — P11-A/B/**C** DONE (deployed prod) · ⬜ P11-D/E/F pending · 🚫 Phase 9 Ops blocked (anh main coordinate).
> ⚠️ **Count drift fixed S40:** endpoints ~223→**211**, FE pages 53→**65**, menu keys 85→**~53**. Tables **84 confirmed correct** (DbSet 77 + Identity 7). 3 số "khó fake" (mig/gotcha/git) luôn đúng. Cause: số "incremented mỗi session" over/under-count optimistic — re-ground định kỳ.
---
## 🔥 In Progress (S50)
## 🔥 In Progress (S51)
| Task | Owner | Status |
|---|---|---|
| _(none — S50 closed S49 infra-finish: Harness 1·2·3 **verified-runtime** (2 monitor sub + wave-mode B6 isolation + email send-path). **NEXT product (anh pick):** P11-C Vehicle+Driver (Mig 44, recon ready) / gotcha #57 LeaveType+Shift filtered-unique (coords ready: LeaveTypeConfiguration.cs:19 + ShiftPatternConfiguration.cs:19) / P11-D-F / Phase 9 Ops)_ | 👤 | ✅ |
| _(none — S51 closed P11-C Vehicle+Driver (Mig 44) + gotcha #57 ext-to-3-HRM-catalog (Mig 45), deployed prod Run #371. **NEXT product (anh pick):** P11-D ItTicket SLA / P11-E AttendanceReport+OtPolicy multiplier / P11-F MaTicket / Phase 9 Ops. gotcha #57 EXT Master (Mig 46) running in worktree session.)_ | 👤 | ✅ |
**S40 done:** ✅ Consolidation (`d2f52ba`) · ✅ Curate 4 agent MEMORY >25KB→<8.4KB (`78c9de3`) · RAG catch-up chunk S37-S40 (rerank 0.867) · **AI_INFRA bulletin 2026-05-29 adopt 4/4** (MỤC2 Tiered Memory Policy v1 `6f08d1f` + MỤC3 /session-start+/session-end slash commands `c8ff5e1`). Full RAG re-index = AI_INFRA op (cần VOYAGE_API_KEY).
@ -44,6 +44,14 @@
## ✅ Recently Done (newest on top — 3 session; cũ hơn → session logs)
### S51 (2026-06-08) — ✅ P11-C Vehicle+Driver catalogs (Mig 44) + gotcha #57 ext 3 HRM catalog (Mig 45) — HMW fan-out, deployed prod
- **Commit `30a99aa` Gitea Run #371 PASS ~4m18s, verified prod.** HMW-mode ON, 7-agent fan-out: 🟦 recon 🟨 BE 🟧 FE (parallel file-disjoint) 🟪 test-before 🟥 reviewer 🟩 cicd. RAG/Qdrant DOWN cả session ground-truth on-disk (đáng tin hơn stale RAG index 05-29).
- **Mig 44 `AddVehicleAndDriverCatalogs`** 2 catalog table (Vehicles + Drivers, `AuditableEntity`), filtered-unique Code day-1 (gotcha #57 từ đầu). Extend HrmConfigs declarative: BE Region 5/6 CRUD (mirror LeaveType) + Controller +2 route-group (8 endpoint, GET public / write Roles=Admin) + MenuKeys +2 +All (auto Admin perm) + DbInitializer idempotent seed 2 veh/2 drv. FE KIND_CONFIG +2 kind ×2 app (SHA256 mirror) + 4-place (types/page/menuKeys/Layout staticMap), `:kind`-driven (no new App.tsx route). Tables 91→**92** (cicd `sys.tables` ground-truth).
- **Mig 45 `FilterHrmCatalogUniqueIndexesByIsDeleted`** (bundled gotcha #57) LeaveType + ShiftPattern + **OtPolicy** bare `.IsUnique()` `.HasFilter("[IsDeleted]=0")`. **OtPolicy BỊ BỎ SÓT khỏi backlog "2 catalog" → em main bắt được khi grep toàn bộ config** (= 3 HRM catalog). test-before: +5 `HrmConfigFilteredUniqueTests` (181→**186**), REDGREEN observed.
- **🟥 reviewer caught 1 MAJOR** (pre-commit): Driver FEBE required-field mismatch (FE optional vs BE `NotEmpty`+NOT NULL) root = inconsistent em-main brief across BE/FE fixed (FE +`required:true`). Lesson: parallel fan-out shared-contract inconsistency chỉ lộ lúc integration; green tests correct contract.
- **🟩 cicd Run #371 PASS:** test 186 · Mig 44+45 prod · 5 `IX_*_Code` filtered `([IsDeleted]=(0))` live (3 HRM was NULL) · bundle rotate admin `Cg9mvltU`/user `YgqDvsqr` · health 200 · /hrm-configs/{vehicles,drivers} 401 unauth + 200 authed (2 seed each).
- **gotcha #57 EXT** (investigator audit complete): Department/Supplier/Project (Master, confirmed-reachable via global query-filter quirk) **worktree session** Mig 46 (3 idx). ContractClause/MeetingRoom/EmployeeProfile = SKIP (not reachable). session log `2026-06-08-S51-p11c-vehicle-driver-gotcha57.md`.
### S50 (2026-06-07) — ✅ S49 Harness 1·2·3 verified-runtime closeout (HMW-mode ON · infra-finish, no product code, CI-skip)
- **`/session-start` bootstrap** spawned **2 monitor sub** (tooling-auditor H1 + harvest-curator H2) RE-REPORT **verified-runtime CONFIRMED** (S49 milestone (a) closed). H1 caught 3 doc-freshness drifts em main patched (plugin 1518 · skill-index 3143 mig + 4957 gotcha · verified-runtime markers). H2 harvest 🟢 clean (0-byte/orphan/corruption=0). Test gate **181 PASS**, RAG 2415.
- **(b) H2 wave-mode VERIFIED** wave-run `h2-verify` (Workflow Run `wf_b7e4d6ef-787`, 2-agent: investigator-codebase read-only + test-specialist write-direct). **B6 isolation HELD**: git status = 6-baseline only (0 canonical/agent-memory leak), sub-MDs gitignored, **chunk 2415→2415** (0 rogue RAG write). B4 both-paths exercised. Bonus: gotcha #57 confirmed open + exact coords (`LeaveTypeConfiguration.cs:19` + `ShiftPatternConfiguration.cs:19` vs fixed `HolidayConfiguration.cs:18`).
@ -136,13 +144,14 @@
## 🎯 Next up
### 🔄 Phase 11 — Polish/wire skeleton (IN PROGRESS)
- **P11-A** wire ApproveV2 + LevelOpinions 4 module (Leave/OT/Travel/Vehicle) **DONE S42** (Run #250, deployed prod). CodeGen MaDonTu cho 4 module ĐÃ gộp luôn (WorkflowAppCodeSequences) P11-F phần ItTicket MaTicket còn lại.
- **P11-B** LeaveBalance business logic **DONE S43** (Mig 42, trừ phép terminal + số + FK guard, Gitea #367 prod).
- **P11-C** Vehicle+Driver catalog Mig 43 · **P11-D** ItTicket auto-assign + SLA timer · **P11-E** AttendanceReport + Excel + OtPolicy multiplier · **P11-F** CodeGen atomic MaTicket (MaDonTu xong S42)
- **P11-A** wire ApproveV2 + LevelOpinions 4 module **DONE S42** (Run #250, prod).
- **P11-B** LeaveBalance business logic **DONE S43** (Mig 42, prod).
- **P11-C** Vehicle+Driver catalog **DONE S51** (Mig 44, Run #371, prod). HrmConfigs +2 kind declarative; VehicleBooking giữ free-text (FK link defer).
- **P11-D** ItTicket auto-assign + SLA timer · **P11-E** AttendanceReport + Excel + OtPolicy multiplier · **P11-F** CodeGen atomic MaTicket (MaDonTu xong S42)
### 🔧 Maintenance backlog
- RAG re-ingest `solution_erp` S42-S43 content (store_memory stopgap live; full re-index = AI_INFRA op)
- **Test coverage gaps Gap1/2/3 DONE S45** (+27 test). NEW backlog (gotcha #57): **LeaveType.Code + ShiftPattern.Code UNIQUE chưa filter `[IsDeleted]=0`** (cùng class Holiday Mig 43 recreate-on-soft-deleted-slot 500; test-before khi fix). **Wave-verified S50 (exact coords):** `LeaveTypeConfiguration.cs:19` + `ShiftPatternConfiguration.cs:19` bare `.IsUnique()` vs fixed `HolidayConfiguration.cs:18 .HasFilter("[IsDeleted] = 0")`; test template = `HrmConfigHolidayTests.cs:180-197` (Case 7). Cân nhắc gộp vào P11-C khi đụng HrmConfigs.
- **gotcha #57 LeaveType+ShiftPattern+OtPolicy filtered-unique DONE S51** (Mig 45, **3 HRM catalog** OtPolicy bị bỏ sót khỏi backlog "2 catalog", bắt được via grep + test-before 5 case). **EXT (worktree session, Mig 46):** Department/Supplier/Project (Master, confirmed-reachable via global query-filter quirk); ContractClause/MeetingRoom/EmployeeProfile = SKIP (audit-verified not-reachable).
- Skill + doc drift audit cron **2026-06-01 DONE (S44)** 42 count-drift fixes + AI_INFRA bundle adopt; next **2026-07-01**
### 🚫 Phase 9 Ops (blocked — anh main coordinate)

View File

@ -0,0 +1,34 @@
# Session 51 (2026-06-08) — P11-C Vehicle+Driver catalogs + gotcha #57 ext
> HMW-mode ON · product feature · deployed prod. Commit `30a99aa` → Gitea Run #371 PASS ~4m18s.
## Bối cảnh
- `/session-start` bootstrap → **RAG/Qdrant DOWN** phát hiện ngay (search_memory `ConnectError 127.0.0.1:6333`; `rag-infra.ps1 -Action ensure` = AI_INFRA-repo op). Anh chọn "bỏ qua, làm product" → chạy file-based fallback cả session (ground-truth on-disk còn đáng tin hơn RAG index stale 05-29).
- Anh chọn task **P11-C Vehicle+Driver** + `/ultra-on` (HMW consent) + "làm đi / chạy luôn cho nhanh".
## Quy trình HMW (7-agent fan-out)
1. **Recon** — investigator background treo lâu → em main fallback tự recon on-disk: HrmConfigs = 4 catalog RIÊNG (LeaveType/Holiday/ShiftPattern/OtPolicy), KHÔNG discriminated; FE `:kind`-driven KIND_CONFIG Record. `TestApplicationDbContext` inherit `ApplicationDbContext` → +DbSet không cần sửa tests/. `VehicleBooking` free-text (VehicleLicense/VehicleName/DriverName), comment "defer catalog Phase 11".
2. **Schema chốt (em main solo)** — Mig 44: Vehicle {Code/Name/LicensePlate/SeatCount} + Driver {Code/Name/PhoneNumber/LicenseNumber/LicenseClass}, filtered-unique Code **từ đầu** (tránh gotcha #57).
3. **Wave 1 parallel (file-disjoint):** 🟨 implementer-backend (Mig 44 + 2 entity + 2 config + DbSet + HrmConfigFeatures Region 5/6 + Controller +2 route + MenuKeys + DbInitializer seed) ∥ 🟧 implementer-frontend (KIND_CONFIG +2 + 4-place mirror ×2 app, SHA256 identical). Build 0 err.
4. **gotcha #57 bundled (Mig 45):** test-specialist test-before → **2 RED observed** (LeaveType+ShiftPattern bare `.IsUnique()``SQLite Error 19`). Em main grep TOÀN BỘ config → **OtPolicy cũng bare** (bị bỏ sót khỏi backlog "2 catalog"!) → fix cả **3** config + Mig 45 + thêm OtPolicy test. 181→**186** all GREEN.
5. **🟥 reviewer (pre-commit) caught 1 MAJOR:** Driver FE↔BE required-field mismatch — FE render phoneNumber/licenseNumber/licenseClass OPTIONAL nhưng BE validator `NotEmpty()` + EF `.IsRequired()` NOT NULL → empty submit 400/500. Root = **inconsistent em-main brief** (BE "mirror Vehicle"=required vs FE spec quên required). Fix: FE +`required:true` (align all-required như Vehicle). Other cats clean.
6. **Commit `30a99aa` + push → 🟩 cicd Run #371 PASS.**
## Kết quả (verified prod)
- Mig 44+45 trong `__EFMigrationsHistory`; Vehicles+Drivers tables EXIST; tables 91→**92** (cicd `sys.tables` ground-truth — narrative "93" = +1 drift cũ).
- **gotcha #57 proof:** 5 `IX_*_Code` filtered `([IsDeleted]=(0))` (Vehicle/Driver/LeaveType/Shift/OtPolicy) — 3 HRM was NULL pre-Mig 45.
- Bundle rotate admin `DPPTx2Kw→Cg9mvltU` / user `CjoUEsoV→YgqDvsqr`. Smoke 200; /hrm-configs/{vehicles,drivers} 401 unauth + 200 authed (2 seed each).
- Test 186 (58 Domain + 128 Infra).
## gotcha #57 EXTENSION (→ worktree session)
Investigator audit (completed before kill, MEMORY line 73): **FIX 3 Master** (Department/Supplier/Project — confirmed-reachable: global `HasQueryFilter(!IsDeleted)` auto-ẩn soft-deleted khỏi Create check → check PASS → unfiltered index 500; nghịch lý global filter LÀM lộ bug). **SKIP 3**: ContractClause (no CRUD handler), MeetingRoom (Delete=IsActive not IsDeleted), EmployeeProfile (Create chặn reuse "Cần khôi phục"). **Mig 46 = 3 idx.** Anh click chip → spawn worktree session riêng (isolated) own task này; main session bỏ ext (tránh Mig 46 conflict).
## Lessons
- **Parallel fan-out contract risk:** BE∥FE file-disjoint chạy độc lập theo brief → bất kỳ inconsistency trong SHARED em-main contract chỉ lộ lúc integration. Green tests ≠ correct contract (no test chạm empty-optional path). reviewer = the net (caught it). Brief cross-stack PHẢI nhất quán required/optional 2 đầu.
- **Thoroughness grep > backlog trust:** backlog ghi "2 catalog" nhưng grep toàn bộ ra 3 (OtPolicy sót). Đừng tin count backlog — verify từ code.
- **Truncation recurrence (gotcha #53):** BE return + reviewer return + (audit) đều truncate cuối → em main ground-truth on-disk (git status/diff + read) + proxy reviewer MEMORY.
- **cicd anti-pattern #3:** transient mid-deploy bundle hash (CVbyotwa/BBlyMlJH) — KHÔNG trust tới status=success.
- **RAG MCP client reconnect ≠ Qdrant up:** client lên lại nhưng backend down → vẫn ConnectError.
## Tags
`[s51, p11-c, mig44, mig45, vehicle-driver, gotcha57, hmw-fanout, contract-mismatch-catch, rag-down]`

View File

@ -1044,7 +1044,7 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
---
### 57. Soft-delete entity + UNIQUE index PHẢI filter `[IsDeleted] = 0` (Session 45)
### 57. Soft-delete entity + UNIQUE index PHẢI filter `[IsDeleted] = 0` (Session 45, ext S51)
**Triệu chứng:** Entity soft-delete (AuditableEntity) có UNIQUE index trên business key (Code / composite). Handler check trùng đã loại soft-deleted (`AnyAsync(x => x.Key == k && !x.IsDeleted)`) → định cho phép reuse slot. NHƯNG nếu DB UNIQUE index KHÔNG filter → xoá (soft) 1 row rồi tạo lại cùng key → handler PASS app-check nhưng `SaveChangesAsync` ném `DbUpdateException` (SQL Server 2627 / SQLite Error 19) → HTTP **500** (không phải Conflict sạch hay insert OK). Reachable thật: admin xoá nhầm 1 ngày lễ / mã catalog rồi nhập lại đúng.
@ -1052,7 +1052,9 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
**Fix:** EF config filtered index — `e.HasIndex(x => x.Key).IsUnique().HasFilter("[IsDeleted] = 0")` (composite: `new { x.A, x.B }`). Migration DropIndex + CreateIndex(filter). SQL Server + SQLite test đều honor (bracket-quote + partial index OK).
**Đã áp 13× sẵn:** Catalogs ×4, Contract/PE/Proposal/Budget/WorkflowApps code-unique. **Fixed S45:** Holiday `(Year,Date)` Mig 43. ⚠️ **CÒN unfiltered (backlog test-before):** `LeaveTypeConfiguration.cs` Code + `ShiftPatternConfiguration.cs` Code — cùng bug class.
**Đã áp sẵn:** Catalogs ×4, Contract/PE/Proposal/Budget/WorkflowApps code-unique. **Fixed S45:** Holiday `(Year,Date)` Mig 43. **Fixed S51 (Mig 45 `FilterHrmCatalogUniqueIndexesByIsDeleted`):** LeaveType + ShiftPattern + **OtPolicy** Code = **3 HRM catalog** (OtPolicy bị BỎ SÓT khỏi backlog "2 catalog" — bắt được khi grep TOÀN BỘ config). Vehicle/Driver (Mig 44) filtered day-1. ✅ test-before `HrmConfigFilteredUniqueTests.cs` (5 case, RED→GREEN).
⚠️ **EXT backlog (worktree session S51, Mig 46):** **FIX 3 (Master)** `Department`/`Supplier`/`Project` Code — CONFIRMED-reachable: AuditableEntity + GLOBAL `HasQueryFilter(!IsDeleted)` auto-ẩn soft-deleted khỏi Create check → check PASS → unfiltered index ném 500 (nghịch lý: global filter LÀM lộ bug, ngược HRM cần manual `!IsDeleted`). **SKIP 3** (audit-verified KHÔNG reachable): ContractClause (no CRUD handler — chỉ DbSet), MeetingRoom (Delete set `IsActive=false` NOT IsDeleted), EmployeeProfile (Create chặn reuse by-design — UserId ConflictException "Cần khôi phục" + EmployeeCode auto-gen atomic). Mọi bare-unique khác = composite junction / nullable-code đã `IS NOT NULL` filter / no-soft-delete.
**References:** Mig 43 `FilterHolidayUniqueIndexByIsDeleted` · `HolidayConfiguration.cs` · `HrmConfigHolidayTests.cs` Case 7 · surfaced bởi test-specialist Gap1 S45.