Skip to content

Commit

Permalink
feat(shipping-extensions): added checker for facets and country
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnvdbrug committed Jan 16, 2025
1 parent 58df601 commit b4e17b0
Show file tree
Hide file tree
Showing 16 changed files with 1,012 additions and 154 deletions.
15 changes: 15 additions & 0 deletions packages/test/src/admin.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ fragment OrderFields on Order {
trackingCode
customFields
}
shippingLines {
id
price
priceWithTax
shippingMethod {
id
code
name
}
}
lines {
id
quantity
Expand Down Expand Up @@ -122,6 +132,11 @@ mutation UpdateProductVariants($input: [UpdateProductVariantInput!]!) {
stockOnHand
trackInventory
outOfStockThreshold
facetValues {
id
code
name
}
__typename
}
}
Expand Down
520 changes: 435 additions & 85 deletions packages/test/src/generated/admin-graphql.ts

Large diffs are not rendered by default.

270 changes: 234 additions & 36 deletions packages/test/src/generated/shop-graphql.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/test/src/products-import.csv
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ name , slug , description
Laptop , laptop , "Now equipped with seventh-generation Intel Core processors, Laptop is snappier than ever. From daily tasks like launching apps and opening files to more advanced computing, you can power through your day thanks to faster SSDs and Turbo Boost processing up to 3.6GHz." , , category:electronics|brand:Apple , "screen size|RAM" , "13 inch|8GB" , L2201308 , 1299.00 , standard , 100 , false , , category:electronics
, , , , , , "15 inch|8GB" , L2201508 , 1399.00 , standard , 100 , false , , category:computers
, , , , , , "13 inch|16GB" , L2201316 , 9.00 , standard , 100 , false , , category:electronics
, , , , , , "15 inch|16GB" , L2201516 , 2299.00 , standard , 100 , false , , category:electronics
, , , , , , "15 inch|16GB" , L2201516 , 2299.00 , standard , 100 , false , , category:electronics|edition:limited
10 changes: 10 additions & 0 deletions packages/test/src/shop.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ fragment OrderFields on Order {
lastName
emailAddress
}
shippingLines {
id
price
priceWithTax
shippingMethod {
id
code
name
}
}
lines {
id
quantity
Expand Down
5 changes: 5 additions & 0 deletions packages/vendure-plugin-shipping-extensions/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 2.9.0 (2025-01-16)

- Added new shipping checker: Check by country and facet values
- Optimization of the Weight and Country checker: Hydrate all `lines.productVariant.product` in one call.

# 2.8.0 (2024-12-19)

- Update Vendure to 3.1.1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
CountryService,
FacetValueChecker,
Injector,
LanguageCode,
RequestContext,
ShippingEligibilityChecker,
} from '@vendure/core';
import { isEligibleForCountry } from './util';

