fix(psa): resource assignment targets CW owner, status PATCH verifies apply
Previous `resources`-string PATCH was silently ignored by CW — the `resources` field is server-derived from the ticket's owner + schedule entries, not freely writable. Status PATCH could also silently no-op when a cross-board status id was sent. - add_resource: when the ticket is unassigned, set the `owner` MemberReference (the canonical writable primary-assignee field). If already owned by someone else, append the identifier to the `resources` co-assignee string best-effort. - remove_resource: clear `owner` (with remove→replace:null fallback) if the target is the current owner, otherwise strip from `resources`. - list_resources: merge owner + resources string, deduped by member id, so the UI reflects both single-owner and multi-resource assignments. - update_ticket_status: verify CW applied the status by comparing the response body's status.id — raises PSAError with a clear message when CW silently rejects the change (e.g., status invalid for ticket's board), instead of reporting spurious success. - Frontend: surface the backend error detail in the toast so users see the real reason instead of a generic "Failed to update" message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import axios from 'axios'
|
||||
import { ticketsApi } from '@/api/tickets'
|
||||
import { toast } from '@/lib/toast'
|
||||
import type { PSATicketSearchResult, PSATicketStatusItem } from '@/types/integrations'
|
||||
@@ -22,8 +23,11 @@ export function TicketDetailHeader({ ticket, currentStatusId, currentStatusName,
|
||||
const result: PSATicketStatusUpdate = await ticketsApi.updateStatus(Number(ticket.id), statusId)
|
||||
onStatusUpdated(result.ticket_id, result.new_status, result.new_status_id)
|
||||
toast.success(`Status updated to ${result.new_status}`)
|
||||
} catch {
|
||||
toast.error('Failed to update status')
|
||||
} catch (err) {
|
||||
const detail = axios.isAxiosError(err)
|
||||
? (err.response?.data as { detail?: string })?.detail
|
||||
: undefined
|
||||
toast.error(detail || 'Failed to update status')
|
||||
} finally {
|
||||
setUpdating(false)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import axios from 'axios'
|
||||
import { UserPlus, X, User } from 'lucide-react'
|
||||
import { ticketsApi } from '@/api/tickets'
|
||||
import { toast } from '@/lib/toast'
|
||||
@@ -27,8 +28,11 @@ export function TicketResourceManager({ ticketId, resources, allMembers, onChang
|
||||
setAdding(false)
|
||||
setSelectedMemberId('')
|
||||
onChanged()
|
||||
} catch {
|
||||
toast.error('Failed to add resource')
|
||||
} catch (err) {
|
||||
const detail = axios.isAxiosError(err)
|
||||
? (err.response?.data as { detail?: string })?.detail
|
||||
: undefined
|
||||
toast.error(detail || 'Failed to add resource')
|
||||
} finally {
|
||||
setBusy(null)
|
||||
}
|
||||
@@ -40,8 +44,11 @@ export function TicketResourceManager({ ticketId, resources, allMembers, onChang
|
||||
await ticketsApi.removeResource(ticketId, memberId)
|
||||
toast.success('Resource removed')
|
||||
onChanged()
|
||||
} catch {
|
||||
toast.error('Failed to remove resource')
|
||||
} catch (err) {
|
||||
const detail = axios.isAxiosError(err)
|
||||
? (err.response?.data as { detail?: string })?.detail
|
||||
: undefined
|
||||
toast.error(detail || 'Failed to remove resource')
|
||||
} finally {
|
||||
setBusy(null)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user