@myparcel-dev/pdk-admin 1.14.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/package.json +6 -6
- package/src/__tests__/__snapshots__/exports.spec.ts.snap +8 -1
- package/src/__tests__/doComponentTestTeardown.ts +2 -0
- package/src/__tests__/mocks/index.ts +1 -1
- package/src/__tests__/mocks/mockDefaultAccount.ts +1 -0
- package/src/__tests__/mocks/mockDefaultGlobalContext.ts +2 -2
- package/src/__tests__/mocks/{mockDefaultPlatform.ts → mockDefaultProposition.ts} +1 -3
- package/src/__tests__/mocks/mockDefaultShop.ts +1 -0
- package/src/__tests__/utils/getActionIds.ts +12 -0
- package/src/__tests__/utils/mockOrderData.ts +20 -0
- package/src/actions/composables/mutations/orders/useExportOrdersMutation.ts +1 -1
- package/src/actions/composables/queries/account/useOrderCapabilitiesQuery.ts +67 -0
- package/src/actions/composables/queries/account/useShipmentCapabilitiesQuery.ts +96 -0
- package/src/actions/definitions/orders.ts +6 -8
- package/src/actions/index.ts +1 -0
- package/src/actions/orderModeActions.spec.ts +150 -0
- package/src/actions/orderModeActions.ts +90 -0
- package/src/components/OrderBox/ShipmentOptionsBox.spec.ts +109 -0
- package/src/components/OrderBox/ShipmentOptionsBox.vue +9 -25
- package/src/components/OrderListItem/OrderModeOrderListItem.spec.ts +125 -0
- package/src/components/OrderListItem/OrderModeOrderListItem.vue +10 -6
- package/src/components/PluginSettings/PluginSettingsForms.vue +7 -4
- package/src/components/common/DeliveryOptionsExcerptCarrierName.vue +3 -3
- package/src/components/modals/ShipmentOptionsModal.spec.ts +79 -0
- package/src/components/modals/ShipmentOptionsModal.vue +4 -21
- package/src/composables/components/useToggleInputContext.ts +2 -2
- package/src/composables/components/useTriStateInputContext.ts +2 -2
- package/src/composables/context/index.ts +1 -0
- package/src/composables/context/useOrderMode.spec.ts +75 -0
- package/src/composables/context/useOrderMode.ts +26 -0
- package/src/composables/language/useLanguage.spec.ts +2 -2
- package/src/composables/shipments/useShipmentData.ts +2 -2
- package/src/data/index.ts +1 -0
- package/src/data/orderMode.spec.ts +22 -0
- package/src/data/orderMode.ts +50 -0
- package/src/forms/form-builder/builders/buildAfterUpdate.spec.ts +5 -8
- package/src/forms/form-builder/operations/executeOperations.spec.ts +4 -7
- package/src/forms/form-builder/operations/executeSetPropOperation.spec.ts +4 -7
- package/src/forms/form-builder/operations/executeSetValueOperation.spec.ts +3 -2
- package/src/forms/form-builder/utils/createValueSetter.ts +21 -8
- package/src/forms/helpers/addCapabilitiesClearNotification.ts +39 -0
- package/src/forms/helpers/getDeliveryTypes.ts +5 -10
- package/src/forms/helpers/getFieldLabel.ts +7 -2
- package/src/forms/helpers/getPackageTypes.ts +7 -12
- package/src/forms/helpers/index.ts +3 -10
- package/src/forms/helpers/useFormCapabilities.spec.ts +227 -0
- package/src/forms/helpers/useFormCapabilities.ts +176 -0
- package/src/forms/pluginSettings/createPluginSettingsForm.ts +2 -2
- package/src/forms/pluginSettings/filterPluginSettingsView.spec.ts +158 -0
- package/src/forms/pluginSettings/filterPluginSettingsView.ts +96 -0
- package/src/forms/pluginSettings/index.ts +1 -0
- package/src/forms/pluginSettings/resolveSettingsValues.spec.ts +62 -0
- package/src/forms/pluginSettings/resolveSettingsValues.ts +16 -0
- package/src/forms/shipmentOptions/createShipmentOptionsForm.spec.ts +108 -0
- package/src/forms/shipmentOptions/createShipmentOptionsForm.ts +114 -52
- package/src/forms/shipmentOptions/field.ts +6 -66
- package/src/forms/shipmentOptions/fieldFactoryRegistry.ts +26 -0
- package/src/forms/shipmentOptions/fields/createCarrierField.ts +119 -30
- package/src/forms/shipmentOptions/fields/createDigitalStampRangeField.ts +3 -3
- package/src/forms/shipmentOptions/fields/createInsuranceField.ts +43 -22
- package/src/forms/shipmentOptions/fields/createPackageTypeField.ts +1 -2
- package/src/forms/shipmentOptions/fields/createShipmentOptionField.spec.ts +177 -0
- package/src/forms/shipmentOptions/fields/createShipmentOptionField.ts +90 -8
- package/src/forms/shipmentOptions/fields/index.ts +0 -9
- package/src/forms/shipmentOptions/useCapabilitiesAutoClear.spec.ts +347 -0
- package/src/forms/shipmentOptions/useCapabilitiesAutoClear.ts +452 -0
- package/src/forms/shipmentOptions/useCapabilitiesWatcher.spec.ts +14 -0
- package/src/forms/shipmentOptions/useCapabilitiesWatcher.ts +29 -0
- package/src/forms/shipmentOptions/wireProxyCapabilities.spec.ts +160 -0
- package/src/forms/shipmentOptions/wireProxyCapabilities.ts +102 -0
- package/src/index.ts +2 -0
- package/src/pdk/instance/plugins/createRegisterComponentsPlugin.ts +2 -0
- package/src/sdk/composables/api/useFetchCarrier.ts +39 -4
- package/src/sdk/composables/usePdkAdminApi.ts +8 -3
- package/src/stores/useActionStore.spec.ts +101 -0
- package/src/stores/useActionStore.ts +4 -28
- package/src/stores/useQueryStore.ts +15 -0
- package/src/types/actions/endpoints.types.ts +2 -1
- package/src/types/sdk.types.ts +54 -1
- package/src/utils/forms/formToBody.ts +2 -1
- package/src/utils/forms/getEnabledValues.spec.ts +66 -0
- package/src/utils/forms/getEnabledValues.ts +25 -0
- package/src/utils/forms/index.ts +1 -0
- package/src/utils/translations/getDynamicTranslation.ts +1 -1
- package/src/views/OrderBox.vue +11 -5
- package/src/views/OrderListItem.spec.ts +71 -0
- package/src/views/OrderListItem.vue +6 -7
- package/src/forms/helpers/createHasShipmentOptionWatcher.ts +0 -12
- package/src/forms/helpers/getCarrier.ts +0 -11
- package/src/forms/helpers/getInsuranceOptions.ts +0 -23
- package/src/forms/helpers/hasPostNlAgeCheck.ts +0 -11
- package/src/forms/helpers/hasShipmentOption.ts +0 -8
- package/src/forms/helpers/isPackageTypeMailbox.ts +0 -7
- package/src/forms/helpers/isPackageTypePackage.ts +0 -7
- package/src/forms/helpers/isPackageTypePackageOrSmall.ts +0 -7
- package/src/forms/helpers/isPackageTypeSmallPackage.ts +0 -7
- package/src/forms/helpers/setPostNlAgeCheckSubtext.ts +0 -17
- package/src/forms/shipmentOptions/fields/createAgeCheckField.ts +0 -33
- package/src/forms/shipmentOptions/fields/createDirectReturnField.ts +0 -8
- package/src/forms/shipmentOptions/fields/createHideSenderField.ts +0 -8
- package/src/forms/shipmentOptions/fields/createLargeFormatField.ts +0 -8
- package/src/forms/shipmentOptions/fields/createOnlyRecipientField.ts +0 -21
- package/src/forms/shipmentOptions/fields/createPriorityDeliveryField.ts +0 -12
- package/src/forms/shipmentOptions/fields/createReceiptCodeField.ts +0 -21
- package/src/forms/shipmentOptions/fields/createSameDayDeliveryField.ts +0 -8
- package/src/forms/shipmentOptions/fields/createSignatureField.ts +0 -11
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {computed, type ComputedRef, type Ref, ref, toValue, unref} from 'vue';
|
|
2
2
|
import {type MaybeRef} from '@vueuse/core';
|
|
3
|
-
import {type Shipment} from '@myparcel-dev/pdk-common';
|
|
4
3
|
import {type Carrier} from '@myparcel-dev/sdk';
|
|
4
|
+
import {type Shipment} from '@myparcel-dev/pdk-common';
|
|
5
5
|
import {type AnyActionDefinition} from '../../types';
|
|
6
6
|
import {useQueryStore} from '../../stores';
|
|
7
7
|
import {instantiateActions} from '../../services';
|
|
@@ -32,7 +32,7 @@ export const useShipmentData = (id: MaybeRef<number>): UseShipmentData => {
|
|
|
32
32
|
orderId ? queryStore.getQueriesForOrder(orderId) : undefined,
|
|
33
33
|
].filter(Boolean);
|
|
34
34
|
|
|
35
|
-
const carrierName = shipment.value?.carrier?.
|
|
35
|
+
const carrierName = shipment.value?.carrier?.carrier;
|
|
36
36
|
const carriersQuery = carrierName ? useFetchCarrier(carrierName) : undefined;
|
|
37
37
|
|
|
38
38
|
return {
|
package/src/data/index.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest';
|
|
2
|
+
import {OrderMode, resolveOrderMode, SubscriptionFeature} from './orderMode';
|
|
3
|
+
|
|
4
|
+
describe('resolveOrderMode', () => {
|
|
5
|
+
it('returns OrderV2 when both ORDER_MANAGEMENT and LEGACY_ORDER_MANAGEMENT are present', () => {
|
|
6
|
+
expect(resolveOrderMode([SubscriptionFeature.OrderManagement, SubscriptionFeature.LegacyOrderManagement])).toBe(
|
|
7
|
+
OrderMode.OrderV2,
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('returns OrderV2 when only ORDER_MANAGEMENT is present', () => {
|
|
12
|
+
expect(resolveOrderMode([SubscriptionFeature.OrderManagement])).toBe(OrderMode.OrderV2);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('returns OrderV1 when only LEGACY_ORDER_MANAGEMENT is present', () => {
|
|
16
|
+
expect(resolveOrderMode([SubscriptionFeature.LegacyOrderManagement])).toBe(OrderMode.OrderV1);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('returns Shipments when no features are present', () => {
|
|
20
|
+
expect(resolveOrderMode([])).toBe(OrderMode.Shipments);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export enum OrderMode {
|
|
2
|
+
Shipments = 'Shipments',
|
|
3
|
+
OrderV1 = 'OrderV1',
|
|
4
|
+
OrderV2 = 'OrderV2',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum SubscriptionFeature {
|
|
8
|
+
LegacyOrderManagement = 'LEGACY_ORDER_MANAGEMENT',
|
|
9
|
+
OrderManagement = 'ORDER_MANAGEMENT',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Mapping from the PHP-side `AccountFeaturesServiceInterface::ORDER_MODE_*`
|
|
14
|
+
* integer constants to the JS-side {@see OrderMode} enum.
|
|
15
|
+
*/
|
|
16
|
+
const ORDER_MODE_BY_NUMERIC: Record<number, OrderMode> = {
|
|
17
|
+
0: OrderMode.Shipments,
|
|
18
|
+
1: OrderMode.OrderV1,
|
|
19
|
+
2: OrderMode.OrderV2,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Translate the numeric `effectiveOrderMode` emitted by the PDK admin context
|
|
24
|
+
* (when present) into the JS-side {@see OrderMode} enum. Returns `undefined`
|
|
25
|
+
* when the value is missing or not one of the known modes, so callers can fall
|
|
26
|
+
* back to feature-based resolution.
|
|
27
|
+
*/
|
|
28
|
+
export const orderModeFromContextValue = (value: unknown): OrderMode | undefined => {
|
|
29
|
+
if (typeof value === 'number' && value in ORDER_MODE_BY_NUMERIC) {
|
|
30
|
+
return ORDER_MODE_BY_NUMERIC[value];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Derive the active {@see OrderMode} from the raw IAM subscription features.
|
|
38
|
+
*
|
|
39
|
+
* Use this only as a fallback for environments where the PDK admin context does
|
|
40
|
+
* not yet surface a pre-computed `effectiveOrderMode`. Prefer the context value
|
|
41
|
+
* so any business rules layered on top in PHP (e.g. INT-1590's sales-channel
|
|
42
|
+
* downgrade) take effect on the JS side automatically.
|
|
43
|
+
*/
|
|
44
|
+
export const resolveOrderMode = (features: string[]): OrderMode => {
|
|
45
|
+
if (features.includes(SubscriptionFeature.OrderManagement)) return OrderMode.OrderV2;
|
|
46
|
+
|
|
47
|
+
if (features.includes(SubscriptionFeature.LegacyOrderManagement)) return OrderMode.OrderV1;
|
|
48
|
+
|
|
49
|
+
return OrderMode.Shipments;
|
|
50
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ref} from 'vue';
|
|
2
2
|
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
3
|
-
import {mount} from '@vue/test-utils';
|
|
3
|
+
import {flushPromises, mount} from '@vue/test-utils';
|
|
4
4
|
import {defineForm, MagicForm, useFormBuilder} from '@myparcel-dev/vue-form-builder';
|
|
5
5
|
import {buildAfterUpdate} from './buildAfterUpdate';
|
|
6
6
|
|
|
@@ -31,19 +31,16 @@ describe('buildAfterUpdate', () => {
|
|
|
31
31
|
],
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
mount(MagicForm, {props: {form}});
|
|
35
35
|
|
|
36
36
|
expect(afterUpdate).not.toHaveBeenCalled();
|
|
37
37
|
|
|
38
38
|
form.setValue('test', 'test');
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
testField?.afterUpdate(testField);
|
|
40
|
+
// afterUpdate fires async, so wait for it to settle.
|
|
41
|
+
await flushPromises();
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
expect(afterUpdate).toHaveBeenCalledTimes(1);
|
|
43
|
+
expect(afterUpdate).toHaveBeenCalled();
|
|
47
44
|
expect(form.getValue('test')).toBe('hello');
|
|
48
45
|
});
|
|
49
46
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ref} from 'vue';
|
|
2
2
|
import {afterEach, describe, expect, it, vi} from 'vitest';
|
|
3
|
-
import {mount} from '@vue/test-utils';
|
|
3
|
+
import {flushPromises, mount} from '@vue/test-utils';
|
|
4
4
|
import {defineForm, MagicForm, useFormBuilder} from '@myparcel-dev/vue-form-builder';
|
|
5
5
|
import {buildAfterUpdate} from '../builders';
|
|
6
6
|
|
|
@@ -49,17 +49,14 @@ describe('executeOperations', () => {
|
|
|
49
49
|
],
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
mount(MagicForm, {props: {form}});
|
|
53
53
|
|
|
54
54
|
expect(method).not.toHaveBeenCalled();
|
|
55
55
|
|
|
56
56
|
form.setValue('test', 'test');
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
testField?.afterUpdate(testField);
|
|
61
|
-
|
|
62
|
-
await wrapper.vm.$nextTick();
|
|
58
|
+
// afterUpdate fires async, so wait for it to settle.
|
|
59
|
+
await flushPromises();
|
|
63
60
|
|
|
64
61
|
expect(method).toHaveBeenCalledTimes(1);
|
|
65
62
|
expect(method).toHaveBeenCalledWith({$value: 'hello'});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ref} from 'vue';
|
|
2
2
|
import {afterEach, describe, expect, it} from 'vitest';
|
|
3
|
-
import {mount} from '@vue/test-utils';
|
|
3
|
+
import {flushPromises, mount} from '@vue/test-utils';
|
|
4
4
|
import {defineForm, MagicForm, useFormBuilder} from '@myparcel-dev/vue-form-builder';
|
|
5
5
|
import {type FormSetPropOperation, type PropVal} from '../types';
|
|
6
6
|
import {buildAfterUpdate} from '../builders';
|
|
@@ -147,15 +147,12 @@ describe('executeSetPropOperation', () => {
|
|
|
147
147
|
],
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
mount(MagicForm, {props: {form}});
|
|
151
151
|
|
|
152
152
|
form.setValue('aardbei', 'test');
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
testField?.afterUpdate(testField);
|
|
157
|
-
|
|
158
|
-
await wrapper.vm.$nextTick();
|
|
154
|
+
// afterUpdate fires async, so wait for it to settle.
|
|
155
|
+
await flushPromises();
|
|
159
156
|
|
|
160
157
|
expect({
|
|
161
158
|
aardbei: form.getField('aardbei')?.props,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {ref} from 'vue';
|
|
2
2
|
import {afterEach, describe, expect, it} from 'vitest';
|
|
3
3
|
import {flushPromises, mount} from '@vue/test-utils';
|
|
4
|
-
import {defineForm, MagicForm, useFormBuilder} from '@myparcel-dev/vue-form-builder';
|
|
4
|
+
import {type InteractiveElementInstance, defineForm, MagicForm, useFormBuilder} from '@myparcel-dev/vue-form-builder';
|
|
5
5
|
import {type AnyVal, type FormSetValueOperation} from '../types';
|
|
6
|
+
import {setFieldRef} from '../utils';
|
|
6
7
|
import {buildAfterUpdate} from '../builders';
|
|
7
8
|
|
|
8
9
|
interface TestInput {
|
|
@@ -192,7 +193,7 @@ describe('executeSetValueOperation', () => {
|
|
|
192
193
|
|
|
193
194
|
mount(MagicForm, {props: {form}});
|
|
194
195
|
|
|
195
|
-
form.
|
|
196
|
+
setFieldRef(form.getField('test') as InteractiveElementInstance, 'test');
|
|
196
197
|
|
|
197
198
|
await flushPromises();
|
|
198
199
|
|
|
@@ -4,18 +4,31 @@ import {type AnyVal} from '../types';
|
|
|
4
4
|
|
|
5
5
|
export type FormValueSetter = (value: AnyVal, target?: string) => void;
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Set a field's ref value, handling both Vue Ref and raw value cases.
|
|
9
|
+
*
|
|
10
|
+
* vue-form-builder replaces `field.ref` with a raw value after the first
|
|
11
|
+
* render cycle, so we can't always rely on `field.ref.value = x`.
|
|
12
|
+
*/
|
|
13
|
+
export const setFieldRef = (field: InteractiveElementInstance, value: AnyVal): void => {
|
|
14
|
+
if (isRef(field.ref)) {
|
|
15
|
+
field.ref.value = value;
|
|
16
|
+
} else {
|
|
17
|
+
// @ts-expect-error ref was replaced with a raw value by the component render
|
|
18
|
+
field.ref = value;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
7
22
|
export const createValueSetter = (instance: InteractiveElementInstance, prefix: string): FormValueSetter => {
|
|
8
23
|
return (value, target) => {
|
|
9
24
|
if (target) {
|
|
10
|
-
instance.form.
|
|
11
|
-
} else {
|
|
12
|
-
if (isRef(instance.ref)) {
|
|
13
|
-
instance.ref.value = value;
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
25
|
+
const targetField = instance.form.getField(`${prefix}${target}`) as InteractiveElementInstance | undefined;
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
if (targetField) {
|
|
28
|
+
setFieldRef(targetField, value);
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
setFieldRef(instance, value);
|
|
19
32
|
}
|
|
20
33
|
};
|
|
21
34
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {Variant} from '@myparcel-dev/pdk-common';
|
|
2
|
+
import {type PdkNotification} from '../../types';
|
|
3
|
+
import {useModalStore, type useNotificationStore} from '../../stores';
|
|
4
|
+
import {AdminModalKey, NotificationCategory} from '../../data';
|
|
5
|
+
|
|
6
|
+
export const CAPABILITIES_CLEARED_NOTIFICATION_ID = 'capabilities_cleared';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Add (or replace) the rolling "capabilities cleared" notification.
|
|
10
|
+
*
|
|
11
|
+
* The notification store skips `add` when an entry with the same id is already present, so
|
|
12
|
+
* we explicitly remove the prior one first to surface the new content list. Pass an
|
|
13
|
+
* already-localized `title` and `lines` — translation happens at the call site (auto-clear)
|
|
14
|
+
* because that's the layer holding the carrier name + axis context for parameterized lookups.
|
|
15
|
+
*
|
|
16
|
+
* Targets `NotificationCategory.Modal` when the shipment-options modal is open and
|
|
17
|
+
* `NotificationCategory.Action` otherwise — the modal shells (preset packages) only mount a
|
|
18
|
+
* `Modal` container, while the order-detail `ShipmentOptionsBox` mounts an `Action` container.
|
|
19
|
+
*/
|
|
20
|
+
export const addCapabilitiesClearNotification = (
|
|
21
|
+
notificationStore: ReturnType<typeof useNotificationStore>,
|
|
22
|
+
title: string,
|
|
23
|
+
lines: string[],
|
|
24
|
+
): void => {
|
|
25
|
+
if (lines.length === 0) return;
|
|
26
|
+
|
|
27
|
+
const isModal = AdminModalKey.ShipmentOptions === useModalStore().opened;
|
|
28
|
+
|
|
29
|
+
notificationStore.remove(CAPABILITIES_CLEARED_NOTIFICATION_ID);
|
|
30
|
+
|
|
31
|
+
notificationStore.add({
|
|
32
|
+
id: CAPABILITIES_CLEARED_NOTIFICATION_ID,
|
|
33
|
+
variant: Variant.Warning,
|
|
34
|
+
title,
|
|
35
|
+
content: lines,
|
|
36
|
+
category: isModal ? NotificationCategory.Modal : NotificationCategory.Action,
|
|
37
|
+
timeout: false,
|
|
38
|
+
} as PdkNotification);
|
|
39
|
+
};
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import {type
|
|
2
|
-
import {DeliveryTypeName} from '@myparcel-dev/constants';
|
|
1
|
+
import {type CarrierModel} from '@myparcel-dev/pdk-common';
|
|
3
2
|
import {getDynamicTranslation} from '../../utils';
|
|
4
3
|
import {type SelectOption} from '../../types';
|
|
5
|
-
import {getCarrier} from './getCarrier';
|
|
6
4
|
|
|
7
|
-
export const getDeliveryTypes = (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (form) {
|
|
11
|
-
const carrier = getCarrier(form);
|
|
12
|
-
array = array.filter((name) => carrier?.capabilities.deliveryTypes.includes(name));
|
|
5
|
+
export const getDeliveryTypes = (carrier: CarrierModel | undefined): SelectOption[] => {
|
|
6
|
+
if (!carrier) {
|
|
7
|
+
return [];
|
|
13
8
|
}
|
|
14
9
|
|
|
15
|
-
return
|
|
10
|
+
return carrier.deliveryTypes.map((name) => ({
|
|
16
11
|
label: getDynamicTranslation('delivery_type', name),
|
|
17
12
|
value: name,
|
|
18
13
|
}));
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import {snakeCase} from 'lodash-unified';
|
|
2
|
-
import {type Shipment} from '@myparcel-dev/pdk-common';
|
|
3
2
|
import {SHIPMENT_OPTIONS} from '../shipmentOptions';
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Generates a translation key for a shipment option field label.
|
|
6
|
+
*
|
|
7
|
+
* Accepts `string` because option names come from the carrier context at
|
|
8
|
+
* runtime. Converts to snake_case for the translation lookup.
|
|
9
|
+
*/
|
|
10
|
+
export const getFieldLabel = (name: string): string =>
|
|
6
11
|
snakeCase(`${SHIPMENT_OPTIONS}_${name}`);
|
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
import {type
|
|
2
|
-
import {PackageTypeName} from '@myparcel-dev/constants';
|
|
1
|
+
import {type CarrierModel} from '@myparcel-dev/pdk-common';
|
|
3
2
|
import {getPackageTypeTranslation} from '../../utils';
|
|
4
3
|
import {type SelectOption} from '../../types';
|
|
5
|
-
import {getCarrier} from './getCarrier';
|
|
6
4
|
|
|
7
|
-
export const getPackageTypes = (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (form) {
|
|
11
|
-
const carrier = getCarrier(form);
|
|
12
|
-
array = array.filter((name) => carrier?.capabilities.packageTypes.includes(name));
|
|
5
|
+
export const getPackageTypes = (carrier: CarrierModel | undefined): SelectOption[] => {
|
|
6
|
+
if (!carrier) {
|
|
7
|
+
return [];
|
|
13
8
|
}
|
|
14
9
|
|
|
15
|
-
return
|
|
16
|
-
label: getPackageTypeTranslation(
|
|
17
|
-
value:
|
|
10
|
+
return carrier.packageTypes.map((packageType) => ({
|
|
11
|
+
label: getPackageTypeTranslation(packageType),
|
|
12
|
+
value: packageType,
|
|
18
13
|
}));
|
|
19
14
|
};
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
export * from './addBulkEditNotification';
|
|
2
|
+
export * from './addCapabilitiesClearNotification';
|
|
2
3
|
export * from './createDefaultOption';
|
|
3
|
-
export * from './createHasShipmentOptionWatcher';
|
|
4
4
|
export * from './createLabel';
|
|
5
5
|
export * from './defineFormField';
|
|
6
|
-
export * from './
|
|
6
|
+
export * from './getDeliveryTypes';
|
|
7
7
|
export * from './getFieldLabel';
|
|
8
8
|
export * from './getFormCarrierName';
|
|
9
|
-
export * from './getInsuranceOptions';
|
|
10
9
|
export * from './getPackageTypes';
|
|
11
|
-
export * from './hasPostNlAgeCheck';
|
|
12
|
-
export * from './hasShipmentOption';
|
|
13
|
-
export * from './isPackageTypeMailbox';
|
|
14
|
-
export * from './isPackageTypePackage';
|
|
15
|
-
export * from './isPackageTypePackageOrSmall';
|
|
16
|
-
export * from './isPackageTypeSmallPackage';
|
|
17
10
|
export * from './resolveFormComponent';
|
|
18
11
|
export * from './setFieldProp';
|
|
19
|
-
export * from './setPostNlAgeCheckSubtext';
|
|
20
12
|
export * from './triStateFieldIsEnabled';
|
|
21
13
|
export * from './updateFieldsDefaults';
|
|
14
|
+
export * from './useFormCapabilities';
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {describe, expect, it, vi, beforeEach} from 'vitest';
|
|
2
|
+
import {ref} from 'vue';
|
|
3
|
+
import {AdminContextKey, BackendEndpoint, type CarrierModel, TriState} from '@myparcel-dev/pdk-common';
|
|
4
|
+
|
|
5
|
+
type FakeQuery = {
|
|
6
|
+
status: ReturnType<typeof ref<string>>;
|
|
7
|
+
data: ReturnType<typeof ref<unknown>>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const orderModifier = (id: string) => `${id}.order`;
|
|
11
|
+
const shipmentModifier = (id: string) => `${id}.shipment`;
|
|
12
|
+
|
|
13
|
+
const buildQueryStore = () => {
|
|
14
|
+
const queries = new Map<string, FakeQuery>();
|
|
15
|
+
const dynamicCarriers = ref<CarrierModel[]>([]);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
queries,
|
|
19
|
+
dynamicCarriers,
|
|
20
|
+
setQuery: (modifier: string, status: string, data: unknown) =>
|
|
21
|
+
queries.set(`${BackendEndpoint.ProxyCapabilities}.${modifier}`, {
|
|
22
|
+
status: ref(status),
|
|
23
|
+
data: ref(data),
|
|
24
|
+
}),
|
|
25
|
+
setDynamicCarriers: (carriers: CarrierModel[]) => {
|
|
26
|
+
dynamicCarriers.value = carriers;
|
|
27
|
+
},
|
|
28
|
+
has: (endpoint: BackendEndpoint, modifier: string) =>
|
|
29
|
+
queries.has(`${endpoint}.${modifier}`),
|
|
30
|
+
get: (endpoint: BackendEndpoint, modifier?: string) => {
|
|
31
|
+
if (endpoint === BackendEndpoint.FetchContext && modifier === AdminContextKey.Dynamic) {
|
|
32
|
+
return {
|
|
33
|
+
status: ref('success'),
|
|
34
|
+
data: ref({carriers: dynamicCarriers.value}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return queries.get(`${endpoint}.${modifier}`) ?? {status: ref('idle'), data: ref(undefined)};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
let queryStore: ReturnType<typeof buildQueryStore>;
|
|
44
|
+
let mockOrderId: string | undefined = 'order-1';
|
|
45
|
+
|
|
46
|
+
vi.mock('../../stores', () => ({
|
|
47
|
+
useQueryStore: () => queryStore,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
vi.mock('../../utils', () => ({
|
|
51
|
+
getOrderId: () => mockOrderId,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
vi.mock('../../composables', () => ({
|
|
55
|
+
Format: {Currency: 'currency'},
|
|
56
|
+
useContext: () => ({carriers: []}),
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const buildCarrier = (overrides: Partial<CarrierModel>): CarrierModel =>
|
|
60
|
+
({
|
|
61
|
+
carrier: 'POSTNL',
|
|
62
|
+
packageTypes: [],
|
|
63
|
+
deliveryTypes: [],
|
|
64
|
+
options: {},
|
|
65
|
+
...overrides,
|
|
66
|
+
}) as CarrierModel;
|
|
67
|
+
|
|
68
|
+
const formStub = (carrierName: string) => ({
|
|
69
|
+
getValue: (name: string) => (name === 'deliveryOptions.carrier' ? carrierName : undefined),
|
|
70
|
+
}) as never;
|
|
71
|
+
|
|
72
|
+
describe('useFormCapabilities', () => {
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
queryStore = buildQueryStore();
|
|
75
|
+
mockOrderId = 'order-1';
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('getCarrierCapabilitiesForOrder', () => {
|
|
79
|
+
it('returns the order-query carrier when the query is success and the chosen carrier is in the results', async () => {
|
|
80
|
+
const order = buildCarrier({carrier: 'POSTNL', packageTypes: ['PACKAGE']});
|
|
81
|
+
const dynamic = buildCarrier({carrier: 'POSTNL', packageTypes: ['DIGITAL_STAMP']});
|
|
82
|
+
queryStore.setQuery(orderModifier('order-1'), 'success', [order]);
|
|
83
|
+
queryStore.setDynamicCarriers([dynamic]);
|
|
84
|
+
|
|
85
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
86
|
+
const capabilities = useFormCapabilities();
|
|
87
|
+
|
|
88
|
+
expect(capabilities.getCarrierCapabilitiesForOrder(formStub('POSTNL'))).toEqual(order);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('falls back to dynamic carriers when the query is loading', async () => {
|
|
92
|
+
const dynamic = buildCarrier({carrier: 'POSTNL'});
|
|
93
|
+
queryStore.setQuery(orderModifier('order-1'), 'loading', undefined);
|
|
94
|
+
queryStore.setDynamicCarriers([dynamic]);
|
|
95
|
+
|
|
96
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
97
|
+
|
|
98
|
+
expect(useFormCapabilities().getCarrierCapabilitiesForOrder(formStub('POSTNL'))).toEqual(dynamic);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('falls back to dynamic carriers when the query is errored (loading-state masquerade is intentional)', async () => {
|
|
102
|
+
const dynamic = buildCarrier({carrier: 'POSTNL'});
|
|
103
|
+
queryStore.setQuery(orderModifier('order-1'), 'error', undefined);
|
|
104
|
+
queryStore.setDynamicCarriers([dynamic]);
|
|
105
|
+
|
|
106
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
107
|
+
|
|
108
|
+
expect(useFormCapabilities().getCarrierCapabilitiesForOrder(formStub('POSTNL'))).toEqual(dynamic);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('falls back to dynamic carriers when the orderId is missing (bulk path)', async () => {
|
|
112
|
+
mockOrderId = undefined;
|
|
113
|
+
const dynamic = buildCarrier({carrier: 'POSTNL'});
|
|
114
|
+
queryStore.setDynamicCarriers([dynamic]);
|
|
115
|
+
|
|
116
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
117
|
+
|
|
118
|
+
expect(useFormCapabilities().getCarrierCapabilitiesForOrder(formStub('POSTNL'))).toEqual(dynamic);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('getCarrierCapabilitiesForShipment tri-state', () => {
|
|
123
|
+
it('returns the narrow shipment carrier when the query is success and the carrier matches', async () => {
|
|
124
|
+
const shipment = buildCarrier({
|
|
125
|
+
carrier: 'POSTNL',
|
|
126
|
+
options: {requiresSignature: {isRequired: true} as never},
|
|
127
|
+
});
|
|
128
|
+
queryStore.setQuery(shipmentModifier('order-1'), 'success', [shipment]);
|
|
129
|
+
|
|
130
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
131
|
+
|
|
132
|
+
expect(useFormCapabilities().getCarrierCapabilitiesForShipment(formStub('POSTNL'))).toEqual(shipment);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('falls back to order data when the shipment query is loading', async () => {
|
|
136
|
+
const order = buildCarrier({carrier: 'POSTNL', packageTypes: ['PACKAGE']});
|
|
137
|
+
queryStore.setQuery(orderModifier('order-1'), 'success', [order]);
|
|
138
|
+
queryStore.setQuery(shipmentModifier('order-1'), 'loading', undefined);
|
|
139
|
+
|
|
140
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
141
|
+
|
|
142
|
+
expect(useFormCapabilities().getCarrierCapabilitiesForShipment(formStub('POSTNL'))).toEqual(order);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('returns undefined when the shipment query is success but the chosen carrier is not in the results (invalid combo)', async () => {
|
|
146
|
+
queryStore.setQuery(shipmentModifier('order-1'), 'success', [buildCarrier({carrier: 'DPD'})]);
|
|
147
|
+
|
|
148
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
149
|
+
|
|
150
|
+
expect(useFormCapabilities().getCarrierCapabilitiesForShipment(formStub('POSTNL'))).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('hasShipmentOption', () => {
|
|
155
|
+
it('returns true when the shipment carrier exposes the option', async () => {
|
|
156
|
+
const shipment = buildCarrier({
|
|
157
|
+
carrier: 'POSTNL',
|
|
158
|
+
options: {requiresSignature: {} as never},
|
|
159
|
+
});
|
|
160
|
+
queryStore.setQuery(shipmentModifier('order-1'), 'success', [shipment]);
|
|
161
|
+
|
|
162
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
163
|
+
|
|
164
|
+
expect(useFormCapabilities().hasShipmentOption(formStub('POSTNL'), 'requiresSignature')).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('returns false when the option is absent from the carrier', async () => {
|
|
168
|
+
const shipment = buildCarrier({carrier: 'POSTNL', options: {}});
|
|
169
|
+
queryStore.setQuery(shipmentModifier('order-1'), 'success', [shipment]);
|
|
170
|
+
|
|
171
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
172
|
+
|
|
173
|
+
expect(useFormCapabilities().hasShipmentOption(formStub('POSTNL'), 'requiresSignature')).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('getInsuranceOptions', () => {
|
|
178
|
+
const formatter = {format: (_: unknown, n: number) => `€${n}`} as never;
|
|
179
|
+
|
|
180
|
+
it('returns [] when max insurance amount is zero', async () => {
|
|
181
|
+
const shipment = buildCarrier({
|
|
182
|
+
carrier: 'POSTNL',
|
|
183
|
+
options: {
|
|
184
|
+
insurance: {
|
|
185
|
+
insuredAmount: {
|
|
186
|
+
min: {amount: 0, currency: 'EUR'},
|
|
187
|
+
max: {amount: 0, currency: 'EUR'},
|
|
188
|
+
default: {amount: 0, currency: 'EUR'},
|
|
189
|
+
},
|
|
190
|
+
} as never,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
queryStore.setQuery(shipmentModifier('order-1'), 'success', [shipment]);
|
|
194
|
+
|
|
195
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
196
|
+
|
|
197
|
+
expect(useFormCapabilities().getInsuranceOptions(formStub('POSTNL'), formatter)).toEqual([]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('switches step from 250 to 500 across the 500€ threshold', async () => {
|
|
201
|
+
const shipment = buildCarrier({
|
|
202
|
+
carrier: 'POSTNL',
|
|
203
|
+
options: {
|
|
204
|
+
insurance: {
|
|
205
|
+
insuredAmount: {
|
|
206
|
+
min: {amount: 0, currency: 'EUR'},
|
|
207
|
+
// max is 1000€ in cents
|
|
208
|
+
max: {amount: 100_000, currency: 'EUR'},
|
|
209
|
+
default: {amount: 0, currency: 'EUR'},
|
|
210
|
+
},
|
|
211
|
+
} as never,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
queryStore.setQuery(shipmentModifier('order-1'), 'success', [shipment]);
|
|
215
|
+
|
|
216
|
+
const {useFormCapabilities} = await import('./useFormCapabilities');
|
|
217
|
+
const opts = useFormCapabilities().getInsuranceOptions(formStub('POSTNL'), formatter);
|
|
218
|
+
const values = opts.map((o) => o.value);
|
|
219
|
+
|
|
220
|
+
// First entry is the inherit default; subsequent values are cent amounts as strings.
|
|
221
|
+
expect(values[0]).toBe(TriState.Inherit);
|
|
222
|
+
// 0, 250, 500 (low steps) — then 1000 (high step from 500). Exact sequence verifies
|
|
223
|
+
// the boundary semantics — replacing `<` with `<=` on LOW_THRESHOLD would shift these.
|
|
224
|
+
expect(values.slice(1)).toEqual(['0', '25000', '50000', '100000']);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|