Skip to content

Commit

Permalink
feat(customer): manage default address selection (#6295)
Browse files Browse the repository at this point in the history
**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.
  • Loading branch information
srindom authored Feb 1, 2024
1 parent a28822e commit a2bf675
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,76 @@ describe("POST /admin/customers/:id/addresses", () => {

expect(customerWithAddresses.addresses?.length).toEqual(1)
})

it("sets new shipping address as default and unsets the old one", async () => {
const customer = await customerModuleService.create({
first_name: "John",
last_name: "Doe",
addresses: [
{
first_name: "John",
last_name: "Doe",
address_1: "Test street 1",
is_default_shipping: true,
},
],
})

const api = useApi() as any
const response = await api.post(
`/admin/customers/${customer.id}/addresses`,
{
first_name: "John",
last_name: "Doe",
address_1: "Test street 2",
is_default_shipping: true,
},
adminHeaders
)

expect(response.status).toEqual(200)

const [address] = await customerModuleService.listAddresses({
customer_id: customer.id,
is_default_shipping: true,
})

expect(address.address_1).toEqual("Test street 2")
})

it("sets new billing address as default and unsets the old one", async () => {
const customer = await customerModuleService.create({
first_name: "John",
last_name: "Doe",
addresses: [
{
first_name: "John",
last_name: "Doe",
address_1: "Test street 1",
is_default_billing: true,
},
],
})

const api = useApi() as any
const response = await api.post(
`/admin/customers/${customer.id}/addresses`,
{
first_name: "John",
last_name: "Doe",
address_1: "Test street 2",
is_default_billing: true,
},
adminHeaders
)

expect(response.status).toEqual(200)

const [address] = await customerModuleService.listAddresses({
customer_id: customer.id,
is_default_billing: true,
})

expect(address.address_1).toEqual("Test street 2")
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,89 @@ describe("POST /admin/customers/:id/addresses/:address_id", () => {
})
)
})

it("updates a new address to be default and unsets old one", async () => {
const customer = await customerModuleService.create({
first_name: "John",
last_name: "Doe",
})
const [, address] = await customerModuleService.addAddresses([
{
customer_id: customer.id,
first_name: "John",
last_name: "Doe",
address_1: "Test street 1",
is_default_shipping: true,
},
{
customer_id: customer.id,
first_name: "John",
last_name: "Doe",
address_1: "Test street 2",
},
])

const api = useApi() as any
const response = await api.post(
`/admin/customers/${customer.id}/addresses/${address.id}`,
{
first_name: "jane",
is_default_shipping: true,
},
adminHeaders
)

expect(response.status).toEqual(200)

const [defaultAddress] = await customerModuleService.listAddresses({
customer_id: customer.id,
is_default_shipping: true,
})

expect(defaultAddress.first_name).toEqual("jane")
expect(defaultAddress.address_1).toEqual("Test street 2")
})

// do the same as above but for billing address
it("updates a new address to be default and unsets old one", async () => {
const customer = await customerModuleService.create({
first_name: "John",
last_name: "Doe",
})
const [, address] = await customerModuleService.addAddresses([
{
customer_id: customer.id,
first_name: "John",
last_name: "Doe",
address_1: "Test street 1",
is_default_billing: true,
},
{
customer_id: customer.id,
first_name: "John",
last_name: "Doe",
address_1: "Test street 2",
},
])

const api = useApi() as any
const response = await api.post(
`/admin/customers/${customer.id}/addresses/${address.id}`,
{
first_name: "jane",
is_default_billing: true,
},
adminHeaders
)

expect(response.status).toEqual(200)

const [defaultAddress] = await customerModuleService.listAddresses({
customer_id: customer.id,
is_default_billing: true,
})

expect(defaultAddress.first_name).toEqual("jane")
expect(defaultAddress.address_1).toEqual("Test street 2")
})
})
2 changes: 2 additions & 0 deletions packages/core-flows/src/customer/steps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
@@ -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 }
)
}
)
Original file line number Diff line number Diff line change
@@ -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 }
)
}
)
2 changes: 2 additions & 0 deletions packages/core-flows/src/customer/steps/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./unset-address-for-create"
export * from "./unset-address-for-update"
Original file line number Diff line number Diff line change
@@ -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)
)
}
Original file line number Diff line number Diff line change
@@ -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)
)
}
Loading

0 comments on commit a2bf675

Please sign in to comment.