Persist status-change comments transactionally with the update (#37)
A comment accompanying a status change (required or user-supplied) was posted via a separate, independent HTTP call/write (add_comment.php, or a second add_comment call in lt.ticketStatus.submit()'s requires_comment retry path) before the status update itself. A failure partway through — or the client never issuing the second call — could leave a "reason" comment persisted with no matching status change, or vice versa, with no rollback tying the two together. api/update_ticket.php and api/ticket_status_api.php now post the comment and apply the status update inside one transaction, rolling back both on any failure. assets/js/ticket.js and lt.ticketStatus.submit() in assets/js/base.js no longer make a separate add_comment.php call; they pass the comment directly to update_ticket.php, which persists it server-side alongside the status change. Verified against real MariaDB by extracting the live ApiTicketController and the ticket_status_api.php transaction logic and running them directly: a forced optimistic-lock conflict correctly rolled back both the comment and the status change, and a successful call persisted exactly one comment alongside the status change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117oBw2jN4kALYeS8HPq4zV
This commit is contained in:
+27
-21
@@ -126,23 +126,6 @@ if ($workflowModel->transitionRequiresComment($currentStatus, $newStatus) && $co
|
||||
exit;
|
||||
}
|
||||
|
||||
// Post the comment first (per-key label) so a close-with-reason is one call.
|
||||
if ($comment !== '') {
|
||||
$commentModel = new CommentModel($conn);
|
||||
$commentResult = $commentModel->addComment($ticketId, [
|
||||
'user_name' => $keyName,
|
||||
'comment_text' => $comment,
|
||||
'markdown_enabled' => !empty($data['markdown_enabled']),
|
||||
], $createdBy);
|
||||
if (empty($commentResult['success'])) {
|
||||
error_log('ticket_status_api: addComment failed for ticket ' . $ticketId
|
||||
. ': ' . ($commentResult['error'] ?? 'unknown'));
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => 'Failed to add comment']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the status change. updateTicket sets updated_by/updated_at and handles
|
||||
// closed_at (set on close, cleared on reopen) via its own SQL.
|
||||
$updateData = [
|
||||
@@ -155,10 +138,33 @@ $updateData = [
|
||||
'priority' => (int)$ticket['priority'],
|
||||
];
|
||||
|
||||
$updateResult = $ticketModel->updateTicket($updateData, $createdBy);
|
||||
if (empty($updateResult['success'])) {
|
||||
error_log('ticket_status_api: updateTicket failed for ticket ' . $ticketId
|
||||
. ': ' . ($updateResult['error'] ?? 'unknown'));
|
||||
// Post the comment and apply the status change in one transaction, so a
|
||||
// failure partway through can't leave a "reason" comment persisted with no
|
||||
// matching status change (previously these were two independent writes with
|
||||
// no shared rollback).
|
||||
$conn->begin_transaction();
|
||||
try {
|
||||
if ($comment !== '') {
|
||||
$commentModel = new CommentModel($conn);
|
||||
$commentResult = $commentModel->addComment($ticketId, [
|
||||
'user_name' => $keyName,
|
||||
'comment_text' => $comment,
|
||||
'markdown_enabled' => !empty($data['markdown_enabled']),
|
||||
], $createdBy);
|
||||
if (empty($commentResult['success'])) {
|
||||
throw new Exception($commentResult['error'] ?? 'Failed to add comment');
|
||||
}
|
||||
}
|
||||
|
||||
$updateResult = $ticketModel->updateTicket($updateData, $createdBy);
|
||||
if (empty($updateResult['success'])) {
|
||||
throw new Exception($updateResult['error'] ?? 'Failed to update ticket status');
|
||||
}
|
||||
|
||||
$conn->commit();
|
||||
} catch (Exception $e) {
|
||||
$conn->rollback();
|
||||
error_log('ticket_status_api: transaction failed for ticket ' . $ticketId . ': ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => 'Failed to update ticket status']);
|
||||
exit;
|
||||
|
||||
Reference in New Issue
Block a user