feat(customer): manage default address selection (#6295)

**What**
- Catches unique constraints on customer_id, is_default_billing/is_default_shipping and reformats
- Adds an step to create and update of addresses that unsets the previous default shipping/billing address if necessary.
  - This creates a behavior in the API where you can always set an address to be default and it will automatically unset the previous one for you.
This commit is contained in:
Sebastian Rindom
2024-02-01 13:28:14 +01:00
committed by GitHub
parent a28822e0d4
commit a2bf6756ac
16 changed files with 506 additions and 15 deletions

View File

@@ -4,3 +4,5 @@ export * from "./delete-customers"
export * from "./create-addresses"
export * from "./update-addresses"
export * from "./delete-addresses"
export * from "./maybe-unset-default-billing-addresses"
export * from "./maybe-unset-default-shipping-addresses"

View File

@@ -0,0 +1,61 @@
import {
ICustomerModuleService,
CreateCustomerAddressDTO,
FilterableCustomerAddressProps,
CustomerAddressDTO,
} from "@medusajs/types"
import { createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { unsetForUpdate, unsetForCreate } from "./utils"
import { isDefined } from "@medusajs/utils"
type StepInput = {
create?: CreateCustomerAddressDTO[]
update?: {
selector: FilterableCustomerAddressProps
update: Partial<CustomerAddressDTO>
}
}
export const maybeUnsetDefaultBillingAddressesStepId =
"maybe-unset-default-billing-customer-addresses"
export const maybeUnsetDefaultBillingAddressesStep = createStep(
maybeUnsetDefaultBillingAddressesStepId,
async (data: StepInput, { container }) => {
const customerModuleService = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
if (isDefined(data.create)) {
return unsetForCreate(
data.create,
customerModuleService,
"is_default_billing"
)
}
if (isDefined(data.update)) {
return unsetForUpdate(
data.update,
customerModuleService,
"is_default_billing"
)
}
throw new Error("Invalid step input")
},
async (addressesToSet, { container }) => {
if (!addressesToSet?.length) {
return
}
const customerModuleService = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await customerModuleService.updateAddress(
{ id: addressesToSet },
{ is_default_billing: true }
)
}
)

View File

@@ -0,0 +1,60 @@
import {
ICustomerModuleService,
CreateCustomerAddressDTO,
FilterableCustomerAddressProps,
CustomerAddressDTO,
} from "@medusajs/types"
import { createStep } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { unsetForUpdate, unsetForCreate } from "./utils"
import { isDefined } from "@medusajs/utils"
type StepInput = {
create?: CreateCustomerAddressDTO[]
update?: {
selector: FilterableCustomerAddressProps
update: Partial<CustomerAddressDTO>
}
}
export const maybeUnsetDefaultShippingAddressesStepId =
"maybe-unset-default-shipping-customer-addresses"
export const maybeUnsetDefaultShippingAddressesStep = createStep(
maybeUnsetDefaultShippingAddressesStepId,
async (data: StepInput, { container }) => {
const customerModuleService = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
if (isDefined(data.create)) {
return unsetForCreate(
data.create,
customerModuleService,
"is_default_shipping"
)
}
if (isDefined(data.update)) {
return unsetForUpdate(
data.update,
customerModuleService,
"is_default_shipping"
)
}
throw new Error("Invalid step input")
},
async (addressesToSet, { container }) => {
if (!addressesToSet?.length) {
return
}
const customerModuleService = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await customerModuleService.updateAddress(
{ id: addressesToSet },
{ is_default_shipping: true }
)
}
)

View File

@@ -0,0 +1,2 @@
export * from "./unset-address-for-create"
export * from "./unset-address-for-update"

View File

@@ -0,0 +1,33 @@
import {
CreateCustomerAddressDTO,
ICustomerModuleService,
} from "@medusajs/types"
import { StepResponse } from "@medusajs/workflows-sdk"
export const unsetForCreate = async (
data: CreateCustomerAddressDTO[],
customerService: ICustomerModuleService,
field: "is_default_billing" | "is_default_shipping"
) => {
const customerIds = data.reduce<string[]>((acc, curr) => {
if (curr[field]) {
acc.push(curr.customer_id)
}
return acc
}, [])
const customerDefaultAddresses = await customerService.listAddresses({
customer_id: customerIds,
[field]: true,
})
await customerService.updateAddress(
{ customer_id: customerIds, [field]: true },
{ [field]: false }
)
return new StepResponse(
void 0,
customerDefaultAddresses.map((address) => address.id)
)
}

View File

@@ -0,0 +1,40 @@
import {
CustomerAddressDTO,
FilterableCustomerAddressProps,
ICustomerModuleService,
} from "@medusajs/types"
import { StepResponse } from "@medusajs/workflows-sdk"
export const unsetForUpdate = async (
data: {
selector: FilterableCustomerAddressProps
update: Partial<CustomerAddressDTO>
},
customerService: ICustomerModuleService,
field: "is_default_billing" | "is_default_shipping"
) => {
if (!data.update[field]) {
return new StepResponse(void 0)
}
const affectedCustomers = await customerService.listAddresses(data.selector, {
select: ["id", "customer_id"],
})
const customerIds = affectedCustomers.map((address) => address.customer_id)
const customerDefaultAddresses = await customerService.listAddresses({
customer_id: customerIds,
[field]: true,
})
await customerService.updateAddress(
{ customer_id: customerIds, [field]: true },
{ [field]: false }
)
return new StepResponse(
void 0,
customerDefaultAddresses.map((address) => address.id)
)
}

View File

@@ -1,6 +1,15 @@
import { CreateCustomerAddressDTO, CustomerAddressDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createCustomerAddressesStep } from "../steps"
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import {
createCustomerAddressesStep,
maybeUnsetDefaultBillingAddressesStep,
maybeUnsetDefaultShippingAddressesStep,
} from "../steps"
type WorkflowInput = { addresses: CreateCustomerAddressDTO[] }
@@ -8,6 +17,15 @@ export const createCustomerAddressesWorkflowId = "create-customer-addresses"
export const createCustomerAddressesWorkflow = createWorkflow(
createCustomerAddressesWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerAddressDTO[]> => {
const unsetInput = transform(input, (data) => ({
create: data.addresses,
}))
parallelize(
maybeUnsetDefaultShippingAddressesStep(unsetInput),
maybeUnsetDefaultBillingAddressesStep(unsetInput)
)
return createCustomerAddressesStep(input.addresses)
}
)

View File

@@ -2,8 +2,17 @@ import {
FilterableCustomerAddressProps,
CustomerAddressDTO,
} from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { updateCustomerAddressesStep } from "../steps"
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import {
maybeUnsetDefaultBillingAddressesStep,
maybeUnsetDefaultShippingAddressesStep,
updateCustomerAddressesStep,
} from "../steps"
type WorkflowInput = {
selector: FilterableCustomerAddressProps
@@ -14,6 +23,15 @@ export const updateCustomerAddressesWorkflowId = "update-customer-addresses"
export const updateCustomerAddressesWorkflow = createWorkflow(
updateCustomerAddressesWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerAddressDTO[]> => {
const unsetInput = transform(input, (data) => ({
update: data,
}))
parallelize(
maybeUnsetDefaultShippingAddressesStep(unsetInput),
maybeUnsetDefaultBillingAddressesStep(unsetInput)
)
return updateCustomerAddressesStep(input)
}
)