Seed and self-heal Gift Certificate Liability account 2500 (audit O5)
Account 2500 is resolved by number as the GC liability (GiftCertificatesController),
but the per-tenant seeder never created it — so tenants onboarded after the
AccountingGapsPhase2 migration had no GC liability account and gift-certificate GL
postings silently no-op'd. The default-company seeder also created 2500 as
"Long-Term Loan", mislabeling that company's GC obligations.
- SeedDataService.Accounts: seed 2500 "Gift Certificate Liability" (IsSystem)
- SeedData: seed 2500 as GC liability; move long-term loan to 2900
- EnsureSystemAccountsAsync: self-heal — rename a 2500 still named "Long-Term Loan"
(preserving user renames) and ensure a 2500 exists
- migration FixGiftCertificateLiabilityAccount: move long-term loan to 2900 where a
2500="Long-Term Loan" exists without a 2900, relabel the mislabeled 2500, and
safety-net insert a 2500 for any company lacking one
Non-destructive: no account Id/number/balance is changed (same pattern as O1).
Verified on dev: existing GC-liability rows preserved, no spurious accounts added.
All audit findings O1-O5 resolved. Build clean; 291 unit tests pass; migration applied.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+123
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PowderCoating.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixGiftCertificateLiabilityAccount : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// O5 remediation. Account 2500 is resolved by number as the Gift Certificate Liability
|
||||
// (GiftCertificatesController), but the default-company chart seeded it as "Long-Term Loan",
|
||||
// so GC obligations were mislabeled there and the AccountingGapsPhase2 GC-liability seed was
|
||||
// skipped by its NOT EXISTS guard.
|
||||
|
||||
// 1) Preserve the long-term loan account: move it to 2900 for any company whose 2500 is still
|
||||
// named "Long-Term Loan" and that lacks a 2900. (Companies onboarded via the per-tenant
|
||||
// seeder already have a 2900 "Business Loan", so the NOT EXISTS guard leaves them alone.)
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO Accounts
|
||||
(AccountNumber, Name, AccountType, AccountSubType,
|
||||
IsSystem, IsActive, Description,
|
||||
CompanyId, CreatedAt, IsDeleted, CurrentBalance, OpeningBalance)
|
||||
SELECT '2900', 'Long-Term Loan',
|
||||
2, -- AccountType.Liability
|
||||
11, -- AccountSubType.LongTermLiability
|
||||
0, 1, 'Long-term equipment or business loan',
|
||||
c.Id, GETUTCDATE(), 0, 0, 0
|
||||
FROM Companies c
|
||||
WHERE c.IsDeleted = 0
|
||||
AND EXISTS (SELECT 1 FROM Accounts a WHERE a.CompanyId = c.Id
|
||||
AND a.AccountNumber = '2500' AND a.IsDeleted = 0 AND a.Name = 'Long-Term Loan')
|
||||
AND NOT EXISTS (SELECT 1 FROM Accounts a WHERE a.CompanyId = c.Id
|
||||
AND a.AccountNumber = '2900' AND a.IsDeleted = 0);
|
||||
");
|
||||
|
||||
// 2) Relabel the mislabeled 2500 to Gift Certificate Liability (only where it still carries the
|
||||
// old default name, so a user's own rename is preserved). Id / number / balance untouched.
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE Accounts
|
||||
SET Name = 'Gift Certificate Liability',
|
||||
Description = 'Outstanding gift certificate obligations owed to certificate holders',
|
||||
IsSystem = 1
|
||||
WHERE AccountNumber = '2500' AND IsDeleted = 0 AND Name = 'Long-Term Loan';
|
||||
");
|
||||
|
||||
// 3) Safety net: ensure every company has a 2500 Gift Certificate Liability (covers any tenant
|
||||
// onboarded after AccountingGapsPhase2 ran that never received one — without it GC GL no-ops).
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO Accounts
|
||||
(AccountNumber, Name, AccountType, AccountSubType,
|
||||
IsSystem, IsActive, Description,
|
||||
CompanyId, CreatedAt, IsDeleted, CurrentBalance, OpeningBalance)
|
||||
SELECT '2500', 'Gift Certificate Liability',
|
||||
2, -- AccountType.Liability
|
||||
12, -- AccountSubType.OtherCurrentLiability
|
||||
1, 1, 'Outstanding gift certificate obligations owed to certificate holders',
|
||||
c.Id, GETUTCDATE(), 0, 0, 0
|
||||
FROM Companies c
|
||||
WHERE c.IsDeleted = 0
|
||||
AND NOT EXISTS (SELECT 1 FROM Accounts a WHERE a.CompanyId = c.Id
|
||||
AND a.AccountNumber = '2500' AND a.IsDeleted = 0);
|
||||
");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 1,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 20, 0, 29, 46, 909, DateTimeKind.Utc).AddTicks(3976));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 2,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 20, 0, 29, 46, 909, DateTimeKind.Utc).AddTicks(3981));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 3,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 20, 0, 29, 46, 909, DateTimeKind.Utc).AddTicks(3982));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Best-effort reversal: the 2500 relabel is intentionally NOT undone (reverting would
|
||||
// re-introduce the mislabel), and 2500 rows are left in place since most pre-date this
|
||||
// migration. Only soft-delete the empty 2900 accounts this migration added.
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE Accounts SET IsDeleted = 1
|
||||
WHERE AccountNumber = '2900' AND IsDeleted = 0 AND Name = 'Long-Term Loan' AND CurrentBalance = 0;
|
||||
");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 1,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 19, 23, 31, 4, 905, DateTimeKind.Utc).AddTicks(7611));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 2,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 19, 23, 31, 4, 905, DateTimeKind.Utc).AddTicks(7618));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 3,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 19, 23, 31, 4, 905, DateTimeKind.Utc).AddTicks(7619));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user