diff --git a/core/components/minishop3/tests/OrderSaveRecalcRaceTest.php b/core/components/minishop3/tests/OrderSaveRecalcRaceTest.php new file mode 100644 index 00000000..9ab90afd --- /dev/null +++ b/core/components/minishop3/tests/OrderSaveRecalcRaceTest.php @@ -0,0 +1,50 @@ + 'OrderView.vue', $actionsBar => 'OrderFormActionsBar.vue'] as $path => $label) { + if (!is_readable($path)) { + $fail("cannot read {$label}"); + } +} + +$orderViewSource = file_get_contents($orderView); +$actionsBarSource = file_get_contents($actionsBar); + +if ($orderViewSource === false || $actionsBarSource === false) { + $fail('cannot read vue sources'); +} + +if (!preg_match('/async function saveOrder\(\)[\s\S]*?if \(recalculatingCost\.value \|\| saving\.value\)/', $orderViewSource)) { + $fail('saveOrder must bail out when recalculatingCost or saving is active'); +} + +if (!preg_match('/async function recalculateOrderCost[\s\S]*?if \(saving\.value \|\| recalculatingCost\.value\)/', $orderViewSource)) { + $fail('recalculateOrderCost must bail out when saving or recalculatingCost is active'); +} + +if (!str_contains($actionsBarSource, 'recalculatingCost')) { + $fail('OrderFormActionsBar must accept recalculatingCost prop'); +} + +if (!str_contains($actionsBarSource, ':disabled="recalculatingCost"')) { + $fail('Save button must be disabled while recalculatingCost'); +} + +fwrite(STDOUT, "OK OrderSaveRecalcRaceTest\n"); +exit(0); diff --git a/vueManager/src/components/OrderView.vue b/vueManager/src/components/OrderView.vue index 589f7286..6b9f5f95 100644 --- a/vueManager/src/components/OrderView.vue +++ b/vueManager/src/components/OrderView.vue @@ -1024,6 +1024,9 @@ async function recalculateOrderCost(opts = {}) { if (isCreateMode.value || !orderId.value || orderId.value === 'new') { return } + if (saving.value || recalculatingCost.value) { + return + } recalculatingCost.value = true costRecalcWarnings.value = [] @@ -1065,6 +1068,10 @@ async function recalculateOrderCost(opts = {}) { * Save order */ async function saveOrder() { + if (recalculatingCost.value || saving.value) { + return + } + saving.value = true try { diff --git a/vueManager/src/components/order/OrderAddressTab.vue b/vueManager/src/components/order/OrderAddressTab.vue index a7d638c5..be400f62 100644 --- a/vueManager/src/components/order/OrderAddressTab.vue +++ b/vueManager/src/components/order/OrderAddressTab.vue @@ -53,6 +53,7 @@ const { createOrder, saveOrder, goBack, + recalculatingCost, } = orderCtx const { _ } = useLexicon() @@ -242,6 +243,7 @@ const showAddressTabActions = computed( v-if="showAddressTabActions" :is-create-mode="isCreateMode" :saving="saving" + :recalculating-cost="recalculatingCost" @create="createOrder" @save="saveOrder" @cancel="goBack" diff --git a/vueManager/src/components/order/OrderFormActionsBar.vue b/vueManager/src/components/order/OrderFormActionsBar.vue index 5ce86aa2..5b871490 100644 --- a/vueManager/src/components/order/OrderFormActionsBar.vue +++ b/vueManager/src/components/order/OrderFormActionsBar.vue @@ -5,6 +5,8 @@ import Button from 'primevue/button' defineProps({ isCreateMode: { type: Boolean, required: true }, saving: { type: Boolean, default: false }, + /** Blocks save while manager cost recalculation is in flight (#379). */ + recalculatingCost: { type: Boolean, default: false }, }) const emit = defineEmits(['create', 'save', 'cancel']) @@ -26,6 +28,7 @@ const { _ } = useLexicon() :label="_('save')" icon="pi pi-check" :loading="saving" + :disabled="recalculatingCost" @click="emit('save')" />