[598-30][@ant] feat: подключить кнопку «Запустить поставку» на столе «Мои предложения» + актуализировать supply-types — чтобы поставщик запускал открытую подписку из UI (раньше только через API), как заявлено в документации
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Queries } from '@coopenomics/sdk';
|
||||
import { Mutations, Queries } from '@coopenomics/sdk';
|
||||
import { client } from 'src/shared/api/client';
|
||||
import type { MarketplaceOfferPage } from '../types';
|
||||
|
||||
@@ -35,3 +35,19 @@ export async function fetchMyOffers(
|
||||
});
|
||||
return result[Queries.Marketplace.ListMyOffers.name] as unknown as MarketplaceOfferPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Эпик 4 / Story 4.2: поставщик вручную запускает поставку по своему
|
||||
* предложению с открытой подпиской (cycle_type=open_subscription).
|
||||
*
|
||||
* Backend Resolver: marketplace-cycle.resolver.ts → marketplaceTriggerOpenSubscription
|
||||
* (guard 'Offer' 'update:own'). Нажатие = акцепт всего накопленного пула:
|
||||
* сервер формирует сводную заявку status=ACCEPTED и принимает заказы разом.
|
||||
* Ошибки backend (пустой пул, не open_subscription, не ACTIVE, чужой Offer)
|
||||
* приходят как GraphQL-исключения — пробрасываем их вызывающему компоненту.
|
||||
*/
|
||||
export async function triggerOpenSubscription(offer_id: string): Promise<void> {
|
||||
await client.Mutation(Mutations.Marketplace.TriggerOpenSubscription.mutation, {
|
||||
variables: { input: { offer_id } },
|
||||
});
|
||||
}
|
||||
|
||||
+62
-4
@@ -1,13 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { Notify } from 'quasar';
|
||||
import { Dialog, Notify } from 'quasar';
|
||||
import {
|
||||
CatalogOfferCard,
|
||||
type CatalogOffer,
|
||||
type CatalogOfferStatus,
|
||||
} from 'src/widgets/Marketplace/CatalogOfferCard';
|
||||
import { fetchMyOffers } from '../api';
|
||||
import type { MarketplaceOfferStatusView, MarketplaceOfferView } from '../types';
|
||||
import { fetchMyOffers, triggerOpenSubscription } from '../api';
|
||||
import type {
|
||||
MarketplaceOfferCycleTypeView,
|
||||
MarketplaceOfferStatusView,
|
||||
MarketplaceOfferView,
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
* Эпик 3 / Story 3.4: offerer-стол «Мои предложения».
|
||||
@@ -31,6 +35,9 @@ const currentPage = ref(1);
|
||||
const loading = ref(false);
|
||||
const statusFilter = ref<MarketplaceOfferStatusView | null>(null);
|
||||
const search = ref('');
|
||||
// id предложений, по которым прямо сейчас идёт запуск поставки — блокируем
|
||||
// повторное нажатие кнопки до ответа backend.
|
||||
const triggeringIds = ref<Set<string>>(new Set());
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const STATUS_FILTER_OPTIONS: Array<{ label: string; value: MarketplaceOfferStatusView | null }> = [
|
||||
@@ -60,7 +67,7 @@ const filtered = computed(() => {
|
||||
return list;
|
||||
});
|
||||
|
||||
const cards = computed<Array<CatalogOffer & { domainStatus: MarketplaceOfferStatusView; rejectReason: string | null }>>(() =>
|
||||
const cards = computed<Array<CatalogOffer & { domainStatus: MarketplaceOfferStatusView; cycleType: MarketplaceOfferCycleTypeView; rejectReason: string | null }>>(() =>
|
||||
filtered.value.map((o) => ({
|
||||
id: o.id,
|
||||
title: o.product_name,
|
||||
@@ -70,6 +77,7 @@ const cards = computed<Array<CatalogOffer & { domainStatus: MarketplaceOfferStat
|
||||
unitLabel: o.unit_of_measure,
|
||||
status: STATUS_TO_CARD[o.status],
|
||||
domainStatus: o.status,
|
||||
cycleType: o.cycle_type,
|
||||
rejectReason: o.reject_reason,
|
||||
})),
|
||||
);
|
||||
@@ -106,6 +114,46 @@ function loadMore(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Эпик 4 / Story 4.2: ручной запуск поставки по предложению с открытой
|
||||
* подпиской. Доступен только для ACTIVE-предложений с cycle_type=open_subscription.
|
||||
* Нажатие = акцепт всего накопленного пула заказов, действие необратимо —
|
||||
* поэтому подтверждаем через диалог. После успеха перезагружаем список
|
||||
* (остатки и счётчики меняются по итогам формирования партии).
|
||||
*/
|
||||
function triggerSupply(offerId: string, title: string): void {
|
||||
Dialog.create({
|
||||
title: 'Запустить поставку?',
|
||||
message:
|
||||
`Все накопленные заказы по предложению «${title}» будут разом зафиксированы в одну ` +
|
||||
'партию и приняты к поставке. Отменить запуск нельзя.',
|
||||
cancel: { label: 'Отмена', flat: true, noCaps: true },
|
||||
ok: { label: 'Запустить поставку', color: 'primary', unelevated: true, noCaps: true },
|
||||
persistent: true,
|
||||
}).onOk(() => {
|
||||
void runTrigger(offerId);
|
||||
});
|
||||
}
|
||||
|
||||
async function runTrigger(offerId: string): Promise<void> {
|
||||
triggeringIds.value = new Set(triggeringIds.value).add(offerId);
|
||||
try {
|
||||
await triggerOpenSubscription(offerId);
|
||||
Notify.create({
|
||||
type: 'positive',
|
||||
message: 'Поставка запущена: заказы зафиксированы в партию и приняты.',
|
||||
});
|
||||
await load(1, false);
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
Notify.create({ type: 'negative', message });
|
||||
} finally {
|
||||
const next = new Set(triggeringIds.value);
|
||||
next.delete(offerId);
|
||||
triggeringIds.value = next;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await load(1, false);
|
||||
pollTimer = setInterval(() => {
|
||||
@@ -192,6 +240,16 @@ q-page.mp-role-offerer.mp-my-offers(role="region", aria-label="Мои предл
|
||||
div.mp-my-offers__reason(v-if="card.domainStatus === 'REJECTED' && card.rejectReason")
|
||||
q-icon(name="fa-solid fa-circle-exclamation", color="negative", size="14px")
|
||||
| {{ card.rejectReason }}
|
||||
q-btn(
|
||||
v-if="card.domainStatus === 'ACTIVE' && card.cycleType === 'open_subscription'",
|
||||
color="primary",
|
||||
unelevated,
|
||||
no-caps,
|
||||
icon="fa-solid fa-truck-fast",
|
||||
label="Запустить поставку",
|
||||
:loading="triggeringIds.has(card.id)",
|
||||
@click="triggerSupply(card.id, card.title)"
|
||||
)
|
||||
|
||||
div.mp-my-offers__more(v-if="hasMore")
|
||||
q-btn(
|
||||
|
||||
@@ -78,8 +78,8 @@ tags:
|
||||
|
||||
Подходит для постоянного сбора под товар «по мере набора»: поставщик сам решает, достаточно ли заказов, чтобы везти.
|
||||
|
||||
!!! warning "Запуск подписки пока выполняется со стороны API"
|
||||
Действие «Запустить поставку» поддержано на стороне сервера (операция запуска открытой подписки) и в SDK, но **отдельная кнопка на рабочем столе поставщика ещё не подключена** — на текущем этапе запуск инициируется через API/оператора. Кнопку в интерфейсе поставщика добавит следующий UI-шаг.
|
||||
!!! info "Где нажать «Запустить поставку»"
|
||||
Кнопка **«Запустить поставку»** находится на столе поставщика, в разделе **«Мои предложения»** — на карточке каждого опубликованного (статус *Опубликовано*) предложения с открытой подпиской. Перед запуском система просит подтверждение: действие необратимо. После подтверждения все накопленные заказы разом фиксируются в одну сводную заявку, которая сразу принимается, — отдельно акцептовать партию не нужно. Если по предложению ещё нет ни одного заказа, запуск отклоняется с пояснением «Пул заказов пуст — запускать нечего».
|
||||
|
||||
### Для заказчика
|
||||
|
||||
|
||||
Reference in New Issue
Block a user