let injector: Injector;
/**
* Checks if an order only has items with given facets
*/
export const facetAndCountryChecker = new ShippingEligibilityChecker({
code: 'shipping-by-facets-and-country',
description: [
{
languageCode: LanguageCode.en,
value: 'Check by facets and country',
},
],
args: {
facets: {
type: 'ID',
list: true,
label: [{ languageCode: LanguageCode.en, value: `Facets` }],
description: [
{
languageCode: LanguageCode.en,
value: `All items in order should have all of the facets`,
},
],
ui: {
component: 'facet-value-form-input',
},
},
countries: {
type: 'string',
list: true,
ui: {
component: 'select-form-input',
options: [
{
value: 'nl',
label: [{ languageCode: LanguageCode.en, value: 'Nederland' }],
},
],
},
},
excludeCountries: {
type: 'boolean',
description: [
{
languageCode: LanguageCode.en,
value: 'Eligible for all countries except the ones listed above',
},
],
ui: {
component: 'boolean-form-input',
},
},
},
async init(_injector) {
injector = _injector;
const ctx = RequestContext.empty();
// Populate the countries arg list
const countries = await _injector.get(CountryService).findAll(ctx);
this.args.countries.ui.options = countries.items.map((c) => ({
value: c.code,
label: [
{
languageCode: LanguageCode.en,
value: c.name,
},
],
}));
},
async check(ctx, order, { facets, countries, excludeCountries }) {
const isEligibleByCountry = isEligibleForCountry(
order,
countries,
excludeCountries
);
if (isEligibleByCountry === false) {
return false;
}
// Shipping country is allowed, continue checking facets
for (const line of order.lines) {
const hasFacetValues = await injector
.get(FacetValueChecker)
.hasFacetValues(line, facets);
if (!hasFacetValues) {
// One of the lines doesn't have the facetValue, no need to check any more
return false;
}
}
return true;
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, it, expect } from 'vitest';
import { isEligibleForCountry } from './util';
import { Order } from '@vendure/core';

describe('isEligibleForCountry', () => {
it('Is eligible if the order country is in the list and excludeCountries is false', () => {
const order = {
shippingAddress: {
countryCode: 'US',
},
} as Order;
const result = isEligibleForCountry(order, ['US', 'CA'], false);
expect(result).toBe(true);
});

it('Is not eligible if the order country is in the list and excludeCountries is true', () => {
const order = {
shippingAddress: {
countryCode: 'US',
},
} as Order;
const result = isEligibleForCountry(order, ['US', 'CA'], true);
expect(result).toBe(false);
});

it('Is not eligible if the order country is not in the list', () => {
const order = {
shippingAddress: {
countryCode: 'MX',
},
} as Order;
const result = isEligibleForCountry(order, ['US', 'CA'], false);
expect(result).toBe(false);
});

it('should return true if the order country is not in the list and excludeCountries is true', () => {
const order = {
shippingAddress: {
countryCode: 'MX',
},
} as Order;
const result = isEligibleForCountry(order, ['US', 'CA'], true);
expect(result).toBe(true);
});
});
25 changes: 25 additions & 0 deletions packages/vendure-plugin-shipping-extensions/src/config/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Order } from '@vendure/core';

/**
* Checks if an order is eligible for the given country codes.
* When excludedCountries=true: The order is eligible if it's country is NOT in the configured countryCodes
*/
export function isEligibleForCountry(
order: Order,
countryCodes: string[],
excludeCountries: boolean
): boolean {
const shippingCountry = order.shippingAddress.countryCode;
const orderIsInSelectedCountry = shippingCountry
? countryCodes.includes(shippingCountry)
: false;
if (orderIsInSelectedCountry && excludeCountries) {
// Not eligible, because order.country is in our excluded-country-list
return false;
}
if (!orderIsInSelectedCountry && !excludeCountries) {
// Not eligible, because order.country is not in our list, but it should be
return false;
}
return true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ShippingEligibilityChecker,
} from '@vendure/core';
import { ShippingExtensionsPlugin } from '../shipping-extensions.plugin';
import { isEligibleForCountry } from './util';

export function calculateOrderWeight(order: Order): number {
return order.lines.reduce((acc, line) => {
Expand Down Expand Up @@ -103,25 +104,22 @@ export const weightAndCountryChecker = new ShippingEligibilityChecker({
order,
{ minWeight, maxWeight, countries, excludeCountries }
) {
const shippingCountry = order.shippingAddress.countryCode;
const orderIsInSelectedCountry = shippingCountry
? countries.includes(shippingCountry)
: false;
if (orderIsInSelectedCountry && excludeCountries) {
// Not eligible, because order.country is in our excluded-country-list
return false;
}
if (!orderIsInSelectedCountry && !excludeCountries) {
// Not eligible, because order.country is not in our list, but it should be
const isEligibleByCountry = isEligibleForCountry(
order,
countries,
excludeCountries
);
if (isEligibleByCountry === false) {
return false;
}
// Shipping country is allowed, continue checking order weight
await entityHydrator.hydrate(ctx, order, { relations: ['lines'] });
for (const line of order.lines) {
await entityHydrator.hydrate(ctx, line, {
relations: ['productVariant', 'productVariant.product'],
});
}
await entityHydrator.hydrate(ctx, order, {
relations: [
'lines',
'lines.productVariant',
'lines.productVariant.product',
],
});
let totalOrderWeight = 0;
if (ShippingExtensionsPlugin.options?.weightCalculationFunction) {
totalOrderWeight =
Expand Down
1 change: 1 addition & 0 deletions packages/vendure-plugin-shipping-extensions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './strategies/uk-postalcode-to-geolocation-strategy';
export * from './strategies/order-address-to-geolocation-strategy';
export * from './config/distance-based-shipping-calculator';
export * from './config/order-in-country-promotion-condition';
export * from './config/facet-and-country-checker';
export * from './config/weight-and-country-checker';
export * from './config/zone-aware-flat-rate-shipping-calculator';
export * from './services/zone-aware-shipping-tax-calculation.service';
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
import path from 'path';
import { zoneAwareFlatRateShippingCalculator } from './config/zone-aware-flat-rate-shipping-calculator';
import { ZoneAwareShippingTaxCalculationService } from './services/zone-aware-shipping-tax-calculation.service';
import { facetAndCountryChecker } from './config/facet-and-country-checker';

export interface ShippingExtensionsOptions {
/**
Expand Down Expand Up @@ -60,6 +61,9 @@ export interface ShippingExtensionsOptions {
config.shippingOptions.shippingEligibilityCheckers.push(
weightAndCountryChecker
);
config.shippingOptions.shippingEligibilityCheckers.push(
facetAndCountryChecker
);
config.promotionOptions.promotionConditions.push(
orderInCountryPromotionCondition
);
Expand Down
10 changes: 5 additions & 5 deletions packages/vendure-plugin-shipping-extensions/test/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ import path from 'path';
AdminUiPlugin.init({
port: 3002,
route: 'admin',
app: compileUiExtensions({
outputPath: path.join(__dirname, '__admin-ui'),
extensions: [ShippingExtensionsPlugin.ui],
devMode: true,
}),
// app: compileUiExtensions({
// outputPath: path.join(__dirname, '__admin-ui'),
// extensions: [ShippingExtensionsPlugin.ui],
// devMode: true,
// }),
}),
ShippingExtensionsPlugin.init({
weightUnit: 'kg',
Expand Down
Loading

0 comments on commit b4e17b0

Please sign in to comment.