Edit order pricing inline
This commit is contained in:
@@ -5,9 +5,13 @@ import {
|
||||
CompleteOrderDocument,
|
||||
ManagerFinalizeOrderDocument,
|
||||
ManagerOrdersDocument,
|
||||
ManagerSetOrderOfferDocument,
|
||||
StartOrderWorkDocument,
|
||||
type ManagerOrdersQuery,
|
||||
} from '~/composables/graphql/generated';
|
||||
import {
|
||||
formatPrice,
|
||||
orderLogisticsStateText,
|
||||
orderDeliveryStateText,
|
||||
orderLineStateText,
|
||||
} from '~/composables/useOrderDetailPresentation';
|
||||
@@ -19,21 +23,125 @@ definePageMeta({
|
||||
const route = useRoute();
|
||||
const orderId = computed(() => String(route.params.id || ''));
|
||||
|
||||
type ManagerOrderItem = ManagerOrdersQuery['managerOrders'][number];
|
||||
|
||||
const ordersQuery = useQuery(ManagerOrdersDocument, { status: null });
|
||||
|
||||
const finalizeMutation = useMutation(ManagerFinalizeOrderDocument);
|
||||
const blockMutation = useMutation(BlockOrderDocument);
|
||||
const startWorkMutation = useMutation(StartOrderWorkDocument);
|
||||
const completeWorkMutation = useMutation(CompleteOrderDocument);
|
||||
const setOfferMutation = useMutation(ManagerSetOrderOfferDocument);
|
||||
|
||||
const currentOrder = computed(() =>
|
||||
(ordersQuery.result.value?.managerOrders ?? []).find((item) => item.id === orderId.value),
|
||||
const itemPriceDrafts = reactive<Record<string, string>>({});
|
||||
const deliveryTermsDraft = ref('');
|
||||
const deliveryFeeDraft = ref('');
|
||||
|
||||
const currentOrder = computed<ManagerOrderItem | null>(() =>
|
||||
(ordersQuery.result.value?.managerOrders ?? []).find((item: ManagerOrderItem) => item.id === orderId.value) ?? null,
|
||||
);
|
||||
|
||||
watch(
|
||||
currentOrder,
|
||||
(order) => {
|
||||
for (const key of Object.keys(itemPriceDrafts)) {
|
||||
delete itemPriceDrafts[key];
|
||||
}
|
||||
|
||||
if (!order) {
|
||||
deliveryTermsDraft.value = '';
|
||||
deliveryFeeDraft.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const item of order.items) {
|
||||
itemPriceDrafts[item.id] = item.unitPrice == null ? '' : String(item.unitPrice);
|
||||
}
|
||||
|
||||
deliveryTermsDraft.value = order.deliveryTerms ?? '';
|
||||
deliveryFeeDraft.value = order.deliveryFee == null ? '' : String(order.deliveryFee);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function parseMoneyDraft(value: string) {
|
||||
const trimmed = String(value).replace(',', '.').trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = Number(trimmed);
|
||||
if (!Number.isFinite(normalized) || normalized < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Math.round((normalized + Number.EPSILON) * 100) / 100;
|
||||
}
|
||||
|
||||
function draftUnitPrice(itemId: string, fallback?: number | null) {
|
||||
return parseMoneyDraft(itemPriceDrafts[itemId] ?? '') ?? fallback ?? null;
|
||||
}
|
||||
|
||||
function draftLineTotal(item: ManagerOrderItem['items'][number]) {
|
||||
const unitPrice = draftUnitPrice(item.id, item.unitPrice);
|
||||
if (unitPrice == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Math.round((unitPrice * item.quantity + Number.EPSILON) * 100) / 100;
|
||||
}
|
||||
|
||||
const draftDeliveryTerms = computed(() => deliveryTermsDraft.value.trim() || currentOrder.value?.deliveryTerms || null);
|
||||
const draftDeliveryFee = computed(() => parseMoneyDraft(deliveryFeeDraft.value) ?? currentOrder.value?.deliveryFee ?? null);
|
||||
|
||||
const offerReady = computed(() => {
|
||||
if (!currentOrder.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const deliveryFee = parseMoneyDraft(deliveryFeeDraft.value);
|
||||
if (deliveryFee == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return currentOrder.value.items.every((item) => parseMoneyDraft(itemPriceDrafts[item.id] ?? '') != null);
|
||||
});
|
||||
|
||||
const offerTotal = computed(() => {
|
||||
if (!currentOrder.value || !offerReady.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const productsTotal = currentOrder.value.items.reduce((sum, item) => (
|
||||
sum + item.quantity * (parseMoneyDraft(itemPriceDrafts[item.id] ?? '') ?? 0)
|
||||
), 0);
|
||||
|
||||
return Math.round((productsTotal + (parseMoneyDraft(deliveryFeeDraft.value) ?? 0) + Number.EPSILON) * 100) / 100;
|
||||
});
|
||||
|
||||
async function refetchOrder() {
|
||||
await ordersQuery.refetch({ status: null });
|
||||
}
|
||||
|
||||
async function saveOffer() {
|
||||
if (!currentOrder.value || !offerReady.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;
|
||||
@@ -112,9 +220,23 @@ async function completeOrder() {
|
||||
<div class="surface-card rounded-3xl p-5">
|
||||
<h2 class="text-xl font-bold text-[#123824]">Состав заказа</h2>
|
||||
<ul class="mt-4 space-y-3">
|
||||
<li v-for="item in currentOrder.items" :key="item.id" class="manager-mini-card space-y-2">
|
||||
<p class="text-sm font-semibold text-[#123824]">{{ item.productName }} × {{ item.quantity }}</p>
|
||||
<p class="text-sm text-[#5c7b69]">{{ orderLineStateText(currentOrder.totalPrice) }}</p>
|
||||
<li v-for="item in currentOrder.items" :key="item.id" class="manager-mini-card space-y-3">
|
||||
<div class="space-y-1">
|
||||
<p class="text-sm font-semibold text-[#123824]">{{ item.productName }} × {{ item.quantity }}</p>
|
||||
<p class="text-sm text-[#5c7b69]">{{ orderLineStateText(draftUnitPrice(item.id, item.unitPrice), draftLineTotal(item)) }}</p>
|
||||
</div>
|
||||
|
||||
<label class="form-control">
|
||||
<span class="mb-2 text-xs font-semibold uppercase tracking-[0.22em] text-[#6a8a76]">Цена за единицу</span>
|
||||
<input
|
||||
v-model="itemPriceDrafts[item.id]"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="Например, 125.50"
|
||||
class="input input-bordered w-full rounded-2xl bg-white"
|
||||
>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -125,9 +247,49 @@ async function completeOrder() {
|
||||
<div class="manager-mini-card text-sm text-[#123824]">
|
||||
Адрес: {{ currentOrder.deliveryAddress || 'Адрес пока не указан' }}
|
||||
</div>
|
||||
<div class="manager-mini-card text-sm text-[#123824]">
|
||||
{{ orderDeliveryStateText(currentOrder.deliveryTerms) }}
|
||||
<div class="manager-mini-card space-y-3 text-sm text-[#123824]">
|
||||
<p>{{ orderDeliveryStateText(draftDeliveryTerms) }}</p>
|
||||
<label class="form-control">
|
||||
<span class="mb-2 text-xs font-semibold uppercase tracking-[0.22em] text-[#6a8a76]">Комментарий по доставке</span>
|
||||
<input
|
||||
v-model="deliveryTermsDraft"
|
||||
type="text"
|
||||
placeholder="Например, доставка до склада 2-3 дня"
|
||||
class="input input-bordered w-full rounded-2xl bg-white"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div class="manager-mini-card space-y-3 text-sm text-[#123824]">
|
||||
<p>{{ orderLogisticsStateText(draftDeliveryFee) }}</p>
|
||||
<label class="form-control">
|
||||
<span class="mb-2 text-xs font-semibold uppercase tracking-[0.22em] text-[#6a8a76]">Стоимость логистики</span>
|
||||
<input
|
||||
v-model="deliveryFeeDraft"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="Например, 3000"
|
||||
class="input input-bordered w-full rounded-2xl bg-white"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap items-center justify-between gap-3 border-t border-[#d6ebde] pt-4">
|
||||
<p class="text-sm text-[#5c7b69]">
|
||||
{{
|
||||
offerTotal == null
|
||||
? 'Итог по заказу посчитается автоматически после заполнения всех цен.'
|
||||
: `Предварительный итог: ${formatPrice(offerTotal)}`
|
||||
}}
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-primary rounded-full border-0 px-5"
|
||||
:disabled="!offerReady || setOfferMutation.loading.value"
|
||||
@click="saveOffer"
|
||||
>
|
||||
Сохранить условия
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user