From 24503a190a5ec159ee170395cecfba6e3c21f285 Mon Sep 17 00:00:00 2001
From: Edie Lemoine <edie@myparcel.nl>
Date: Wed, 15 Mar 2023 17:43:46 +0100
Subject: [PATCH] feat: make bulk edit/export/print work

---
 .../src/components/DefaultSelectInput.vue     |  2 +-
 apps/admin/src/common.ts                      |  3 +-
 .../queries/orders/useFetchOrdersQuery.ts     |  6 +-
 .../Modals/ShipmentOptionsModal.vue           | 56 ++++++-------
 .../Modals/ShipmentOptionsModalForm.vue       | 24 +-----
 .../OrderBox/OrderShipmentsTableRow.vue       |  2 +-
 .../components/common/ShipmentOptionsForm.vue |  7 +-
 .../composables/useInputWithOptionsContext.ts |  6 +-
 .../frontend/core/src/composables/useOrder.ts | 18 ++++-
 .../core/src/forms/helpers/setFieldProp.ts    |  9 +--
 .../createShipmentOptionsForm.ts              | 81 ++++++++++++-------
 .../helpers/addBulkEditNotification.ts        | 18 +++++
 .../helpers/getCarrierOptions.ts              |  3 +-
 .../helpers/getPackageTypes.ts                |  4 +-
 .../forms/shipmentOptions/helpers/index.ts    |  1 +
 .../core/src/forms/shipmentOptions/index.ts   |  1 +
 .../src/sdk/composables/api/useCarrier.ts     |  6 +-
 .../core/src/stores/useNotificationStore.ts   | 25 +++---
 .../frontend/core/src/stores/useQueryStore.ts |  6 +-
 libs/frontend/core/src/types/common.types.ts  | 10 ++-
 .../src/utils/forms/createShipmentFormName.ts |  8 +-
 libs/frontend/core/src/utils/getOrderId.ts    |  3 +-
 22 files changed, 174 insertions(+), 125 deletions(-)
 create mode 100644 libs/frontend/core/src/forms/shipmentOptions/helpers/addBulkEditNotification.ts

diff --git a/apps/admin-components/src/components/DefaultSelectInput.vue b/apps/admin-components/src/components/DefaultSelectInput.vue
index 7f6f83d53..307feca5b 100644
--- a/apps/admin-components/src/components/DefaultSelectInput.vue
+++ b/apps/admin-components/src/components/DefaultSelectInput.vue
@@ -23,5 +23,5 @@ import {ElementInstance, OptionsProp, useSelectInputContext} from '@myparcel-pdk
 const props = defineProps<{element: ElementInstance<OptionsProp>; modelValue: string | number}>();
 const emit = defineEmits<(e: 'update:modelValue', value: string | number) => void>();
 
-const {options} = useSelectInputContext(props, emit);
+const {options, id, model} = useSelectInputContext(props, emit);
 </script>
