Files
doormilxpress_astryx/src/api/doormile/endpoints.js

862 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
};