Fix: Throughput API v1 available - Details posted to Irving. POWERBI_KEY was missing from the .ENV file, so was not live.
Add: Editor now supports editing a mix's resolved formula directly, with % and kg dual entry on ingredient rows
Fix: Mix Editor should bring through correct ingredients. New resolved formula (same logic we use in Mix Calculator).
Fix: Security headers on all API responses (hardening)
Add: New mix button available on the Mix Editor.
Add: New ingredient button available on the Ingredient Editor
This commit is contained in:
2026-06-17 21:55:04 +12:00
parent 7db95e2027
commit 3f8279af10
24 changed files with 3820 additions and 37 deletions
+54
View File
@@ -7,6 +7,14 @@ import type {
ClientUserCreateInput,
ClientUserModulePermission,
ClientUserUpdateInput,
InternalUser,
InternalRoleOption,
InternalRole,
InternalRoleCreateInput,
InternalRoleModuleDefinition,
InternalRoleUpdateInput,
InternalUserCreateInput,
InternalUserUpdateInput,
LoginResponse,
EditorMixCreateInput,
EditorMixUpdateInput,
@@ -17,6 +25,7 @@ import type {
EditorIngredientRow,
EditorIngredientCreateInput,
EditorIngredientUpdateInput,
EditorChangeEvent,
EditorProductFormula,
EditorProductRow,
EditorProductUpdateInput,
@@ -49,6 +58,7 @@ import type {
XeroContactList,
XeroContactLinkRow,
Scenario,
ThroughputDeleteAllResult,
ThroughputEntry,
ThroughputEntryCreateInput,
ThroughputEntryUpdateInput,
@@ -453,6 +463,10 @@ export const api = {
method: 'PATCH',
body: JSON.stringify(payload)
}, 'client'),
editorMixHistory: (mixId: number) =>
request<EditorChangeEvent[]>(`/api/editor/mixes/${mixId}/history`, {}, 'client'),
editorIngredientHistory: (ingredientId: number) =>
request<EditorChangeEvent[]>(`/api/editor/ingredients/${ingredientId}/history`, {}, 'client'),
productCosts: (fetcher?: ApiFetch) =>
cachedFetchJson<ProductCostBreakdown[]>('/api/powerbi/product-costs', 'client', fetcher),
productCostingItems: (fetcher?: ApiFetch) =>
@@ -505,6 +519,8 @@ export const api = {
formData.append('file', file);
return uploadFile<ThroughputImportResult>('/api/throughput/import', formData, 'client');
},
deleteAllThroughputEntries: () =>
request<ThroughputDeleteAllResult>('/api/throughput/entries', { method: 'DELETE' }, 'client'),
createThroughputProduct: (payload: ThroughputProductCreateInput) =>
request<ThroughputProduct>('/api/throughput/products', {
method: 'POST',
@@ -541,6 +557,44 @@ export const api = {
method: 'PATCH',
body: JSON.stringify(payload)
}, 'client'),
// --- Internal user management (lean/admin: manage_users) ------------------
accessUsers: (fetcher?: ApiFetch) =>
request<InternalUser[]>('/api/access/users', { method: 'GET' }, 'client', fetcher),
accessAssignableRoles: (fetcher?: ApiFetch) =>
request<InternalRoleOption[]>('/api/access/assignable-roles', { method: 'GET' }, 'client', fetcher),
accessRoles: (fetcher?: ApiFetch) =>
request<InternalRole[]>('/api/access/roles', { method: 'GET' }, 'client', fetcher),
accessRoleModules: (fetcher?: ApiFetch) =>
request<InternalRoleModuleDefinition[]>('/api/access/role-modules', { method: 'GET' }, 'client', fetcher),
createAccessRole: (payload: InternalRoleCreateInput) =>
request<InternalRole>('/api/access/roles', {
method: 'POST',
body: JSON.stringify(payload)
}, 'client'),
updateAccessRole: (roleId: number, payload: InternalRoleUpdateInput) =>
request<InternalRole>(`/api/access/roles/${roleId}`, {
method: 'PATCH',
body: JSON.stringify(payload)
}, 'client'),
deleteAccessRole: (roleId: number) =>
request<void>(`/api/access/roles/${roleId}`, { method: 'DELETE' }, 'client'),
createAccessUser: (payload: InternalUserCreateInput) =>
request<InternalUser>('/api/access/users', {
method: 'POST',
body: JSON.stringify(payload)
}, 'client'),
updateAccessUser: (userId: number, payload: InternalUserUpdateInput) =>
request<InternalUser>(`/api/access/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify(payload)
}, 'client'),
setAccessUserPassword: (userId: number, newPassword: string) =>
request<InternalUser>(`/api/access/users/${userId}/password`, {
method: 'POST',
body: JSON.stringify({ new_password: newPassword })
}, 'client'),
deleteAccessUser: (userId: number) =>
request<void>(`/api/access/users/${userId}`, { method: 'DELETE' }, 'client'),
adminLogin: (email: string, password: string) =>
request<LoginResponse>('/api/auth/admin/login', {
method: 'POST',