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:
1 parent
a28822e0d4
commit
a2bf6756ac
16 files changed
+506
-15
No files matched your search
+56
-2
@@ -100,6 +100,37 @@ describe("Customer Module Service", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to create two default shipping", async () => {
|
||||
const customerData = {
|
||||
company_name: "Acme Corp",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
addresses: [
|
||||
{
|
||||
address_1: "Testvej 1",
|
||||
address_2: "Testvej 2",
|
||||
city: "Testby",
|
||||
country_code: "DK",
|
||||
province: "Test",
|
||||
postal_code: "8000",
|
||||
phone: "123456789",
|
||||
metadata: { membership: "gold" },
|
||||
is_default_shipping: true,
|
||||
},
|
||||
{
|
||||
address_1: "Test Ave 1",
|
||||
address_2: "Test Ave 2",
|
||||
city: "Testville",
|
||||
country_code: "US",
|
||||
is_default_shipping: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
await expect(service.create(customerData)).rejects.toThrow(
|
||||
"A default shipping address already exists"
|
||||
)
|
||||
})
|
||||
|
||||
it("should create multiple customers", async () => {
|
||||
const customersData = [
|
||||
{
|
||||
@@ -662,7 +693,7 @@ describe("Customer Module Service", () => {
|
||||
country_code: "US",
|
||||
is_default_shipping: true,
|
||||
})
|
||||
).rejects.toThrow()
|
||||
).rejects.toThrow("A default shipping address already exists")
|
||||
})
|
||||
|
||||
it("should only be possible to add one default billing address per customer", async () => {
|
||||
@@ -696,7 +727,7 @@ describe("Customer Module Service", () => {
|
||||
country_code: "US",
|
||||
is_default_billing: true,
|
||||
})
|
||||
).rejects.toThrow()
|
||||
).rejects.toThrow("A default billing address already exists")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -813,6 +844,29 @@ describe("Customer Module Service", () => {
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail when updating address to a default shipping address when one already exists", async () => {
|
||||
const customer = await service.create({
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
addresses: [
|
||||
{
|
||||
address_name: "Home",
|
||||
address_1: "123 Main St",
|
||||
is_default_shipping: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
const address = await service.addAddresses({
|
||||
customer_id: customer.id,
|
||||
address_name: "Work",
|
||||
address_1: "456 Main St",
|
||||
})
|
||||
|
||||
await expect(
|
||||
service.updateAddress(address.id, { is_default_shipping: true })
|
||||
).rejects.toThrow("A default shipping address already exists")
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAddresses", () => {
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
CustomerTypes,
|
||||
SoftDeleteReturn,
|
||||
RestoreReturn,
|
||||
CustomerUpdatableFields,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import {
|
||||
@@ -18,9 +17,17 @@ import {
|
||||
mapObjectTo,
|
||||
isString,
|
||||
isObject,
|
||||
isDuplicateError,
|
||||
} from "@medusajs/utils"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import * as services from "../services"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import { EntityManager } from "@mikro-orm/core"
|
||||
|
||||
const UNIQUE_CUSTOMER_SHIPPING_ADDRESS =
|
||||
"IDX_customer_address_unique_customer_shipping"
|
||||
const UNIQUE_CUSTOMER_BILLING_ADDRESS =
|
||||
"IDX_customer_address_unique_customer_billing"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
@@ -97,15 +104,10 @@ export default class CustomerModuleService implements ICustomerModuleService {
|
||||
) {
|
||||
const data = Array.isArray(dataOrArray) ? dataOrArray : [dataOrArray]
|
||||
|
||||
// keep address data for creation
|
||||
const addressData = data.map((d) => d.addresses)
|
||||
|
||||
const customers = await this.customerService_.create(data, sharedContext)
|
||||
|
||||
// decorate addresses with customer ids
|
||||
// filter out addresses without data
|
||||
const addressDataWithCustomerIds = addressData
|
||||
.map((addresses, i) => {
|
||||
const addressDataWithCustomerIds = data
|
||||
.map(({ addresses }, i) => {
|
||||
if (!addresses) {
|
||||
return []
|
||||
}
|
||||
@@ -117,7 +119,7 @@ export default class CustomerModuleService implements ICustomerModuleService {
|
||||
})
|
||||
.flat()
|
||||
|
||||
await this.addressService_.create(addressDataWithCustomerIds, sharedContext)
|
||||
await this.addAddresses(addressDataWithCustomerIds, sharedContext)
|
||||
|
||||
const serialized = await this.baseRepository_.serialize<
|
||||
CustomerTypes.CustomerDTO[]
|
||||
@@ -457,6 +459,8 @@ export default class CustomerModuleService implements ICustomerModuleService {
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.flush(sharedContext).catch(this.handleDbErrors)
|
||||
|
||||
const serialized = await this.baseRepository_.serialize<
|
||||
CustomerTypes.CustomerAddressDTO[]
|
||||
>(addresses, { populate: true })
|
||||
@@ -522,6 +526,9 @@ export default class CustomerModuleService implements ICustomerModuleService {
|
||||
updateData,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.flush(sharedContext).catch(this.handleDbErrors)
|
||||
|
||||
const serialized = await this.baseRepository_.serialize<
|
||||
CustomerTypes.CustomerAddressDTO[]
|
||||
>(addresses, { populate: true })
|
||||
@@ -775,4 +782,30 @@ export default class CustomerModuleService implements ICustomerModuleService {
|
||||
)
|
||||
: void 0
|
||||
}
|
||||
|
||||
private async flush(context: Context) {
|
||||
const em = (context.manager ?? context.transactionManager) as EntityManager
|
||||
await em.flush()
|
||||
}
|
||||
|
||||
private async handleDbErrors(err: any) {
|
||||
if (isDuplicateError(err)) {
|
||||
switch (err.constraint) {
|
||||
case UNIQUE_CUSTOMER_SHIPPING_ADDRESS:
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.DUPLICATE_ERROR,
|
||||
"A default shipping address already exists"
|
||||
)
|
||||
case UNIQUE_CUSTOMER_BILLING_ADDRESS:
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.DUPLICATE_ERROR,
|
||||
"A default billing address already exists"
|
||||
)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user