Skip to content

Commit

Permalink
feat: customer bulk endpoint form managing customer groups (#9761)
Browse files Browse the repository at this point in the history
Co-authored-by: Oli Juhl <[email protected]>
  • Loading branch information
fPolic and olivermrbl authored Oct 25, 2024
1 parent bb6d7c6 commit 259d050
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 32 deletions.
62 changes: 62 additions & 0 deletions integration-tests/http/__tests__/customer/admin/customer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,68 @@ medusaIntegrationTestRunner({
})
})

describe("POST /admin/customers/:id/customer-groups", () => {
it("should batch add and remove customer to/from customer groups", async () => {
const group1 = (
await api.post(
"/admin/customer-groups",
{
name: "VIP 1",
},
adminHeaders
)
).data.customer_group

const group2 = (
await api.post(
"/admin/customer-groups",
{
name: "VIP 2",
},
adminHeaders
)
).data.customer_group

const group3 = (
await api.post(
"/admin/customer-groups",
{
name: "VIP 3",
},
adminHeaders
)
).data.customer_group

// Add with cg endpoint so we can test remove
await api.post(
`/admin/customer-groups/${group1.id}/customers`,
{
add: [customer1.id],
},
adminHeaders
)

const response = await api.post(
`/admin/customers/${customer1.id}/customer-groups?fields=groups.id`,
{
remove: [group1.id],
add: [group2.id, group3.id],
},
adminHeaders
)

expect(response.status).toEqual(200)

expect(response.data.customer.groups.length).toEqual(2)
expect(response.data.customer.groups).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: group2.id }),
expect.objectContaining({ id: group3.id }),
])
)
})
})

describe("GET /admin/customers/:id", () => {
it("should fetch a customer", async () => {
const response = await api.get(
Expand Down
33 changes: 33 additions & 0 deletions packages/admin/dashboard/src/hooks/api/customers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { sdk } from "../../lib/client"
import { queryClient } from "../../lib/query-client"
import { queryKeysFactory } from "../../lib/query-key-factory"
import { customerGroupsQueryKeys } from "./customer-groups"

const CUSTOMERS_QUERY_KEY = "customers" as const
export const customersQueryKeys = queryKeysFactory(CUSTOMERS_QUERY_KEY)
Expand Down Expand Up @@ -115,3 +116,35 @@ export const useDeleteCustomer = (
...options,
})
}

export const useBatchCustomerCustomerGroups = (
id: string,
options?: UseMutationOptions<
HttpTypes.AdminCustomerResponse,
FetchError,
HttpTypes.AdminBatchLink
>
) => {
return useMutation({
mutationFn: (payload) =>
sdk.admin.customer.batchCustomerGroups(id, payload),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: customerGroupsQueryKeys.details(),
})
queryClient.invalidateQueries({
queryKey: customerGroupsQueryKeys.lists(),
})

queryClient.invalidateQueries({
queryKey: customersQueryKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: customersQueryKeys.details(),
})

options?.onSuccess?.(data, variables, context)
},
...options,
})
}
6 changes: 6 additions & 0 deletions packages/admin/dashboard/src/i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,12 @@
"list": {
"noRecordsMessage": "Please create a customer group first."
}
},
"removed": {
"success": "Customer removed from: {{groups}}.",
"list": {
"noRecordsMessage": "Please create a customer group first."
}
}
},
"edit": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { useCustomerGroupTableQuery } from "../../../../../hooks/table/query/use
import { useDataTable } from "../../../../../hooks/use-data-table.tsx"
import { sdk } from "../../../../../lib/client/index.ts"
import { queryClient } from "../../../../../lib/query-client.ts"
import { useBatchCustomerCustomerGroups } from "../../../../../hooks/api"

