fix: resolve repo-wide bug with missing HRM and Manufacturing tables in EF model

- Identified and fixed a critical issue where 36 tables existed in the EF model but were absent in the actual database.
- Root cause traced to a `.gitignore` rule that prevented migration files from being tracked, leading to discrepancies between the model snapshot and the database.
- Removed problematic entities from the snapshot, generated a migration to add the missing tables, and confirmed successful application.
- Reverted the `.gitignore` rule to ensure future migrations are tracked properly.
- No changes to existing tables, ensuring no collateral schema drift occurred.
This commit is contained in:
2026-07-31 18:57:32 +05:30
parent 22657f0910
commit a271d1832e
14 changed files with 31023 additions and 5 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,138 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace ERPCore.Infra.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddLedgersNavSeed : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "nav_items",
columns: new[] { "NavItemId", "Code", "Href", "Icon", "Label", "SortOrder" },
values: new object[] { 11, "ledgers", "/dashboard/ledgers", null, "Ledgers", 11 });
migrationBuilder.InsertData(
table: "permissions",
columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" },
values: new object[] { 19, "NAV:ledgers", 11, null });
migrationBuilder.InsertData(
table: "sub_nav_items",
columns: new[] { "SubNavItemId", "Code", "Href", "Icon", "Label", "NavItemId", "SortOrder" },
values: new object[,]
{
{ 9, "ledgers.trial-balance", "/dashboard/ledgers/trial-balance", null, "Trial Balance", 11, 1 },
{ 10, "ledgers.balance-sheet", "/dashboard/ledgers/balance-sheet", null, "Balance Sheet", 11, 2 },
{ 11, "ledgers.general-ledger", "/dashboard/ledgers/general-ledger", null, "General Ledger", 11, 3 },
{ 12, "ledgers.profit-and-loss", "/dashboard/ledgers/profit-and-loss", null, "Profit & Loss", 11, 4 },
{ 13, "ledgers.cash-flow", "/dashboard/ledgers/cash-flow", null, "Cash Flow", 11, 5 },
{ 14, "ledgers.budget-vs-actual", "/dashboard/ledgers/budget-vs-actual", null, "Budget vs Actual", 11, 6 },
{ 15, "ledgers.bank-accounts", "/dashboard/ledgers/bank-accounts", null, "Cash / Bank Accounts", 11, 7 }
});
migrationBuilder.InsertData(
table: "permissions",
columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" },
values: new object[,]
{
{ 20, "NAV:ledgers.trial-balance", null, 9 },
{ 21, "NAV:ledgers.balance-sheet", null, 10 },
{ 22, "NAV:ledgers.general-ledger", null, 11 },
{ 23, "NAV:ledgers.profit-and-loss", null, 12 },
{ 24, "NAV:ledgers.cash-flow", null, 13 },
{ 25, "NAV:ledgers.budget-vs-actual", null, 14 },
{ 26, "NAV:ledgers.bank-accounts", null, 15 }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 19);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 20);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 21);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 22);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 23);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 24);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 25);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 26);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 9);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 10);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 11);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 12);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 13);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 14);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 15);
migrationBuilder.DeleteData(
table: "nav_items",
keyColumn: "NavItemId",
keyValue: 11);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ERPCore.Infra.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddTaxReportNavSeed : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 15,
column: "SortOrder",
value: 8);
migrationBuilder.InsertData(
table: "sub_nav_items",
columns: new[] { "SubNavItemId", "Code", "Href", "Icon", "Label", "NavItemId", "SortOrder" },
values: new object[] { 16, "ledgers.tax-report", "/dashboard/ledgers/tax-report", null, "Tax Report", 11, 7 });
migrationBuilder.InsertData(
table: "permissions",
columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" },
values: new object[] { 27, "NAV:ledgers.tax-report", null, 16 });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 27);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 16);
migrationBuilder.UpdateData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 15,
column: "SortOrder",
value: 7);
}
}
}
@@ -0,0 +1,82 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace ERPCore.Infra.Persistence.Migrations
{
/// <inheritdoc />
public partial class FixProcurementNavIdCollision : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "sub_nav_items",
columns: new[] { "SubNavItemId", "Code", "Href", "Icon", "Label", "NavItemId", "SortOrder" },
values: new object[,]
{
{ 17, "procurement.requisitions", "/dashboard/procurement/requisitions", null, "Requisitions", 4, 1 },
{ 18, "procurement.rfqs", "/dashboard/procurement/rfqs", null, "RFQs", 4, 2 },
{ 19, "procurement.purchase-orders", "/dashboard/procurement/purchase-orders", null, "Purchase Orders", 4, 3 },
{ 20, "procurement.purchase-returns", "/dashboard/procurement/purchase-returns", null, "Purchase Returns", 4, 4 }
});
migrationBuilder.InsertData(
table: "permissions",
columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" },
values: new object[,]
{
{ 28, "NAV:procurement.requisitions", null, 17 },
{ 29, "NAV:procurement.rfqs", null, 18 },
{ 30, "NAV:procurement.purchase-orders", null, 19 },
{ 31, "NAV:procurement.purchase-returns", null, 20 }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 28);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 29);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 30);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 31);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 17);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 18);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 19);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 20);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,106 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace ERPCore.Infra.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddAccountsNavSeed : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "nav_items",
columns: new[] { "NavItemId", "Code", "Href", "Icon", "Label", "SortOrder" },
values: new object[] { 12, "accounts", "/dashboard/accounts", null, "Accounts", 12 });
migrationBuilder.UpdateData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 26,
column: "Code",
value: "NAV:accounts.bank-accounts");
migrationBuilder.UpdateData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 15,
columns: new[] { "Code", "Href", "NavItemId", "SortOrder" },
values: new object[] { "accounts.bank-accounts", "/dashboard/accounts/bank-accounts", 12, 1 });
migrationBuilder.InsertData(
table: "permissions",
columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" },
values: new object[] { 32, "NAV:accounts", 12, null });
migrationBuilder.InsertData(
table: "sub_nav_items",
columns: new[] { "SubNavItemId", "Code", "Href", "Icon", "Label", "NavItemId", "SortOrder" },
values: new object[,]
{
{ 21, "accounts.cheque-books", "/dashboard/accounts/cheque-books", null, "Cheque Books", 12, 2 },
{ 22, "accounts.received-cheques", "/dashboard/accounts/received-cheques", null, "Received Cheques", 12, 3 }
});
migrationBuilder.InsertData(
table: "permissions",
columns: new[] { "PermissionId", "Code", "NavItemId", "SubNavItemId" },
values: new object[,]
{
{ 33, "NAV:accounts.cheque-books", null, 21 },
{ 34, "NAV:accounts.received-cheques", null, 22 }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 32);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 33);
migrationBuilder.DeleteData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 34);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 21);
migrationBuilder.DeleteData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 22);
migrationBuilder.DeleteData(
table: "nav_items",
keyColumn: "NavItemId",
keyValue: 12);
migrationBuilder.UpdateData(
table: "permissions",
keyColumn: "PermissionId",
keyValue: 26,
column: "Code",
value: "NAV:ledgers.bank-accounts");
migrationBuilder.UpdateData(
table: "sub_nav_items",
keyColumn: "SubNavItemId",
keyValue: 15,
columns: new[] { "Code", "Href", "NavItemId", "SortOrder" },
values: new object[] { "ledgers.bank-accounts", "/dashboard/ledgers/bank-accounts", 11, 8 });
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ERPCore.Infra.Persistence.Migrations
{
/// <inheritdoc />
public partial class production : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
+13
View File
@@ -130,6 +130,19 @@ Convention: `docs/01-DOC-GUIDE.md §6`. Update this file in the **same commit**
> **No locked-process issue avoided this time** — `ERPCore.exe` was found running twice during this pass (the user had restarted it between turns to test the Tax Report fix); confirmed with the user before killing it each time, per this session's standing caution around stopping their dev server. `dotnet build` clean (0 warnings/0 errors); `dotnet ef migrations list` shows all 8 migrations with none pending. **Applied to the live database this session** (`dotnet ef database update`) — unlike every prior nav-seed migration this session, this one did not have to wait for a live Postgres instance to become available.
> **Operational step still needed post-deploy (not code):** same as every previous nav addition — an administrator must grant the new `NAV:accounts`/`NAV:accounts.cheque-books`/`NAV:accounts.received-cheques` permissions to the relevant role(s) via **Settings → Roles** before anyone sees the new sidebar entries (the re-homed `NAV:accounts.bank-accounts` keeps whatever grants it already had).
> ### 2026-07-31 (2) — Root-caused and fixed a repo-wide bug: 36 tables (all of HRM + all of Manufacturing) existed in the EF model but not in the actual database, and no `dotnet ef migrations add` could ever surface it
> **User-reported:** after rebasing `feat/general-ledger-service` onto `origin/Dev`, some tables from the other branch weren't being created by `migrations add` + `database update`. Ground-truthed against the live Postgres instance (queried `pg_tables`/`__EFMigrationsHistory` directly, since EF's own diff tooling only ever compares the compiled model against `ErpDbContextModelSnapshot.cs` — never the real database — so it's structurally blind to this class of bug): the database had 45 tables; the current model/snapshot expects 80. All 25 `hr_*` tables and all 11 `production_runs`/`production_templates`/`run_*`/`stage_*`/`template_stages` tables were completely absent, despite `ErpDbContext`/`Infra/Persistence/Configurations` fully describing them and `ErpDbContextModelSnapshot.cs` already listing them.
> **Root cause: a `.gitignore` rule (`**/Migrations/`, added early on to stop *new* EF migrations from being committed) combined disastrously with `ErpDbContextModelSnapshot.cs` staying tracked** (`.gitignore` doesn't retroactively untrack already-tracked files, and the snapshot was one of the original 4 tracked migrations). Every `dotnet ef migrations add` after that point updated the snapshot (which **did** get committed normally, since it was already tracked) but wrote its actual migration `.cs`/`.Designer.cs` pair as new, gitignored, never-committed files. Confirmed via `git show --stat` on every historical commit touching the snapshot: several — including the commit that added the entire HRM module and the one that added Manufacturing (`7d6e597`) — show large snapshot insertions with **zero** migration files in the same commit. Net effect: the snapshot has been silently lying about the applied-migration history for a long time; `dotnet ef migrations add` never detects a "missing" table because, as far as the (already-tracked, already-correct-looking) snapshot is concerned, nothing has changed — the actual `CreateTable` migration simply never existed anywhere in git, on any machine that didn't happen to still have it sitting locally, ungitignored-but-untracked.
> **Fix, in order:**
> 1. Confirmed the exact 36-table gap by comparing `pg_tables` against every `b.ToTable(...)` call in the snapshot (script, not archaeology — this is the only way to get ground truth once the snapshot itself is suspect).
> 2. Temporarily removed just those 36 entities' blocks from `ErpDbContextModelSnapshot.cs` (verified 23 balanced-brace occurrences per entity removed cleanly, nothing else touched), so `dotnet ef migrations add` would have something real to diff against.
> 3. Generated **`AddMissingHrmAndManufacturingTables`** — verified its `Up()` contains exactly 36 `CreateTable` calls (matching the missing-table list precisely, no more/fewer) and its `Down()` exactly 36 matching `DropTable` calls; no `AlterColumn`/`DropColumn`/`RenameColumn` against any pre-existing table, confirming this was a pure addition with zero collateral schema drift.
> 4. Applied it (`dotnet ef database update`); re-queried `pg_tables` live — all 81 tables (80 + `__EFMigrationsHistory`) now present. Confirmed fully settled by scaffolding one more throwaway migration afterward and checking it came back empty (no remaining model/snapshot drift), then removing it.
> 5. **Fixed the actual root cause, not just this one symptom:** reverted the `.gitignore` rule — EF Core migrations are now tracked like any other source file, so this can't recur the same way. Every migration created since the rule was added (`AddLedgersNavSeed`, `AddTaxReportNavSeed`, `FixProcurementNavIdCollision`, `AddAccountsNavSeed`, the empty `production` migration, and this pass's `AddMissingHrmAndManufacturingTables`) was sitting on disk ungitignored-but-uncommitted the whole time — now staged to actually join the repo.
> **Verified:** `dotnet build` clean (0 errors, pre-existing `CS8981` naming warning on the already-present `production` migration class only); `dotnet ef migrations list` shows all 10 migrations, none pending. **A locally running `ERPCore.exe` had to be stopped mid-session (user's explicit approval obtained first) to free the build lock**, same recurring issue as every previous migration pass this week.
> **Left as-is, deliberately:** the empty `production` migration (`20260731123720_production.cs`) — it's a harmless no-op (it was the user's own prior attempt to fix this exact bug, which came back empty for the reason explained above) and renaming/removing it now would just be churn; the real fix landed in the next migration.
> **Action needed from the user:** the `.gitignore` fix means these migration files are no longer excluded, but nothing has been `git add`ed or committed yet — per standing instruction, commits only happen when explicitly asked.
## Deferred (Phase 2+ — do NOT build now, hooks only)
- [ ] Vendor invoice + three-way match
- [ ] Reservation/allocation fulfilment