@burdenoff/microfe-movethewheels 2026.1010.2 → 2026.1011.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MoveTheWheelsRoot.js +12 -1
- package/dist/MoveTheWheelsRoutes.js +57 -49
- package/dist/generated/shipment-status-authority-operations.d.ts +4473 -0
- package/dist/generated/tracking-quality-operations.d.ts +4302 -0
- package/dist/hooks/useEpod.js +85 -77
- package/dist/hooks/useShipmentStatusAuthority.js +127 -0
- package/dist/hooks/useTracking.js +15 -14
- package/dist/hooks/useTrackingEventQuality.js +60 -0
- package/dist/movethewheels/hooks/useEpod.d.ts +23 -0
- package/dist/movethewheels/hooks/useShipmentStatusAuthority.d.ts +23 -0
- package/dist/movethewheels/hooks/useTrackingEventQuality.d.ts +18 -0
- package/dist/movethewheels/pages/ShipmentStatusAuthorityPage.d.ts +1 -0
- package/dist/movethewheels/pages/TrackingEventQualityPage.d.ts +1 -0
- package/dist/movethewheels/utils/shipmentStatusDraft.d.ts +45 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/radio-tower.js +36 -0
- package/dist/pages/DeliveryCapturePage.js +181 -116
- package/dist/pages/ShipmentStatusAuthorityPage.js +563 -0
- package/dist/pages/TrackingEventQualityPage.js +224 -0
- package/dist/src/generated/shipment-status-authority-operations.js +86 -0
- package/dist/src/generated/tracking-quality-operations.js +44 -0
- package/dist/utils/shipmentStatusDraft.js +41 -0
- package/package.json +16 -7
- package/patches/README.md +25 -0
- package/patches/braces@3.0.3.patch +87 -0
- package/src/generated/shipment-status-authority-operations.ts +5503 -0
- package/src/generated/tracking-quality-operations.ts +5323 -0
- package/src/movethewheels/MoveTheWheelsRoot.tsx +14 -0
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
- package/src/movethewheels/hooks/useEpod.ts +47 -12
- package/src/movethewheels/hooks/useShipmentStatusAuthority.ts +179 -0
- package/src/movethewheels/hooks/useTracking.ts +27 -12
- package/src/movethewheels/hooks/useTrackingEventQuality.ts +110 -0
- package/src/movethewheels/pages/DeliveryCapturePage.tsx +113 -6
- package/src/movethewheels/pages/ShipmentStatusAuthorityPage.tsx +639 -0
- package/src/movethewheels/pages/TrackingEventQualityPage.tsx +250 -0
- package/src/movethewheels/utils/shipmentStatusDraft.ts +118 -0
- package/src/operations/shipment-status-authority/status.graphql +80 -0
- package/src/operations/tracking-quality/quality.graphql +15 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { RadioTower } from 'lucide-react';
|
|
3
|
+
import { PageLayout } from '../components/PageLayout';
|
|
4
|
+
import { Button } from '../components/ui';
|
|
5
|
+
import { reuseCard, reuseSelect, ReuseState } from '../components/ReuseControls';
|
|
6
|
+
import { useConditionQuery } from '../hooks/useContainerCondition';
|
|
7
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
8
|
+
import {
|
|
9
|
+
QualityIntegrationsDocument,
|
|
10
|
+
QualityDeliveriesDocument,
|
|
11
|
+
QualityShipmentDocument,
|
|
12
|
+
trackingOutcomes,
|
|
13
|
+
} from '../hooks/useTrackingEventQuality';
|
|
14
|
+
import type {
|
|
15
|
+
TrackingQualityIntegrationsQuery,
|
|
16
|
+
TrackingQualityDeliveriesQuery,
|
|
17
|
+
TrackingQualityShipmentQuery,
|
|
18
|
+
} from '../../generated/tracking-quality-operations';
|
|
19
|
+
|
|
20
|
+
const LIMIT = 20;
|
|
21
|
+
function Pager({
|
|
22
|
+
offset,
|
|
23
|
+
length,
|
|
24
|
+
busy,
|
|
25
|
+
onChange,
|
|
26
|
+
}: {
|
|
27
|
+
offset: number;
|
|
28
|
+
length: number | undefined;
|
|
29
|
+
busy: boolean;
|
|
30
|
+
onChange: (offset: number) => void;
|
|
31
|
+
}) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
34
|
+
<Button
|
|
35
|
+
variant="outline"
|
|
36
|
+
disabled={busy || offset === 0}
|
|
37
|
+
onClick={() => onChange(Math.max(0, offset - LIMIT))}
|
|
38
|
+
>
|
|
39
|
+
Previous page
|
|
40
|
+
</Button>
|
|
41
|
+
<span>{length === undefined ? 'Page unavailable' : `${length} records on this page`}</span>
|
|
42
|
+
<Button
|
|
43
|
+
variant="outline"
|
|
44
|
+
disabled={busy || length !== LIMIT || offset >= 9980}
|
|
45
|
+
onClick={() => onChange(offset + LIMIT)}
|
|
46
|
+
>
|
|
47
|
+
Next page
|
|
48
|
+
</Button>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default function TrackingEventQualityPage() {
|
|
54
|
+
const { workspaceId, tenantId, currentUser, apiGatewayUrl } = useMoveTheWheels();
|
|
55
|
+
return (
|
|
56
|
+
<PageLayout
|
|
57
|
+
title="Tracking event quality"
|
|
58
|
+
subtitle="Review delivery results before investigating a shipment."
|
|
59
|
+
icon={<RadioTower aria-hidden="true" />}
|
|
60
|
+
>
|
|
61
|
+
<QualityWorkbench key={`${workspaceId}:${tenantId}:${currentUser?.id}:${apiGatewayUrl}`} />
|
|
62
|
+
</PageLayout>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
function QualityWorkbench() {
|
|
66
|
+
const [integrationOffset, setIntegrationOffset] = useState(0);
|
|
67
|
+
const [selected, setSelected] = useState('');
|
|
68
|
+
const [offset, setOffset] = useState(0);
|
|
69
|
+
const [revision, setRevision] = useState(0);
|
|
70
|
+
const [shipmentId, setShipmentId] = useState<string | null>(null);
|
|
71
|
+
const integrations = useConditionQuery<TrackingQualityIntegrationsQuery>(
|
|
72
|
+
QualityIntegrationsDocument,
|
|
73
|
+
{ pagination: { limit: LIMIT, offset: integrationOffset } },
|
|
74
|
+
revision
|
|
75
|
+
);
|
|
76
|
+
const deliveries = useConditionQuery<TrackingQualityDeliveriesQuery>(
|
|
77
|
+
QualityDeliveriesDocument,
|
|
78
|
+
selected ? { id: selected, pagination: { limit: LIMIT, offset } } : null,
|
|
79
|
+
revision
|
|
80
|
+
);
|
|
81
|
+
const options = integrations.data?.integrations;
|
|
82
|
+
const records = deliveries.data?.integration?.events;
|
|
83
|
+
return (
|
|
84
|
+
<div className="space-y-6">
|
|
85
|
+
<p>
|
|
86
|
+
Matching event keys and payloads create no additional tracking event. A changed payload with
|
|
87
|
+
the same key is a conflict. Updates without a key can create new events.
|
|
88
|
+
</p>
|
|
89
|
+
<p>
|
|
90
|
+
Each delivery admits at most five updates. An oversized or malformed delivery is rejected
|
|
91
|
+
before updates are recorded. Delivery history can include older records without item
|
|
92
|
+
results.
|
|
93
|
+
</p>
|
|
94
|
+
<section className={reuseCard} aria-label="Integration selection">
|
|
95
|
+
<ReuseState loading={integrations.loading} error={integrations.error} />
|
|
96
|
+
{options?.length === 0 && <p>No integrations on this page.</p>}
|
|
97
|
+
<label className="block">
|
|
98
|
+
Integration
|
|
99
|
+
<select
|
|
100
|
+
className={reuseSelect}
|
|
101
|
+
value={selected}
|
|
102
|
+
disabled={!options || integrations.loading}
|
|
103
|
+
onChange={(event) => {
|
|
104
|
+
setSelected(event.target.value);
|
|
105
|
+
setOffset(0);
|
|
106
|
+
setShipmentId(null);
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<option value="">Choose an integration</option>
|
|
110
|
+
{options?.map((integration) => (
|
|
111
|
+
<option key={integration.id} value={integration.id}>
|
|
112
|
+
{integration.name} · {integration.provider} · {integration.status}
|
|
113
|
+
</option>
|
|
114
|
+
))}
|
|
115
|
+
</select>
|
|
116
|
+
</label>
|
|
117
|
+
<Pager
|
|
118
|
+
offset={integrationOffset}
|
|
119
|
+
length={options?.length}
|
|
120
|
+
busy={integrations.loading}
|
|
121
|
+
onChange={(next) => {
|
|
122
|
+
setIntegrationOffset(next);
|
|
123
|
+
setSelected('');
|
|
124
|
+
setOffset(0);
|
|
125
|
+
setShipmentId(null);
|
|
126
|
+
}}
|
|
127
|
+
/>
|
|
128
|
+
</section>
|
|
129
|
+
{selected && (
|
|
130
|
+
<section className={reuseCard} aria-label="Tracking delivery history">
|
|
131
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
132
|
+
<h2 className="text-lg font-semibold">Delivery history</h2>
|
|
133
|
+
<Button
|
|
134
|
+
variant="outline"
|
|
135
|
+
disabled={deliveries.loading}
|
|
136
|
+
onClick={() => setRevision((value) => value + 1)}
|
|
137
|
+
>
|
|
138
|
+
Refresh results
|
|
139
|
+
</Button>
|
|
140
|
+
</div>
|
|
141
|
+
<ReuseState loading={deliveries.loading} error={deliveries.error} />
|
|
142
|
+
{deliveries.data?.integration === null && (
|
|
143
|
+
<p role="status">Integration unavailable in this workspace.</p>
|
|
144
|
+
)}
|
|
145
|
+
{records?.length === 0 && <p>No deliveries on this page.</p>}
|
|
146
|
+
{records?.map((delivery) => {
|
|
147
|
+
const outcomes = trackingOutcomes(delivery.outcomes);
|
|
148
|
+
return (
|
|
149
|
+
<article key={delivery.id} className="space-y-3 rounded-lg border p-4">
|
|
150
|
+
<h3 className="font-semibold">
|
|
151
|
+
{delivery.status} · {new Date(delivery.receivedAt).toLocaleString()}
|
|
152
|
+
</h3>
|
|
153
|
+
{outcomes === null ? (
|
|
154
|
+
<p>Per-update results unavailable for this delivery.</p>
|
|
155
|
+
) : outcomes.length === 0 ? (
|
|
156
|
+
<p>
|
|
157
|
+
No normalized updates were attempted. This is not a count of raw sender items.
|
|
158
|
+
</p>
|
|
159
|
+
) : (
|
|
160
|
+
<ol className="space-y-3">
|
|
161
|
+
{outcomes.map((outcome) => (
|
|
162
|
+
<li key={outcome.index} className="break-words rounded-md bg-bg-surface p-3">
|
|
163
|
+
<p>
|
|
164
|
+
Update {outcome.index + 1}: <strong>{outcome.status}</strong>
|
|
165
|
+
</p>
|
|
166
|
+
<p>Event key: {outcome.externalEventId ?? 'Missing — not deduplicated'}</p>
|
|
167
|
+
<p>Shipment reference: {outcome.shipmentReference || 'Not recorded'}</p>
|
|
168
|
+
{outcome.errorCode && (
|
|
169
|
+
<p>Reason: {outcome.errorCode.replaceAll('_', ' ').toLowerCase()}</p>
|
|
170
|
+
)}
|
|
171
|
+
{(outcome.status === 'ACCEPTED' || outcome.status === 'DUPLICATE') && (
|
|
172
|
+
<p>
|
|
173
|
+
Status decision:{' '}
|
|
174
|
+
{outcome.projection?.status === 'APPLIED'
|
|
175
|
+
? 'Applied to the recorded shipment status'
|
|
176
|
+
: outcome.projection?.status === 'HELD'
|
|
177
|
+
? `Held — ${outcome.projection.heldReason?.replaceAll('_', ' ').toLowerCase()}`
|
|
178
|
+
: 'No status decision recorded in this legacy result'}
|
|
179
|
+
</p>
|
|
180
|
+
)}
|
|
181
|
+
{outcome.status === 'CONFLICT' && (
|
|
182
|
+
<p>The earlier receipt stays unchanged; this update adds no event.</p>
|
|
183
|
+
)}
|
|
184
|
+
{outcome.shipmentId && (
|
|
185
|
+
<Button
|
|
186
|
+
variant="outline"
|
|
187
|
+
onClick={() => setShipmentId(outcome.shipmentId)}
|
|
188
|
+
>
|
|
189
|
+
View shipment
|
|
190
|
+
</Button>
|
|
191
|
+
)}
|
|
192
|
+
</li>
|
|
193
|
+
))}
|
|
194
|
+
</ol>
|
|
195
|
+
)}
|
|
196
|
+
</article>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
<Pager
|
|
200
|
+
offset={offset}
|
|
201
|
+
length={records?.length}
|
|
202
|
+
busy={deliveries.loading}
|
|
203
|
+
onChange={setOffset}
|
|
204
|
+
/>
|
|
205
|
+
</section>
|
|
206
|
+
)}
|
|
207
|
+
{shipmentId && (
|
|
208
|
+
<QualityShipment key={shipmentId} id={shipmentId} onClose={() => setShipmentId(null)} />
|
|
209
|
+
)}
|
|
210
|
+
<p className="text-sm text-text-secondary">
|
|
211
|
+
These results describe recorded ingestion, not physical shipment truth. The chosen-source
|
|
212
|
+
clock policy and manual corrections control recorded shipment status. Provider corrections
|
|
213
|
+
and replay need separate reviewed policies.
|
|
214
|
+
</p>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function QualityShipment({ id, onClose }: { id: string; onClose: () => void }) {
|
|
220
|
+
const query = useConditionQuery<TrackingQualityShipmentQuery>(QualityShipmentDocument, { id });
|
|
221
|
+
return (
|
|
222
|
+
<section className={reuseCard} aria-label="Shipment tracking summary">
|
|
223
|
+
<h2 className="text-lg font-semibold">Shipment tracking summary</h2>
|
|
224
|
+
<Button variant="outline" onClick={onClose}>
|
|
225
|
+
Close shipment summary
|
|
226
|
+
</Button>
|
|
227
|
+
<ReuseState loading={query.loading} error={query.error} />
|
|
228
|
+
{query.data?.shipment === null && <p>Shipment unavailable in this workspace.</p>}
|
|
229
|
+
{query.data?.shipment && (
|
|
230
|
+
<>
|
|
231
|
+
<p>
|
|
232
|
+
{query.data.shipment.shipmentNumber} · {query.data.shipment.status}
|
|
233
|
+
</p>
|
|
234
|
+
<p>Tracking number: {query.data.shipment.trackingNumber ?? 'Not recorded'}</p>
|
|
235
|
+
<p>
|
|
236
|
+
{query.data.shipment.trackingEvents.length} tracking events on the first page (up to
|
|
237
|
+
20).
|
|
238
|
+
</p>
|
|
239
|
+
<ul>
|
|
240
|
+
{query.data.shipment.trackingEvents.map((event) => (
|
|
241
|
+
<li key={event.id}>
|
|
242
|
+
{event.eventType} · {new Date(event.timestamp).toLocaleString()}
|
|
243
|
+
</li>
|
|
244
|
+
))}
|
|
245
|
+
</ul>
|
|
246
|
+
</>
|
|
247
|
+
)}
|
|
248
|
+
</section>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export interface ShipmentSourceDraft {
|
|
2
|
+
integrationId: string;
|
|
3
|
+
basisTrackingEventId: string;
|
|
4
|
+
maxSourceAgeSeconds: string;
|
|
5
|
+
maxFutureSkewSeconds: string;
|
|
6
|
+
reason: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function statusDecisionReason(value: string): string {
|
|
10
|
+
const reason = value.trim();
|
|
11
|
+
if (!reason || reason.length > 1000) throw new Error('Enter a reason of 1–1000 characters.');
|
|
12
|
+
return reason;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function explicitSeconds(value: string, minimum: number, maximum: number, label: string): number {
|
|
16
|
+
if (!/^\d+$/.test(value.trim()))
|
|
17
|
+
throw new Error(`${label} must be an explicit whole number of seconds.`);
|
|
18
|
+
const result = Number(value.trim());
|
|
19
|
+
if (!Number.isSafeInteger(result) || result < minimum || result > maximum)
|
|
20
|
+
throw new Error(`${label} must be between ${minimum} and ${maximum} seconds.`);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function shipmentSourceInput(draft: ShipmentSourceDraft, expectedStatusRevision: number) {
|
|
25
|
+
if (!draft.integrationId || !draft.basisTrackingEventId)
|
|
26
|
+
throw new Error('Choose one integration and one recorded basis event.');
|
|
27
|
+
return {
|
|
28
|
+
expectedStatusRevision,
|
|
29
|
+
integrationId: draft.integrationId,
|
|
30
|
+
basisTrackingEventId: draft.basisTrackingEventId,
|
|
31
|
+
maxSourceAgeSeconds: explicitSeconds(
|
|
32
|
+
draft.maxSourceAgeSeconds,
|
|
33
|
+
1,
|
|
34
|
+
2592000,
|
|
35
|
+
'Maximum source age'
|
|
36
|
+
),
|
|
37
|
+
maxFutureSkewSeconds: explicitSeconds(
|
|
38
|
+
draft.maxFutureSkewSeconds,
|
|
39
|
+
0,
|
|
40
|
+
86400,
|
|
41
|
+
'Allowed future skew'
|
|
42
|
+
),
|
|
43
|
+
reason: statusDecisionReason(draft.reason),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface StatusAcknowledgement {
|
|
48
|
+
id: string;
|
|
49
|
+
status: string;
|
|
50
|
+
statusRevision: number;
|
|
51
|
+
statusAuthority: string;
|
|
52
|
+
statusAuthorityEpoch: number;
|
|
53
|
+
designatedIntegrationId?: string | null;
|
|
54
|
+
statusBasisEventId?: string | null;
|
|
55
|
+
statusBasisSourceTime?: string | null;
|
|
56
|
+
maxSourceAgeSeconds?: number | null;
|
|
57
|
+
maxFutureSkewSeconds?: number | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function sourceStatusAcknowledged(
|
|
61
|
+
actual: StatusAcknowledgement | null | undefined,
|
|
62
|
+
expected: {
|
|
63
|
+
id: string;
|
|
64
|
+
epoch: number;
|
|
65
|
+
input: ReturnType<typeof shipmentSourceInput>;
|
|
66
|
+
basis: { id: string; eventType: string; providerEventTime?: string | null } | undefined;
|
|
67
|
+
}
|
|
68
|
+
): boolean {
|
|
69
|
+
const statuses: Record<string, string> = {
|
|
70
|
+
PICKED_UP: 'PICKED_UP',
|
|
71
|
+
IN_TRANSIT: 'IN_TRANSIT',
|
|
72
|
+
ARRIVED: 'IN_TRANSIT',
|
|
73
|
+
DEPARTED: 'IN_TRANSIT',
|
|
74
|
+
OUT_FOR_DELIVERY: 'IN_TRANSIT',
|
|
75
|
+
DELIVERED: 'DELIVERED',
|
|
76
|
+
FAILED: 'FAILED',
|
|
77
|
+
RETURNED: 'RETURNED',
|
|
78
|
+
};
|
|
79
|
+
const basis = expected.basis;
|
|
80
|
+
const time = basis?.providerEventTime ? Date.parse(basis.providerEventTime) : NaN;
|
|
81
|
+
return (
|
|
82
|
+
!!actual &&
|
|
83
|
+
!!basis &&
|
|
84
|
+
Number.isFinite(time) &&
|
|
85
|
+
actual.id === expected.id &&
|
|
86
|
+
actual.statusRevision === expected.input.expectedStatusRevision + 1 &&
|
|
87
|
+
actual.statusAuthority === 'INTEGRATION_EVENT_TIME' &&
|
|
88
|
+
actual.statusAuthorityEpoch === expected.epoch + 1 &&
|
|
89
|
+
actual.designatedIntegrationId === expected.input.integrationId &&
|
|
90
|
+
actual.statusBasisEventId === basis.id &&
|
|
91
|
+
basis.id === expected.input.basisTrackingEventId &&
|
|
92
|
+
typeof actual.statusBasisSourceTime === 'string' &&
|
|
93
|
+
Date.parse(actual.statusBasisSourceTime) === time &&
|
|
94
|
+
actual.maxSourceAgeSeconds === expected.input.maxSourceAgeSeconds &&
|
|
95
|
+
actual.maxFutureSkewSeconds === expected.input.maxFutureSkewSeconds &&
|
|
96
|
+
typeof statuses[basis.eventType] === 'string' &&
|
|
97
|
+
actual.status === statuses[basis.eventType]
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function manualStatusAcknowledged(
|
|
102
|
+
actual: StatusAcknowledgement | null | undefined,
|
|
103
|
+
expected: { id: string; revision: number; epoch: number; status: string }
|
|
104
|
+
): boolean {
|
|
105
|
+
return (
|
|
106
|
+
!!actual &&
|
|
107
|
+
actual.id === expected.id &&
|
|
108
|
+
actual.statusRevision === expected.revision + 1 &&
|
|
109
|
+
actual.status === expected.status &&
|
|
110
|
+
actual.statusAuthority === 'MANUAL_HOLD' &&
|
|
111
|
+
actual.statusAuthorityEpoch === expected.epoch + 1 &&
|
|
112
|
+
actual.designatedIntegrationId === null &&
|
|
113
|
+
actual.statusBasisEventId === null &&
|
|
114
|
+
actual.statusBasisSourceTime === null &&
|
|
115
|
+
actual.maxSourceAgeSeconds === null &&
|
|
116
|
+
actual.maxFutureSkewSeconds === null
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
fragment ShipmentStatusAuthorityFields on MtwShipment {
|
|
2
|
+
id
|
|
3
|
+
shipmentNumber
|
|
4
|
+
status
|
|
5
|
+
statusRevision
|
|
6
|
+
statusAuthority
|
|
7
|
+
statusAuthorityEpoch
|
|
8
|
+
designatedIntegrationId
|
|
9
|
+
statusBasisEventId
|
|
10
|
+
statusBasisSourceTime
|
|
11
|
+
maxSourceAgeSeconds
|
|
12
|
+
maxFutureSkewSeconds
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
query StatusAuthorityShipments($filter: ShipmentFilterInput, $pagination: PaginationInput!) {
|
|
16
|
+
shipments(filter: $filter, pagination: $pagination) {
|
|
17
|
+
...ShipmentStatusAuthorityFields
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
query StatusAuthorityDetail($id: ID!, $pagination: PaginationInput!, $evidencePagination: PaginationInput!) {
|
|
22
|
+
shipment(id: $id) {
|
|
23
|
+
...ShipmentStatusAuthorityFields
|
|
24
|
+
trackingEvents(pagination: $evidencePagination) {
|
|
25
|
+
id
|
|
26
|
+
eventType
|
|
27
|
+
sourceIntegrationId
|
|
28
|
+
sourceTimestampProvenance
|
|
29
|
+
providerEventTime
|
|
30
|
+
serverReceivedAt
|
|
31
|
+
timestamp
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
shipmentStatusDecisions(shipmentId: $id, pagination: $pagination) {
|
|
35
|
+
id
|
|
36
|
+
origin
|
|
37
|
+
outcome
|
|
38
|
+
heldReason
|
|
39
|
+
observedRevision
|
|
40
|
+
resultRevision
|
|
41
|
+
priorStatus
|
|
42
|
+
resultStatus
|
|
43
|
+
trackingEventId
|
|
44
|
+
proofId
|
|
45
|
+
reason
|
|
46
|
+
recordedAt
|
|
47
|
+
configuredIntegrationId
|
|
48
|
+
configuredMaxSourceAgeSeconds
|
|
49
|
+
configuredMaxFutureSkewSeconds
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
query StatusAuthoritySources($pagination: PaginationInput!) {
|
|
54
|
+
integrations(pagination: $pagination) {
|
|
55
|
+
id
|
|
56
|
+
name
|
|
57
|
+
provider
|
|
58
|
+
status
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
mutation StatusAuthorityConfigure($id: ID!, $input: ConfigureShipmentStatusSourceInput!) {
|
|
63
|
+
configureShipmentStatusSource(id: $id, input: $input) {
|
|
64
|
+
...ShipmentStatusAuthorityFields
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
mutation StatusAuthorityManual($id: ID!, $input: UpdateShipmentInput!) {
|
|
69
|
+
updateShipment(id: $id, input: $input) {
|
|
70
|
+
...ShipmentStatusAuthorityFields
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
query StatusAuthorityOwnedDelivery($deliveryJobId: ID!) {
|
|
75
|
+
myEpodShipmentStatus(deliveryJobId: $deliveryJobId) {
|
|
76
|
+
id
|
|
77
|
+
status
|
|
78
|
+
statusRevision
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
query TrackingQualityIntegrations($pagination: PaginationInput!) {
|
|
2
|
+
integrations(pagination: $pagination) { id name provider status }
|
|
3
|
+
}
|
|
4
|
+
query TrackingQualityDeliveries($id: ID!, $pagination: PaginationInput!) {
|
|
5
|
+
integration(id: $id) {
|
|
6
|
+
id name provider status
|
|
7
|
+
events(pagination: $pagination) { id status receivedAt processedAt outcomes }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
query TrackingQualityShipment($id: ID!) {
|
|
11
|
+
shipment(id: $id) {
|
|
12
|
+
id shipmentNumber status trackingNumber
|
|
13
|
+
trackingEvents(pagination: {limit: 20, offset: 0}) { id eventType timestamp }
|
|
14
|
+
}
|
|
15
|
+
}
|