@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
|
@@ -3,33 +3,23 @@
|
|
|
3
3
|
|
|
4
4
|
import {toRaw} from 'vue';
|
|
5
5
|
import {get} from 'lodash-unified';
|
|
6
|
-
import {defineForm, type FormInstance} from '@myparcel-dev/vue-form-builder';
|
|
6
|
+
import {defineForm, type FormInstance, type InteractiveElementConfiguration} from '@myparcel-dev/vue-form-builder';
|
|
7
7
|
import {type OneOrMore, toArray} from '@myparcel-dev/ts-utils';
|
|
8
|
-
import {type Plugin} from '@myparcel-dev/pdk-common';
|
|
8
|
+
import {AdminContextKey, type Plugin} from '@myparcel-dev/pdk-common';
|
|
9
9
|
import {addBulkEditNotification} from '../helpers';
|
|
10
10
|
import {createShipmentFormName} from '../../utils';
|
|
11
11
|
import {useModalStore} from '../../stores';
|
|
12
12
|
import {AdminModalKey} from '../../data';
|
|
13
|
-
import {useAdminConfig} from '../../composables';
|
|
13
|
+
import {useAdminConfig, useContext} from '../../composables';
|
|
14
|
+
import {useCapabilitiesAutoClear} from './useCapabilitiesAutoClear';
|
|
15
|
+
import {wireProxyCapabilities} from './wireProxyCapabilities';
|
|
14
16
|
import {type ShipmentOptionsRefs} from './types';
|
|
15
|
-
import {
|
|
17
|
+
import {createShipmentOptionField} from './fields/createShipmentOptionField';
|
|
16
18
|
import {createDeliveryTypeField} from './fields/createDeliveryTypeField';
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
createDirectReturnField,
|
|
22
|
-
createInsuranceField,
|
|
23
|
-
createPriorityDeliveryField,
|
|
24
|
-
createLabelAmountField,
|
|
25
|
-
createLargeFormatField,
|
|
26
|
-
createOnlyRecipientField,
|
|
27
|
-
createPackageTypeField,
|
|
28
|
-
createSameDayDeliveryField,
|
|
29
|
-
createSignatureField,
|
|
30
|
-
createReceiptCodeField,
|
|
31
|
-
} from './fields';
|
|
32
|
-
import {ALL_FIELDS, FIELD_CARRIER} from './field';
|
|
19
|
+
import {createCarrierField} from './fields/createCarrierField';
|
|
20
|
+
import {createDigitalStampRangeField, createLabelAmountField, createPackageTypeField} from './fields';
|
|
21
|
+
import {fieldFactoryRegistry} from './fieldFactoryRegistry';
|
|
22
|
+
import {FIELD_CARRIER, FIELD_DELIVERY_TYPE, FIELD_LABEL_AMOUNT, FIELD_MANUAL_WEIGHT, FIELD_PACKAGE_TYPE, FIELD_SHIPMENT_OPTIONS_PREFIX} from './field';
|
|
33
23
|
|
|
34
24
|
export const createShipmentOptionsForm = (orders?: OneOrMore<Plugin.ModelPdkOrder>): FormInstance => {
|
|
35
25
|
const ordersArray = toArray(orders ?? []).map(toRaw);
|
|
@@ -46,40 +36,112 @@ export const createShipmentOptionsForm = (orders?: OneOrMore<Plugin.ModelPdkOrde
|
|
|
46
36
|
|
|
47
37
|
const order = ((isBulk ? undefined : ordersArray[0]) ?? {}) as Plugin.ModelContextOrderDataContext;
|
|
48
38
|
|
|
49
|
-
const
|
|
50
|
-
|
|
39
|
+
const dynamicContext = useContext(AdminContextKey.Dynamic);
|
|
40
|
+
const allOptionKeys = collectAllOptionKeys(dynamicContext.carriers);
|
|
41
|
+
const refs = buildDynamicRefs(order, allOptionKeys);
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
}, {} as ShipmentOptionsRefs);
|
|
54
|
-
|
|
55
|
-
refs[FIELD_CARRIER] = get(order, `${FIELD_CARRIER}.externalIdentifier`);
|
|
56
|
-
|
|
57
|
-
return defineForm(createShipmentFormName(order.externalIdentifier), {
|
|
43
|
+
const form = defineForm(createShipmentFormName(order.externalIdentifier), {
|
|
58
44
|
...(isModal ? config.formConfigOverrides?.modal : null),
|
|
59
45
|
...config.formConfigOverrides?.shipmentOptions,
|
|
60
|
-
fields:
|
|
61
|
-
// General delivery options
|
|
62
|
-
createCarrierField(refs, order.inheritedDeliveryOptions),
|
|
63
|
-
|
|
64
|
-
createPackageTypeField(refs),
|
|
65
|
-
|
|
66
|
-
createDeliveryTypeField(refs),
|
|
67
|
-
|
|
68
|
-
createLabelAmountField(refs),
|
|
69
|
-
|
|
70
|
-
createDigitalStampRangeField(refs, order),
|
|
71
|
-
|
|
72
|
-
// Actual shipment options
|
|
73
|
-
createAgeCheckField(refs),
|
|
74
|
-
createSignatureField(refs),
|
|
75
|
-
createOnlyRecipientField(refs),
|
|
76
|
-
createDirectReturnField(refs),
|
|
77
|
-
createLargeFormatField(refs),
|
|
78
|
-
createHideSenderField(refs),
|
|
79
|
-
createSameDayDeliveryField(refs),
|
|
80
|
-
createReceiptCodeField(refs),
|
|
81
|
-
createInsuranceField(refs),
|
|
82
|
-
createPriorityDeliveryField(refs),
|
|
83
|
-
],
|
|
46
|
+
fields: createShipmentOptionsFields(refs, order, allOptionKeys),
|
|
84
47
|
});
|
|
48
|
+
|
|
49
|
+
if (!isBulk && order.externalIdentifier) {
|
|
50
|
+
const wired = wireProxyCapabilities(form, order);
|
|
51
|
+
|
|
52
|
+
if (wired) {
|
|
53
|
+
useCapabilitiesAutoClear(
|
|
54
|
+
form,
|
|
55
|
+
allOptionKeys,
|
|
56
|
+
order.externalIdentifier,
|
|
57
|
+
order.inheritedDeliveryOptions,
|
|
58
|
+
wired.selection,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return form;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Collect the union of all option keys across all carriers from the dynamic context.
|
|
68
|
+
*
|
|
69
|
+
* Fields are created for every unique option key so they exist when the user
|
|
70
|
+
* switches carriers. Visibility and disabled state are controlled by
|
|
71
|
+
* `hasShipmentOption`, which checks whether the currently selected carrier
|
|
72
|
+
* supports each option.
|
|
73
|
+
*
|
|
74
|
+
* Runtime carrier-specific data (isRequired, insuredAmount, etc.) is read
|
|
75
|
+
* from the currently selected carrier via `getCarrier(form)`, not from
|
|
76
|
+
* creation-time option data.
|
|
77
|
+
*/
|
|
78
|
+
const collectAllOptionKeys = (carriers: {options?: Record<string, unknown> | null}[]): string[] => {
|
|
79
|
+
const keys = new Set<string>();
|
|
80
|
+
|
|
81
|
+
for (const carrier of carriers) {
|
|
82
|
+
if (!carrier.options) continue;
|
|
83
|
+
|
|
84
|
+
for (const key of Object.keys(carrier.options)) {
|
|
85
|
+
keys.add(key);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [...keys];
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build refs dynamically from carrier options and static fields.
|
|
94
|
+
*
|
|
95
|
+
* Instead of a hardcoded ALL_FIELDS list, refs are built from the union of
|
|
96
|
+
* all carrier options in the dynamic context.
|
|
97
|
+
*/
|
|
98
|
+
const buildDynamicRefs = (
|
|
99
|
+
order: Plugin.ModelContextOrderDataContext,
|
|
100
|
+
optionKeys: string[],
|
|
101
|
+
): ShipmentOptionsRefs => {
|
|
102
|
+
const refs: ShipmentOptionsRefs = {};
|
|
103
|
+
|
|
104
|
+
// Static field refs
|
|
105
|
+
refs[FIELD_CARRIER] = get(order, `${FIELD_CARRIER}.carrier`);
|
|
106
|
+
refs[FIELD_LABEL_AMOUNT] = get(order, FIELD_LABEL_AMOUNT);
|
|
107
|
+
refs[FIELD_PACKAGE_TYPE] = get(order, FIELD_PACKAGE_TYPE);
|
|
108
|
+
refs[FIELD_DELIVERY_TYPE] = get(order, FIELD_DELIVERY_TYPE);
|
|
109
|
+
refs[FIELD_MANUAL_WEIGHT] = get(order, FIELD_MANUAL_WEIGHT);
|
|
110
|
+
|
|
111
|
+
// Dynamic shipment option refs from carrier options
|
|
112
|
+
for (const key of optionKeys) {
|
|
113
|
+
const fieldName = `${FIELD_SHIPMENT_OPTIONS_PREFIX}.${key}`;
|
|
114
|
+
refs[fieldName] = get(order, fieldName);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return refs;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const createShipmentOptionsFields = (
|
|
121
|
+
refs: ShipmentOptionsRefs,
|
|
122
|
+
order: Plugin.ModelContextOrderDataContext,
|
|
123
|
+
optionKeys: string[],
|
|
124
|
+
): InteractiveElementConfiguration[] => {
|
|
125
|
+
// Static fields — always present
|
|
126
|
+
const staticFields = [
|
|
127
|
+
createCarrierField(refs, order.inheritedDeliveryOptions),
|
|
128
|
+
createPackageTypeField(refs),
|
|
129
|
+
createDeliveryTypeField(refs),
|
|
130
|
+
createLabelAmountField(refs),
|
|
131
|
+
createDigitalStampRangeField(refs, order),
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
// Dynamic shipment option fields — driven by carrier.options
|
|
135
|
+
const dynamicFields = optionKeys.map((key) => {
|
|
136
|
+
const fieldName = `${FIELD_SHIPMENT_OPTIONS_PREFIX}.${key}`;
|
|
137
|
+
const factory = fieldFactoryRegistry[key];
|
|
138
|
+
|
|
139
|
+
if (factory) {
|
|
140
|
+
return factory(refs, fieldName);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return createShipmentOptionField(refs, fieldName);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return [...staticFields, ...dynamicFields];
|
|
85
147
|
};
|
|
@@ -1,42 +1,18 @@
|
|
|
1
|
-
import {type Shipment} from '@myparcel-dev/pdk-common';
|
|
2
|
-
|
|
3
1
|
export type FieldName = string;
|
|
4
2
|
|
|
5
3
|
const DELIVERY_OPTIONS_PREFIX = 'deliveryOptions';
|
|
6
4
|
|
|
7
5
|
const PHYSICAL_PROPERTIES_PREFIX = 'physicalProperties';
|
|
8
6
|
|
|
9
|
-
export const SHIPMENT_OPTIONS =
|
|
10
|
-
|
|
11
|
-
const SHIPMENT_OPTIONS_PREFIX = `${DELIVERY_OPTIONS_PREFIX}.${SHIPMENT_OPTIONS}`;
|
|
12
|
-
|
|
13
|
-
export const CARRIER = 'carrier' satisfies keyof Shipment.ModelDeliveryOptions;
|
|
14
|
-
|
|
15
|
-
export const LABEL_AMOUNT = 'labelAmount' satisfies keyof Shipment.ModelDeliveryOptions;
|
|
16
|
-
|
|
17
|
-
export const PACKAGE_TYPE = 'packageType' satisfies keyof Shipment.ModelDeliveryOptions;
|
|
18
|
-
|
|
19
|
-
export const DELIVERY_TYPE = 'deliveryType' satisfies keyof Shipment.ModelDeliveryOptions;
|
|
20
|
-
|
|
21
|
-
export const AGE_CHECK = 'ageCheck' satisfies keyof Shipment.ModelShipmentOptions;
|
|
22
|
-
|
|
23
|
-
export const RECEIPT_CODE = 'receiptCode' satisfies keyof Shipment.ModelShipmentOptions;
|
|
24
|
-
|
|
25
|
-
export const DIRECT_RETURN = 'return' satisfies keyof Shipment.ModelShipmentOptions;
|
|
26
|
-
|
|
27
|
-
export const HIDE_SENDER = 'hideSender' satisfies keyof Shipment.ModelShipmentOptions;
|
|
28
|
-
|
|
29
|
-
export const LARGE_FORMAT = 'largeFormat' satisfies keyof Shipment.ModelShipmentOptions;
|
|
7
|
+
export const SHIPMENT_OPTIONS = 'shipmentOptions';
|
|
30
8
|
|
|
31
|
-
export const
|
|
9
|
+
export const CARRIER = 'carrier';
|
|
32
10
|
|
|
33
|
-
export const
|
|
11
|
+
export const LABEL_AMOUNT = 'labelAmount';
|
|
34
12
|
|
|
35
|
-
export const
|
|
13
|
+
export const PACKAGE_TYPE = 'packageType';
|
|
36
14
|
|
|
37
|
-
export const
|
|
38
|
-
|
|
39
|
-
export const PRIORITY_DELIVERY = 'priorityDelivery' satisfies keyof Shipment.ModelShipmentOptions;
|
|
15
|
+
export const DELIVERY_TYPE = 'deliveryType';
|
|
40
16
|
|
|
41
17
|
export const MANUAL_WEIGHT = 'manualWeight';
|
|
42
18
|
|
|
@@ -54,42 +30,6 @@ export const FIELD_PACKAGE_TYPE: FieldName = `${DELIVERY_OPTIONS_PREFIX}.${PACKA
|
|
|
54
30
|
|
|
55
31
|
export const FIELD_DELIVERY_TYPE: FieldName = `${DELIVERY_OPTIONS_PREFIX}.${DELIVERY_TYPE}`;
|
|
56
32
|
|
|
57
|
-
export const FIELD_AGE_CHECK: FieldName = `${SHIPMENT_OPTIONS_PREFIX}.${AGE_CHECK}`;
|
|
58
|
-
|
|
59
|
-
export const FIELD_DIRECT_RETURN: FieldName = `${SHIPMENT_OPTIONS_PREFIX}.${DIRECT_RETURN}`;
|
|
60
|
-
|
|
61
|
-
export const FIELD_HIDE_SENDER: FieldName = `${SHIPMENT_OPTIONS_PREFIX}.${HIDE_SENDER}`;
|
|
62
|
-
|
|
63
|
-
export const FIELD_LARGE_FORMAT: FieldName = `${SHIPMENT_OPTIONS_PREFIX}.${LARGE_FORMAT}`;
|
|
64
|
-
|
|
65
|
-
export const FIELD_ONLY_RECIPIENT: FieldName = `${SHIPMENT_OPTIONS_PREFIX}.${ONLY_RECIPIENT}`;
|
|
66
|
-
|
|
67
|
-
export const FIELD_SAME_DAY_DELIVERY: FieldName = `${SHIPMENT_OPTIONS_PREFIX}.${SAME_DAY_DELIVERY}`;
|
|
68
|
-
|
|
69
|
-
export const FIELD_SIGNATURE: FieldName = `${SHIPMENT_OPTIONS_PREFIX}.${SIGNATURE}`;
|
|
70
|
-
|
|
71
|
-
export const FIELD_INSURANCE: FieldName = `${SHIPMENT_OPTIONS_PREFIX}.${INSURANCE}`;
|
|
72
|
-
|
|
73
|
-
export const FIELD_PRIORITY_DELIVERY: FieldName = `${SHIPMENT_OPTIONS_PREFIX}.${PRIORITY_DELIVERY}`;
|
|
74
|
-
|
|
75
33
|
export const FIELD_MANUAL_WEIGHT: FieldName = `${PHYSICAL_PROPERTIES_PREFIX}.${MANUAL_WEIGHT}`;
|
|
76
34
|
|
|
77
|
-
export const
|
|
78
|
-
|
|
79
|
-
export const ALL_FIELDS = [
|
|
80
|
-
FIELD_AGE_CHECK,
|
|
81
|
-
FIELD_CARRIER,
|
|
82
|
-
FIELD_DIRECT_RETURN,
|
|
83
|
-
FIELD_HIDE_SENDER,
|
|
84
|
-
FIELD_INSURANCE,
|
|
85
|
-
FIELD_PRIORITY_DELIVERY,
|
|
86
|
-
FIELD_LABEL_AMOUNT,
|
|
87
|
-
FIELD_LARGE_FORMAT,
|
|
88
|
-
FIELD_ONLY_RECIPIENT,
|
|
89
|
-
FIELD_MANUAL_WEIGHT,
|
|
90
|
-
FIELD_PACKAGE_TYPE,
|
|
91
|
-
FIELD_DELIVERY_TYPE,
|
|
92
|
-
FIELD_SAME_DAY_DELIVERY,
|
|
93
|
-
FIELD_SIGNATURE,
|
|
94
|
-
FIELD_RECEIPT_CODE,
|
|
95
|
-
] as const;
|
|
35
|
+
export const FIELD_SHIPMENT_OPTIONS_PREFIX: FieldName = `${DELIVERY_OPTIONS_PREFIX}.${SHIPMENT_OPTIONS}`;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {type InteractiveElementConfiguration} from '@myparcel-dev/vue-form-builder';
|
|
2
|
+
import {type ShipmentOptionsRefs} from './types';
|
|
3
|
+
import {createInsuranceField} from './fields/createInsuranceField';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Factory function signature for custom shipment option field creators.
|
|
7
|
+
*
|
|
8
|
+
* Uses `string` for fieldName because option names are determined at runtime
|
|
9
|
+
* from the carrier context and are not a fixed compile-time set.
|
|
10
|
+
*/
|
|
11
|
+
export type FieldFactory = (refs: ShipmentOptionsRefs, fieldName: string) => InteractiveElementConfiguration;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registry of shipment option names that require custom field rendering.
|
|
15
|
+
*
|
|
16
|
+
* Options NOT listed here are rendered as generic TriState toggles by
|
|
17
|
+
* `createShipmentOptionField`. Each entry maps an option name (as it appears in
|
|
18
|
+
* `carrier.options`) to its custom factory function.
|
|
19
|
+
*
|
|
20
|
+
* Current exceptions:
|
|
21
|
+
* - `insurance`: renders as a select/dropdown with amount brackets derived from
|
|
22
|
+
* `insuredAmount` in the option data.
|
|
23
|
+
*/
|
|
24
|
+
export const fieldFactoryRegistry: Record<string, FieldFactory> = {
|
|
25
|
+
insurance: createInsuranceField,
|
|
26
|
+
};
|
|
@@ -1,69 +1,159 @@
|
|
|
1
|
-
import {toValue} from 'vue';
|
|
1
|
+
import {toValue, watch} from 'vue';
|
|
2
|
+
import {snakeCase} from 'lodash-unified';
|
|
2
3
|
import {type InteractiveElementConfiguration} from '@myparcel-dev/vue-form-builder';
|
|
3
|
-
import {AdminContextKey, type Plugin} from '@myparcel-dev/pdk-common';
|
|
4
|
-
import {PackageTypeName} from '@myparcel-dev/constants';
|
|
4
|
+
import {AdminContextKey, BackendEndpoint, type CarrierModel, type Plugin} from '@myparcel-dev/pdk-common';
|
|
5
5
|
import {type ShipmentOptionsRefs} from '../types';
|
|
6
|
-
import {FIELD_CARRIER, FIELD_DELIVERY_TYPE,
|
|
7
|
-
import {getDeliveryTypes} from '../../helpers/getDeliveryTypes';
|
|
6
|
+
import {FIELD_CARRIER, FIELD_DELIVERY_TYPE, FIELD_PACKAGE_TYPE, PROP_OPTIONS} from '../field';
|
|
8
7
|
import {
|
|
9
8
|
defineFormField,
|
|
10
|
-
|
|
9
|
+
getDeliveryTypes,
|
|
11
10
|
getPackageTypes,
|
|
12
11
|
resolveFormComponent,
|
|
13
12
|
setFieldProp,
|
|
14
|
-
setPostNlAgeCheckSubtext,
|
|
15
13
|
updateFieldsDefaults,
|
|
14
|
+
useFormCapabilities,
|
|
16
15
|
} from '../../helpers';
|
|
17
|
-
import {createAssetUrl} from '../../../utils';
|
|
16
|
+
import {createAssetUrl, getOrderId} from '../../../utils';
|
|
18
17
|
import {type RadioGroupOption} from '../../../types';
|
|
18
|
+
import {useQueryStore} from '../../../stores';
|
|
19
19
|
import {useFetchCarrier} from '../../../sdk';
|
|
20
20
|
import {AdminComponent} from '../../../data';
|
|
21
|
-
import {useContext, useLanguage
|
|
21
|
+
import {useContext, useLanguage} from '../../../composables';
|
|
22
22
|
import {createRef} from './createRef';
|
|
23
23
|
|
|
24
|
+
type CarrierMeta = {label: string; image?: string};
|
|
25
|
+
|
|
26
|
+
type OrderCarriersState =
|
|
27
|
+
| undefined // No query registered (bulk or before wireProxyCapabilities runs) — show all.
|
|
28
|
+
| 'loading' // Order query is loading — show no options yet.
|
|
29
|
+
| CarrierModel[]; // Success — only carriers in this list are available.
|
|
30
|
+
|
|
24
31
|
// eslint-disable-next-line max-lines-per-function
|
|
25
32
|
export const createCarrierField = (
|
|
26
33
|
refs: ShipmentOptionsRefs,
|
|
27
34
|
inheritedDeliveryOptions: Plugin.ModelContextOrderDataContext['inheritedDeliveryOptions'],
|
|
28
35
|
): InteractiveElementConfiguration => {
|
|
29
36
|
const dynamicContext = useContext(AdminContextKey.Dynamic);
|
|
30
|
-
|
|
31
|
-
const
|
|
37
|
+
const capabilities = useFormCapabilities();
|
|
38
|
+
const queryStore = useQueryStore();
|
|
39
|
+
const orderId = getOrderId();
|
|
40
|
+
const orderModifier = typeof orderId === 'string' ? `${orderId}.order` : undefined;
|
|
32
41
|
|
|
33
42
|
const {translate} = useLanguage();
|
|
34
43
|
|
|
44
|
+
const staticMeta = new Map<string, CarrierMeta>();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Read the order-capabilities query state. Returns:
|
|
48
|
+
* - `undefined` when there's no query (bulk path, or pre-wire) → fallback: render all carriers.
|
|
49
|
+
* - `undefined` when the query is registered but disabled (e.g. order has no country code, so
|
|
50
|
+
* `useOrderCapabilitiesQuery`'s `enabled` gate is false). TanStack reports `status: 'pending'`
|
|
51
|
+
* indefinitely in that state, so we detect it via `fetchStatus === 'idle'` and fall back to
|
|
52
|
+
* the unfiltered carrier list instead of trapping the user in a permanent loading state.
|
|
53
|
+
* - `'loading'` while the query hasn't resolved → render no options to avoid a flash where
|
|
54
|
+
* carriers appear and then a subset disappears once the response lands.
|
|
55
|
+
* - `CarrierModel[]` on success → render only the carriers in this list (others are hidden).
|
|
56
|
+
* - On error: treated as `undefined` so the merchant can still operate the form, accepting
|
|
57
|
+
* that a stale or empty filter is better than an empty radio.
|
|
58
|
+
*/
|
|
59
|
+
const readOrderCarriers = (): OrderCarriersState => {
|
|
60
|
+
if (!orderModifier || !queryStore.has(BackendEndpoint.ProxyCapabilities, orderModifier)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const query = queryStore.get(BackendEndpoint.ProxyCapabilities, orderModifier);
|
|
65
|
+
const status = toValue(query.status);
|
|
66
|
+
const fetchStatus = toValue(query.fetchStatus);
|
|
67
|
+
|
|
68
|
+
if (status === 'success') {
|
|
69
|
+
return (toValue(query.data) ?? []) as CarrierModel[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (status === 'error') {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (fetchStatus === 'idle') {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return 'loading';
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const computeOptions = (state: OrderCarriersState): RadioGroupOption[] => {
|
|
84
|
+
if (state === 'loading') return [];
|
|
85
|
+
|
|
86
|
+
const validCarrierNames = Array.isArray(state) ? new Set(state.map((c) => c.carrier)) : null;
|
|
87
|
+
const carriersToRender = validCarrierNames
|
|
88
|
+
? dynamicContext.carriers.filter((carrier) => validCarrierNames.has(carrier.carrier ?? ''))
|
|
89
|
+
: dynamicContext.carriers;
|
|
90
|
+
|
|
91
|
+
return carriersToRender.map((carrier): RadioGroupOption => {
|
|
92
|
+
const name = carrier.carrier ?? '';
|
|
93
|
+
const meta = staticMeta.get(name);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
plainLabel: meta?.label ?? name,
|
|
97
|
+
value: name,
|
|
98
|
+
image: meta?.image,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
35
103
|
return defineFormField({
|
|
36
104
|
name: FIELD_CARRIER,
|
|
37
105
|
label: 'carrier',
|
|
38
|
-
ref: createRef<string>(refs, FIELD_CARRIER,
|
|
106
|
+
ref: createRef<string>(refs, FIELD_CARRIER, dynamicContext.shop.defaultCarrier ?? undefined),
|
|
39
107
|
component: resolveFormComponent(AdminComponent.RadioGroup),
|
|
40
108
|
props: {
|
|
41
109
|
options: [],
|
|
42
110
|
},
|
|
43
111
|
|
|
44
112
|
onBeforeMount: async (field) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
113
|
+
// Fetch per-carrier metadata (logo + localized human name) once. Stored in `staticMeta`
|
|
114
|
+
// so the reactive options watcher below can stitch label/image together with the
|
|
115
|
+
// current order-capabilities filter without re-fetching.
|
|
116
|
+
await Promise.all(
|
|
117
|
+
dynamicContext.carriers.map(async (carrier) => {
|
|
118
|
+
const query = useFetchCarrier(carrier.carrier);
|
|
119
|
+
|
|
48
120
|
await query.suspense();
|
|
49
121
|
|
|
50
122
|
const apiCarrier = toValue(query.data);
|
|
51
123
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!carrier.isDefault) {
|
|
55
|
-
plainLabel += ` ${carrier.label ?? translate(`carrier_type_${carrier.type}`)}`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
plainLabel,
|
|
60
|
-
value: carrier.externalIdentifier ?? apiCarrier?.name ?? '',
|
|
124
|
+
staticMeta.set(carrier.carrier ?? '', {
|
|
125
|
+
label: apiCarrier?.human ?? translate(`carrier_${snakeCase(carrier.carrier)}`) ?? '',
|
|
61
126
|
image: apiCarrier?.meta.logo_svg ? createAssetUrl(apiCarrier.meta.logo_svg) : undefined,
|
|
62
|
-
};
|
|
127
|
+
});
|
|
63
128
|
}),
|
|
64
129
|
);
|
|
65
130
|
|
|
66
|
-
|
|
131
|
+
// Reactively rebuild the carrier radio options whenever the order-capabilities query
|
|
132
|
+
// changes state (loading → success, or weight/cc edits triggering a refetch). Carriers
|
|
133
|
+
// that aren't valid for the current order context (e.g. weight below the minimum) are
|
|
134
|
+
// hidden from the radio entirely — preventing the dead-end scenario where the form
|
|
135
|
+
// populates with packageType / deliveryType radios that have no valid combo. The
|
|
136
|
+
// RadioGroup component doesn't honor per-option `disabled` flags, so filter is the
|
|
137
|
+
// only way to express "not available" today.
|
|
138
|
+
//
|
|
139
|
+
// Also refresh package/delivery type dropdowns from the same response: weight/cc edits
|
|
140
|
+
// can shrink the available types for a carrier without changing the chosen carrier, and
|
|
141
|
+
// `afterUpdate` only runs on carrier change. Without this refresh those dropdowns would
|
|
142
|
+
// keep stale options after the order-capabilities response narrows.
|
|
143
|
+
watch(
|
|
144
|
+
() => readOrderCarriers(),
|
|
145
|
+
(state) => {
|
|
146
|
+
setFieldProp(field, PROP_OPTIONS, computeOptions(state));
|
|
147
|
+
|
|
148
|
+
const carrier = capabilities.getCarrierCapabilitiesForOrder(field.form);
|
|
149
|
+
|
|
150
|
+
if (carrier) {
|
|
151
|
+
setFieldProp(field.form, FIELD_PACKAGE_TYPE, PROP_OPTIONS, getPackageTypes(carrier));
|
|
152
|
+
setFieldProp(field.form, FIELD_DELIVERY_TYPE, PROP_OPTIONS, getDeliveryTypes(carrier));
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{immediate: true},
|
|
156
|
+
);
|
|
67
157
|
|
|
68
158
|
await field?.afterUpdate?.(field, toValue(field.ref), undefined);
|
|
69
159
|
|
|
@@ -73,11 +163,10 @@ export const createCarrierField = (
|
|
|
73
163
|
afterUpdate: (field, newCarrier: string) => {
|
|
74
164
|
updateFieldsDefaults(newCarrier, field, inheritedDeliveryOptions);
|
|
75
165
|
|
|
76
|
-
|
|
77
|
-
setFieldProp(field.form, FIELD_PACKAGE_TYPE, PROP_OPTIONS, getPackageTypes(field.form));
|
|
78
|
-
setFieldProp(field.form, FIELD_DELIVERY_TYPE, PROP_OPTIONS, getDeliveryTypes(field.form));
|
|
166
|
+
const carrier = capabilities.getCarrierCapabilitiesForOrder(field.form);
|
|
79
167
|
|
|
80
|
-
|
|
168
|
+
setFieldProp(field.form, FIELD_PACKAGE_TYPE, PROP_OPTIONS, getPackageTypes(carrier));
|
|
169
|
+
setFieldProp(field.form, FIELD_DELIVERY_TYPE, PROP_OPTIONS, getDeliveryTypes(carrier));
|
|
81
170
|
},
|
|
82
171
|
});
|
|
83
172
|
};
|
|
@@ -2,7 +2,6 @@ import {ref, toValue} from 'vue';
|
|
|
2
2
|
import {get} from 'lodash-unified';
|
|
3
3
|
import {type InteractiveElementConfiguration} from '@myparcel-dev/vue-form-builder';
|
|
4
4
|
import {type Plugin, TriState} from '@myparcel-dev/pdk-common';
|
|
5
|
-
import {PackageTypeName} from '@myparcel-dev/constants';
|
|
6
5
|
import {type ShipmentOptionsRefs} from '../types';
|
|
7
6
|
import {FIELD_MANUAL_WEIGHT, FIELD_PACKAGE_TYPE} from '../field';
|
|
8
7
|
import {createDefaultOption, defineFormField, resolveFormComponent} from '../../helpers';
|
|
@@ -34,8 +33,9 @@ export const createDigitalStampRangeField = (
|
|
|
34
33
|
label: 'weight_range',
|
|
35
34
|
ref: ref(selectedValue ?? TriState.Inherit),
|
|
36
35
|
component: resolveFormComponent(AdminComponent.SelectInput),
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// @TODO Remove package type checks when the context provides visibility restrictions per option.
|
|
37
|
+
visibleWhen: (field) => field.form.getValue(FIELD_PACKAGE_TYPE) === 'DIGITAL_STAMP',
|
|
38
|
+
disabledWhen: (field) => field.form.getValue(FIELD_PACKAGE_TYPE) !== 'DIGITAL_STAMP',
|
|
39
39
|
props: {
|
|
40
40
|
subtext: {
|
|
41
41
|
key: 'order_weight',
|
|
@@ -1,31 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from '
|
|
11
|
-
import { type ElementInstance } from '../../../types';
|
|
12
|
-
import { AdminComponent } from '../../../data';
|
|
13
|
-
import { useLocalizedFormatter } from '../../../composables';
|
|
14
|
-
import { createShipmentOptionField } from './createShipmentOptionField';
|
|
15
|
-
import { createRef } from './createRef';
|
|
1
|
+
import {watch} from 'vue';
|
|
2
|
+
import {type InteractiveElementConfiguration} from '@myparcel-dev/vue-form-builder';
|
|
3
|
+
import {type ShipmentOptionsRefs} from '../types';
|
|
4
|
+
import {PROP_OPTIONS} from '../field';
|
|
5
|
+
import {resolveFormComponent, setFieldProp, useFormCapabilities} from '../../helpers';
|
|
6
|
+
import {type ElementInstance} from '../../../types';
|
|
7
|
+
import {AdminComponent} from '../../../data';
|
|
8
|
+
import {useLocalizedFormatter} from '../../../composables';
|
|
9
|
+
import {createShipmentOptionField} from './createShipmentOptionField';
|
|
10
|
+
import {createRef} from './createRef';
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Custom field factory for the insurance shipment option.
|
|
14
|
+
*
|
|
15
|
+
* Renders as a select/dropdown instead of a TriState toggle. Insurance amount brackets are
|
|
16
|
+
* derived from the currently selected carrier's shipment-scoped `insuredAmount` data (min/max)
|
|
17
|
+
* and refresh automatically whenever that data changes — i.e. on carrier change AND on
|
|
18
|
+
* packageType / deliveryType / weight / cc change, since those all narrow the shipment-scoped
|
|
19
|
+
* capabilities response.
|
|
20
|
+
*
|
|
21
|
+
* Visibility / disabled state is inherited from {@link createShipmentOptionField} — driven
|
|
22
|
+
* purely by `hasShipmentOption` (i.e. by the capabilities response). No manual package-type
|
|
23
|
+
* gating: if insurance isn't valid for the current combination, the API won't include it in
|
|
24
|
+
* `carrier.options` and the field will hide.
|
|
25
|
+
*/
|
|
26
|
+
export const createInsuranceField = (
|
|
27
|
+
refs: ShipmentOptionsRefs,
|
|
28
|
+
fieldName: string,
|
|
29
|
+
): InteractiveElementConfiguration => {
|
|
18
30
|
const formatter = useLocalizedFormatter();
|
|
31
|
+
const capabilities = useFormCapabilities();
|
|
32
|
+
let stopWatcher: (() => void) | undefined;
|
|
19
33
|
|
|
20
|
-
return createShipmentOptionField(refs,
|
|
21
|
-
ref: createRef(refs,
|
|
34
|
+
return createShipmentOptionField(refs, fieldName, {
|
|
35
|
+
ref: createRef(refs, fieldName, 0),
|
|
22
36
|
component: resolveFormComponent(AdminComponent.SelectInput),
|
|
23
|
-
visibleWhen: createHasShipmentOptionWatcher(INSURANCE, false, isPackageTypePackageOrSmall),
|
|
24
|
-
disabledWhen: createHasShipmentOptionWatcher(INSURANCE, true, isPackageTypePackageOrSmall),
|
|
25
37
|
onBeforeMount(field: ElementInstance) {
|
|
26
|
-
|
|
38
|
+
stopWatcher?.();
|
|
27
39
|
|
|
28
|
-
|
|
40
|
+
// Watch the shipment-scoped carrier capabilities directly. The resolver tracks both the
|
|
41
|
+
// selected carrier (via `form.getValue(FIELD_CARRIER)`) and the shipment-capabilities
|
|
42
|
+
// query data, so any axis change that updates `insuredAmount` triggers a refresh.
|
|
43
|
+
stopWatcher = watch(
|
|
44
|
+
() => capabilities.getCarrierCapabilitiesForShipment(field.form),
|
|
45
|
+
() => {
|
|
46
|
+
setFieldProp(field, PROP_OPTIONS, capabilities.getInsuranceOptions(field.form, formatter));
|
|
47
|
+
},
|
|
48
|
+
{immediate: true},
|
|
49
|
+
);
|
|
29
50
|
},
|
|
30
51
|
});
|
|
31
52
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {snakeCase} from 'lodash-unified';
|
|
2
2
|
import {type InteractiveElementConfiguration} from '@myparcel-dev/vue-form-builder';
|
|
3
|
-
import {PackageTypeName} from '@myparcel-dev/constants';
|
|
4
3
|
import {type ShipmentOptionsRefs} from '../types';
|
|
5
4
|
import {FIELD_PACKAGE_TYPE, SHIPMENT_OPTIONS} from '../field';
|
|
6
5
|
import {defineFormField, resolveFormComponent} from '../../helpers';
|
|
@@ -11,7 +10,7 @@ export const createPackageTypeField = (refs: ShipmentOptionsRefs): InteractiveEl
|
|
|
11
10
|
return defineFormField({
|
|
12
11
|
name: FIELD_PACKAGE_TYPE,
|
|
13
12
|
label: snakeCase(`${SHIPMENT_OPTIONS}_package_type`),
|
|
14
|
-
ref: createRef<
|
|
13
|
+
ref: createRef<string>(refs, FIELD_PACKAGE_TYPE, 'PACKAGE'),
|
|
15
14
|
component: resolveFormComponent(AdminComponent.RadioGroup),
|
|
16
15
|
// options defined in afterUpdate of `createCarrierField`
|
|
17
16
|
});
|