[598-30][@ant] feat: подключить кнопку «Запустить поставку» на столе «Мои предложения» + актуализировать supply-types — чтобы поставщик запускал открытую подписку из UI (раньше только через API), как заявлено в документации

This commit is contained in:
ant
2026-05-26 05:51:01 +00:00
parent 8e92b4e068
commit a889a28b10
3 changed files with 81 additions and 7 deletions
@@ -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 } },
});
}
@@ -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 "Где нажать «Запустить поставку»"
Кнопка **«Запустить поставку»** находится на столе поставщика, в разделе **«Мои предложения»** — на карточке каждого опубликованного (статус *Опубликовано*) предложения с открытой подпиской. Перед запуском система просит подтверждение: действие необратимо. После подтверждения все накопленные заказы разом фиксируются в одну сводную заявку, которая сразу принимается, — отдельно акцептовать партию не нужно. Если по предложению ещё нет ни одного заказа, запуск отклоняется с пояснением «Пул заказов пуст — запускать нечего».
### Для заказчика