{"record":{"id":"03a48dd46b9beee2","repo":"fullstackhero/dotnet-starter-kit","slug":"only-a-resolved-ticket-can-be-closed-current-status-is","errorCode":null,"errorMessage":"Only a resolved ticket can be closed — current status is {Status}. Resolve it first.","messagePattern":"Only a resolved ticket can be closed — current status is (.+?)\\. Resolve it first\\.","errorType":"exception","errorClass":"CustomException","httpStatus":409,"severity":"error","filePath":"src/Modules/Tickets/Modules.Tickets/Domain/Ticket.cs","lineNumber":148,"sourceCode":"        ResolutionNote = string.IsNullOrWhiteSpace(resolutionNote) ? null : resolutionNote.Trim();\n        ResolvedAtUtc = DateTime.UtcNow;\n        UpdatedAtUtc = DateTime.UtcNow;\n        TransitionStatus(TicketStatus.Resolved);\n    }\n\n    /// <summary>\n    /// Finalizes a resolved ticket (Resolved → Closed). Idempotent when already Closed;\n    /// rejects any other source state with a 409 so the documented state machine holds.\n    /// </summary>\n    public void Close()\n    {\n        if (Status == TicketStatus.Closed)\n        {\n            return;\n        }\n        if (Status != TicketStatus.Resolved)\n        {\n            throw new CustomException(\n                $\"Only a resolved ticket can be closed — current status is {Status}. Resolve it first.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.Conflict);\n        }\n\n        ClosedAtUtc = DateTime.UtcNow;\n        UpdatedAtUtc = DateTime.UtcNow;\n        TransitionStatus(TicketStatus.Closed);\n    }\n\n    /// <summary>\n    /// Edits the mutable details of an open/in-progress/resolved ticket. A closed ticket is\n    /// frozen — it must be reopened first.\n    /// </summary>\n    public void UpdateDetails(string title, string? description, TicketPriority priority)\n    {\n        ArgumentException.ThrowIfNullOrWhiteSpace(title);\n        if (Status == TicketStatus.Closed)","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Tickets/Modules.Tickets/Domain/Ticket.cs#L130-L166","documentation":"Ticket.Close enforces that only a Resolved ticket can be closed; closing from any other status throws CustomException with HTTP 409 Conflict and reports the current status. Closing is the terminal step of the Open → Resolved → Closed workflow.","triggerScenarios":"Calling Close on a ticket whose Status is Open (or any status other than Resolved/Closed). Closed tickets return silently (idempotent); everything else throws.","commonSituations":"Support agent closing a ticket without first marking it resolved; client skipping the Resolve step; stale UI showing Resolved while the ticket was reopened; batch scripts that close tickets without transitioning them through Resolved.","solutions":["Call Resolve(resolutionNote) first, then Close — the workflow requires Resolved before Closed.","Reload the ticket and check Status before closing.","Make client close actions conditional: only enable Close when status is Resolved.","Handle the 409 in the client by refreshing the ticket and prompting for the Resolve step."],"exampleFix":"// before\nticket.Close(); // 409 unless Resolved\n// after\nif (ticket.Status == TicketStatus.Open)\n{\n    ticket.Resolve(\"Auto-resolved before close\");\n}\nticket.Close();","handlingStrategy":"validation","validationCode":"if (ticket.status !== TicketStatus.Resolved) {\n  throw new Error(`Ticket must be Resolved before closing (current: ${ticket.status}).`);\n}\nticket.close();","typeGuard":"bool CanClose(Ticket t) => t.Status == TicketStatus.Resolved;","tryCatchPattern":"try\n{\n    ticket.Close();\n}\ncatch (CustomException ex) when (ex.StatusCode == HttpStatusCode.Conflict)\n{\n    ticket.Resolve(\"Resolved prior to close\");\n    ticket.Close();\n}","preventionTips":["Follow the workflow: Resolve before Close; never close from Open.","Enable the Close action in the UI only when status is Resolved.","Refresh the ticket before closing if the page has been idle.","In automation, transition through Resolved explicitly rather than jumping to Close."],"tags":["tickets","domain-rules","conflict","state-machine"],"backgroundTag":"invalid-state-transition","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"}