feat(fulfillment): Shipping options, rules CRUD + rules based context filtering (#6455)
**What**
- Update shipping options with its rules and type
- create/update rules independently
- context based validation fundation
- 🔴 list shipping options with context rules fitlering will come in a separate pr to keep this one smaller
FIXES CORE-1743
FIXES CORE-1764
This commit is contained in:
1 parent
78b6d46584
commit
788c4a1e36
19 files changed
+1766
-54
No files matched your search
+834
-3
@@ -812,7 +812,7 @@ moduleIntegrationTestRunner({
|
|||||||
{
|
{
|
||||||
attribute: "test-attribute",
|
attribute: "test-attribute",
|
||||||
operator: "in",
|
operator: "in",
|
||||||
value: "test-value",
|
value: ["test-value"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -887,7 +887,7 @@ moduleIntegrationTestRunner({
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "test-attribute",
|
attribute: "test-attribute",
|
||||||
operator: "in",
|
operator: "eq",
|
||||||
value: "test-value",
|
value: "test-value",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -909,7 +909,7 @@ moduleIntegrationTestRunner({
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "test-attribute",
|
attribute: "test-attribute",
|
||||||
operator: "in",
|
operator: "eq",
|
||||||
value: "test-value",
|
value: "test-value",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -953,6 +953,145 @@ moduleIntegrationTestRunner({
|
|||||||
++i
|
++i
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should fail to create a new shipping option with invalid rules", async function () {
|
||||||
|
const shippingProfile = await service.createShippingProfiles({
|
||||||
|
name: "test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
const fulfillmentSet = await service.create({
|
||||||
|
name: "test",
|
||||||
|
type: "test-type",
|
||||||
|
})
|
||||||
|
const serviceZone = await service.createServiceZones({
|
||||||
|
name: "test",
|
||||||
|
fulfillment_set_id: fulfillmentSet.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: change that for a real provider instead of fake data manual inserted data
|
||||||
|
const [{ id: providerId }] =
|
||||||
|
await MikroOrmWrapper.forkManager().execute(
|
||||||
|
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createData: CreateShippingOptionDTO = {
|
||||||
|
name: "test-option",
|
||||||
|
price_type: "flat",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: providerId,
|
||||||
|
type: {
|
||||||
|
code: "test-type",
|
||||||
|
description: "test-description",
|
||||||
|
label: "test-label",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test-attribute",
|
||||||
|
operator: "invalid",
|
||||||
|
value: "test-value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const err = await service
|
||||||
|
.createShippingOptions(createData)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(err).toBeDefined()
|
||||||
|
expect(err.message).toBe(
|
||||||
|
"Rule operator invalid is not supported. Must be one of in, eq, ne, gt, gte, lt, lte, nin"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("on create shipping option rules", () => {
|
||||||
|
it("should create a new rule", async () => {
|
||||||
|
const shippingProfile = await service.createShippingProfiles({
|
||||||
|
name: "test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
const fulfillmentSet = await service.create({
|
||||||
|
name: "test",
|
||||||
|
type: "test-type",
|
||||||
|
})
|
||||||
|
const serviceZone = await service.createServiceZones({
|
||||||
|
name: "test",
|
||||||
|
fulfillment_set_id: fulfillmentSet.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
// service provider
|
||||||
|
const [{ id: providerId }] =
|
||||||
|
await MikroOrmWrapper.forkManager().execute(
|
||||||
|
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOption = await service.createShippingOptions({
|
||||||
|
name: "test-option",
|
||||||
|
price_type: "flat",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: providerId,
|
||||||
|
type: {
|
||||||
|
code: "test-type",
|
||||||
|
description: "test-description",
|
||||||
|
label: "test-label",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test-attribute",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test-value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const ruleData = {
|
||||||
|
attribute: "test-attribute",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test-value",
|
||||||
|
shipping_option_id: shippingOption.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
const rule = await service.createShippingOptionRules(ruleData)
|
||||||
|
|
||||||
|
expect(rule).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
attribute: ruleData.attribute,
|
||||||
|
operator: ruleData.operator,
|
||||||
|
value: ruleData.value,
|
||||||
|
shipping_option_id: ruleData.shipping_option_id,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const rules = await service.listShippingOptionRules()
|
||||||
|
expect(rules).toHaveLength(2)
|
||||||
|
expect(rules).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: rule.id,
|
||||||
|
attribute: ruleData.attribute,
|
||||||
|
operator: ruleData.operator,
|
||||||
|
value: ruleData.value,
|
||||||
|
shipping_option_id: shippingOption.id,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: shippingOption.rules[0].id,
|
||||||
|
attribute: shippingOption.rules[0].attribute,
|
||||||
|
operator: shippingOption.rules[0].operator,
|
||||||
|
value: shippingOption.rules[0].value,
|
||||||
|
shipping_option_id: shippingOption.id,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("on update", () => {
|
describe("on update", () => {
|
||||||
@@ -1680,6 +1819,698 @@ moduleIntegrationTestRunner({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("on update shipping options", () => {
|
||||||
|
it("should update a shipping option", async () => {
|
||||||
|
const fulfillmentSet = await service.create({
|
||||||
|
name: "test",
|
||||||
|
type: "test-type",
|
||||||
|
})
|
||||||
|
const serviceZone = await service.createServiceZones({
|
||||||
|
name: "test",
|
||||||
|
fulfillment_set_id: fulfillmentSet.id,
|
||||||
|
})
|
||||||
|
const shippingProfile = await service.createShippingProfiles({
|
||||||
|
name: "test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
|
||||||
|
const [serviceProvider] =
|
||||||
|
await MikroOrmWrapper.forkManager().execute(
|
||||||
|
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionData = {
|
||||||
|
name: "test",
|
||||||
|
price_type: "flat",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "test",
|
||||||
|
description: "test",
|
||||||
|
label: "test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const shippingOption = await service.createShippingOptions(
|
||||||
|
shippingOptionData
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateData = {
|
||||||
|
id: shippingOption.id,
|
||||||
|
name: "updated-test",
|
||||||
|
price_type: "calculated",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "updated-test",
|
||||||
|
description: "updated-test",
|
||||||
|
label: "updated-test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 2000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "new-test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "new-test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedShippingOption = await service.updateShippingOptions(
|
||||||
|
updateData
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updatedShippingOption).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: updateData.id,
|
||||||
|
name: updateData.name,
|
||||||
|
price_type: updateData.price_type,
|
||||||
|
service_zone_id: updateData.service_zone_id,
|
||||||
|
shipping_profile_id: updateData.shipping_profile_id,
|
||||||
|
service_provider_id: updateData.service_provider_id,
|
||||||
|
shipping_option_type_id: expect.any(String),
|
||||||
|
type: expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
code: updateData.type.code,
|
||||||
|
description: updateData.type.description,
|
||||||
|
label: updateData.type.label,
|
||||||
|
}),
|
||||||
|
data: updateData.data,
|
||||||
|
rules: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
attribute: updateData.rules[0].attribute,
|
||||||
|
operator: updateData.rules[0].operator,
|
||||||
|
value: updateData.rules[0].value,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const rules = await service.listShippingOptionRules()
|
||||||
|
expect(rules).toHaveLength(1)
|
||||||
|
expect(rules[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: updatedShippingOption.rules[0].id,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const types = await service.listShippingOptionTypes()
|
||||||
|
expect(types).toHaveLength(1)
|
||||||
|
expect(types[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
code: updateData.type.code,
|
||||||
|
description: updateData.type.description,
|
||||||
|
label: updateData.type.label,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a shipping option without updating the rules or the type", async () => {
|
||||||
|
const fulfillmentSet = await service.create({
|
||||||
|
name: "test",
|
||||||
|
type: "test-type",
|
||||||
|
})
|
||||||
|
const serviceZone = await service.createServiceZones({
|
||||||
|
name: "test",
|
||||||
|
fulfillment_set_id: fulfillmentSet.id,
|
||||||
|
})
|
||||||
|
const shippingProfile = await service.createShippingProfiles({
|
||||||
|
name: "test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
|
||||||
|
const [serviceProvider] =
|
||||||
|
await MikroOrmWrapper.forkManager().execute(
|
||||||
|
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionData = {
|
||||||
|
name: "test",
|
||||||
|
price_type: "flat",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "test",
|
||||||
|
description: "test",
|
||||||
|
label: "test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const shippingOption = await service.createShippingOptions(
|
||||||
|
shippingOptionData
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateData = {
|
||||||
|
id: shippingOption.id,
|
||||||
|
name: "updated-test",
|
||||||
|
price_type: "calculated",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
data: {
|
||||||
|
amount: 2000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
await service.updateShippingOptions(updateData)
|
||||||
|
|
||||||
|
const updatedShippingOption = await service.retrieveShippingOption(
|
||||||
|
shippingOption.id,
|
||||||
|
{
|
||||||
|
relations: ["rules", "type"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updatedShippingOption).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: updateData.id,
|
||||||
|
name: updateData.name,
|
||||||
|
price_type: updateData.price_type,
|
||||||
|
service_zone_id: updateData.service_zone_id,
|
||||||
|
shipping_profile_id: updateData.shipping_profile_id,
|
||||||
|
service_provider_id: updateData.service_provider_id,
|
||||||
|
shipping_option_type_id: expect.any(String),
|
||||||
|
type: expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
code: shippingOptionData.type.code,
|
||||||
|
description: shippingOptionData.type.description,
|
||||||
|
label: shippingOptionData.type.label,
|
||||||
|
}),
|
||||||
|
data: updateData.data,
|
||||||
|
rules: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
attribute: shippingOptionData.rules[0].attribute,
|
||||||
|
operator: shippingOptionData.rules[0].operator,
|
||||||
|
value: shippingOptionData.rules[0].value,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const rules = await service.listShippingOptionRules()
|
||||||
|
expect(rules).toHaveLength(1)
|
||||||
|
expect(rules[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: updatedShippingOption.rules[0].id,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const types = await service.listShippingOptionTypes()
|
||||||
|
expect(types).toHaveLength(1)
|
||||||
|
expect(types[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
code: shippingOptionData.type.code,
|
||||||
|
description: shippingOptionData.type.description,
|
||||||
|
label: shippingOptionData.type.label,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a collection of shipping options", async () => {
|
||||||
|
const fulfillmentSet = await service.create({
|
||||||
|
name: "test",
|
||||||
|
type: "test-type",
|
||||||
|
})
|
||||||
|
const serviceZone = await service.createServiceZones({
|
||||||
|
name: "test",
|
||||||
|
fulfillment_set_id: fulfillmentSet.id,
|
||||||
|
})
|
||||||
|
const shippingProfile = await service.createShippingProfiles({
|
||||||
|
name: "test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
|
||||||
|
const [serviceProvider] =
|
||||||
|
await MikroOrmWrapper.forkManager().execute(
|
||||||
|
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionData = [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
price_type: "flat",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "test",
|
||||||
|
description: "test",
|
||||||
|
label: "test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test2",
|
||||||
|
price_type: "calculated",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "test",
|
||||||
|
description: "test",
|
||||||
|
label: "test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const shippingOptions = await service.createShippingOptions(
|
||||||
|
shippingOptionData
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateData = [
|
||||||
|
{
|
||||||
|
id: shippingOptions[0].id,
|
||||||
|
name: "updated-test",
|
||||||
|
price_type: "calculated",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "updated-test",
|
||||||
|
description: "updated-test",
|
||||||
|
label: "updated-test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 2000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "new-test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "new-test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: shippingOptions[1].id,
|
||||||
|
name: "updated-test",
|
||||||
|
price_type: "calculated",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "updated-test",
|
||||||
|
description: "updated-test",
|
||||||
|
label: "updated-test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 2000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "new-test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "new-test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const updatedShippingOption = await service.updateShippingOptions(
|
||||||
|
updateData
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const data_ of updateData) {
|
||||||
|
const expectedShippingOption = updatedShippingOption.find(
|
||||||
|
(shippingOption) => shippingOption.id === data_.id
|
||||||
|
)
|
||||||
|
expect(expectedShippingOption).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: data_.id,
|
||||||
|
name: data_.name,
|
||||||
|
price_type: data_.price_type,
|
||||||
|
service_zone_id: data_.service_zone_id,
|
||||||
|
shipping_profile_id: data_.shipping_profile_id,
|
||||||
|
service_provider_id: data_.service_provider_id,
|
||||||
|
shipping_option_type_id: expect.any(String),
|
||||||
|
type: expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
code: data_.type.code,
|
||||||
|
description: data_.type.description,
|
||||||
|
label: data_.type.label,
|
||||||
|
}),
|
||||||
|
data: data_.data,
|
||||||
|
rules: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
attribute: data_.rules[0].attribute,
|
||||||
|
operator: data_.rules[0].operator,
|
||||||
|
value: data_.rules[0].value,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules = await service.listShippingOptionRules()
|
||||||
|
expect(rules).toHaveLength(2)
|
||||||
|
expect(rules).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: updatedShippingOption[0].rules[0].id,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
id: updatedShippingOption[1].rules[0].id,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
const types = await service.listShippingOptionTypes()
|
||||||
|
expect(types).toHaveLength(2)
|
||||||
|
expect(types).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
code: updateData[0].type.code,
|
||||||
|
description: updateData[0].type.description,
|
||||||
|
label: updateData[0].type.label,
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
code: updateData[1].type.code,
|
||||||
|
description: updateData[1].type.description,
|
||||||
|
label: updateData[1].type.label,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to update a non-existent shipping option", async () => {
|
||||||
|
const fulfillmentSet = await service.create({
|
||||||
|
name: "test",
|
||||||
|
type: "test-type",
|
||||||
|
})
|
||||||
|
const serviceZone = await service.createServiceZones({
|
||||||
|
name: "test",
|
||||||
|
fulfillment_set_id: fulfillmentSet.id,
|
||||||
|
})
|
||||||
|
const shippingProfile = await service.createShippingProfiles({
|
||||||
|
name: "test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
|
||||||
|
const [serviceProvider] =
|
||||||
|
await MikroOrmWrapper.forkManager().execute(
|
||||||
|
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionData = {
|
||||||
|
id: "sp_jdafwfleiwuonl",
|
||||||
|
name: "test",
|
||||||
|
price_type: "flat",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "test",
|
||||||
|
description: "test",
|
||||||
|
label: "test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const err = await service
|
||||||
|
.updateShippingOptions(shippingOptionData)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(err).toBeDefined()
|
||||||
|
expect(err.message).toBe(
|
||||||
|
`The following shipping options do not exist: ${shippingOptionData.id}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to update a shipping option when adding non existing rules", async () => {
|
||||||
|
const fulfillmentSet = await service.create({
|
||||||
|
name: "test",
|
||||||
|
type: "test-type",
|
||||||
|
})
|
||||||
|
const serviceZone = await service.createServiceZones({
|
||||||
|
name: "test",
|
||||||
|
fulfillment_set_id: fulfillmentSet.id,
|
||||||
|
})
|
||||||
|
const shippingProfile = await service.createShippingProfiles({
|
||||||
|
name: "test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
|
||||||
|
const [serviceProvider] =
|
||||||
|
await MikroOrmWrapper.forkManager().execute(
|
||||||
|
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionData = {
|
||||||
|
name: "test",
|
||||||
|
price_type: "flat",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "test",
|
||||||
|
description: "test",
|
||||||
|
label: "test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const shippingOption = await service.createShippingOptions(
|
||||||
|
shippingOptionData
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateData = [
|
||||||
|
{
|
||||||
|
id: shippingOption.id,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
id: "sp_jdafwfleiwuonl",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const err = await service
|
||||||
|
.updateShippingOptions(updateData)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(err).toBeDefined()
|
||||||
|
expect(err.message).toBe(
|
||||||
|
`The following rules does not exists: ${updateData[0].rules[0].id} on shipping option ${shippingOption.id}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to update a shipping option when adding invalid rules", async () => {
|
||||||
|
const fulfillmentSet = await service.create({
|
||||||
|
name: "test",
|
||||||
|
type: "test-type",
|
||||||
|
})
|
||||||
|
const serviceZone = await service.createServiceZones({
|
||||||
|
name: "test",
|
||||||
|
fulfillment_set_id: fulfillmentSet.id,
|
||||||
|
})
|
||||||
|
const shippingProfile = await service.createShippingProfiles({
|
||||||
|
name: "test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
|
||||||
|
const [serviceProvider] =
|
||||||
|
await MikroOrmWrapper.forkManager().execute(
|
||||||
|
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptionData = {
|
||||||
|
name: "test",
|
||||||
|
price_type: "flat",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "test",
|
||||||
|
description: "test",
|
||||||
|
label: "test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const shippingOption = await service.createShippingOptions(
|
||||||
|
shippingOptionData
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateData = [
|
||||||
|
{
|
||||||
|
id: shippingOption.id,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "invalid",
|
||||||
|
value: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const err = await service
|
||||||
|
.updateShippingOptions(updateData)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(err).toBeDefined()
|
||||||
|
expect(err.message).toBe(
|
||||||
|
`Rule operator invalid is not supported. Must be one of in, eq, ne, gt, gte, lt, lte, nin`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("on update shipping option rules", () => {
|
||||||
|
it("should update a shipping option rule", async () => {
|
||||||
|
const shippingProfile = await service.createShippingProfiles({
|
||||||
|
name: "test",
|
||||||
|
type: "default",
|
||||||
|
})
|
||||||
|
const fulfillmentSet = await service.create({
|
||||||
|
name: "test",
|
||||||
|
type: "test-type",
|
||||||
|
})
|
||||||
|
const serviceZone = await service.createServiceZones({
|
||||||
|
name: "test",
|
||||||
|
fulfillment_set_id: fulfillmentSet.id,
|
||||||
|
})
|
||||||
|
const [serviceProvider] =
|
||||||
|
await MikroOrmWrapper.forkManager().execute(
|
||||||
|
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOption = await service.createShippingOptions({
|
||||||
|
name: "test",
|
||||||
|
price_type: "flat",
|
||||||
|
service_zone_id: serviceZone.id,
|
||||||
|
shipping_profile_id: shippingProfile.id,
|
||||||
|
service_provider_id: serviceProvider.id,
|
||||||
|
type: {
|
||||||
|
code: "test",
|
||||||
|
description: "test",
|
||||||
|
label: "test",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: 1000,
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
attribute: "test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateData = {
|
||||||
|
id: shippingOption.rules[0].id,
|
||||||
|
attribute: "updated-test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "updated-test",
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedRule = await service.updateShippingOptionRules(
|
||||||
|
updateData
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(updatedRule).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: updateData.id,
|
||||||
|
attribute: updateData.attribute,
|
||||||
|
operator: updateData.operator,
|
||||||
|
value: updateData.value,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to update a non-existent shipping option rule", async () => {
|
||||||
|
const updateData = {
|
||||||
|
id: "sp_jdafwfleiwuonl",
|
||||||
|
attribute: "updated-test",
|
||||||
|
operator: "eq",
|
||||||
|
value: "updated-test",
|
||||||
|
}
|
||||||
|
|
||||||
|
const err = await service
|
||||||
|
.updateShippingOptionRules(updateData)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(err).toBeDefined()
|
||||||
|
expect(err.message).toBe(
|
||||||
|
`ShippingOptionRule with id "${updateData.id}" not found`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ module.exports = {
|
|||||||
"^@services": "<rootDir>/src/services",
|
"^@services": "<rootDir>/src/services",
|
||||||
"^@repositories": "<rootDir>/src/repositories",
|
"^@repositories": "<rootDir>/src/repositories",
|
||||||
"^@types": "<rootDir>/src/types",
|
"^@types": "<rootDir>/src/types",
|
||||||
|
"^@utils": "<rootDir>/src/utils",
|
||||||
},
|
},
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.[jt]s?$": [
|
"^.+\\.[jt]s?$": [
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"test": "jest --runInBand --bail --forceExit -- src/**/__tests__/**/*.ts",
|
"test": "jest --runInBand --bail --forceExit -- src/**/__tests__/**/*.ts",
|
||||||
"test:integration": "jest --runInBand --forceExit -- integration-tests/**/__tests__/**/*.spec.ts",
|
"test:integration": "jest --runInBand --forceExit -- integration-tests/**/__tests__/**/*.spec.ts",
|
||||||
"migration:generate": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:generate",
|
"migration:generate": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:generate",
|
||||||
"migration:initial": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create --initial",
|
"migration:initial": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create --initial -n InitialSetupMigration",
|
||||||
"migration:create": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create",
|
"migration:create": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create",
|
||||||
"migration:up": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:up",
|
"migration:up": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:up",
|
||||||
"orm:cache:clear": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm cache:clear"
|
"orm:cache:clear": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm cache:clear"
|
||||||
|
|||||||
@@ -493,7 +493,20 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"checks": [],
|
"checks": [],
|
||||||
"foreignKeys": {}
|
"foreignKeys": {
|
||||||
|
"service_zone_fulfillment_set_id_foreign": {
|
||||||
|
"constraintName": "service_zone_fulfillment_set_id_foreign",
|
||||||
|
"columnNames": [
|
||||||
|
"fulfillment_set_id"
|
||||||
|
],
|
||||||
|
"localTableName": "public.service_zone",
|
||||||
|
"referencedColumnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"referencedTableName": "public.fulfillment_set",
|
||||||
|
"updateRule": "cascade"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": {
|
"columns": {
|
||||||
@@ -673,7 +686,20 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"checks": [],
|
"checks": [],
|
||||||
"foreignKeys": {}
|
"foreignKeys": {
|
||||||
|
"geo_zone_service_zone_id_foreign": {
|
||||||
|
"constraintName": "geo_zone_service_zone_id_foreign",
|
||||||
|
"columnNames": [
|
||||||
|
"service_zone_id"
|
||||||
|
],
|
||||||
|
"localTableName": "public.geo_zone",
|
||||||
|
"referencedColumnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"referencedTableName": "public.service_zone",
|
||||||
|
"updateRule": "cascade"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": {
|
"columns": {
|
||||||
@@ -713,15 +739,6 @@
|
|||||||
"nullable": false,
|
"nullable": false,
|
||||||
"mappedType": "text"
|
"mappedType": "text"
|
||||||
},
|
},
|
||||||
"shipping_option_id": {
|
|
||||||
"name": "shipping_option_id",
|
|
||||||
"type": "text",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": false,
|
|
||||||
"mappedType": "text"
|
|
||||||
},
|
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "timestamptz",
|
"type": "timestamptz",
|
||||||
@@ -758,16 +775,6 @@
|
|||||||
"name": "shipping_option_type",
|
"name": "shipping_option_type",
|
||||||
"schema": "public",
|
"schema": "public",
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
|
||||||
"keyName": "IDX_shipping_option_type_shipping_option_id",
|
|
||||||
"columnNames": [
|
|
||||||
"shipping_option_id"
|
|
||||||
],
|
|
||||||
"composite": false,
|
|
||||||
"primary": false,
|
|
||||||
"unique": false,
|
|
||||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_shipping_option_type_shipping_option_id\" ON \"shipping_option_type\" (shipping_option_id) WHERE deleted_at IS NULL"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"keyName": "IDX_shipping_option_type_deleted_at",
|
"keyName": "IDX_shipping_option_type_deleted_at",
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
@@ -1093,6 +1100,18 @@
|
|||||||
],
|
],
|
||||||
"checks": [],
|
"checks": [],
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
|
"shipping_option_service_zone_id_foreign": {
|
||||||
|
"constraintName": "shipping_option_service_zone_id_foreign",
|
||||||
|
"columnNames": [
|
||||||
|
"service_zone_id"
|
||||||
|
],
|
||||||
|
"localTableName": "public.shipping_option",
|
||||||
|
"referencedColumnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"referencedTableName": "public.service_zone",
|
||||||
|
"updateRule": "cascade"
|
||||||
|
},
|
||||||
"shipping_option_shipping_profile_id_foreign": {
|
"shipping_option_shipping_profile_id_foreign": {
|
||||||
"constraintName": "shipping_option_shipping_profile_id_foreign",
|
"constraintName": "shipping_option_shipping_profile_id_foreign",
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
@@ -1161,7 +1180,17 @@
|
|||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"mappedType": "text"
|
"enumItems": [
|
||||||
|
"in",
|
||||||
|
"eq",
|
||||||
|
"ne",
|
||||||
|
"gt",
|
||||||
|
"gte",
|
||||||
|
"lt",
|
||||||
|
"lte",
|
||||||
|
"nin"
|
||||||
|
],
|
||||||
|
"mappedType": "enum"
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"name": "value",
|
"name": "value",
|
||||||
@@ -1248,7 +1277,20 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"checks": [],
|
"checks": [],
|
||||||
"foreignKeys": {}
|
"foreignKeys": {
|
||||||
|
"shipping_option_rule_shipping_option_id_foreign": {
|
||||||
|
"constraintName": "shipping_option_rule_shipping_option_id_foreign",
|
||||||
|
"columnNames": [
|
||||||
|
"shipping_option_id"
|
||||||
|
],
|
||||||
|
"localTableName": "public.shipping_option_rule",
|
||||||
|
"referencedColumnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"referencedTableName": "public.shipping_option",
|
||||||
|
"updateRule": "cascade"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": {
|
"columns": {
|
||||||
|
|||||||
+10
-4
@@ -1,6 +1,6 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
export class Migration20240219115644 extends Migration {
|
export class Migration20240221164918_InitialSetupMigration extends Migration {
|
||||||
|
|
||||||
async up(): Promise<void> {
|
async up(): Promise<void> {
|
||||||
this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));');
|
this.addSql('create table if not exists "fulfillment_address" ("id" text not null, "fulfillment_id" text null, "company" text null, "first_name" text null, "last_name" text null, "address_1" text null, "address_2" text null, "city" text null, "country_code" text null, "province" text null, "postal_code" text null, "phone" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "fulfillment_address_pkey" primary key ("id"));');
|
||||||
@@ -26,8 +26,7 @@ export class Migration20240219115644 extends Migration {
|
|||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_geo_zone_service_zone_id" ON "geo_zone" (service_zone_id) WHERE deleted_at IS NULL;');
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_geo_zone_service_zone_id" ON "geo_zone" (service_zone_id) WHERE deleted_at IS NULL;');
|
||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_geo_zone_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_geo_zone_deleted_at" ON "geo_zone" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||||
|
|
||||||
this.addSql('create table if not exists "shipping_option_type" ("id" text not null, "label" text not null, "description" text null, "code" text not null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_type_pkey" primary key ("id"));');
|
this.addSql('create table if not exists "shipping_option_type" ("id" text not null, "label" text not null, "description" text null, "code" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_type_pkey" primary key ("id"));');
|
||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_shipping_option_id" ON "shipping_option_type" (shipping_option_id) WHERE deleted_at IS NULL;');
|
|
||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_deleted_at" ON "shipping_option_type" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_type_deleted_at" ON "shipping_option_type" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||||
|
|
||||||
this.addSql('create table if not exists "shipping_profile" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_profile_pkey" primary key ("id"));');
|
this.addSql('create table if not exists "shipping_profile" ("id" text not null, "name" text not null, "type" text not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_profile_pkey" primary key ("id"));');
|
||||||
@@ -42,7 +41,7 @@ export class Migration20240219115644 extends Migration {
|
|||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_option_type_id" ON "shipping_option" (shipping_option_type_id) WHERE deleted_at IS NULL;');
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_shipping_option_type_id" ON "shipping_option" (shipping_option_type_id) WHERE deleted_at IS NULL;');
|
||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_deleted_at" ON "shipping_option" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_deleted_at" ON "shipping_option" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||||
|
|
||||||
this.addSql('create table if not exists "shipping_option_rule" ("id" text not null, "attribute" text not null, "operator" text not null, "value" jsonb null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_rule_pkey" primary key ("id"));');
|
this.addSql('create table if not exists "shipping_option_rule" ("id" text not null, "attribute" text not null, "operator" text check ("operator" in (\'in\', \'eq\', \'ne\', \'gt\', \'gte\', \'lt\', \'lte\', \'nin\')) not null, "value" jsonb null, "shipping_option_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "shipping_option_rule_pkey" primary key ("id"));');
|
||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_shipping_option_id" ON "shipping_option_rule" (shipping_option_id) WHERE deleted_at IS NULL;');
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_shipping_option_id" ON "shipping_option_rule" (shipping_option_id) WHERE deleted_at IS NULL;');
|
||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_deleted_at" ON "shipping_option_rule" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_shipping_option_rule_deleted_at" ON "shipping_option_rule" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||||
|
|
||||||
@@ -62,10 +61,17 @@ export class Migration20240219115644 extends Migration {
|
|||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_fulfillment_id" ON "fulfillment_item" (fulfillment_id) WHERE deleted_at IS NULL;');
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_fulfillment_id" ON "fulfillment_item" (fulfillment_id) WHERE deleted_at IS NULL;');
|
||||||
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_deleted_at" ON "fulfillment_item" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_fulfillment_item_deleted_at" ON "fulfillment_item" (deleted_at) WHERE deleted_at IS NOT NULL;');
|
||||||
|
|
||||||
|
this.addSql('alter table if exists "service_zone" add constraint "service_zone_fulfillment_set_id_foreign" foreign key ("fulfillment_set_id") references "fulfillment_set" ("id") on update cascade;');
|
||||||
|
|
||||||
|
this.addSql('alter table if exists "geo_zone" add constraint "geo_zone_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade;');
|
||||||
|
|
||||||
|
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_service_zone_id_foreign" foreign key ("service_zone_id") references "service_zone" ("id") on update cascade;');
|
||||||
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_profile_id_foreign" foreign key ("shipping_profile_id") references "shipping_profile" ("id") on update cascade on delete set null;');
|
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_profile_id_foreign" foreign key ("shipping_profile_id") references "shipping_profile" ("id") on update cascade on delete set null;');
|
||||||
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_service_provider_id_foreign" foreign key ("service_provider_id") references "service_provider" ("id") on update cascade on delete set null;');
|
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_service_provider_id_foreign" foreign key ("service_provider_id") references "service_provider" ("id") on update cascade on delete set null;');
|
||||||
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_foreign" foreign key ("shipping_option_type_id") references "shipping_option_type" ("id") on update cascade on delete cascade;');
|
this.addSql('alter table if exists "shipping_option" add constraint "shipping_option_shipping_option_type_id_foreign" foreign key ("shipping_option_type_id") references "shipping_option_type" ("id") on update cascade on delete cascade;');
|
||||||
|
|
||||||
|
this.addSql('alter table if exists "shipping_option_rule" add constraint "shipping_option_rule_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade;');
|
||||||
|
|
||||||
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete set null;');
|
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_shipping_option_id_foreign" foreign key ("shipping_option_id") references "shipping_option" ("id") on update cascade on delete set null;');
|
||||||
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "service_provider" ("id") on update cascade;');
|
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_provider_id_foreign" foreign key ("provider_id") references "service_provider" ("id") on update cascade;');
|
||||||
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade;');
|
this.addSql('alter table if exists "fulfillment" add constraint "fulfillment_delivery_address_id_foreign" foreign key ("delivery_address_id") references "fulfillment_address" ("id") on update cascade;');
|
||||||
@@ -74,7 +74,11 @@ export default class GeoZone {
|
|||||||
@Property({ columnType: "text", nullable: true })
|
@Property({ columnType: "text", nullable: true })
|
||||||
city: string | null = null
|
city: string | null = null
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
@ManyToOne(() => ServiceZone, {
|
||||||
|
type: "text",
|
||||||
|
mapToPk: true,
|
||||||
|
fieldName: "service_zone_id",
|
||||||
|
})
|
||||||
@ServiceZoneIdIndex.MikroORMIndex()
|
@ServiceZoneIdIndex.MikroORMIndex()
|
||||||
service_zone_id: string
|
service_zone_id: string
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,11 @@ export default class ServiceZone {
|
|||||||
@Property({ columnType: "jsonb", nullable: true })
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
metadata: Record<string, unknown> | null = null
|
metadata: Record<string, unknown> | null = null
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
@ManyToOne(() => FulfillmentSet, {
|
||||||
|
type: "text",
|
||||||
|
mapToPk: true,
|
||||||
|
fieldName: "fulfillment_set_id",
|
||||||
|
})
|
||||||
@FulfillmentSetIdIndex.MikroORMIndex()
|
@FulfillmentSetIdIndex.MikroORMIndex()
|
||||||
fulfillment_set_id: string
|
fulfillment_set_id: string
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { DAL } from "@medusajs/types"
|
|||||||
import {
|
import {
|
||||||
BeforeCreate,
|
BeforeCreate,
|
||||||
Entity,
|
Entity,
|
||||||
|
Enum,
|
||||||
Filter,
|
Filter,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
OnInit,
|
OnInit,
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
Property,
|
Property,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import ShippingOption from "./shipping-option"
|
import ShippingOption from "./shipping-option"
|
||||||
|
import { RuleOperator } from "@utils"
|
||||||
|
|
||||||
type ShippingOptionRuleOptionalProps = DAL.SoftDeletableEntityDateColumns
|
type ShippingOptionRuleOptionalProps = DAL.SoftDeletableEntityDateColumns
|
||||||
|
|
||||||
@@ -42,17 +44,26 @@ export default class ShippingOptionRule {
|
|||||||
@Property({ columnType: "text" })
|
@Property({ columnType: "text" })
|
||||||
attribute: string
|
attribute: string
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
@Enum({
|
||||||
operator: string
|
items: () => Object.values(RuleOperator),
|
||||||
|
columnType: "text",
|
||||||
|
})
|
||||||
|
operator: Lowercase<keyof typeof RuleOperator>
|
||||||
|
|
||||||
@Property({ columnType: "jsonb", nullable: true })
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
value: { value: string | string[] } | null = null
|
value: string | string[] | null = null
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
@ManyToOne(() => ShippingOption, {
|
||||||
|
type: "text",
|
||||||
|
mapToPk: true,
|
||||||
|
fieldName: "shipping_option_id",
|
||||||
|
})
|
||||||
@ShippingOptionIdIndex.MikroORMIndex()
|
@ShippingOptionIdIndex.MikroORMIndex()
|
||||||
shipping_option_id: string
|
shipping_option_id: string
|
||||||
|
|
||||||
@ManyToOne(() => ShippingOption, { persist: false })
|
@ManyToOne(() => ShippingOption, {
|
||||||
|
persist: false,
|
||||||
|
})
|
||||||
shipping_option: ShippingOption
|
shipping_option: ShippingOption
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
|
|||||||
@@ -48,12 +48,8 @@ export default class ShippingOptionType {
|
|||||||
@Property({ columnType: "text" })
|
@Property({ columnType: "text" })
|
||||||
code: string
|
code: string
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
|
||||||
@ShippingOptionIdIndex.MikroORMIndex()
|
|
||||||
shipping_option_id: string
|
|
||||||
|
|
||||||
@OneToOne(() => ShippingOption, (so) => so.type, {
|
@OneToOne(() => ShippingOption, (so) => so.type, {
|
||||||
persist: false,
|
type: "text",
|
||||||
})
|
})
|
||||||
shipping_option: ShippingOption
|
shipping_option: ShippingOption
|
||||||
|
|
||||||
@@ -79,12 +75,10 @@ export default class ShippingOptionType {
|
|||||||
@BeforeCreate()
|
@BeforeCreate()
|
||||||
onCreate() {
|
onCreate() {
|
||||||
this.id = generateEntityId(this.id, "sotype")
|
this.id = generateEntityId(this.id, "sotype")
|
||||||
this.shipping_option_id ??= this.shipping_option?.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnInit()
|
@OnInit()
|
||||||
onInit() {
|
onInit() {
|
||||||
this.id = generateEntityId(this.id, "sotype")
|
this.id = generateEntityId(this.id, "sotype")
|
||||||
this.shipping_option_id ??= this.shipping_option?.id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ export default class ShippingOption {
|
|||||||
})
|
})
|
||||||
price_type: ShippingOptionPriceType
|
price_type: ShippingOptionPriceType
|
||||||
|
|
||||||
@ManyToOne(() => ShippingProfile, {
|
@ManyToOne(() => ServiceZone, {
|
||||||
type: "text",
|
type: "text",
|
||||||
fieldName: "service_zone_id",
|
fieldName: "service_zone_id",
|
||||||
mapToPk: true,
|
mapToPk: true,
|
||||||
@@ -129,6 +129,7 @@ export default class ShippingOption {
|
|||||||
@OneToOne(() => ShippingOptionType, (so) => so.shipping_option, {
|
@OneToOne(() => ShippingOptionType, (so) => so.shipping_option, {
|
||||||
owner: true,
|
owner: true,
|
||||||
cascade: [Cascade.PERSIST, Cascade.REMOVE, "soft-remove"] as any,
|
cascade: [Cascade.PERSIST, Cascade.REMOVE, "soft-remove"] as any,
|
||||||
|
orphanRemoval: true,
|
||||||
fieldName: "shipping_option_type_id",
|
fieldName: "shipping_option_type_id",
|
||||||
})
|
})
|
||||||
type: ShippingOptionType
|
type: ShippingOptionType
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
UpdateFulfillmentSetDTO,
|
UpdateFulfillmentSetDTO,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
|
arrayDifference,
|
||||||
getSetDifference,
|
getSetDifference,
|
||||||
InjectManager,
|
InjectManager,
|
||||||
InjectTransactionManager,
|
InjectTransactionManager,
|
||||||
@@ -24,14 +25,19 @@ import {
|
|||||||
GeoZone,
|
GeoZone,
|
||||||
ServiceZone,
|
ServiceZone,
|
||||||
ShippingOption,
|
ShippingOption,
|
||||||
|
ShippingOptionRule,
|
||||||
|
ShippingOptionType,
|
||||||
ShippingProfile,
|
ShippingProfile,
|
||||||
} from "@models"
|
} from "@models"
|
||||||
|
import { validateRules } from "@utils"
|
||||||
|
|
||||||
const generateMethodForModels = [
|
const generateMethodForModels = [
|
||||||
ServiceZone,
|
ServiceZone,
|
||||||
ShippingOption,
|
ShippingOption,
|
||||||
GeoZone,
|
GeoZone,
|
||||||
ShippingProfile,
|
ShippingProfile,
|
||||||
|
ShippingOptionRule,
|
||||||
|
ShippingOptionType,
|
||||||
]
|
]
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
@@ -41,6 +47,8 @@ type InjectedDependencies = {
|
|||||||
geoZoneService: ModulesSdkTypes.InternalModuleService<any>
|
geoZoneService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
shippingProfileService: ModulesSdkTypes.InternalModuleService<any>
|
shippingProfileService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
shippingOptionService: ModulesSdkTypes.InternalModuleService<any>
|
shippingOptionService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
|
shippingOptionRuleService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
|
shippingOptionTypeService: ModulesSdkTypes.InternalModuleService<any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FulfillmentModuleService<
|
export default class FulfillmentModuleService<
|
||||||
@@ -48,7 +56,9 @@ export default class FulfillmentModuleService<
|
|||||||
TServiceZoneEntity extends ServiceZone = ServiceZone,
|
TServiceZoneEntity extends ServiceZone = ServiceZone,
|
||||||
TGeoZoneEntity extends GeoZone = GeoZone,
|
TGeoZoneEntity extends GeoZone = GeoZone,
|
||||||
TShippingProfileEntity extends ShippingProfile = ShippingProfile,
|
TShippingProfileEntity extends ShippingProfile = ShippingProfile,
|
||||||
TShippingOptionEntity extends ShippingOption = ShippingOption
|
TShippingOptionEntity extends ShippingOption = ShippingOption,
|
||||||
|
TShippingOptionRuleEntity extends ShippingOptionRule = ShippingOptionRule,
|
||||||
|
TSippingOptionTypeEntity extends ShippingOptionType = ShippingOptionType
|
||||||
>
|
>
|
||||||
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
||||||
InjectedDependencies,
|
InjectedDependencies,
|
||||||
@@ -59,6 +69,8 @@ export default class FulfillmentModuleService<
|
|||||||
ShippingOption: { dto: FulfillmentTypes.ShippingOptionDTO }
|
ShippingOption: { dto: FulfillmentTypes.ShippingOptionDTO }
|
||||||
GeoZone: { dto: FulfillmentTypes.GeoZoneDTO }
|
GeoZone: { dto: FulfillmentTypes.GeoZoneDTO }
|
||||||
ShippingProfile: { dto: FulfillmentTypes.ShippingProfileDTO }
|
ShippingProfile: { dto: FulfillmentTypes.ShippingProfileDTO }
|
||||||
|
ShippingOptionRule: { dto: FulfillmentTypes.ShippingOptionRuleDTO }
|
||||||
|
ShippingOptionType: { dto: FulfillmentTypes.ShippingOptionTypeDTO }
|
||||||
}
|
}
|
||||||
>(FulfillmentSet, generateMethodForModels, entityNameToLinkableKeysMap)
|
>(FulfillmentSet, generateMethodForModels, entityNameToLinkableKeysMap)
|
||||||
implements IFulfillmentModuleService
|
implements IFulfillmentModuleService
|
||||||
@@ -69,6 +81,8 @@ export default class FulfillmentModuleService<
|
|||||||
protected readonly geoZoneService_: ModulesSdkTypes.InternalModuleService<TGeoZoneEntity>
|
protected readonly geoZoneService_: ModulesSdkTypes.InternalModuleService<TGeoZoneEntity>
|
||||||
protected readonly shippingProfileService_: ModulesSdkTypes.InternalModuleService<TShippingProfileEntity>
|
protected readonly shippingProfileService_: ModulesSdkTypes.InternalModuleService<TShippingProfileEntity>
|
||||||
protected readonly shippingOptionService_: ModulesSdkTypes.InternalModuleService<TShippingOptionEntity>
|
protected readonly shippingOptionService_: ModulesSdkTypes.InternalModuleService<TShippingOptionEntity>
|
||||||
|
protected readonly shippingOptionRuleService_: ModulesSdkTypes.InternalModuleService<TShippingOptionRuleEntity>
|
||||||
|
protected readonly shippingOptionTypeService_: ModulesSdkTypes.InternalModuleService<TSippingOptionTypeEntity>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{
|
||||||
@@ -78,6 +92,8 @@ export default class FulfillmentModuleService<
|
|||||||
geoZoneService,
|
geoZoneService,
|
||||||
shippingProfileService,
|
shippingProfileService,
|
||||||
shippingOptionService,
|
shippingOptionService,
|
||||||
|
shippingOptionRuleService,
|
||||||
|
shippingOptionTypeService,
|
||||||
}: InjectedDependencies,
|
}: InjectedDependencies,
|
||||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||||
) {
|
) {
|
||||||
@@ -89,6 +105,8 @@ export default class FulfillmentModuleService<
|
|||||||
this.geoZoneService_ = geoZoneService
|
this.geoZoneService_ = geoZoneService
|
||||||
this.shippingProfileService_ = shippingProfileService
|
this.shippingProfileService_ = shippingProfileService
|
||||||
this.shippingOptionService_ = shippingOptionService
|
this.shippingOptionService_ = shippingOptionService
|
||||||
|
this.shippingOptionRuleService_ = shippingOptionRuleService
|
||||||
|
this.shippingOptionTypeService_ = shippingOptionTypeService
|
||||||
}
|
}
|
||||||
|
|
||||||
__joinerConfig(): ModuleJoinerConfig {
|
__joinerConfig(): ModuleJoinerConfig {
|
||||||
@@ -235,6 +253,11 @@ export default class FulfillmentModuleService<
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rules = data_.flatMap((d) => d.rules)
|
||||||
|
if (rules.length) {
|
||||||
|
validateRules(rules as Record<string, unknown>[])
|
||||||
|
}
|
||||||
|
|
||||||
const createdShippingOptions = await this.shippingOptionService_.create(
|
const createdShippingOptions = await this.shippingOptionService_.create(
|
||||||
data_,
|
data_,
|
||||||
sharedContext
|
sharedContext
|
||||||
@@ -328,6 +351,61 @@ export default class FulfillmentModuleService<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createShippingOptionRules(
|
||||||
|
data: FulfillmentTypes.CreateShippingOptionRuleDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<FulfillmentTypes.ShippingOptionRuleDTO[]>
|
||||||
|
async createShippingOptionRules(
|
||||||
|
data: FulfillmentTypes.CreateShippingOptionRuleDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<FulfillmentTypes.ShippingOptionRuleDTO>
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async createShippingOptionRules(
|
||||||
|
data:
|
||||||
|
| FulfillmentTypes.CreateShippingOptionRuleDTO[]
|
||||||
|
| FulfillmentTypes.CreateShippingOptionRuleDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<
|
||||||
|
| FulfillmentTypes.ShippingOptionRuleDTO
|
||||||
|
| FulfillmentTypes.ShippingOptionRuleDTO[]
|
||||||
|
> {
|
||||||
|
const createdShippingOptionRules = await this.createShippingOptionRules_(
|
||||||
|
data,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return await this.baseRepository_.serialize<
|
||||||
|
| FulfillmentTypes.ShippingOptionRuleDTO
|
||||||
|
| FulfillmentTypes.ShippingOptionRuleDTO[]
|
||||||
|
>(createdShippingOptionRules, {
|
||||||
|
populate: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
async createShippingOptionRules_(
|
||||||
|
data:
|
||||||
|
| FulfillmentTypes.CreateShippingOptionRuleDTO[]
|
||||||
|
| FulfillmentTypes.CreateShippingOptionRuleDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TShippingOptionRuleEntity | TShippingOptionRuleEntity[]> {
|
||||||
|
const data_ = Array.isArray(data) ? data : [data]
|
||||||
|
|
||||||
|
if (!data_.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRules(data_ as unknown as Record<string, unknown>[])
|
||||||
|
|
||||||
|
const createdShippingOptionRules =
|
||||||
|
await this.shippingOptionRuleService_.create(data_, sharedContext)
|
||||||
|
|
||||||
|
return Array.isArray(data)
|
||||||
|
? createdShippingOptionRules
|
||||||
|
: createdShippingOptionRules[0]
|
||||||
|
}
|
||||||
|
|
||||||
update(
|
update(
|
||||||
data: FulfillmentTypes.UpdateFulfillmentSetDTO[],
|
data: FulfillmentTypes.UpdateFulfillmentSetDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
@@ -375,6 +453,7 @@ export default class FulfillmentModuleService<
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
relations: ["service_zones", "service_zones.geo_zones"],
|
relations: ["service_zones", "service_zones.geo_zones"],
|
||||||
|
take: fulfillmentSetIds.length,
|
||||||
},
|
},
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
@@ -557,6 +636,7 @@ export default class FulfillmentModuleService<
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
relations: ["geo_zones"],
|
relations: ["geo_zones"],
|
||||||
|
take: serviceZoneIds.length,
|
||||||
},
|
},
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
@@ -662,7 +742,7 @@ export default class FulfillmentModuleService<
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<FulfillmentTypes.ShippingOptionDTO>
|
): Promise<FulfillmentTypes.ShippingOptionDTO>
|
||||||
|
|
||||||
@InjectTransactionManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
async updateShippingOptions(
|
async updateShippingOptions(
|
||||||
data:
|
data:
|
||||||
| FulfillmentTypes.UpdateShippingOptionDTO[]
|
| FulfillmentTypes.UpdateShippingOptionDTO[]
|
||||||
@@ -671,9 +751,125 @@ export default class FulfillmentModuleService<
|
|||||||
): Promise<
|
): Promise<
|
||||||
FulfillmentTypes.ShippingOptionDTO[] | FulfillmentTypes.ShippingOptionDTO
|
FulfillmentTypes.ShippingOptionDTO[] | FulfillmentTypes.ShippingOptionDTO
|
||||||
> {
|
> {
|
||||||
|
const updatedShippingOptions = await this.updateShippingOptions_(
|
||||||
|
data,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return await this.baseRepository_.serialize<
|
||||||
|
FulfillmentTypes.ShippingOptionDTO | FulfillmentTypes.ShippingOptionDTO[]
|
||||||
|
>(updatedShippingOptions, {
|
||||||
|
populate: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
async updateShippingOptions_(
|
||||||
|
data:
|
||||||
|
| FulfillmentTypes.UpdateShippingOptionDTO[]
|
||||||
|
| FulfillmentTypes.UpdateShippingOptionDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TShippingOptionEntity | TShippingOptionEntity[]> {
|
||||||
|
const dataArray = Array.isArray(data) ? data : [data]
|
||||||
|
|
||||||
|
if (!dataArray.length) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shippingOptionIds = dataArray.map((s) => s.id)
|
||||||
|
if (!shippingOptionIds.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const shippingOptions = await this.shippingOptionService_.list(
|
||||||
|
{
|
||||||
|
id: shippingOptionIds,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relations: ["rules"],
|
||||||
|
take: shippingOptionIds.length,
|
||||||
|
},
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
const existingShippingOptions = new Map(
|
||||||
|
shippingOptions.map((s) => [s.id, s])
|
||||||
|
)
|
||||||
|
|
||||||
|
FulfillmentModuleService.validateMissingShippingOptions_(
|
||||||
|
shippingOptions,
|
||||||
|
dataArray
|
||||||
|
)
|
||||||
|
|
||||||
|
const ruleIdsToDelete: string[] = []
|
||||||
|
dataArray.forEach((shippingOption) => {
|
||||||
|
if (!shippingOption.rules) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingShippingOption = existingShippingOptions.get(
|
||||||
|
shippingOption.id
|
||||||
|
)! // Garuantueed to exist since the validation above have been performed
|
||||||
|
const existingRules = existingShippingOption.rules
|
||||||
|
|
||||||
|
FulfillmentModuleService.validateMissingShippingOptionRules(
|
||||||
|
existingShippingOption,
|
||||||
|
shippingOption
|
||||||
|
)
|
||||||
|
|
||||||
|
const existingRulesMap = new Map(
|
||||||
|
existingRules.map((rule) => [rule.id, rule])
|
||||||
|
)
|
||||||
|
|
||||||
|
const updatedRules = shippingOption.rules
|
||||||
|
const updatedRuleIds = updatedRules
|
||||||
|
.map((r) => "id" in r && r.id)
|
||||||
|
.filter((id): id is string => !!id)
|
||||||
|
|
||||||
|
const toDeleteRuleIds = arrayDifference(
|
||||||
|
updatedRuleIds,
|
||||||
|
Array.from(existingRulesMap.keys())
|
||||||
|
) as string[]
|
||||||
|
|
||||||
|
if (toDeleteRuleIds.length) {
|
||||||
|
ruleIdsToDelete.push(...toDeleteRuleIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRules = updatedRules
|
||||||
|
.map((rule) => {
|
||||||
|
if (!("id" in rule)) {
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
validateRules(newRules as Record<string, unknown>[])
|
||||||
|
|
||||||
|
shippingOption.rules = shippingOption.rules.map((rule) => {
|
||||||
|
if (!("id" in rule)) {
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
return existingRulesMap.get(rule.id)!
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (ruleIdsToDelete.length) {
|
||||||
|
await this.shippingOptionRuleService_.delete(
|
||||||
|
ruleIdsToDelete,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedShippingOptions = await this.shippingOptionService_.update(
|
||||||
|
dataArray,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return Array.isArray(data)
|
||||||
|
? updatedShippingOptions
|
||||||
|
: updatedShippingOptions[0]
|
||||||
|
}
|
||||||
|
|
||||||
updateShippingProfiles(
|
updateShippingProfiles(
|
||||||
data: FulfillmentTypes.UpdateShippingProfileDTO[],
|
data: FulfillmentTypes.UpdateShippingProfileDTO[],
|
||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
@@ -724,4 +920,107 @@ export default class FulfillmentModuleService<
|
|||||||
|
|
||||||
return Array.isArray(data) ? serialized : serialized[0]
|
return Array.isArray(data) ? serialized : serialized[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateShippingOptionRules(
|
||||||
|
data: FulfillmentTypes.UpdateShippingOptionRuleDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<FulfillmentTypes.ShippingOptionRuleDTO[]>
|
||||||
|
updateShippingOptionRules(
|
||||||
|
data: FulfillmentTypes.UpdateShippingOptionRuleDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<FulfillmentTypes.ShippingOptionRuleDTO>
|
||||||
|
|
||||||
|
@InjectManager("baseRepository_")
|
||||||
|
async updateShippingOptionRules(
|
||||||
|
data:
|
||||||
|
| FulfillmentTypes.UpdateShippingOptionRuleDTO[]
|
||||||
|
| FulfillmentTypes.UpdateShippingOptionRuleDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<
|
||||||
|
| FulfillmentTypes.ShippingOptionRuleDTO[]
|
||||||
|
| FulfillmentTypes.ShippingOptionRuleDTO
|
||||||
|
> {
|
||||||
|
const updatedShippingOptionRules = await this.updateShippingOptionRules_(
|
||||||
|
data,
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return await this.baseRepository_.serialize<
|
||||||
|
| FulfillmentTypes.ShippingOptionRuleDTO
|
||||||
|
| FulfillmentTypes.ShippingOptionRuleDTO[]
|
||||||
|
>(updatedShippingOptionRules, {
|
||||||
|
populate: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
async updateShippingOptionRules_(
|
||||||
|
data:
|
||||||
|
| FulfillmentTypes.UpdateShippingOptionRuleDTO[]
|
||||||
|
| FulfillmentTypes.UpdateShippingOptionRuleDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<TShippingOptionRuleEntity | TShippingOptionRuleEntity[]> {
|
||||||
|
const data_ = Array.isArray(data) ? data : [data]
|
||||||
|
|
||||||
|
if (!data_.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRules(data_ as unknown as Record<string, unknown>[])
|
||||||
|
|
||||||
|
const updatedShippingOptionRules =
|
||||||
|
await this.shippingOptionRuleService_.update(data_, sharedContext)
|
||||||
|
|
||||||
|
return Array.isArray(data)
|
||||||
|
? updatedShippingOptionRules
|
||||||
|
: updatedShippingOptionRules[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static validateMissingShippingOptions_(
|
||||||
|
shippingOptions: ShippingOption[],
|
||||||
|
shippingOptionsData: FulfillmentTypes.UpdateShippingOptionDTO[]
|
||||||
|
) {
|
||||||
|
const missingShippingOptionIds = arrayDifference(
|
||||||
|
shippingOptionsData.map((s) => s.id),
|
||||||
|
shippingOptions.map((s) => s.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (missingShippingOptionIds.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`The following shipping options do not exist: ${Array.from(
|
||||||
|
missingShippingOptionIds
|
||||||
|
).join(", ")}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static validateMissingShippingOptionRules(
|
||||||
|
shippingOption: ShippingOption,
|
||||||
|
shippingOptionUpdateData: FulfillmentTypes.UpdateShippingOptionDTO
|
||||||
|
) {
|
||||||
|
if (!shippingOptionUpdateData.rules) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingRules = shippingOption.rules
|
||||||
|
|
||||||
|
const rulesSet = new Set(existingRules.map((r) => r.id))
|
||||||
|
// Only validate the rules that have an id to validate that they really exists in the shipping option
|
||||||
|
const expectedRuleSet = new Set(
|
||||||
|
shippingOptionUpdateData.rules
|
||||||
|
.map((r) => "id" in r && r.id)
|
||||||
|
.filter((id): id is string => !!id)
|
||||||
|
)
|
||||||
|
const nonAlreadyExistingRules = getSetDifference(expectedRuleSet, rulesSet)
|
||||||
|
|
||||||
|
if (nonAlreadyExistingRules.size) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`The following rules does not exists: ${Array.from(
|
||||||
|
nonAlreadyExistingRules
|
||||||
|
).join(", ")} on shipping option ${shippingOptionUpdateData.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import { isContextValidForRules, RuleOperator } from "../utils"
|
||||||
|
|
||||||
|
describe("isContextValidForRules", () => {
|
||||||
|
const context = {
|
||||||
|
attribute1: "value1",
|
||||||
|
attribute2: "value2",
|
||||||
|
attribute3: "value3",
|
||||||
|
}
|
||||||
|
|
||||||
|
const validRule = {
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.EQ,
|
||||||
|
value: "value1",
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidRule = {
|
||||||
|
attribute: "attribute2",
|
||||||
|
operator: RuleOperator.EQ,
|
||||||
|
value: "wrongValue",
|
||||||
|
}
|
||||||
|
|
||||||
|
it("returns true when all rules are valid and atLeastOneValidRule is false", () => {
|
||||||
|
const rules = [validRule, validRule]
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when all rules are valid and atLeastOneValidRule is true", () => {
|
||||||
|
const rules = [validRule, validRule]
|
||||||
|
const options = { atLeastOneValidRule: true }
|
||||||
|
expect(isContextValidForRules(context, rules, options)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when some rules are valid and atLeastOneValidRule is true", () => {
|
||||||
|
const rules = [validRule, invalidRule]
|
||||||
|
const options = { atLeastOneValidRule: true }
|
||||||
|
expect(isContextValidForRules(context, rules, options)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when some rules are valid and atLeastOneValidRule is false", () => {
|
||||||
|
const rules = [validRule, invalidRule]
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when no rules are valid and atLeastOneValidRule is true", () => {
|
||||||
|
const rules = [invalidRule, invalidRule]
|
||||||
|
const options = { atLeastOneValidRule: true }
|
||||||
|
expect(isContextValidForRules(context, rules, options)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when no rules are valid and atLeastOneValidRule is false", () => {
|
||||||
|
const rules = [invalidRule, invalidRule]
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when the 'gt' operator is valid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.GT,
|
||||||
|
value: "1", // 2 > 1
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when the 'gt' operator is invalid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.GT,
|
||||||
|
value: "0", // 0 > 0
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "0" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when the 'gte' operator is valid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.GTE,
|
||||||
|
value: "2", // 2 >= 2
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when the 'gte' operator is invalid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.GTE,
|
||||||
|
value: "3", // 2 >= 3
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when the 'lt' operator is valid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.LT,
|
||||||
|
value: "3", // 2 < 3
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when the 'lt' operator is invalid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.LT,
|
||||||
|
value: "2", // 2 < 2
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when the 'lte' operator is valid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.LTE,
|
||||||
|
value: "2", // 2 <= 2
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// ... existing tests ...
|
||||||
|
|
||||||
|
it("returns false when the 'lte' operator is invalid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.LTE,
|
||||||
|
value: "1", // 2 <= 1
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when the 'in' operator is valid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.IN,
|
||||||
|
value: ["1", "2", "3"], // 2 in [1, 2, 3]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when the 'in' operator is invalid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.IN,
|
||||||
|
value: ["1", "3", "4"], // 2 in [1, 3, 4]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when the 'nin' operator is valid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.NIN,
|
||||||
|
value: ["1", "3", "4"], // 2 not in [1, 3, 4]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when the 'nin' operator is invalid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.NIN,
|
||||||
|
value: ["1", "2", "3"], // 2 not in [1, 2, 3]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when the 'ne' operator is valid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.NE,
|
||||||
|
value: "1", // 2 != 1
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when the 'ne' operator is invalid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.NE,
|
||||||
|
value: "2", // 2 != 2
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when the 'eq' operator is valid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.EQ,
|
||||||
|
value: "2", // 2 == 2
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false when the 'eq' operator is invalid", () => {
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
attribute: "attribute1",
|
||||||
|
operator: RuleOperator.EQ,
|
||||||
|
value: "1", // 2 == 1
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const context = { attribute1: "2" }
|
||||||
|
expect(isContextValidForRules(context, rules)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './utils'
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { isString, MedusaError, pickValueFromObject } from "@medusajs/utils"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rule engine here is kept inside the module as of now, but it could be moved
|
||||||
|
* to the utils package and be used across the different modules that provides context
|
||||||
|
* based rule filtering.
|
||||||
|
*
|
||||||
|
* TODO: discussion around that should happen at some point
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Rule = {
|
||||||
|
attribute: string
|
||||||
|
operator: RuleOperator
|
||||||
|
value: string | string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RuleOperator {
|
||||||
|
IN = "in",
|
||||||
|
EQ = "eq",
|
||||||
|
NE = "ne",
|
||||||
|
GT = "gt",
|
||||||
|
GTE = "gte",
|
||||||
|
LT = "lt",
|
||||||
|
LTE = "lte",
|
||||||
|
NIN = "nin",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const availableOperators = Object.values(RuleOperator)
|
||||||
|
|
||||||
|
const isDate = (str: string) => {
|
||||||
|
return !isNaN(Date.parse(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
const operatorsPredicate = {
|
||||||
|
in: (contextValue: string, ruleValue: string[]) =>
|
||||||
|
ruleValue.includes(contextValue),
|
||||||
|
nin: (contextValue: string, ruleValue: string[]) =>
|
||||||
|
!ruleValue.includes(contextValue),
|
||||||
|
eq: (contextValue: string, ruleValue: string) => contextValue === ruleValue,
|
||||||
|
ne: (contextValue: string, ruleValue: string) => contextValue !== ruleValue,
|
||||||
|
gt: (contextValue: string, ruleValue: string) => {
|
||||||
|
if (isDate(contextValue) && isDate(ruleValue)) {
|
||||||
|
return new Date(contextValue) > new Date(ruleValue)
|
||||||
|
}
|
||||||
|
return Number(contextValue) > Number(ruleValue)
|
||||||
|
},
|
||||||
|
gte: (contextValue: string, ruleValue: string) => {
|
||||||
|
if (isDate(contextValue) && isDate(ruleValue)) {
|
||||||
|
return new Date(contextValue) >= new Date(ruleValue)
|
||||||
|
}
|
||||||
|
return Number(contextValue) >= Number(ruleValue)
|
||||||
|
},
|
||||||
|
lt: (contextValue: string, ruleValue: string) => {
|
||||||
|
if (isDate(contextValue) && isDate(ruleValue)) {
|
||||||
|
return new Date(contextValue) < new Date(ruleValue)
|
||||||
|
}
|
||||||
|
return Number(contextValue) < Number(ruleValue)
|
||||||
|
},
|
||||||
|
lte: (contextValue: string, ruleValue: string) => {
|
||||||
|
if (isDate(contextValue) && isDate(ruleValue)) {
|
||||||
|
return new Date(contextValue) <= new Date(ruleValue)
|
||||||
|
}
|
||||||
|
return Number(contextValue) <= Number(ruleValue)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate contextValue context object from contextValue set of rules.
|
||||||
|
* By default, all rules must be valid to return true unless the option atLeastOneValidRule is set to true.
|
||||||
|
* @param context
|
||||||
|
* @param rules
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
export function isContextValidForRules(
|
||||||
|
context: Record<string, any>,
|
||||||
|
rules: Rule[],
|
||||||
|
options: {
|
||||||
|
atLeastOneValidRule: boolean
|
||||||
|
} = {
|
||||||
|
atLeastOneValidRule: false,
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const { atLeastOneValidRule } = options
|
||||||
|
|
||||||
|
const loopComparator = atLeastOneValidRule ? rules.some : rules.every
|
||||||
|
const predicate = (rule) => {
|
||||||
|
const { attribute, operator, value } = rule
|
||||||
|
const contextValue = pickValueFromObject(attribute, context)
|
||||||
|
return operatorsPredicate[operator](
|
||||||
|
contextValue,
|
||||||
|
value as string & string[]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return loopComparator.apply(rules, [predicate])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate contextValue rule object
|
||||||
|
* @param rule
|
||||||
|
*/
|
||||||
|
export function validateRule(rule: Record<string, unknown>): boolean {
|
||||||
|
if (!rule.attribute || !rule.operator || !rule.value) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Rule must have an attribute, an operator and contextValue value"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isString(rule.attribute)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Rule attribute must be contextValue string"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isString(rule.operator)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Rule operator must be contextValue string"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!availableOperators.includes(rule.operator as RuleOperator)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Rule operator ${
|
||||||
|
rule.operator
|
||||||
|
} is not supported. Must be one of ${availableOperators.join(", ")}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.operator === RuleOperator.IN || rule.operator === RuleOperator.NIN) {
|
||||||
|
if (!Array.isArray(rule.value)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Rule value must be an array for in/nin operators"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate contextValue set of rules
|
||||||
|
* @param rules
|
||||||
|
*/
|
||||||
|
export function validateRules(rules: Record<string, unknown>[]): boolean {
|
||||||
|
rules.forEach(validateRule)
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
"@models": ["./src/models"],
|
"@models": ["./src/models"],
|
||||||
"@services": ["./src/services"],
|
"@services": ["./src/services"],
|
||||||
"@repositories": ["./src/repositories"],
|
"@repositories": ["./src/repositories"],
|
||||||
"@types": ["./src/types"]
|
"@types": ["./src/types"],
|
||||||
|
"@utils": ["./src/utils"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { NextFunction, Request, Response } from "express"
|
import type { NextFunction, Request, Response } from "express"
|
||||||
|
|
||||||
import type { Customer, User } from "../models"
|
import type { Customer, User } from "../models"
|
||||||
import type { MedusaContainer } from "./global"
|
import { MedusaContainer } from "@medusajs/types"
|
||||||
|
|
||||||
export interface MedusaRequest extends Request {
|
export interface MedusaRequest extends Request {
|
||||||
user?: (User | Customer) & { customer_id?: string; userId?: string }
|
user?: (User | Customer) & { customer_id?: string; userId?: string }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export interface CreateShippingOptionRuleDTO {
|
export interface CreateShippingOptionRuleDTO {
|
||||||
attribute: string
|
attribute: string
|
||||||
operator: string
|
operator: "in" | "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "nin"
|
||||||
value: string | string[]
|
value: string | string[]
|
||||||
shipping_option_id: string
|
shipping_option_id: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ import {
|
|||||||
FilterableGeoZoneProps,
|
FilterableGeoZoneProps,
|
||||||
FilterableServiceZoneProps,
|
FilterableServiceZoneProps,
|
||||||
FilterableShippingOptionProps,
|
FilterableShippingOptionProps,
|
||||||
|
FilterableShippingOptionRuleProps,
|
||||||
|
FilterableShippingOptionTypeProps,
|
||||||
FilterableShippingProfileProps,
|
FilterableShippingProfileProps,
|
||||||
FulfillmentSetDTO,
|
FulfillmentSetDTO,
|
||||||
GeoZoneDTO,
|
GeoZoneDTO,
|
||||||
ServiceZoneDTO,
|
ServiceZoneDTO,
|
||||||
ShippingOptionDTO,
|
ShippingOptionDTO,
|
||||||
|
ShippingOptionRuleDTO,
|
||||||
|
ShippingOptionTypeDTO,
|
||||||
ShippingProfileDTO,
|
ShippingProfileDTO,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
import { FindConfig } from "../common"
|
import { FindConfig } from "../common"
|
||||||
@@ -19,10 +23,12 @@ import {
|
|||||||
CreateGeoZoneDTO,
|
CreateGeoZoneDTO,
|
||||||
CreateServiceZoneDTO,
|
CreateServiceZoneDTO,
|
||||||
CreateShippingOptionDTO,
|
CreateShippingOptionDTO,
|
||||||
|
CreateShippingOptionRuleDTO,
|
||||||
UpdateFulfillmentSetDTO,
|
UpdateFulfillmentSetDTO,
|
||||||
UpdateGeoZoneDTO,
|
UpdateGeoZoneDTO,
|
||||||
UpdateServiceZoneDTO,
|
UpdateServiceZoneDTO,
|
||||||
UpdateShippingOptionDTO,
|
UpdateShippingOptionDTO,
|
||||||
|
UpdateShippingOptionRuleDTO,
|
||||||
} from "./mutations"
|
} from "./mutations"
|
||||||
import { CreateShippingProfileDTO } from "./mutations/shipping-profile"
|
import { CreateShippingProfileDTO } from "./mutations/shipping-profile"
|
||||||
|
|
||||||
@@ -98,6 +104,20 @@ export interface IFulfillmentModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<GeoZoneDTO>
|
): Promise<GeoZoneDTO>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new shipping option rules
|
||||||
|
* @param data
|
||||||
|
* @param sharedContext
|
||||||
|
*/
|
||||||
|
createShippingOptionRules(
|
||||||
|
data: CreateShippingOptionRuleDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<ShippingOptionRuleDTO[]>
|
||||||
|
createShippingOptionRules(
|
||||||
|
data: CreateShippingOptionRuleDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<ShippingOptionRuleDTO>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a fulfillment set
|
* Update a fulfillment set
|
||||||
* @param data
|
* @param data
|
||||||
@@ -168,6 +188,20 @@ export interface IFulfillmentModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<GeoZoneDTO>
|
): Promise<GeoZoneDTO>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a shipping option rule
|
||||||
|
* @param data
|
||||||
|
* @param sharedContext
|
||||||
|
*/
|
||||||
|
updateShippingOptionRules(
|
||||||
|
data: UpdateShippingOptionRuleDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<ShippingOptionRuleDTO[]>
|
||||||
|
updateShippingOptionRules(
|
||||||
|
data: UpdateShippingOptionRuleDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<ShippingOptionRuleDTO>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a fulfillment set
|
* Delete a fulfillment set
|
||||||
* @param ids
|
* @param ids
|
||||||
@@ -208,6 +242,17 @@ export interface IFulfillmentModuleService extends IModuleService {
|
|||||||
deleteGeoZones(ids: string[], sharedContext?: Context): Promise<void>
|
deleteGeoZones(ids: string[], sharedContext?: Context): Promise<void>
|
||||||
deleteGeoZones(id: string, sharedContext?: Context): Promise<void>
|
deleteGeoZones(id: string, sharedContext?: Context): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a shipping option rule
|
||||||
|
* @param ids
|
||||||
|
* @param sharedContext
|
||||||
|
*/
|
||||||
|
deleteShippingOptionRules(
|
||||||
|
ids: string[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<void>
|
||||||
|
deleteShippingOptionRules(id: string, sharedContext?: Context): Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a fulfillment set
|
* Retrieve a fulfillment set
|
||||||
* @param id
|
* @param id
|
||||||
@@ -268,6 +313,30 @@ export interface IFulfillmentModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<GeoZoneDTO>
|
): Promise<GeoZoneDTO>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a shipping option rule
|
||||||
|
* @param id
|
||||||
|
* @param config
|
||||||
|
* @param sharedContext
|
||||||
|
*/
|
||||||
|
retrieveShippingOptionRule(
|
||||||
|
id: string,
|
||||||
|
config?: FindConfig<ShippingOptionRuleDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<ShippingOptionRuleDTO>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a shipping option type
|
||||||
|
* @param id
|
||||||
|
* @param config
|
||||||
|
* @param sharedContext
|
||||||
|
*/
|
||||||
|
retrieveShippingOptionType(
|
||||||
|
id: string,
|
||||||
|
config?: FindConfig<ShippingOptionTypeDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<ShippingOptionTypeDTO>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List fulfillment sets
|
* List fulfillment sets
|
||||||
* @param filters
|
* @param filters
|
||||||
@@ -328,6 +397,30 @@ export interface IFulfillmentModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<GeoZoneDTO[]>
|
): Promise<GeoZoneDTO[]>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List shipping option rules
|
||||||
|
* @param filters
|
||||||
|
* @param config
|
||||||
|
* @param sharedContext
|
||||||
|
*/
|
||||||
|
listShippingOptionRules(
|
||||||
|
filters?: FilterableShippingOptionRuleProps,
|
||||||
|
config?: FindConfig<ShippingOptionRuleDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<ShippingOptionRuleDTO[]>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List shipping option types
|
||||||
|
* @param filters
|
||||||
|
* @param config
|
||||||
|
* @param sharedContext
|
||||||
|
*/
|
||||||
|
listShippingOptionTypes(
|
||||||
|
filters?: FilterableShippingOptionTypeProps,
|
||||||
|
config?: FindConfig<ShippingOptionTypeDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<ShippingOptionTypeDTO[]>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List and count fulfillment sets
|
* List and count fulfillment sets
|
||||||
* @param filters
|
* @param filters
|
||||||
@@ -388,6 +481,30 @@ export interface IFulfillmentModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<[GeoZoneDTO[], number]>
|
): Promise<[GeoZoneDTO[], number]>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List and count shipping option rules
|
||||||
|
* @param filters
|
||||||
|
* @param config
|
||||||
|
* @param sharedContext
|
||||||
|
*/
|
||||||
|
listAndCountShippingOptionRules(
|
||||||
|
filters?: FilterableShippingOptionRuleProps,
|
||||||
|
config?: FindConfig<ShippingOptionRuleDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<[ShippingOptionRuleDTO[], number]>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List and count shipping options types
|
||||||
|
* @param filters
|
||||||
|
* @param config
|
||||||
|
* @param sharedContext
|
||||||
|
*/
|
||||||
|
listAndCountShippingOptionTypes(
|
||||||
|
filters?: FilterableShippingOptionTypeProps,
|
||||||
|
config?: FindConfig<ShippingOptionTypeDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<[ShippingOptionTypeDTO[], number]>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Soft delete fulfillment sets
|
* Soft delete fulfillment sets
|
||||||
* @param fulfillmentIds
|
* @param fulfillmentIds
|
||||||
@@ -454,5 +571,5 @@ export interface IFulfillmentModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<Record<string, string[]> | void>
|
): Promise<Record<string, string[]> | void>
|
||||||
|
|
||||||
// TODO defined the other restore methods
|
// TODO define needed soft delete/delete/restore methods
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ export function internalModuleServiceFactory<
|
|||||||
}
|
}
|
||||||
|
|
||||||
static buildUniqueCompositeKeyValue(keys: string[], data: object) {
|
static buildUniqueCompositeKeyValue(keys: string[], data: object) {
|
||||||
return keys.map((k) => data[k]).join("_")
|
return keys.map((k) => data[k]).join(":")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -287,7 +287,7 @@ export function internalModuleServiceFactory<
|
|||||||
|
|
||||||
;[...keySelectorDataMap.keys()].filter((key) => {
|
;[...keySelectorDataMap.keys()].filter((key) => {
|
||||||
if (!compositeKeysValuesForFoundEntities.has(key)) {
|
if (!compositeKeysValuesForFoundEntities.has(key)) {
|
||||||
const value = key.replace(/_/gi, " - ")
|
const value = key.replace(/:/gi, " - ")
|
||||||
missingEntityValues.push(value)
|
missingEntityValues.push(value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in new issue
Block a user