862 lines
32 KiB
JavaScript
862 lines
32 KiB
JavaScript
import doormileAxios, { DOORMILE_TOKEN_KEY, DOORMILE_USER_KEY, clearStoredSession } from './client';
|
||
|
||
// The Doormile Express admin API — one named async export per
|
||
// `api.doormile.com/api/v1/admin/*` endpoint.
|
||
//
|
||
// Ported verbatim from the Express Console's `pages/api/doormileApi.js`, since
|
||
// this is the contract the backend actually speaks; only the client import
|
||
// changed. Every function unwraps the envelope for its caller: list/GET
|
||
// functions return `response.data.data` directly, mutations return the full
|
||
// `response.data` (`{ success, data | message }`) so callers can check
|
||
// `res.success`.
|
||
//
|
||
// Pages consume these through the React Query hooks in `@/lib/doormileHooks`
|
||
// rather than calling them directly, so cache keys stay coherent across the app.
|
||
|
||
const buildQuery = (params = {}) => {
|
||
const usp = new URLSearchParams();
|
||
Object.entries(params).forEach(([key, value]) => {
|
||
if (value !== undefined && value !== null && value !== '') usp.append(key, value);
|
||
});
|
||
const qs = usp.toString();
|
||
return qs ? `?${qs}` : '';
|
||
};
|
||
|
||
// ==============================|| Auth ||============================== //
|
||
|
||
export const loginAdmin = async (email, password) => {
|
||
const response = await doormileAxios.post('/admin/login', { email, password });
|
||
if (response.data.success) {
|
||
localStorage.setItem(DOORMILE_TOKEN_KEY, response.data.token);
|
||
localStorage.setItem(DOORMILE_USER_KEY, JSON.stringify(response.data.user));
|
||
}
|
||
return response.data;
|
||
};
|
||
|
||
export const logoutAdmin = () => {
|
||
// Clears the auxiliary identity keys too — see clearStoredSession in client.js.
|
||
clearStoredSession();
|
||
};
|
||
|
||
// Drains a paginated list endpoint instead of taking whatever the server's
|
||
// default page size happens to be.
|
||
//
|
||
// GET /admin/customers defaults to `pagesize: 20` — confirmed live against an
|
||
// account with 37 customers, which came back as
|
||
// { pageno: 1, pagesize: 20, pages: 2, total: 37 }. Every caller of
|
||
// getAdminCustomers was therefore seeing 20 of 37 and had no way to know:
|
||
// the Customers page reported "Total Customers 20", and the customer lookups
|
||
// on the orders pages silently missed anyone on page 2.
|
||
//
|
||
// (express-console-api.md claims the default is 500. It is not, at least not
|
||
// for this endpoint. Trust the envelope, not the doc.)
|
||
const PAGE_SIZE = 100; // the doc's own example value — safer than assuming the 1000 cap holds
|
||
const MAX_PAGES = 12;
|
||
|
||
const fetchAllPages = async (path) => {
|
||
const first = await doormileAxios.get(`${path}${buildQuery({ pageno: 1, pagesize: PAGE_SIZE })}`);
|
||
const body = first.data || {};
|
||
const rows = body.data || [];
|
||
const total = Number(body.total);
|
||
if (!Number.isFinite(total) || rows.length >= total) return rows;
|
||
|
||
const pageCount = Math.min(Math.ceil(total / PAGE_SIZE), MAX_PAGES);
|
||
for (let page = 2; page <= pageCount; page += 1) {
|
||
|
||
const next = await doormileAxios.get(`${path}${buildQuery({ pageno: page, pagesize: PAGE_SIZE })}`);
|
||
const more = next.data?.data || [];
|
||
if (!more.length) break;
|
||
rows.push(...more);
|
||
}
|
||
return rows;
|
||
};
|
||
|
||
// ==============================|| Dashboard, profile, reports ||============================== //
|
||
|
||
export const getDashboard = async () => {
|
||
const response = await doormileAxios.get('/admin/dashboard');
|
||
return response.data.data;
|
||
};
|
||
|
||
// jupiter used fromdate/todate + applocationid (city); Doormile uses from/to
|
||
// and no city filter at all (filters by hubid instead — see
|
||
// jupiter2doormile.md §2.3/§6). Response carries by_location/by_hub/by_tenant/
|
||
// by_rider blocks per that doc, not documented in express-console-api.md.
|
||
export const getReports = async (from, to, tenantid, locationid, hubid) => {
|
||
const response = await doormileAxios.get(`/admin/reports${buildQuery({ from, to, tenantid, locationid, hubid })}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
// Confirmed against jupiter2doormile.md (Status: Done) as the replacement for
|
||
// jupiter's getlocationsummary — per-tenant-site report breakdown. Not in
|
||
// express-console-api.md.
|
||
export const getLocationsSummary = async (tenantid, locationid, from, to) => {
|
||
const response = await doormileAxios.get(`/admin/locations/summary${buildQuery({ tenantid, locationid, from, to })}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const getProfile = async () => {
|
||
const response = await doormileAxios.get('/admin/profile');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const updateProfilePassword = async (current_password, new_password) => {
|
||
const response = await doormileAxios.put('/admin/profile/password', { current_password, new_password });
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| App users (staff logins) ||============================== //
|
||
|
||
export const getAppUsers = async () => {
|
||
const response = await doormileAxios.get('/admin/users');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createAppUser = async (data) => {
|
||
const response = await doormileAxios.post('/admin/users', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const updateAppUser = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/users/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deleteAppUser = async (id) => {
|
||
const response = await doormileAxios.delete(`/admin/users/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Partners (fleet / rider suppliers) ||============================== //
|
||
|
||
export const getPartners = async () => {
|
||
const response = await doormileAxios.get('/admin/partners');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createPartner = async (data) => {
|
||
const response = await doormileAxios.post('/admin/partners', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const getPartner = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/partners/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const updatePartner = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/partners/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deletePartner = async (id) => {
|
||
const response = await doormileAxios.delete(`/admin/partners/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Tenants (client companies) ||============================== //
|
||
|
||
export const getAdminTenants = async () => {
|
||
const response = await doormileAxios.get('/admin/tenants');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createAdminTenant = async (data) => {
|
||
const response = await doormileAxios.post('/admin/tenants', data);
|
||
return response.data;
|
||
};
|
||
|
||
/**
|
||
* Onboard a client: the tenant, its console login (doormile_auth) and its
|
||
* appusers row, in one server transaction. Only the onboarding owner login
|
||
* (admin@doormile.com) may call it; anyone else gets 403.
|
||
* @param {{ companyname: string, contactname: string, email: string, phone: string,
|
||
* password: string, applocationid: number, requiredeliveryotp?: boolean }} data
|
||
* @returns {Promise<{ success: boolean, data: { tenant: object, login: object } }>}
|
||
*/
|
||
export const onboardClient = async (data) => {
|
||
const response = await doormileAxios.post('/admin/clients/onboard', data);
|
||
return response.data;
|
||
};
|
||
|
||
/**
|
||
* Edit an onboarded client and its login (owner login only). `authid` is the
|
||
* login's id from GET /admin/clients/onboarded. Only fields sent change;
|
||
* `password` resets the login's password when non-empty.
|
||
*/
|
||
export const updateOnboardedClient = async (authid, data) => {
|
||
const response = await doormileAxios.put(`/admin/clients/${encodeURIComponent(authid)}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
/**
|
||
* Remove a client's console login (owner login only). The company and its
|
||
* order history are kept; the client is marked Inactive if no login remains.
|
||
*/
|
||
export const deleteOnboardedClient = async (authid) => {
|
||
const response = await doormileAxios.delete(`/admin/clients/${encodeURIComponent(authid)}`);
|
||
return response.data;
|
||
};
|
||
|
||
/** Operating cities a new client can be placed in (applocations; owner login only). */
|
||
export const getOnboardingCities = async () => {
|
||
const response = await doormileAxios.get('/admin/clients/cities');
|
||
return response.data.data;
|
||
};
|
||
|
||
/** Clients that have a console login, newest first (owner login only). */
|
||
export const getOnboardedClients = async () => {
|
||
const response = await doormileAxios.get('/admin/clients/onboarded');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const getAdminTenant = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/tenants/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const updateAdminTenant = async (id, data) => {
|
||
// requiredeliveryotp is a pointer server-side — omit the key entirely to leave the setting alone.
|
||
const response = await doormileAxios.put(`/admin/tenants/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deleteAdminTenant = async (id) => {
|
||
const response = await doormileAxios.delete(`/admin/tenants/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
export const getTenantLocations = async (tenantId) => {
|
||
const response = await doormileAxios.get(`/admin/tenants/${tenantId}/locations`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createTenantLocation = async (tenantId, data) => {
|
||
const response = await doormileAxios.post(`/admin/tenants/${tenantId}/locations`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const updateTenantLocation = async (locationId, data) => {
|
||
// Not nested under the tenant — PUT /admin/tenantlocations/:id.
|
||
const response = await doormileAxios.put(`/admin/tenantlocations/${locationId}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Tenant customers (a client's own end customers) ||============================== //
|
||
|
||
export const getTenantCustomers = async () => fetchAllPages('/admin/tenantcustomers');
|
||
|
||
export const createTenantCustomer = async (data) => {
|
||
const response = await doormileAxios.post('/admin/tenantcustomers', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const getTenantCustomer = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/tenantcustomers/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const updateTenantCustomer = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/tenantcustomers/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deleteTenantCustomer = async (id) => {
|
||
const response = await doormileAxios.delete(`/admin/tenantcustomers/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| B2C app customers ||============================== //
|
||
|
||
export const getAdminCustomers = async () => fetchAllPages('/admin/customers');
|
||
|
||
// POST /admin/customers.
|
||
//
|
||
// express-console-api.md lists this resource as GET + PATCH only, and states
|
||
// that a customer there comes into existence through a booking. Added on
|
||
// explicit instruction to create into the store the Customers page actually
|
||
// reads. If the route genuinely does not exist the call returns 404/405, and
|
||
// the assistant surfaces that verbatim with the endpoint name rather than
|
||
// failing silently — so an unsupported route is immediately visible.
|
||
//
|
||
// Body mirrors this resource's own GET shape.
|
||
export const createAdminCustomer = async (data) => {
|
||
const response = await doormileAxios.post('/admin/customers', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const updateAdminCustomer = async (id, data) => {
|
||
const response = await doormileAxios.patch(`/admin/customers/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Hubs ||============================== //
|
||
|
||
export const getHubs = async () => {
|
||
const response = await doormileAxios.get('/admin/hubs');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createHub = async (data) => {
|
||
const response = await doormileAxios.post('/admin/hubs', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const getHub = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/hubs/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const updateHub = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/hubs/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deleteHub = async (id) => {
|
||
// Soft delete server-side.
|
||
const response = await doormileAxios.delete(`/admin/hubs/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Vehicles ||============================== //
|
||
|
||
export const getVehicles = async () => {
|
||
const response = await doormileAxios.get('/admin/vehicles');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createVehicle = async (data) => {
|
||
const response = await doormileAxios.post('/admin/vehicles', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const getVehicle = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/vehicles/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const updateVehicle = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/vehicles/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deleteVehicle = async (id) => {
|
||
// Soft delete server-side.
|
||
const response = await doormileAxios.delete(`/admin/vehicles/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Milers (riders) ||============================== //
|
||
|
||
export const getMilers = async () => {
|
||
// Tenant-scoped server-side from the token — no tenantid param needed/accepted.
|
||
const response = await doormileAxios.get('/admin/milers');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createMiler = async (data) => {
|
||
// configid defaults to 1001 server-side (the miler-app login partition) — do not override it.
|
||
const response = await doormileAxios.post('/admin/milers', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const getMiler = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/milers/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const updateMiler = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/milers/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const blockMiler = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/milers/${id}/block`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const assignMilerVehicle = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/milers/${id}/assign-vehicle`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const notifyMiler = async (milerProfileId, title, message) => {
|
||
const response = await doormileAxios.post(`/admin/milers/${milerProfileId}/notify`, { title, message });
|
||
return response.data;
|
||
};
|
||
|
||
// Confirmed against jupiter2doormile.md (Status: Done — tested against a real
|
||
// request), not in express-console-api.md. jupiter used fromdate/todate;
|
||
// Doormile uses from/to (both YYYY-MM-DD). Aggregate rider KPI counts +
|
||
// current position (currentlatitude/currentlongitude/lastpingat per the doc —
|
||
// unconfirmed beyond those three field names).
|
||
export const getMilerSummary = async (applocationid, from, to, tenantid, hubid) => {
|
||
const response = await doormileAxios.get(`/admin/milers/summary${buildQuery({ applocationid, from, to, tenantid, hubid })}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
// Replaces jupiter's `riderlogs` table — the rider's GPS/status ping history.
|
||
export const getMilerLogs = async (id, from, to, limit) => {
|
||
const response = await doormileAxios.get(`/admin/milers/${id}/logs${buildQuery({ from, to, limit })}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
// No jupiter equivalent — new in Doormile.
|
||
export const getMilerActivity = async (id, from, to) => {
|
||
const response = await doormileAxios.get(`/admin/milers/${id}/activity${buildQuery({ from, to })}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
// ==============================|| Bookings ||============================== //
|
||
|
||
export const getBookings = async (pageno, pagesize) => {
|
||
// If caller specifically requested page > 1 with small page size:
|
||
if (pageno > 1 && pagesize && pagesize < 100) {
|
||
const response = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno, pagesize })}`);
|
||
return response.data.data || [];
|
||
}
|
||
|
||
// The backend hard-caps pagesize at 100 per request.
|
||
// Drain all pages so no bookings are silently lost.
|
||
const firstRes = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno: 1, pagesize: 100 })}`);
|
||
const body = firstRes.data || {};
|
||
const firstRows = body.data || [];
|
||
const total = Number.isFinite(Number(body.total)) ? Number(body.total) : firstRows.length;
|
||
|
||
if (firstRows.length >= total || firstRows.length < 100) {
|
||
return firstRows;
|
||
}
|
||
|
||
const allRows = [...firstRows];
|
||
const totalPages = Math.ceil(total / 100);
|
||
for (let p = 2; p <= totalPages; p++) {
|
||
try {
|
||
const res = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno: p, pagesize: 100 })}`);
|
||
const rows = res.data?.data || [];
|
||
if (!rows.length) break;
|
||
allRows.push(...rows);
|
||
if (allRows.length >= total) break;
|
||
} catch {
|
||
break;
|
||
}
|
||
}
|
||
return allRows;
|
||
};
|
||
|
||
// Same request as getBookings, but keeps the envelope's `total`/`page` instead
|
||
// of throwing them away.
|
||
//
|
||
// getBookings returns `response.data.data` — just the rows — which means no
|
||
// caller can tell a full result from a truncated one. `pagesize` is capped at
|
||
// 100 server-side, and the default is 20 — NOT "default 500, cap 1000", which
|
||
// is what express-console-api.md said until it was corrected. Asking for 1000
|
||
// returns 100 rows and an envelope whose own `pagesize` reads 100, so the
|
||
// under-fetch is silent unless you check it. Any account with more than 100
|
||
// bookings therefore gets a partial list from a single call. That's tolerable
|
||
// for a paginated table (it fetches
|
||
// the next page as you scroll) but NOT for anything that counts or sums, which
|
||
// would report a confidently wrong number.
|
||
//
|
||
// Kept as a separate export rather than changing getBookings' return shape:
|
||
// four callers (fetchDeliveries, fetchCountAPI, orders.js, the assistant's own
|
||
// per-order lookup) already destructure it as a bare array.
|
||
export const getBookingsPage = async (pageno, pagesize) => {
|
||
const response = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno, pagesize })}`);
|
||
const body = response.data || {};
|
||
return {
|
||
rows: body.data || [],
|
||
// `total` is documented as present on list responses; fall back to the row
|
||
// count so a backend that omits it degrades to "this is everything" rather
|
||
// than to NaN-driven pagination.
|
||
total: Number.isFinite(Number(body.total)) ? Number(body.total) : (body.data || []).length,
|
||
page: Number(body.page) || pageno
|
||
};
|
||
};
|
||
|
||
export const createExpressBooking = async (data) => {
|
||
// tenantid is forced to the caller's own tenant server-side on client logins.
|
||
// CityGate applies: pickup pincode prefix must be an open city (641/600/560/500/629).
|
||
const response = await doormileAxios.post('/admin/expressbooking', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const createExpressBookingBulk = async (bookings) => {
|
||
const BATCH_SIZE = 200;
|
||
const list = Array.isArray(bookings) ? bookings : [];
|
||
|
||
if (list.length <= BATCH_SIZE) {
|
||
const response = await doormileAxios.post('/admin/expressbooking/bulk', { bookings: list });
|
||
return response.data;
|
||
}
|
||
|
||
// Automatic chunking for requests exceeding the backend's 200 per-call limit (e.g. 1,000 orders)
|
||
const chunks = [];
|
||
for (let i = 0; i < list.length; i += BATCH_SIZE) {
|
||
chunks.push(list.slice(i, i + BATCH_SIZE));
|
||
}
|
||
|
||
const results = [];
|
||
let totalCreated = 0;
|
||
let lastErrorMessage = '';
|
||
|
||
for (const chunk of chunks) {
|
||
try {
|
||
const response = await doormileAxios.post('/admin/expressbooking/bulk', { bookings: chunk });
|
||
const body = response.data || {};
|
||
results.push(body);
|
||
if (body.success !== false) {
|
||
totalCreated += chunk.length;
|
||
} else {
|
||
lastErrorMessage = body.message || 'One batch failed';
|
||
}
|
||
} catch (err) {
|
||
lastErrorMessage = err.response?.data?.message || err.message || 'Batch request failed';
|
||
results.push({ success: false, message: lastErrorMessage });
|
||
}
|
||
}
|
||
|
||
const anySuccess = totalCreated > 0;
|
||
const allSuccess = totalCreated === list.length;
|
||
|
||
return {
|
||
success: anySuccess,
|
||
message: allSuccess
|
||
? `${list.length} orders created successfully`
|
||
: `${totalCreated} of ${list.length} orders created${lastErrorMessage ? ` (${lastErrorMessage})` : ''}`,
|
||
data: results.flatMap((r) => r.data || []),
|
||
total: totalCreated,
|
||
batches: chunks.length
|
||
};
|
||
};
|
||
|
||
export const getBooking = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/bookings/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const assignMilerToBooking = async (id, data) => {
|
||
const response = await doormileAxios.post(`/admin/bookings/${id}/assign-miler`, data);
|
||
return response.data;
|
||
};
|
||
|
||
// Doormile-native bulk assign: picks real milers via Redis GEO + AI scoring, no
|
||
// external solver involved. Commits server-side in this one call — unlike the
|
||
// workolik solver flow, there's no separate preview/reconcile/commit step.
|
||
// Sequencing (POST /optimization/doormile/sequence) is not deployed yet, so
|
||
// riders with more than one stop come back unsequenced (step: 0) — see
|
||
// doormile-flow.md's State table.
|
||
export const batchAssignBookings = async (bookingIds, maxPerRider = 5) => {
|
||
const response = await doormileAxios.post('/hub/bookings/batch-assign', {
|
||
bookingids: bookingIds,
|
||
max_per_rider: maxPerRider
|
||
});
|
||
return response.data;
|
||
};
|
||
|
||
export const assignVehicleToBooking = async (id, data) => {
|
||
const response = await doormileAxios.post(`/admin/bookings/${id}/assign-vehicle`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const updateBookingStatus = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/bookings/${id}/status`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const cancelBooking = async (id, data) => {
|
||
const response = await doormileAxios.post(`/admin/bookings/${id}/cancel`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const bulkCancelBookings = async (bookingIds) => {
|
||
const response = await doormileAxios.post('/admin/bookings/bulk-cancel', { bookingIds });
|
||
return response.data;
|
||
};
|
||
|
||
// Confirmed against jupiter2doormile.md (Status: Done), not in
|
||
// express-console-api.md.
|
||
export const getBookingTrack = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/bookings/${id}/track`);
|
||
return response.data.data;
|
||
};
|
||
|
||
// ==============================|| Consignments ||============================== //
|
||
|
||
export const getConsignments = async () => {
|
||
const response = await doormileAxios.get('/admin/consignments');
|
||
return response.data.data;
|
||
};
|
||
|
||
// Confirmed against jupiter2doormile.md (Status: Done) — the actual GPS/status
|
||
// trail for a consignment, not in express-console-api.md.
|
||
export const getConsignmentLogs = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/consignments/${id}/logs`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const getConsignment = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/consignments/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const trackConsignment = async (trackingno) => {
|
||
const response = await doormileAxios.get(`/admin/consignments/track/${trackingno}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const updateConsignmentStatus = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/consignments/${id}/status`, data);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Tripsheets (hub-to-hub transport) ||============================== //
|
||
|
||
export const getTripsheets = async () => {
|
||
const response = await doormileAxios.get('/admin/tripsheets');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createTripsheet = async (data) => {
|
||
const response = await doormileAxios.post('/admin/tripsheets', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const getTripsheet = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/tripsheets/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const addTripsheetItem = async (id, consignmentid) => {
|
||
const response = await doormileAxios.post(`/admin/tripsheets/${id}/items`, { consignmentid });
|
||
return response.data;
|
||
};
|
||
|
||
export const removeTripsheetItem = async (id, itemId) => {
|
||
const response = await doormileAxios.delete(`/admin/tripsheets/${id}/items/${itemId}`);
|
||
return response.data;
|
||
};
|
||
|
||
export const dispatchTripsheet = async (id) => {
|
||
const response = await doormileAxios.put(`/admin/tripsheets/${id}/dispatch`);
|
||
return response.data;
|
||
};
|
||
|
||
export const arriveTripsheet = async (id) => {
|
||
const response = await doormileAxios.put(`/admin/tripsheets/${id}/arrive`);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Pricing ||============================== //
|
||
|
||
export const getAdminPricing = async () => {
|
||
const response = await doormileAxios.get('/admin/pricing');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createAdminPricing = async (data) => {
|
||
const response = await doormileAxios.post('/admin/pricing', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const updateAdminPricing = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/pricing/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deleteAdminPricing = async (id) => {
|
||
const response = await doormileAxios.delete(`/admin/pricing/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
export const simulatePricing = async (data) => {
|
||
const response = await doormileAxios.post('/admin/pricing/simulate', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const quotePricing = async (data) => {
|
||
// Same handler as simulate.
|
||
const response = await doormileAxios.post('/admin/pricing/quote', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const getDoormilePricing = async () => {
|
||
const response = await doormileAxios.get('/admin/doormile-pricing');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createDoormilePricing = async (data) => {
|
||
const response = await doormileAxios.post('/admin/doormile-pricing', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const updateDoormilePricing = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/doormile-pricing/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deleteDoormilePricing = async (id) => {
|
||
// Soft delete server-side.
|
||
const response = await doormileAxios.delete(`/admin/doormile-pricing/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Exceptions ||============================== //
|
||
|
||
export const getExceptions = async () => {
|
||
const response = await doormileAxios.get('/admin/exceptions');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const createException = async (data) => {
|
||
const response = await doormileAxios.post('/admin/exceptions', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const getException = async (id) => {
|
||
const response = await doormileAxios.get(`/admin/exceptions/${id}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
export const updateExceptionStatus = async (id, status, resolution) => {
|
||
// status: 'Resolved' | 'Closed'.
|
||
const response = await doormileAxios.put(`/admin/exceptions/${id}/status`, { status, resolution });
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| Competitive intel ||============================== //
|
||
|
||
// Confirmed live: this resource paginates on page/limit, not the
|
||
// pageno/pagesize convention used everywhere else in the API — and defaults
|
||
// to only 50 rows (there are 339+ competitor branches on a real tenant), so
|
||
// callers must page through it explicitly rather than trusting one fetch to
|
||
// return everything. Returns the full envelope (data/limit/page/total) so
|
||
// the caller can build pagination controls.
|
||
export const getCompetitorBranches = async (page, limit) => {
|
||
const response = await doormileAxios.get(`/admin/competitor-branches${buildQuery({ page, limit })}`);
|
||
return response.data;
|
||
};
|
||
|
||
export const createCompetitorBranch = async (data) => {
|
||
const response = await doormileAxios.post('/admin/competitor-branches', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const updateCompetitorBranch = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/competitor-branches/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deleteCompetitorBranch = async (id) => {
|
||
const response = await doormileAxios.delete(`/admin/competitor-branches/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// Same page/limit pagination convention as competitor-branches (confirmed
|
||
// live) — 368+ rows on a real tenant, default page returns only 100.
|
||
export const getCarrierPricing = async (page, limit) => {
|
||
const response = await doormileAxios.get(`/admin/carrier-pricing${buildQuery({ page, limit })}`);
|
||
return response.data;
|
||
};
|
||
|
||
export const createCarrierPricing = async (data) => {
|
||
const response = await doormileAxios.post('/admin/carrier-pricing', data);
|
||
return response.data;
|
||
};
|
||
|
||
export const updateCarrierPricing = async (id, data) => {
|
||
const response = await doormileAxios.put(`/admin/carrier-pricing/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
export const deleteCarrierPricing = async (id) => {
|
||
const response = await doormileAxios.delete(`/admin/carrier-pricing/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// ==============================|| AI agent registry ||============================== //
|
||
// /admin/ai/* — Doormile staff only (a partner-tenant login gets 403). Reads
|
||
// are open to admin/manager/executive; every write is admin-only and audited
|
||
// server-side. See docs/agent-platform-plan.md.
|
||
|
||
export const getAiAgents = async () => {
|
||
const response = await doormileAxios.get('/admin/ai/agents');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const getAiSkills = async () => {
|
||
const response = await doormileAxios.get('/admin/ai/skills');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const getAiTools = async () => {
|
||
const response = await doormileAxios.get('/admin/ai/tools');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const getAiRegistryAudit = async (limit) => {
|
||
const response = await doormileAxios.get(`/admin/ai/audit${buildQuery({ limit })}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
/** @param {string} id @param {{ enabled?: boolean, thresholds?: Record<string, number> }} patch */
|
||
export const updateAiSkill = async (id, patch) => {
|
||
const response = await doormileAxios.patch(`/admin/ai/skills/${encodeURIComponent(id)}`, patch);
|
||
return response.data;
|
||
};
|
||
|
||
/** @param {{ agentid: string, title: string, category?: string, description?: string, sampleprompt?: string, tools: string[] }} skill */
|
||
export const createAiSkill = async (skill) => {
|
||
const response = await doormileAxios.post('/admin/ai/skills', skill);
|
||
return response.data;
|
||
};
|
||
|
||
/**
|
||
* Autonomy ON needs `confirm` set to the agent id — the server refuses it
|
||
* otherwise, so a stray click cannot let an agent act without a human.
|
||
* @param {string} id @param {{ autonomous?: boolean, model?: string, confirm?: string }} patch
|
||
*/
|
||
export const updateAiAgent = async (id, patch) => {
|
||
const response = await doormileAxios.patch(`/admin/ai/agents/${encodeURIComponent(id)}`, patch);
|
||
return response.data;
|
||
};
|
||
|
||
/**
|
||
* What the agents did over the last `days` (1–30): runs and failures per
|
||
* AI_engine agent, decisions by type/outcome, live heartbeat. `receiving` is
|
||
* false when the backend is not subscribed to AI_engine telemetry.
|
||
* @param {number} days
|
||
*/
|
||
/**
|
||
* What is actually wired, for the Skills & Tools banner: whether AI_engine is
|
||
* reading the registry (it polls ~every 30 s; the backend records each read),
|
||
* telemetry, live agents, and whether the Test tab has a model.
|
||
*/
|
||
export const getAiStatus = async () => {
|
||
const response = await doormileAxios.get('/admin/ai/status');
|
||
return response.data.data;
|
||
};
|
||
|
||
export const getAiInsights = async (days = 7) => {
|
||
const response = await doormileAxios.get(`/admin/ai/insights${buildQuery({ days })}`);
|
||
return response.data.data;
|
||
};
|
||
|
||
/**
|
||
* Agent Studio's Test tab: one prompt through Claude with a skill's tools.
|
||
* Read tools run with personal data redacted; write tools come back as
|
||
* proposals and are never executed. Admin only. 503 (code
|
||
* PLAYGROUND_NOT_CONFIGURED) when the server has no Claude client; 429 when
|
||
* the per-user limit is reached.
|
||
* @param {{ agentid: string, skillid?: string, prompt: string }} body
|
||
*/
|
||
export const runAiPlayground = async (body) => {
|
||
const response = await doormileAxios.post('/admin/ai/playground/run', body);
|
||
return response.data.data;
|
||
};
|
||
|
||
/** Recent agent decisions, newest first. `before` is the last id of the previous page. */
|
||
export const getAiDecisions = async ({ type, before, limit } = {}) => {
|
||
const response = await doormileAxios.get(`/admin/ai/decisions${buildQuery({ type, before, limit })}`);
|
||
return response.data.data;
|
||
};
|