diff --git a/litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/CustomerHierarchySettings.test.tsx b/litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/CustomerHierarchySettings.integration.test.tsx
similarity index 89%
rename from litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/CustomerHierarchySettings.test.tsx
rename to litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/CustomerHierarchySettings.integration.test.tsx
index 16b7cf1760c..e5637c75bde 100644
--- a/litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/CustomerHierarchySettings.test.tsx
+++ b/litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/CustomerHierarchySettings.integration.test.tsx
@@ -35,7 +35,7 @@ const member: Member = {
customer_member_id: "member-1",
parent_team_id: "team-1",
alias: "Member One",
- budget_template_id: "template-1",
+ budget_id: "template-1",
budget_override: { max_budget: 20 },
effective_budget: { max_budget: 20 },
budget_override_expires_at: "2026-12-01T12:00:00Z",
@@ -116,13 +116,31 @@ describe("CustomerHierarchySettings", () => {
expect(template).toHaveTextContent("Pro");
await user.click(screen.getByRole("button", { name: "Save changes" }));
await waitFor(() => expect(onSaved).toHaveBeenCalledOnce());
- expect(await lastRequest().clone().json()).toEqual({ budget_template_id: "template-2" });
+ expect(new URL(lastRequest().url).pathname).toBe("/management/v1/customer_members/member-1");
+ expect(await lastRequest().clone().json()).toEqual({ budget_id: "template-2" });
+ });
+
+ it("should clear the template without changing the existing override", async () => {
+ const user = userEvent.setup();
+ fetchMock.mockResolvedValue(json({ ...member, budget_id: null }));
+ render(
+ ,
+ );
+ await chooseSelectOption(user, screen.getByRole("combobox", { name: "Budget template" }), "No template");
+ await user.click(screen.getByRole("button", { name: "Save changes" }));
+ await waitFor(() => expect(onSaved).toHaveBeenCalledOnce());
+ expect(await lastRequest().clone().json()).toEqual({ budget_id: null });
});
it("should identify an unavailable stored template instead of displaying No template", () => {
render(
{
+ it("should load the member template from the API response", () => {
+ expect(customerHierarchyFields({ kind: "member", record: member }).templateId).toBe(member.budget_id);
+ });
+
+ it("should allow an alias-only edit for a member using a template without an override", () => {
+ const entity: CustomerHierarchyEntity = {
+ kind: "member",
+ record: { ...member, budget_override: null, budget_override_expires_at: null },
+ };
+ expect(buildCustomerHierarchyPatch(entity, { ...customerHierarchyFields(entity), alias: "Updated" })).toEqual({
+ ok: true,
+ patch: { kind: "member", body: { alias: "Updated" } },
+ });
+ });
+
+ it.each([
+ ["template-2", "template-2"],
+ ["", null],
+ ])("should submit template selection %s without changing the override", (templateId, budgetId) => {
+ const entity: CustomerHierarchyEntity = { kind: "member", record: member };
+ expect(buildCustomerHierarchyPatch(entity, { ...customerHierarchyFields(entity), templateId })).toEqual({
+ ok: true,
+ patch: { kind: "member", body: { budget_id: budgetId } },
+ });
+ });
+
it("should leave untouched settings out of the patch", () => {
const entity: CustomerHierarchyEntity = { kind: "organization", record: organization };
expect(buildCustomerHierarchyPatch(entity, customerHierarchyFields(entity))).toEqual({
diff --git a/litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/customerHierarchyForms.ts b/litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/customerHierarchyForms.ts
index cce0b915eb7..860ebfb4ab8 100644
--- a/litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/customerHierarchyForms.ts
+++ b/litellm/ui/litellm-dashboard/src/app/(dashboard)/customers/_components/customerHierarchyForms.ts
@@ -66,7 +66,7 @@ export const customerHierarchyFields = (entity: CustomerHierarchyEntity): Custom
tpm: asText(budget?.tpm_limit),
rpm: asText(budget?.rpm_limit),
allowedModels: budget?.allowed_models?.join("\n") ?? "",
- templateId: entity.kind === "member" ? entity.record.budget_template_id ?? "" : "",
+ templateId: entity.kind === "member" ? entity.record.budget_id ?? "" : "",
overrideExpiry: entity.kind === "member" ? localDateTime(entity.record.budget_override_expires_at) : "",
};
};
@@ -125,7 +125,7 @@ const memberPatch = (
const initial = customerHierarchyFields({ kind: "member", record: member });
if (!fields.templateId && !fields.budgetEnabled) return { ok: false, error: "budgetRequired" };
const body: components["schemas"]["MemberPatch"] = aliasChanges(member.alias, fields.alias);
- if (fields.templateId !== initial.templateId) body.budget_template_id = fields.templateId || null;
+ if (fields.templateId !== initial.templateId) body.budget_id = fields.templateId || null;
if (changed) body.budget_override = budget;
if (!fields.budgetEnabled && initial.budgetEnabled) body.budget_override_expires_at = null;
if (fields.budgetEnabled && fields.overrideExpiry !== initial.overrideExpiry) {