Simplify order statuses and manager actions
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useMutation, useQuery } from '@vue/apollo-composable';
|
||||
import {
|
||||
BlockOrderDocument,
|
||||
CompleteOrderDocument,
|
||||
ManagerFinalizeOrderDocument,
|
||||
ManagerSetOrderOfferDocument,
|
||||
OrderDetailDocument,
|
||||
StartOrderWorkDocument,
|
||||
@@ -29,15 +26,14 @@ const orderQuery = useQuery(OrderDetailDocument, () => ({
|
||||
id: orderId.value,
|
||||
}));
|
||||
|
||||
const finalizeMutation = useMutation(ManagerFinalizeOrderDocument);
|
||||
const blockMutation = useMutation(BlockOrderDocument);
|
||||
const startWorkMutation = useMutation(StartOrderWorkDocument);
|
||||
const completeWorkMutation = useMutation(CompleteOrderDocument);
|
||||
const setOfferMutation = useMutation(ManagerSetOrderOfferDocument);
|
||||
|
||||
const itemPriceDrafts = reactive<Record<string, string>>({});
|
||||
const deliveryTermsDraft = ref('');
|
||||
const deliveryFeeDraft = ref('');
|
||||
const autosavePending = ref(false);
|
||||
let autosaveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const currentOrder = computed<ManagerOrderItem | null>(() =>
|
||||
orderQuery.result.value?.order ?? null,
|
||||
@@ -95,6 +91,40 @@ function draftLineTotal(item: ManagerOrderItem['items'][number]) {
|
||||
|
||||
const draftDeliveryTerms = computed(() => deliveryTermsDraft.value.trim() || currentOrder.value?.deliveryTerms || null);
|
||||
const draftDeliveryFee = computed(() => parseMoneyDraft(deliveryFeeDraft.value) ?? currentOrder.value?.deliveryFee ?? null);
|
||||
const canEditOffer = computed(() => (
|
||||
currentOrder.value != null
|
||||
&& ['NEW', 'MANAGER_PROCESSING', 'WAITING_DOUBLE_CONFIRM', 'CONFIRMED'].includes(currentOrder.value.status)
|
||||
));
|
||||
|
||||
const offerSignature = computed(() => {
|
||||
if (!currentOrder.value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
deliveryTerms: deliveryTermsDraft.value.trim(),
|
||||
deliveryFee: parseMoneyDraft(deliveryFeeDraft.value),
|
||||
itemPrices: currentOrder.value.items.map((item) => ({
|
||||
itemId: item.id,
|
||||
unitPrice: parseMoneyDraft(itemPriceDrafts[item.id] ?? ''),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
const publishedSignature = computed(() => {
|
||||
if (!currentOrder.value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
deliveryTerms: currentOrder.value.deliveryTerms ?? '',
|
||||
deliveryFee: currentOrder.value.deliveryFee ?? null,
|
||||
itemPrices: currentOrder.value.items.map((item) => ({
|
||||
itemId: item.id,
|
||||
unitPrice: item.unitPrice ?? null,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
const offerReady = computed(() => {
|
||||
if (!currentOrder.value) {
|
||||
@@ -126,54 +156,28 @@ async function refetchOrder() {
|
||||
}
|
||||
|
||||
async function saveOffer() {
|
||||
if (!currentOrder.value || !offerReady.value) {
|
||||
if (!currentOrder.value || !offerReady.value || !canEditOffer.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await setOfferMutation.mutate({
|
||||
input: {
|
||||
orderId: currentOrder.value.id,
|
||||
itemPrices: currentOrder.value.items.map((item) => ({
|
||||
itemId: item.id,
|
||||
unitPrice: parseMoneyDraft(itemPriceDrafts[item.id] ?? '') ?? 0,
|
||||
})),
|
||||
deliveryTerms: deliveryTermsDraft.value.trim(),
|
||||
deliveryFee: parseMoneyDraft(deliveryFeeDraft.value) ?? 0,
|
||||
},
|
||||
});
|
||||
await refetchOrder();
|
||||
}
|
||||
|
||||
async function approveOrder() {
|
||||
if (!currentOrder.value) {
|
||||
return;
|
||||
autosavePending.value = true;
|
||||
try {
|
||||
await setOfferMutation.mutate({
|
||||
input: {
|
||||
orderId: currentOrder.value.id,
|
||||
itemPrices: currentOrder.value.items.map((item) => ({
|
||||
itemId: item.id,
|
||||
unitPrice: parseMoneyDraft(itemPriceDrafts[item.id] ?? '') ?? 0,
|
||||
})),
|
||||
deliveryTerms: deliveryTermsDraft.value.trim(),
|
||||
deliveryFee: parseMoneyDraft(deliveryFeeDraft.value) ?? 0,
|
||||
},
|
||||
});
|
||||
await refetchOrder();
|
||||
}
|
||||
|
||||
await finalizeMutation.mutate({ orderId: currentOrder.value.id, decision: 'APPROVE' });
|
||||
await refetchOrder();
|
||||
}
|
||||
|
||||
async function rejectOrder() {
|
||||
if (!currentOrder.value) {
|
||||
return;
|
||||
finally {
|
||||
autosavePending.value = false;
|
||||
}
|
||||
|
||||
await finalizeMutation.mutate({ orderId: currentOrder.value.id, decision: 'REJECT' });
|
||||
await refetchOrder();
|
||||
}
|
||||
|
||||
async function blockOrder() {
|
||||
if (!currentOrder.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await blockMutation.mutate({
|
||||
input: {
|
||||
orderId: currentOrder.value.id,
|
||||
reason: 'Нужно уточнение параметров заказа.',
|
||||
},
|
||||
});
|
||||
await refetchOrder();
|
||||
}
|
||||
|
||||
async function startOrder() {
|
||||
@@ -185,14 +189,23 @@ async function startOrder() {
|
||||
await refetchOrder();
|
||||
}
|
||||
|
||||
async function completeOrder() {
|
||||
if (!currentOrder.value) {
|
||||
return;
|
||||
}
|
||||
watch(
|
||||
[offerReady, offerSignature, publishedSignature],
|
||||
([ready, nextSignature, currentSignature]) => {
|
||||
if (autosaveTimer) {
|
||||
clearTimeout(autosaveTimer);
|
||||
autosaveTimer = null;
|
||||
}
|
||||
|
||||
await completeWorkMutation.mutate({ orderId: currentOrder.value.id });
|
||||
await refetchOrder();
|
||||
}
|
||||
if (!ready || !canEditOffer.value || !nextSignature || nextSignature === currentSignature || autosavePending.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
autosaveTimer = setTimeout(() => {
|
||||
void saveOffer();
|
||||
}, 700);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -217,6 +230,7 @@ async function completeOrder() {
|
||||
<OrdersOrderStatusTimelineCard
|
||||
:status="currentOrder.status"
|
||||
:created-at="currentOrder.createdAt"
|
||||
audience="manager"
|
||||
/>
|
||||
|
||||
<div class="surface-card rounded-3xl p-5">
|
||||
@@ -237,6 +251,7 @@ async function completeOrder() {
|
||||
step="0.01"
|
||||
placeholder="Например, 125.50"
|
||||
class="input input-bordered w-full rounded-2xl bg-white"
|
||||
:disabled="!canEditOffer"
|
||||
>
|
||||
</label>
|
||||
</li>
|
||||
@@ -258,6 +273,7 @@ async function completeOrder() {
|
||||
type="text"
|
||||
placeholder="Например, доставка до склада 2-3 дня"
|
||||
class="input input-bordered w-full rounded-2xl bg-white"
|
||||
:disabled="!canEditOffer"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
@@ -272,6 +288,7 @@ async function completeOrder() {
|
||||
step="0.01"
|
||||
placeholder="Например, 3000"
|
||||
class="input input-bordered w-full rounded-2xl bg-white"
|
||||
:disabled="!canEditOffer"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
@@ -282,29 +299,21 @@ async function completeOrder() {
|
||||
{{
|
||||
offerTotal == null
|
||||
? 'Итог по заказу посчитается автоматически после заполнения всех цен.'
|
||||
: `Предварительный итог: ${formatPrice(offerTotal)}`
|
||||
: autosavePending
|
||||
? `Сохраняем и отправляем клиенту: ${formatPrice(offerTotal)}`
|
||||
: `Предложение отправится клиенту автоматически: ${formatPrice(offerTotal)}`
|
||||
}}
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-primary rounded-full border-0 px-5"
|
||||
:disabled="!offerReady || setOfferMutation.loading.value"
|
||||
@click="saveOffer"
|
||||
>
|
||||
Сохранить условия
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="surface-card rounded-3xl p-5">
|
||||
<div v-if="['WAITING_DOUBLE_CONFIRM', 'CONFIRMED'].includes(currentOrder.status)" class="surface-card rounded-3xl p-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h2 class="text-xl font-bold text-[#123824]">Действия менеджера</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button class="btn btn-success btn-sm border-0" @click="approveOrder">Подтвердить</button>
|
||||
<button class="btn btn-error btn-sm border-0" @click="rejectOrder">Отклонить</button>
|
||||
<button class="btn btn-warning btn-sm border-0" @click="blockOrder">Заблокировать</button>
|
||||
<button class="btn btn-accent btn-sm border-0" :disabled="currentOrder.status !== 'CONFIRMED'" @click="startOrder">В работу</button>
|
||||
<button class="btn btn-neutral btn-sm border-0" :disabled="currentOrder.status !== 'IN_PROGRESS'" @click="completeOrder">Завершить</button>
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-[#123824]">Следующий шаг</h2>
|
||||
<p class="mt-1 text-sm text-[#5c7b69]">Когда всё согласовано, менеджер просто переводит заказ в работу.</p>
|
||||
</div>
|
||||
<button class="btn btn-accent rounded-full border-0 px-5" @click="startOrder">Пустить в работу</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user