{"record":{"id":"c1410819e3f12cee","repo":"fullstackhero/dotnet-starter-kit","slug":"invoice-query-invoiceid-not-found-getinvoicepdfqueryhandler","errorCode":null,"errorMessage":"Invoice {query.InvoiceId} not found.","messagePattern":"Invoice (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Billing/Modules.Billing/Features/v1/Invoices/GetInvoicePdf/GetInvoicePdfQueryHandler.cs","lineNumber":33,"sourceCode":"    : IQueryHandler<GetInvoicePdfQuery, InvoicePdfResult>\n{\n    public async ValueTask<InvoicePdfResult> Handle(GetInvoicePdfQuery query, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(query);\n\n        // BillingDbContext is not tenant-filtered: root may download ANY tenant's invoice PDF; a tenant\n        // caller is pinned to its own, so a cross-tenant id resolves to 404 and never leaks a PDF.\n        var callerTenantId = tenantAccessor.MultiTenantContext?.TenantInfo?.Id\n            ?? throw new UnauthorizedException(\"Tenant context is required.\");\n        var isRoot = callerTenantId == MultitenancyConstants.Root.Id;\n\n        var invoice = await dbContext.Invoices.AsNoTracking()\n            .Include(i => i.LineItems)\n            .FirstOrDefaultAsync(\n                i => i.Id == query.InvoiceId && (isRoot || i.TenantId == callerTenantId),\n                cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Invoice {query.InvoiceId} not found.\");\n\n        var dto = invoice.ToDto();\n        var content = renderer.Render(dto);\n        return new InvoicePdfResult(content, $\"{dto.InvoiceNumber}.pdf\");\n    }\n}\n","sourceCodeStart":15,"sourceCodeEnd":40,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Billing/Modules.Billing/Features/v1/Invoices/GetInvoicePdf/GetInvoicePdfQueryHandler.cs#L15-L40","documentation":"GetInvoicePdfQueryHandler fetches the invoice (tenant-pinned for non-root callers) before rendering the PDF; if no invoice matches the id within the allowed tenant scope it throws NotFoundException(\"Invoice {id} not found.\"). Cross-tenant ids intentionally produce the same 404.","triggerScenarios":"GET /invoices/{id}/pdf with a non-existent invoice id; a tenant caller requesting another tenant's invoice id; an id valid in another environment/database; a deleted invoice id.","commonSituations":"A stale dashboard link to an invoice that was re-created (new id) after a plan reassignment; downloading a PDF for an invoice created in staging while pointing at prod; sharing a PDF URL between tenants.","solutions":["Verify the invoice id exists in the Invoices table and, for non-root callers, that its TenantId matches the caller.","Re-fetch the invoice list (GET /invoices) and derive the PDF link from a current DTO instead of a stored URL.","For cross-tenant PDF access, authenticate as root; otherwise the TenantId filter will yield a 404.","Check for environment mismatch (staging id used against production API)."],"exampleFix":"// before: id from an old email link\nGET /invoices/9d2e.../pdf\n\n// after: resolve from list first\nconst invoices = await apiFetch('/invoices');\nconst inv = invoices.items.find(i => i.invoiceNumber === 'INV-0042');\nreturn apiFetch(`/invoices/${inv.id}/pdf`);","handlingStrategy":"try-catch","validationCode":"const invoices = await apiFetch('/invoices');\nif (!invoices.items.some(i => i.id === invoiceId)) return null; // PDF would 404 too","typeGuard":"function isNotFound(e) { return e?.status === 404 || /not found/i.test(e?.message ?? ''); }","tryCatchPattern":"try { return await apiFetch(`/invoices/${id}/pdf`); }\ncatch (e) { if (isNotFound(e)) { show('Invoice not found or not accessible'); return null; } throw e; }","preventionTips":["Build PDF links from freshly fetched invoice DTOs, not stored URLs.","Check environment alignment: an id valid in staging will 404 in prod.","Use a root token when cross-tenant PDF access is legitimately required."],"tags":["billing","not-found","pdf","multitenancy"],"backgroundTag":"resource-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"}