{"record":{"id":"b4a106ef25670293","repo":"fullstackhero/dotnet-starter-kit","slug":"plan-command-planid-not-found","errorCode":null,"errorMessage":"Plan {command.PlanId} not found.","messagePattern":"Plan (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Billing/Modules.Billing/Features/v1/Plans/UpdatePlan/UpdatePlanCommandHandler.cs","lineNumber":17,"sourceCode":"using FSH.Framework.Core.Exceptions;\nusing FSH.Modules.Billing.Contracts.v1.Plans;\nusing FSH.Modules.Billing.Data;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Billing.Features.v1.Plans.UpdatePlan;\n\npublic sealed class UpdatePlanCommandHandler(BillingDbContext dbContext)\n    : ICommandHandler<UpdatePlanCommand, Guid>\n{\n    public async ValueTask<Guid> Handle(UpdatePlanCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var plan = await dbContext.Plans.FirstOrDefaultAsync(p => p.Id == command.PlanId, cancellationToken).ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Plan {command.PlanId} not found.\");\n\n        plan.Update(command.Name, command.MonthlyBasePrice, command.OverageRates, command.Interval, command.AnnualPrice);\n        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return plan.Id;\n    }\n}\n","sourceCodeStart":1,"sourceCodeEnd":24,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Billing/Modules.Billing/Features/v1/Plans/UpdatePlan/UpdatePlanCommandHandler.cs#L1-L24","documentation":"UpdatePlanCommandHandler fetches the plan by PlanId before applying updates; if no plan row with that id exists it throws NotFoundException(\"Plan {id} not found.\"). Unlike invoice handlers this one is id-based and unscoped, so a miss means the id truly isn't in the Plans table.","triggerScenarios":"PUT /plans/{id} (v1) with a nonexistent plan GUID, an id from a different database/environment, an id of another aggregate (e.g. subscription id pasted into the plan form), or a plan deleted before the update landed.","commonSituations":"Stale admin UI holding a plan id from a previous seed run (re-seeding regenerated ids); copying a subscription or tenant id into the plan edit dialog; concurrent cleanup job removed the plan while an editor was open.","solutions":["Verify the id exists: SELECT * FROM \"Plans\" WHERE \"Id\" = '<id>'; if absent, re-list plans and use a current id.","Refresh the admin plans page so the form uses ids from the live database rather than a stale cache.","Re-seed the environment if plan ids changed (DbMigrator apply --seed) and update any stored references.","Double-check you are editing the right aggregate — plan ids differ from subscription ids."],"exampleFix":"// before: stale id after reseed\nawait apiFetch(`/plans/${cachedPlanId}`, { method: 'PUT', body });\n\n// after: resolve fresh id by key\nconst plans = await apiFetch('/plans');\nconst id = plans.find(p => p.key === 'pro').id;\nawait apiFetch(`/plans/${id}`, { method: 'PUT', body });","handlingStrategy":"validation","validationCode":"const plans = await apiFetch('/plans');\nif (!plans.some(p => p.id === planId)) throw new Error(`Plan ${planId} does not exist; refresh the list`);","typeGuard":"function planExists(plans, id) { return plans.some(p => p.id === id); }","tryCatchPattern":"try { await apiFetch(`/plans/${planId}`, { method: 'PUT', body }); }\ncatch (e) { if (isNotFound(e)) { await refreshPlanList(); show('Plan was removed; reselect it'); } else throw e; }","preventionTips":["Refresh the plans list before edits instead of trusting long-lived cached ids.","Re-seeding regenerates ids — update any stored references after running the DbMigrator.","Double-check the id belongs to the Plans table, not subscriptions or tenants."],"tags":["billing","not-found","plans","crud"],"backgroundTag":"entity-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}