type CustomerGroupSectionProps = {
customer: HttpTypes.AdminCustomer
Expand Down Expand Up @@ -57,6 +58,9 @@ export const CustomerGroupSection = ({
}
)

const { mutateAsync: batchCustomerCustomerGroups } =
useBatchCustomerCustomerGroups(customer.id)

const filters = useCustomerGroupTableFilters()
const columns = useColumns(customer.id)

Expand Down Expand Up @@ -94,20 +98,15 @@ export const CustomerGroupSection = ({
}

try {
/**
* TODO: use this for now until add customer groups to customers batch is implemented
*/
const promises = customerGroupIds.map((id) =>
sdk.admin.customerGroup.batchCustomers(id, {
remove: [customer.id],
await batchCustomerCustomerGroups({ remove: customerGroupIds })

toast.success(
t("customers.groups.removed.success", {
groups: customer_groups!
.filter((cg) => customerGroupIds.includes(cg.id))
.map((cg) => cg?.name),
})
)

await Promise.all(promises)

await queryClient.invalidateQueries({
queryKey: customerGroupsQueryKeys.lists(),
})
} catch (e) {
toast.error(e.message)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,12 @@ import {
} from "../../../../../components/modals"
import { DataTable } from "../../../../../components/table/data-table"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import {
customerGroupsQueryKeys,
useCustomerGroups,
} from "../../../../../hooks/api/customer-groups"
import { useCustomerGroups } from "../../../../../hooks/api/customer-groups"
import { useCustomerGroupTableColumns } from "../../../../../hooks/table/columns/use-customer-group-table-columns"
import { useCustomerGroupTableFilters } from "../../../../../hooks/table/filters/use-customer-group-table-filters"
import { useCustomerGroupTableQuery } from "../../../../../hooks/table/query/use-customer-group-table-query"
import { useDataTable } from "../../../../../hooks/use-data-table"
import { sdk } from "../../../../../lib/client"
import { queryClient } from "../../../../../lib/query-client"
import { useBatchCustomerCustomerGroups } from "../../../../../hooks/api"

type AddCustomerGroupsFormProps = {
customerId: string
Expand All @@ -45,6 +41,9 @@ export const AddCustomerGroupsForm = ({
const { handleSuccess } = useRouteModal()
const [isPending, setIsPending] = useState(false)

const { mutateAsync: batchCustomerCustomerGroups } =
useBatchCustomerCustomerGroups(customerId)

const form = useForm<zod.infer<typeof AddCustomerGroupsSchema>>({
defaultValues: {
customer_group_ids: [],
Expand Down Expand Up @@ -117,16 +116,7 @@ export const AddCustomerGroupsForm = ({
const handleSubmit = form.handleSubmit(async (data) => {
setIsPending(true)
try {
/**
* TODO: use this for now until add customer groups to customers batch is implemented
*/
const promises = data.customer_group_ids.map((id) =>
sdk.admin.customerGroup.batchCustomers(id, {
add: [customerId],
})
)

await Promise.all(promises)
await batchCustomerCustomerGroups({ add: data.customer_group_ids })

toast.success(
t("customers.groups.add.success", {
Expand All @@ -137,10 +127,6 @@ export const AddCustomerGroupsForm = ({
})
)

await queryClient.invalidateQueries({
queryKey: customerGroupsQueryKeys.lists(),
})

handleSuccess(`/customers/${customerId}`)
} catch (e) {
toast.error(e.message)
Expand Down
1 change: 1 addition & 0 deletions packages/core/core-flows/src/customer-group/steps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./update-customer-groups"
export * from "./delete-customer-groups"
export * from "./create-customer-groups"
export * from "./link-customers-customer-group"
export * from "./link-customer-groups-customer"
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
ICustomerModuleService,
LinkWorkflowInput,
} from "@medusajs/framework/types"
import { Modules, promiseAll } from "@medusajs/framework/utils"
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"

export const linkCustomerGroupsToCustomerStepId =
"link-customers-to-customer-group"
/**
* This step creates one or more links between a customer and customer groups records.
*/
export const linkCustomerGroupsToCustomerStep = createStep(
linkCustomerGroupsToCustomerStepId,
async (data: LinkWorkflowInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(Modules.CUSTOMER)

const toAdd = (data.add ?? []).map((customerGroupId) => {
return {
customer_group_id: customerGroupId,
customer_id: data.id,
}
})

const toRemove = (data.remove ?? []).map((customerGroupId) => {
return {
customer_group_id: customerGroupId,
customer_id: data.id,
}
})

const promises: Promise<any>[] = []
if (toAdd.length) {
promises.push(service.addCustomerToGroup(toAdd))
}
if (toRemove.length) {
promises.push(service.removeCustomerFromGroup(toRemove))
}
await promiseAll(promises)

return new StepResponse(void 0, { toAdd, toRemove })
},
async (prevData, { container }) => {
if (!prevData) {
return
}
const service = container.resolve<ICustomerModuleService>(Modules.CUSTOMER)

if (prevData.toAdd.length) {
await service.removeCustomerFromGroup(prevData.toAdd)
}
if (prevData.toRemove.length) {
await service.addCustomerToGroup(prevData.toRemove)
}
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./update-customer-groups"
export * from "./delete-customer-groups"
export * from "./create-customer-groups"
export * from "./link-customers-customer-group"
export * from "./link-customer-groups-customer"
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LinkWorkflowInput } from "@medusajs/framework/types"
import { WorkflowData, createWorkflow } from "@medusajs/framework/workflows-sdk"
import { linkCustomerGroupsToCustomerStep } from "../steps"

export const linkCustomerGroupsToCustomerWorkflowId =
"link-customer-groups-to-customer"
/**
* This workflow creates one or more links between a customer and customer groups.
*/
export const linkCustomerGroupsToCustomerWorkflow = createWorkflow(
linkCustomerGroupsToCustomerWorkflowId,
(input: WorkflowData<LinkWorkflowInput>): WorkflowData<void> => {
return linkCustomerGroupsToCustomerStep(input)
}
)
34 changes: 34 additions & 0 deletions packages/core/js-sdk/src/admin/customer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,38 @@ export class Customer {
}
)
}

/**
* This method manages customer groups for a customer.
* It sends a request to the [Manage Customers](https://docs.medusajs.com/api/admin#customers_postcustomersidcustomergroups)
* API route.
*
* @param id - The customer's ID.
* @param body - The groups to add customer to or remove customer from.
* @param headers - Headers to pass in the request
* @returns The customers details.
*
* @example
* sdk.admin.customer.batchCustomerGroups("cus_123", {
* add: ["cusgroup_123"],
* remove: ["cusgroup_321"]
* })
* .then(({ customer }) => {
* console.log(customer)
* })
*/
async batchCustomerGroups(
id: string,
body: HttpTypes.AdminBatchLink,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminCustomerResponse>(
`/admin/customers/${id}/customer-groups`,
{
method: "POST",
headers,
body,
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { linkCustomerGroupsToCustomerWorkflow } from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"

import { HttpTypes, LinkMethodRequest } from "@medusajs/framework/types"

import { refetchCustomer } from "../../helpers"

export const POST = async (
req: AuthenticatedMedusaRequest<LinkMethodRequest>,
res: MedusaResponse<HttpTypes.AdminCustomerResponse>
) => {
const { id } = req.params
const { add, remove } = req.validatedBody

const workflow = linkCustomerGroupsToCustomerWorkflow(req.scope)
await workflow.run({
input: {
id,
add,
remove,
},
})

const customer = await refetchCustomer(
id,
req.scope,
req.remoteQueryConfig.fields
)

res.status(200).json({ customer: customer })
}
Loading

0 comments on commit 259d050

Please sign in to comment.