diff --git a/apps/admin/src/common.ts b/apps/admin/src/common.ts
index c8eaee1d4..6e2710510 100644
--- a/apps/admin/src/common.ts
+++ b/apps/admin/src/common.ts
@@ -1,7 +1,6 @@
 export type {
   Account,
   AdminComponentMap,
-  AdminComponent,
   Base,
   Carrier,
   Form,
@@ -84,4 +83,4 @@ export {
   webhooksDeleteAction,
 } from '@myparcel-pdk/frontend-core/src';
 
-export {AdminView, Size, Status, Variant} from '@myparcel-pdk/common/src';
+export {AdminComponent, AdminView, Size, Status, Variant} from '@myparcel-pdk/common/src';
diff --git a/libs/frontend/core/src/actions/composables/queries/orders/useFetchOrdersQuery.ts b/libs/frontend/core/src/actions/composables/queries/orders/useFetchOrdersQuery.ts
index a0a80c91a..4d6674364 100644
--- a/libs/frontend/core/src/actions/composables/queries/orders/useFetchOrdersQuery.ts
+++ b/libs/frontend/core/src/actions/composables/queries/orders/useFetchOrdersQuery.ts
@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
-import {useQuery, useQueryClient} from '@tanstack/vue-query';
+import {QueryKey, useQuery, useQueryClient} from '@tanstack/vue-query';
 import {BackendEndpoint} from '@myparcel-pdk/common/src';
 import {EndpointResponse} from '../../../../types';
 import {QUERY_KEY_ORDER} from '../queryKeys';
@@ -7,8 +7,8 @@ import {encodeArrayParameter} from '../../../../utils';
 import {fillOrderQueryData} from '../../../../pdk';
 import {usePdkAdminApi} from '../../../../sdk';
 
-export const useFetchOrdersQuery = (externalIdentifier: string) => {
-  const queryKey = [QUERY_KEY_ORDER, {id: externalIdentifier}] as const;
+export const useFetchOrdersQuery = (externalIdentifier?: string) => {
+  const queryKey: QueryKey = [QUERY_KEY_ORDER, ...(externalIdentifier ? [{id: externalIdentifier}] : [])] as const;
   const queryClient = useQueryClient();
 
   return useQuery<EndpointResponse<BackendEndpoint.FetchOrders>>(
diff --git a/libs/frontend/core/src/components/Modals/ShipmentOptionsModal.vue b/libs/frontend/core/src/components/Modals/ShipmentOptionsModal.vue
index 2f3244cf2..9b57d669e 100644
--- a/libs/frontend/core/src/components/Modals/ShipmentOptionsModal.vue
+++ b/libs/frontend/core/src/components/Modals/ShipmentOptionsModal.vue
@@ -7,8 +7,9 @@
   </PdkModal>
 </template>
 
-<script lang="ts">
-import {defineAsyncComponent, defineComponent} from 'vue';
+<script setup lang="ts">
+import {BACKEND_ENDPOINTS_ORDERS, BackendEndpoint} from '@myparcel-pdk/common/src';
+import {computed, defineAsyncComponent} from 'vue';
 import {
   modalCloseAction,
   orderExportAction,
@@ -18,38 +19,33 @@ import {
 } from '../../actions';
 import {usePluginSettings, useStoreQuery} from '../../composables';
 import {AdminModalKey} from '../../types';
-import {BackendEndpoint} from '@myparcel-pdk/common/src';
 import {defineActions} from '../../services';
+import {get} from '@vueuse/core';
 
 /**
  * Shipment options modal. Opened by clicking the "Create" button in the "Labels" column in the orders list.
  */
-export default defineComponent({
-  name: 'ShipmentOptionsModal',
-  components: {
-    ShipmentOptionsModalForm: defineAsyncComponent(() => import('./ShipmentOptionsModalForm.vue')),
-  },
-
-  setup: () => {
-    const pluginSettings = usePluginSettings();
-    const {orderMode} = pluginSettings.general;
-
-    const exportOrdersQuery = useStoreQuery(BackendEndpoint.ExportOrders);
-
-    return {
-      modalKey: AdminModalKey.ShipmentOptions,
-      actions: defineActions([
-        {
-          ...modalCloseAction,
-          disabled: exportOrdersQuery.isLoading,
-        },
-        {
-          ...ordersUpdateAction,
-          disabled: exportOrdersQuery.isLoading,
-        },
-        ...(orderMode ? [orderExportAction] : [orderExportToShipmentsAction, ordersExportPrintShipmentsAction]),
-      ]),
-    };
-  },
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+const ShipmentOptionsModalForm = defineAsyncComponent(() => import('./ShipmentOptionsModalForm.vue'));
+
+const pluginSettings = usePluginSettings();
+
+const {orderMode} = pluginSettings.general;
+
+const orderQueries = BACKEND_ENDPOINTS_ORDERS.map((endpoint: BackendEndpoint) => useStoreQuery(endpoint));
+
+const modalKey = AdminModalKey.ShipmentOptions;
+
+const actions = computed(() => {
+  const disabled = orderQueries.some((query) => get(query.isLoading));
+
+  const actions = [
+    modalCloseAction,
+    ordersUpdateAction,
+    ...(orderMode ? [orderExportAction] : [orderExportToShipmentsAction, ordersExportPrintShipmentsAction]),
+  ];
+
+  return defineActions(actions.map((action) => ({...action, disabled})));
 });
 </script>
diff --git a/libs/frontend/core/src/components/Modals/ShipmentOptionsModalForm.vue b/libs/frontend/core/src/components/Modals/ShipmentOptionsModalForm.vue
index 500501bb6..471483a98 100644
--- a/libs/frontend/core/src/components/Modals/ShipmentOptionsModalForm.vue
+++ b/libs/frontend/core/src/components/Modals/ShipmentOptionsModalForm.vue
@@ -1,28 +1,10 @@
 <template>
-  <ShipmentOptionsForm
-    v-if="order"
-    :order="order" />
+  <ShipmentOptionsForm v-if="queries.some((query) => query.data)" />
 </template>
 
 <script setup lang="ts">
 import ShipmentOptionsForm from '../common/ShipmentOptionsForm.vue';
-import {computed} from 'vue';
-import {useFetchOrdersQuery} from '../../actions';
-import {useModalStore} from '../../stores';
+import {useOrders} from '../../composables/useOrder';
 
-const modalStore = useModalStore();
-
-const order = computed(() => {
-  if (!modalStore.context) {
-    return null;
-  }
-
-  const orderQuery = useFetchOrdersQuery(modalStore.context);
-
-  if (!orderQuery.data.value) {
-    return null;
-  }
-
-  return orderQuery.data.value;
-});
+const queries = useOrders();
 </script>
diff --git a/libs/frontend/core/src/components/OrderBox/OrderShipmentsTableRow.vue b/libs/frontend/core/src/components/OrderBox/OrderShipmentsTableRow.vue
index bebb81cbc..e50443a90 100644
--- a/libs/frontend/core/src/components/OrderBox/OrderShipmentsTableRow.vue
+++ b/libs/frontend/core/src/components/OrderBox/OrderShipmentsTableRow.vue
@@ -1,5 +1,5 @@
 <template>
-  <PdkTableRow :loading="loading">
+  <PdkTableRow>
     <PdkTableCol>
       <PdkCheckboxInput
         v-model="selected"
diff --git a/libs/frontend/core/src/components/common/ShipmentOptionsForm.vue b/libs/frontend/core/src/components/common/ShipmentOptionsForm.vue
index 8a960bd65..358ba6a9f 100644
--- a/libs/frontend/core/src/components/common/ShipmentOptionsForm.vue
+++ b/libs/frontend/core/src/components/common/ShipmentOptionsForm.vue
@@ -6,11 +6,10 @@
 import {MagicForm} from '@myparcel/vue-form-builder/src';
 import {createShipmentOptionsForm} from '../../forms';
 import {get} from '@vueuse/core';
-import {markRaw} from 'vue';
-import {useOrder} from '../../composables/useOrder';
+import {useOrders} from '../../composables/useOrder';
 
-const query = useOrder();
+const queries = useOrders();
 
 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-const shipmentOptionsForm = createShipmentOptionsForm(markRaw(get(query.data)!));
+const shipmentOptionsForm = createShipmentOptionsForm(queries.map((query) => get(query.data)!));
 </script>
diff --git a/libs/frontend/core/src/composables/useInputWithOptionsContext.ts b/libs/frontend/core/src/composables/useInputWithOptionsContext.ts
index ea23512b7..484ee4e15 100644
--- a/libs/frontend/core/src/composables/useInputWithOptionsContext.ts
+++ b/libs/frontend/core/src/composables/useInputWithOptionsContext.ts
@@ -1,10 +1,10 @@
 import {ComputedRef, Ref, WritableComputedRef, computed, onMounted, watch} from 'vue';
 import {ElementInstance, OptionsProp} from '../types';
+import {get, useVModel} from '@vueuse/core';
 import {SelectOptionWithLabel} from '@myparcel-pdk/common';
 import {generateFieldId} from '../utils';
 import {translateSelectOption} from '../helpers';
 import {useLanguage} from './translations';
-import {useVModel} from '@vueuse/core';
 
 export type SelectInputProps<T = unknown> = {
   modelValue: T;
@@ -55,7 +55,7 @@ export const useInputWithOptionsContext: UseInputWithOptionsContext = (props, em
     watch(
       options,
       (newOptions) => {
-        const hasExistingValue = model.value && newOptions.some((option) => option.value === model.value);
+        const hasExistingValue = get(model) && newOptions.some((option) => option.value === get(model));
 
         if (hasExistingValue || newOptions.length === 0) {
           return;
@@ -63,7 +63,7 @@ export const useInputWithOptionsContext: UseInputWithOptionsContext = (props, em
 
         model.value = newOptions[0].value;
       },
-      {immediate: Number(options.value?.length) > 0},
+      {immediate: Number(get(options)?.length) > 0},
     );
   });
 
diff --git a/libs/frontend/core/src/composables/useOrder.ts b/libs/frontend/core/src/composables/useOrder.ts
index 5036a2745..60e31698c 100644
--- a/libs/frontend/core/src/composables/useOrder.ts
+++ b/libs/frontend/core/src/composables/useOrder.ts
@@ -1,10 +1,24 @@
 import {BackendEndpoint} from '@myparcel-pdk/common/src';
 import {ResolvedQuery} from '../stores';
 import {getOrderId} from '../utils';
+import {toArray} from '@myparcel/ts-utils';
 import {useStoreQuery} from './useStoreQuery';
 
 export const useOrder = (externalIdentifier?: string): ResolvedQuery<`${BackendEndpoint.FetchOrders}.${string}`> => {
-  externalIdentifier ??= getOrderId();
+  const resolvedExternalIdentifier = externalIdentifier ?? getOrderId();
 
-  return useStoreQuery(BackendEndpoint.FetchOrders, externalIdentifier);
+  if (Array.isArray(resolvedExternalIdentifier)) {
+    throw new Error('use useOrders for multiple orders');
+  }
+
+  return useStoreQuery(BackendEndpoint.FetchOrders, resolvedExternalIdentifier);
+};
+
+export const useOrders = (
+  externalIdentifiers?: string[],
+): ResolvedQuery<`${BackendEndpoint.FetchOrders}.${string}`>[] => {
+  return toArray(externalIdentifiers ?? getOrderId()).map((externalIdentifier) => {
+    console.log(externalIdentifier);
+    return useOrder(externalIdentifier);
+  });
 };
diff --git a/libs/frontend/core/src/forms/helpers/setFieldProp.ts b/libs/frontend/core/src/forms/helpers/setFieldProp.ts
index 0b8c212ba..e96088bef 100644
--- a/libs/frontend/core/src/forms/helpers/setFieldProp.ts
+++ b/libs/frontend/core/src/forms/helpers/setFieldProp.ts
@@ -1,5 +1,6 @@
 import {ElementInstance} from '../../types';
 import {FormInstance} from '@myparcel/vue-form-builder/src';
+import {get} from '@vueuse/core';
 import {isOfType} from '@myparcel/ts-utils';
 
 type SetFieldProp = {
@@ -13,7 +14,7 @@ export const setFieldProp: SetFieldProp = (arg1, arg2, arg3, arg4) => {
   let value = arg3;
 
   if (isOfType<FormInstance>(arg1, 'fields')) {
-    const foundField = arg1.fields.value.find((field) => field.name === arg2);
+    const foundField = get(arg1.fields).find((field) => field.name === arg2);
 
     if (!foundField) {
       throw new Error(`Field ${arg2} not found`);
@@ -28,11 +29,5 @@ export const setFieldProp: SetFieldProp = (arg1, arg2, arg3, arg4) => {
     return;
   }
 
-  console.log({
-    field: field.name,
-    key,
-    value,
-  });
-
   field.props[key] = value as never;
 };
diff --git a/libs/frontend/core/src/forms/shipmentOptions/createShipmentOptionsForm.ts b/libs/frontend/core/src/forms/shipmentOptions/createShipmentOptionsForm.ts
index 04786e77f..40ffea9de 100644
--- a/libs/frontend/core/src/forms/shipmentOptions/createShipmentOptionsForm.ts
+++ b/libs/frontend/core/src/forms/shipmentOptions/createShipmentOptionsForm.ts
@@ -14,55 +14,76 @@ import {
   SAME_DAY_DELIVERY,
   SIGNATURE,
 } from './field';
-import {AdminComponent, Plugin} from '@myparcel-pdk/common/src';
+import {AdminComponent, Plugin, SelectOptionWithPlainLabel} from '@myparcel-pdk/common/src';
 import {AdminContextKey, AdminModalKey, ElementInstance} from '../../types';
 import {CarrierName, PackageTypeName} from '@myparcel/constants';
+import {OneOrMore, toArray} from '@myparcel/ts-utils';
+import {addBulkEditNotification, getPackageTypes, hasShipmentOption, isPackageTypePackage} from './helpers';
 import {defineFormField, resolveFormComponent, setFieldProp} from '../helpers';
-import {getPackageTypes, hasShipmentOption, isPackageTypePackage} from './helpers';
+import {markRaw, ref} from 'vue';
 import {useAdminConfig, useContext, useLocalizedFormatter} from '../../composables';
 import {createShipmentFormName} from '../../utils';
 import {defineForm} from '@myparcel/vue-form-builder/src';
+import {get} from '@vueuse/core';
 import {getFormattedInsurancePossibilities} from './helpers/getFormattedInsurancePossibilities';
-import {ref} from 'vue';
 import {useCarrier} from '../../sdk';
 import {useModalStore} from '../../stores';
 
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,max-lines-per-function,complexity
-export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => {
+export const createShipmentOptionsForm = (orders?: OneOrMore<Plugin.ModelPdkOrder>) => {
+  const ordersArray = toArray(orders ?? []);
+  ordersArray.forEach((order) => markRaw(order));
+
   const dynamicContext = useContext(AdminContextKey.Dynamic);
+
   const config = useAdminConfig();
   const modalStore = useModalStore();
-
-  const carrierNames = dynamicContext.carrierOptions.map((options) => options.carrier.name);
   const formatter = useLocalizedFormatter();
 
-  return defineForm(createShipmentFormName(order.externalIdentifier), {
-    ...(modalStore.opened === AdminModalKey.ShipmentOptions ? config.formConfigOverrides?.modal : null),
+  const isBulk = ordersArray.length > 1;
+  const isModal = modalStore.opened === AdminModalKey.ShipmentOptions;
+
+  if (isBulk) {
+    addBulkEditNotification(isModal);
+  }
+
+  const values: Partial<Plugin.ModelPdkOrder> = isBulk ? {} : ordersArray[0];
+
+  return defineForm(createShipmentFormName(values.externalIdentifier), {
+    ...(isModal ? config.formConfigOverrides?.modal : null),
     ...config.formConfigOverrides?.shipmentOptions,
     fields: [
       defineFormField({
         name: CARRIER,
         label: 'carrier',
-        ref: ref<CarrierName>(order.deliveryOptions?.carrier as CarrierName),
+        ref: ref<CarrierName>(values.deliveryOptions?.carrier as CarrierName),
         component: resolveFormComponent(AdminComponent.SelectInput),
+        props: {
+          options: [],
+        },
 
         // @ts-expect-error todo
         onBeforeMount: async (field) => {
-          const carriers = await Promise.all(
-            carrierNames.map(async (name) => {
-              const carrier = useCarrier(name);
-              return carrier.suspense();
+          const carrierSelectOptions = await Promise.all(
+            dynamicContext.carrierOptions.map(async (options): Promise<SelectOptionWithPlainLabel> => {
+              const query = useCarrier(options.carrier.name);
+              await query.suspense();
+              const data = get(query.data);
+
+              return {
+                plainLabel: data?.human ?? '',
+                value: data?.name ?? '',
+              };
             }),
           );
 
-          setFieldProp(
-            field,
-            PROP_OPTIONS,
-            carriers.map((carrier) => ({
-              plainLabel: carrier.data?.human,
-              value: carrier.data?.name,
-            })),
-          );
+          setFieldProp(field, PROP_OPTIONS, carrierSelectOptions);
+
+          // if (!get(field.ref)) {
+          //   field.ref = carrierSelectOptions[0].value;
+          // }
+
+          field.afterUpdate(field);
         },
 
         afterUpdate: (field) => {
@@ -74,7 +95,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => {
       defineFormField({
         name: LABEL_AMOUNT,
         label: 'label_amount',
-        ref: ref(order.deliveryOptions?.labelAmount ?? 1),
+        ref: ref(values.deliveryOptions?.labelAmount ?? 1),
         component: resolveFormComponent(AdminComponent.NumberInput),
         props: {
           min: 1,
@@ -85,14 +106,14 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => {
       defineFormField({
         name: PACKAGE_TYPE,
         label: 'shipment_options_package_type',
-        ref: ref<PackageTypeName>((order.deliveryOptions?.packageType as PackageTypeName) ?? PackageTypeName.Package),
+        ref: ref<PackageTypeName>((values.deliveryOptions?.packageType as PackageTypeName) ?? PackageTypeName.Package),
         component: resolveFormComponent(AdminComponent.SelectInput),
       }),
 
       defineFormField({
         name: SIGNATURE,
         label: 'shipment_options_signature',
-        ref: ref(order.deliveryOptions?.shipmentOptions.signature ?? false),
+        ref: ref(values.deliveryOptions?.shipmentOptions.signature ?? false),
         component: resolveFormComponent(AdminComponent.ToggleInput),
         visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'signature'),
       }),
@@ -100,7 +121,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => {
       defineFormField({
         name: ONLY_RECIPIENT,
         label: 'shipment_options_only_recipient',
-        ref: ref(order.deliveryOptions?.shipmentOptions.onlyRecipient ?? false),
+        ref: ref(values.deliveryOptions?.shipmentOptions.onlyRecipient ?? false),
         component: resolveFormComponent(AdminComponent.ToggleInput),
         visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'onlyRecipient'),
       }),
@@ -108,7 +129,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => {
       defineFormField({
         name: AGE_CHECK,
         component: resolveFormComponent(AdminComponent.ToggleInput),
-        ref: ref(order.deliveryOptions?.shipmentOptions.ageCheck ?? false),
+        ref: ref(values.deliveryOptions?.shipmentOptions.ageCheck ?? false),
         label: 'shipment_options_age_check',
         visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'ageCheck'),
       }),
@@ -116,7 +137,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => {
       defineFormField({
         name: DIRECT_RETURN,
         component: resolveFormComponent(AdminComponent.ToggleInput),
-        ref: ref(order.deliveryOptions?.shipmentOptions.return ?? false),
+        ref: ref(values.deliveryOptions?.shipmentOptions.return ?? false),
         label: 'shipment_options_return',
         visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'return'),
       }),
@@ -124,7 +145,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => {
       defineFormField({
         name: LARGE_FORMAT,
         component: resolveFormComponent(AdminComponent.ToggleInput),
-        ref: ref(order.deliveryOptions?.shipmentOptions.largeFormat ?? false),
+        ref: ref(values.deliveryOptions?.shipmentOptions.largeFormat ?? false),
         label: 'shipment_options_large_format',
         visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'largeFormat'),
       }),
@@ -132,7 +153,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => {
       defineFormField({
         name: SAME_DAY_DELIVERY,
         component: resolveFormComponent(AdminComponent.ToggleInput),
-        ref: ref(order.deliveryOptions?.shipmentOptions.sameDayDelivery ?? false),
+        ref: ref(values.deliveryOptions?.shipmentOptions.sameDayDelivery ?? false),
         label: 'shipment_options_same_day_delivery',
         visibleWhen: ({form}) => isPackageTypePackage(form) && hasShipmentOption(form, 'sameDayDelivery'),
       }),
@@ -140,7 +161,7 @@ export const createShipmentOptionsForm = (order: Plugin.ModelPdkOrder) => {
       defineFormField({
         name: INSURANCE,
         component: resolveFormComponent(AdminComponent.SelectInput),
-        ref: ref(order.deliveryOptions?.shipmentOptions.insurance ?? 0),
+        ref: ref(values.deliveryOptions?.shipmentOptions.insurance ?? 0),
         label: 'shipment_options_insurance',
         props: {
           options: [],
diff --git a/libs/frontend/core/src/forms/shipmentOptions/helpers/addBulkEditNotification.ts b/libs/frontend/core/src/forms/shipmentOptions/helpers/addBulkEditNotification.ts
new file mode 100644
index 000000000..bd7cbbe5d
--- /dev/null
+++ b/libs/frontend/core/src/forms/shipmentOptions/helpers/addBulkEditNotification.ts
@@ -0,0 +1,18 @@
+import {Notification, NotificationCategory} from '../../../types';
+import {Variant} from '@myparcel-pdk/common';
+import {useNotificationStore} from '../../../stores';
+
+const TRANSLATION_KEY = 'bulk_orders_warning';
+
+export const addBulkEditNotification = (isModal: boolean): void => {
+  const notificationStore = useNotificationStore();
+
+  notificationStore.add({
+    id: TRANSLATION_KEY,
+    variant: Variant.Info,
+    category: isModal ? NotificationCategory.Modal : undefined,
+    content: `${TRANSLATION_KEY}_description`,
+    title: TRANSLATION_KEY,
+    timeout: false,
+  } as Notification);
+};
diff --git a/libs/frontend/core/src/forms/shipmentOptions/helpers/getCarrierOptions.ts b/libs/frontend/core/src/forms/shipmentOptions/helpers/getCarrierOptions.ts
index fd1aa8068..4aa938154 100644
--- a/libs/frontend/core/src/forms/shipmentOptions/helpers/getCarrierOptions.ts
+++ b/libs/frontend/core/src/forms/shipmentOptions/helpers/getCarrierOptions.ts
@@ -2,11 +2,12 @@ import {AdminContextKey} from '../../../types';
 import {CARRIER} from '../field';
 import {Carrier} from '@myparcel-pdk/common/src';
 import {FormInstance} from '@myparcel/vue-form-builder/src';
+import {get} from '@vueuse/core';
 import {useContext} from '../../../composables';
 
 export const getCarrierOptions = (form: FormInstance): Carrier.ModelCarrierOptions | undefined => {
   const dynamicContext = useContext(AdminContextKey.Dynamic);
-  const chosenCarrier = form.model[CARRIER].ref.value;
+  const chosenCarrier = get(form.model[CARRIER].ref);
 
   return dynamicContext.carrierOptions.find((options) => options.carrier.name === chosenCarrier);
 };
diff --git a/libs/frontend/core/src/forms/shipmentOptions/helpers/getPackageTypes.ts b/libs/frontend/core/src/forms/shipmentOptions/helpers/getPackageTypes.ts
index 82cbc3034..a78fd9e05 100644
--- a/libs/frontend/core/src/forms/shipmentOptions/helpers/getPackageTypes.ts
+++ b/libs/frontend/core/src/forms/shipmentOptions/helpers/getPackageTypes.ts
@@ -2,11 +2,9 @@ import {FormInstance} from '@myparcel/vue-form-builder/src';
 import {PackageTypeName} from '@myparcel/constants';
 import {SelectOption} from '@myparcel-pdk/common/src';
 import {getCarrierOptions} from './getCarrierOptions';
-import {useLanguage} from '../../../composables';
 
 export const getPackageTypes = (form?: FormInstance): SelectOption[] => {
   let array = Object.values(PackageTypeName);
-  const {translate} = useLanguage();
 
   if (form) {
     const carrierOptions = getCarrierOptions(form);
@@ -15,7 +13,7 @@ export const getPackageTypes = (form?: FormInstance): SelectOption[] => {
   }
 
   return array.map((name) => ({
-    label: translate(`package_type_${name}`),
+    label: `package_type_${name}`,
     value: name,
   }));
 };
diff --git a/libs/frontend/core/src/forms/shipmentOptions/helpers/index.ts b/libs/frontend/core/src/forms/shipmentOptions/helpers/index.ts
index e39190c92..49fe6a175 100644
--- a/libs/frontend/core/src/forms/shipmentOptions/helpers/index.ts
+++ b/libs/frontend/core/src/forms/shipmentOptions/helpers/index.ts
@@ -3,3 +3,4 @@ export * from './getInsurancePossibilities';
 export * from './getPackageTypes';
 export * from './hasShipmentOption';
 export * from './isPackageTypePackage';
+export {addBulkEditNotification} from './addBulkEditNotification';
diff --git a/libs/frontend/core/src/forms/shipmentOptions/index.ts b/libs/frontend/core/src/forms/shipmentOptions/index.ts
index 72648f225..9ddb3f471 100644
--- a/libs/frontend/core/src/forms/shipmentOptions/index.ts
+++ b/libs/frontend/core/src/forms/shipmentOptions/index.ts
@@ -1,3 +1,4 @@
 export * from './createShipmentOptionsForm';
 export * from './field';
 export * from './helpers';
+export {addBulkEditNotification} from './helpers';
diff --git a/libs/frontend/core/src/sdk/composables/api/useCarrier.ts b/libs/frontend/core/src/sdk/composables/api/useCarrier.ts
index 0f99166e5..dd4e37b3a 100644
--- a/libs/frontend/core/src/sdk/composables/api/useCarrier.ts
+++ b/libs/frontend/core/src/sdk/composables/api/useCarrier.ts
@@ -5,15 +5,15 @@ import {CarrierName} from '@myparcel/constants';
 import {QUERY_KEY_CARRIERS} from './useCarriers';
 import {useMyParcelApi} from '../useMyParcelApi';
 
-export const useCarrier = (carrier: CarrierName) => {
+export const useCarrier = (carrier?: CarrierName) => {
   const queryClient = useQueryClient();
-  const queryKey = [QUERY_KEY_CARRIERS, carrier];
+  const queryKey = [QUERY_KEY_CARRIERS, ...(carrier ? [carrier] : [])] as const;
 
   return useQuery<EndpointResponse<GetCarrier>[number]>(
     queryKey,
     async () => {
       const sdk = useMyParcelApi();
-      const carriers = await sdk.getCarrier({path: {carrier}});
+      const carriers = await sdk.getCarrier(carrier ? {path: {carrier}} : undefined);
 
       return carriers[0];
     },
diff --git a/libs/frontend/core/src/stores/useNotificationStore.ts b/libs/frontend/core/src/stores/useNotificationStore.ts
index 250e9d57f..2415d2035 100644
--- a/libs/frontend/core/src/stores/useNotificationStore.ts
+++ b/libs/frontend/core/src/stores/useNotificationStore.ts
@@ -1,8 +1,9 @@
-import {Notification, NotificationCategory} from '../';
+import {Notification, NotificationCategory, NotificationId} from '../';
 import {defineStore} from 'pinia';
+import {isEnumValue} from '@myparcel/ts-utils';
 import {ref} from 'vue';
 
-let id = 0;
+let autoId = 0;
 
 const DEFAULT_TIMEOUT = 5000;
 
@@ -17,29 +18,33 @@ export const useNotificationStore = defineStore('notifications', () => {
      */
     add(notification: Notification) {
       notification.category ??= NotificationCategory.General;
-      const notificationId = id++;
+      const id = notification.id ?? autoId++;
 
-      if (notification.timeout) {
+      if (notification.timeout !== false) {
         const timeout = typeof notification.timeout === 'number' ? notification.timeout : DEFAULT_TIMEOUT;
 
         setTimeout(() => {
-          this.remove(notificationId);
+          this.remove(id);
         }, timeout);
       }
 
-      notifications.value.push({...notification, id: notificationId});
+      if (notifications.value.some((notification) => notification.id === id)) {
+        return;
+      }
+
+      notifications.value.push({...notification, id});
     },
 
     /**
      * Remove one or more notifications from the store by category or id.
      */
-    remove(input: number | NotificationCategory) {
+    remove(input: NotificationId | NotificationCategory) {
       let filter: (notification: Notification) => boolean;
 
-      if (typeof input === 'number') {
-        filter = (notification) => notification.id !== input;
-      } else {
+      if (isEnumValue(input, NotificationCategory)) {
         filter = (notification) => notification.category !== input;
+      } else {
+        filter = (notification) => notification.id !== input;
       }
 
       notifications.value = notifications.value.filter(filter);
diff --git a/libs/frontend/core/src/stores/useQueryStore.ts b/libs/frontend/core/src/stores/useQueryStore.ts
index c727e0794..671086bea 100644
--- a/libs/frontend/core/src/stores/useQueryStore.ts
+++ b/libs/frontend/core/src/stores/useQueryStore.ts
@@ -22,6 +22,7 @@ import {BackendEndpoint} from '@myparcel-pdk/common/src';
 import {MutationMode} from '../services';
 import {defineStore} from 'pinia';
 import {getOrderId} from '../utils';
+import {toArray} from '@myparcel/ts-utils';
 
 export type QueryKey =
   | BackendEndpoint
@@ -132,8 +133,11 @@ export const useQueryStore = defineStore('query', () => {
         throw new Error('No order id found');
       }
 
-      register(`${BackendEndpoint.FetchOrders}.${id}`, useFetchOrdersQuery(id));
+      toArray(id).forEach((orderId) => {
+        register(`${BackendEndpoint.FetchOrders}.${orderId}`, useFetchOrdersQuery(orderId));
+      });
 
+      register(BackendEndpoint.FetchOrders, useFetchOrdersQuery());
       register(BackendEndpoint.ExportOrders, useExportOrdersMutation(mode));
       register(BackendEndpoint.PrintOrders, usePrintOrdersMutation());
       register(BackendEndpoint.UpdateOrders, useUpdateOrdersMutation());
diff --git a/libs/frontend/core/src/types/common.types.ts b/libs/frontend/core/src/types/common.types.ts
index bd0a90869..b917f5aa5 100644
--- a/libs/frontend/core/src/types/common.types.ts
+++ b/libs/frontend/core/src/types/common.types.ts
@@ -1,8 +1,16 @@
 import {OneOrMore} from '@myparcel/ts-utils';
 import {Variant} from '@myparcel-pdk/common/src';
 
+export type NotificationId = number | string;
+
 export interface Notification {
-  id?: number;
+  /**
+   * ID of a notification. If not provided, a unique id will be generated. It may be a string, but make sure this string
+   * does not exist in NotificationCategory.
+   *
+   * @see NotificationCategory
+   */
+  id?: NotificationId;
   category?: NotificationCategory;
   title?: string;
   content?: OneOrMore<string>;
diff --git a/libs/frontend/core/src/utils/forms/createShipmentFormName.ts b/libs/frontend/core/src/utils/forms/createShipmentFormName.ts
index a1ae5635f..9370e56f5 100644
--- a/libs/frontend/core/src/utils/forms/createShipmentFormName.ts
+++ b/libs/frontend/core/src/utils/forms/createShipmentFormName.ts
@@ -1,3 +1,9 @@
 import {AdminModalKey} from '../../types';
 
-export const createShipmentFormName = (orderId: string): string => `${AdminModalKey.ShipmentOptions}_${orderId}`;
+export const createShipmentFormName = (orderId?: string): string => {
+  if (!orderId) {
+    orderId = 'bulk';
+  }
+
+  return `${AdminModalKey.ShipmentOptions}_${orderId}`;
+};
diff --git a/libs/frontend/core/src/utils/getOrderId.ts b/libs/frontend/core/src/utils/getOrderId.ts
index 48c4c9baf..151a1c920 100644
--- a/libs/frontend/core/src/utils/getOrderId.ts
+++ b/libs/frontend/core/src/utils/getOrderId.ts
@@ -1,8 +1,9 @@
 import {useInstanceContext, useModalOrder} from '../composables';
 import {AdminInstance} from '../data';
 import {AdminInstanceContextKey} from '../types';
+import {OneOrMore} from '@myparcel/ts-utils';
 
-export const getOrderId = (instance?: AdminInstance): undefined | string => {
+export const getOrderId = (instance?: AdminInstance): undefined | OneOrMore<string> => {
   return (
     useModalOrder() ??
     instance?.context?.[AdminInstanceContextKey.OrderIdentifier] ??