fix: [BLOCKER] status RESOLVED race condition - use setQueryData from PATCH response, no refetch race

This commit is contained in:
Nemo
2026-03-24 16:13:36 +08:00
parent a5cac16924
commit 2325d5ad62

View File

@@ -85,8 +85,11 @@ export default function TicketDetailScreen() {
const updateStatus = useMutation({ const updateStatus = useMutation({
mutationFn: async ({ status, activationTicket, subId }: { status: string; activationTicket: boolean; subId?: string }) => { mutationFn: async ({ status, activationTicket, subId }: { status: string; activationTicket: boolean; subId?: string }) => {
await api.patch(`/api/v1/tickets/${id}`, { status }); // Return the patched ticket so onSuccess can set cache directly
// If this is an activation ticket being RESOLVED → activate subscription const res = await api.patch(`/api/v1/tickets/${id}`, { status });
const updatedTicket = res.data;
// Activate subscription when resolving an activation ticket
if ((status === 'RESOLVED' || status === 'CLOSED') && activationTicket && subId) { if ((status === 'RESOLVED' || status === 'CLOSED') && activationTicket && subId) {
await api.patch(`/api/v1/subscriptions/${subId}`, { status: 'ACTIVE' }).catch(() => {}); await api.patch(`/api/v1/subscriptions/${subId}`, { status: 'ACTIVE' }).catch(() => {});
} }
@@ -94,15 +97,19 @@ export default function TicketDetailScreen() {
await api.post(`/api/v1/tickets/${id}/messages`, { await api.post(`/api/v1/tickets/${id}/messages`, {
body: `Status changed to ${status.replace('_', ' ')} by ${who}`, body: `Status changed to ${status.replace('_', ' ')} by ${who}`,
}).catch(() => {}); }).catch(() => {});
return updatedTicket;
}, },
onSuccess: async () => { onSuccess: (updatedTicket) => {
// Directly inject fresh data into cache — avoids race condition with refetch
if (updatedTicket) {
qc.setQueryData(['task', id], (old: any) => ({ ...(old ?? {}), ...updatedTicket }));
}
setShowStatusPicker(false); setShowStatusPicker(false);
await qc.invalidateQueries({ queryKey: ['task', id] }); // Invalidate list queries in background (non-blocking)
await qc.invalidateQueries({ queryKey: ['tasks'] }); qc.invalidateQueries({ queryKey: ['tasks'] });
await qc.invalidateQueries({ queryKey: ['clients'] }); qc.invalidateQueries({ queryKey: ['clients'] });
await refetch(); qc.invalidateQueries({ queryKey: ['task-client', updatedTicket?.clientId] });
await refetchClient().catch(() => {});
await refetchInvoices().catch(() => {});
}, },
onError: (e: any) => { onError: (e: any) => {
const msg = e?.response?.data?.message ?? 'Could not update status.'; const msg = e?.response?.data?.message ?? 'Could not update status.';
@@ -130,8 +137,11 @@ export default function TicketDetailScreen() {
const confirmInstallation = async () => { const confirmInstallation = async () => {
setInstConfirming(true); setInstConfirming(true);
try { try {
// 1. Resolve the ticket // 1. Resolve the ticket — use response to directly update cache
await api.patch(`/api/v1/tickets/${id}`, { status: 'RESOLVED' }); const patchRes = await api.patch(`/api/v1/tickets/${id}`, { status: 'RESOLVED' });
if (patchRes.data) {
qc.setQueryData(['task', id], (old: any) => ({ ...(old ?? {}), ...patchRes.data }));
}
// 2. Update client location with recorded coordinates (skip if GPS unavailable) // 2. Update client location with recorded coordinates (skip if GPS unavailable)
if (ticket?.clientId && coords) { if (ticket?.clientId && coords) {
@@ -163,12 +173,9 @@ export default function TicketDetailScreen() {
setInstNotes(''); setInstNotes('');
setCoords(null); setCoords(null);
// Invalidate + force refetch so the status shows RESOLVED immediately // Invalidate list queries in background (cache already updated above)
await qc.invalidateQueries({ queryKey: ['tasks'] }); qc.invalidateQueries({ queryKey: ['tasks'] });
await qc.invalidateQueries({ queryKey: ['task', id] }); qc.invalidateQueries({ queryKey: ['clients'] });
await refetch();
await qc.invalidateQueries({ queryKey: ['client', ticket?.clientId] });
await qc.invalidateQueries({ queryKey: ['client-tickets', ticket?.clientId] });
Alert.alert( Alert.alert(
'Installation Complete! ✓', 'Installation Complete! ✓',
'Ticket resolved, location recorded, and activation ticket created.', 'Ticket resolved, location recorded, and activation ticket created.',