fix(marketplace): правки по ревью PR #17 (комментарии @ant)

- 416 rename Chairmen→Chairman; 419/421/422/423 убраны as unknown (строгие
  Zeus-типы, скаляры через Omit+override); 420 статусы через Zeus enum;
  424/425 SuccessAlert/FailAlert в OrderCreateDialog; 417/418 дедуп
  normalizeTxHash/rethrowChainError в shared/chain-tx.util; 415 единый filter
  через InputType DTO (+ regen schema/zeus/SDK); 426/427 @OnEvent из cooptypes
  WalletContract (добавлены wallet-действия authwthd/declinewthd).

Controller tsc зелёный; desktop vue-tsc — 2 pre-existing ошибки (q-tab :name=null).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 01:41:31 +05:00
parent 370a7e2900
commit de1791955d
56 changed files with 1748 additions and 838 deletions
+7 -1
View File
@@ -72,7 +72,7 @@ Workflow:
pnpm jest tests/unit/marketplace/marketplace-onboarding-service.test.ts --runInBand
```
`pnpm generate-schema` / `pnpm generate-client`**не запускать локально**; та же memory/CPU полка вешает контейнер controller'а. Либо CI, либо пользователь сам когда контейнер остановлен.
`pnpm generate-schema` / `pnpm generate-client`**МОЖНО и НУЖНО запускать когда требуется** (разрешено пользователем явно). Любое изменение GraphQL-сигнатуры резолвера или типов контрактов обязано сопровождаться полным циклом регенерации (generate-schema → generate-client → sdk build, при правках cooptypes — ещё и cooptypes build), чтобы desktop получил актуальные типы. Не оставлять заглушку «до regen».
Перед коммитом достаточно `tsc --noEmit` (быстрый, не блокирует).
@@ -86,6 +86,12 @@ pnpm jest tests/unit/marketplace/marketplace-onboarding-service.test.ts --runInB
**Не дёргать `Mutations.Auth.Login` напрямую**`LoginInput` ждёт `{email, now, signature}`, генерация подписи внутри SDK Client. Refresh: `Mutations.Auth.Refresh.mutation` с `{access_token, refresh_token}`. Канон используется в `blago-cli/src/session/index.ts` (loginInteractive) и в EMP-коннекторе `connectors/cooperative-tsk-login-connector` (Story 11.5).
## DRY — любое 2-кратное повторение выносится в общее (ОБЯЗАТЕЛЬНО)
Любой кусок кода (валидация, маппинг, guard, построение payload, helper-логика), повторённый **второй раз**, обязан быть вынесен в общее: `shared/`-helper / util / базовый класс (controller) или соответствующий FSD-слой `shared/` (desktop). Это **обязательное правило**, не рекомендация — не «то тут то там стряпать одно и то же».
**Триггер:** заметил второе вхождение → сразу выноси, не копируй. Применяется и в controller, и в desktop. (Зафиксировано пользователем в ревью PR #17 «Стол заказов».)
## Backend (controller) каноны
### 3 базовые роли — User / Member / Chairman
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,14 @@
import { Injectable, Inject, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { WalletContract } from 'cooptypes';
import { PAYMENT_REPOSITORY, PaymentRepository } from '~/domain/gateway/repositories/payment.repository';
import { PaymentStatusEnum } from '~/domain/gateway/enums/payment-status.enum';
import type { ActionDomainInterface } from '~/domain/parser/interfaces/action-domain.interface';
const WALLET = WalletContract.contractName.production;
const AUTH_WITHDRAW_EVENT = `action::${WALLET}::${WalletContract.Actions.AuthWithdraw.actionName}`;
const DECLINE_WITHDRAW_EVENT = `action::${WALLET}::${WalletContract.Actions.DeclineWithdraw.actionName}`;
/**
* Переключает статус исходящего платежа в зависимости от on-chain решения совета.
*
@@ -23,7 +28,7 @@ export class WithdrawAuthorizationListener {
private readonly paymentRepository: PaymentRepository,
) {}
@OnEvent('action::wallet::authwthd')
@OnEvent(AUTH_WITHDRAW_EVENT)
async onAuthWithdraw(action: ActionDomainInterface): Promise<void> {
const withdraw_hash = action?.data?.withdraw_hash as string | undefined;
if (!withdraw_hash) return;
@@ -41,7 +46,7 @@ export class WithdrawAuthorizationListener {
this.logger.log(`authwthd: платёж ${payment.id} → PENDING (совет авторизовал выплату)`);
}
@OnEvent('action::wallet::declinewthd')
@OnEvent(DECLINE_WITHDRAW_EVENT)
async onDeclineWithdraw(action: ActionDomainInterface): Promise<void> {
const withdraw_hash = action?.data?.withdraw_hash as string | undefined;
if (!withdraw_hash) return;
@@ -1,4 +1,5 @@
import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql';
import { Field, ID, InputType, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IsArray, IsEnum, IsOptional, IsString } from 'class-validator';
import type { MarketplaceOutgoingPaymentRequestDomainEntity } from '../../domain/entities/marketplace-outgoing-payment-request.entity';
export enum MarketplaceOutgoingPaymentRequestStatusEnum {
@@ -15,6 +16,38 @@ registerEnumType(MarketplaceOutgoingPaymentRequestStatusEnum, {
'marketplace отображает результат только для истории.',
});
@InputType('MarketplaceListOutgoingPaymentsAsSupplierFilterInput')
export class MarketplaceListOutgoingPaymentsAsSupplierFilterInputDTO {
@Field(() => [MarketplaceOutgoingPaymentRequestStatusEnum], {
nullable: true,
description: 'Фильтр по статусам выплат. Пусто — показывать все статусы.',
})
@IsOptional()
@IsArray()
@IsEnum(MarketplaceOutgoingPaymentRequestStatusEnum, { each: true })
statuses?: MarketplaceOutgoingPaymentRequestStatusEnum[];
}
@InputType('MarketplaceListOutgoingPaymentsFilterInput')
export class MarketplaceListOutgoingPaymentsFilterInputDTO {
@Field(() => String, {
nullable: true,
description: 'Поставщик-получатель выплаты. Пусто — по всем поставщикам.',
})
@IsOptional()
@IsString()
supplier_account?: string;
@Field(() => [MarketplaceOutgoingPaymentRequestStatusEnum], {
nullable: true,
description: 'Фильтр по статусам выплат. Пусто — показывать все статусы.',
})
@IsOptional()
@IsArray()
@IsEnum(MarketplaceOutgoingPaymentRequestStatusEnum, { each: true })
statuses?: MarketplaceOutgoingPaymentRequestStatusEnum[];
}
@ObjectType('MarketplaceOutgoingPaymentRequest')
export class MarketplaceOutgoingPaymentRequestDTO {
@Field(() => ID)
@@ -8,9 +8,9 @@ import type { IMarketplaceCurrentMember } from '../dto/marketplace-current-membe
import { mapUserRoleToCoreRoles } from '../membership/core-roles.mapper';
import { mapCoreRolesToMarketplaceRoles } from '../membership/marketplace-roles.mapper';
import {
MARKETPLACE_KU_CHAIRMEN_SERVICE,
type MarketplaceKuChairmenService,
} from '../services/marketplace-ku-chairmen.service';
MARKETPLACE_KU_CHAIRMAN_SERVICE,
type MarketplaceKuChairmanService,
} from '../services/marketplace-ku-chairman.service';
import {
MARKETPLACE_WHITELIST_SERVICE,
type MarketplaceWhitelistService,
@@ -37,8 +37,8 @@ export class MarketplaceMembershipGuard implements CanActivate {
constructor(
@Inject(MARKETPLACE_WHITELIST_SERVICE)
private readonly whitelistService: MarketplaceWhitelistService,
@Inject(MARKETPLACE_KU_CHAIRMEN_SERVICE)
private readonly kuChairmenService: MarketplaceKuChairmenService
@Inject(MARKETPLACE_KU_CHAIRMAN_SERVICE)
private readonly kuChairmanService: MarketplaceKuChairmanService
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
@@ -62,11 +62,11 @@ export class MarketplaceMembershipGuard implements CanActivate {
const coreRoles = mapUserRoleToCoreRoles(user.role);
// Story 3.1 / Эпик 2: оба источника берутся из dedicated-сервисов с
// TTL-кешем (MarketplaceWhitelistService / MarketplaceKuChairmenService),
// TTL-кешем (MarketplaceWhitelistService / MarketplaceKuChairmanService),
// чтобы guard на каждом GraphQL-запросе не лез в RPC и в БД на N+1.
const [isOfferer, isKuChairman] = await Promise.all([
this.whitelistService.isOfferer(config.coopname, user.username),
this.kuChairmenService.isKuChairman(config.coopname, user.username),
this.kuChairmanService.isKuChairman(config.coopname, user.username),
]);
const marketplaceRoles = mapCoreRolesToMarketplaceRoles(coreRoles, {
isOfferer,
@@ -33,9 +33,9 @@ import {
MARKETPLACE_WHITELIST_SERVICE,
} from './services/marketplace-whitelist.service';
import {
MarketplaceKuChairmenService,
MARKETPLACE_KU_CHAIRMEN_SERVICE,
} from './services/marketplace-ku-chairmen.service';
MarketplaceKuChairmanService,
MARKETPLACE_KU_CHAIRMAN_SERVICE,
} from './services/marketplace-ku-chairman.service';
import {
MarketplaceVitrineService,
MARKETPLACE_VITRINE_SERVICE,
@@ -204,10 +204,10 @@ import { MarketplaceInventoryEntity } from '../infrastructure/entities/marketpla
// Эпик 2 / Story 2.x — источник isKuChairman (trustee ИЛИ trusted
// одного из branches кооператива) для marketplace-роли `operator`.
{
provide: MARKETPLACE_KU_CHAIRMEN_SERVICE,
useClass: MarketplaceKuChairmenService,
provide: MARKETPLACE_KU_CHAIRMAN_SERVICE,
useClass: MarketplaceKuChairmanService,
},
MarketplaceKuChairmenService,
MarketplaceKuChairmanService,
{
provide: MARKETPLACE_VITRINE_SERVICE,
useClass: MarketplaceVitrineService,
@@ -328,8 +328,8 @@ import { MarketplaceInventoryEntity } from '../infrastructure/entities/marketpla
MarketplaceCoopAcceptanceService,
MARKETPLACE_WHITELIST_SERVICE,
MarketplaceWhitelistService,
MARKETPLACE_KU_CHAIRMEN_SERVICE,
MarketplaceKuChairmenService,
MARKETPLACE_KU_CHAIRMAN_SERVICE,
MarketplaceKuChairmanService,
MARKETPLACE_VITRINE_SERVICE,
MarketplaceVitrineService,
MARKETPLACE_OFFER_SERVICE,
@@ -22,7 +22,7 @@ export type MarketplaceRole =
* `MarketplaceWhitelistService.isOfferer`.
*
* `isKuChairman` — Эпик 2 (ПВЗ): пайщик имеет операционные полномочия
* на хотя бы одном КУ кооператива. Источник — `MarketplaceKuChairmenService.isKuChairman`,
* на хотя бы одном КУ кооператива. Источник — `MarketplaceKuChairmanService.isKuChairman`,
* семантика «trustee ИЛИ trusted одного из branches» (trustee и trusted
* равны в правах по столу ПВЗ — приёмка, выдача, маркировка, склад).
*/
@@ -9,9 +9,9 @@ import { MarketplaceRoleGuard } from '../guards/marketplace-role.guard';
import { canAccess } from '../access/marketplace-access-matrix';
import type { MarketplaceRole } from '../membership/marketplace-roles.mapper';
import {
MARKETPLACE_KU_CHAIRMEN_SERVICE,
type MarketplaceKuChairmenService,
} from '../services/marketplace-ku-chairmen.service';
MARKETPLACE_KU_CHAIRMAN_SERVICE,
type MarketplaceKuChairmanService,
} from '../services/marketplace-ku-chairman.service';
import type { IMarketplaceCurrentMember } from '../dto/marketplace-current-member.dto';
import {
MarketplaceAplReceptionByIdInputDTO,
@@ -52,8 +52,8 @@ export class MarketplaceAplReceptionResolver {
private readonly service: MarketplaceAplReceptionService,
@Inject(MARKETPLACE_APL_RECEPTION_REPOSITORY)
private readonly receptionRepo: MarketplaceAplReceptionDomainRepository,
@Inject(MARKETPLACE_KU_CHAIRMEN_SERVICE)
private readonly kuChairmenService: MarketplaceKuChairmenService
@Inject(MARKETPLACE_KU_CHAIRMAN_SERVICE)
private readonly kuChairmanService: MarketplaceKuChairmanService
) {}
@Mutation(() => MarketplaceAplReceptionResultDTO, {
@@ -182,7 +182,7 @@ export class MarketplaceAplReceptionResolver {
if (!reception || reception.coopname !== coopname) {
throw new NotFoundException('Акт приёмки не найден.');
}
const isMember = await this.kuChairmenService.isMemberOfBranch(
const isMember = await this.kuChairmanService.isMemberOfBranch(
coopname,
reception.braname,
member.username
@@ -219,7 +219,7 @@ export class MarketplaceAplReceptionResolver {
// capability `Receiving:create`). Оператор только с правами своего КУ обязан
// быть членом запрашиваемого участка, иначе утечёт лента приёмок чужого КУ.
if (!canAccess(roles, 'Receiving', 'read:all')) {
const isMember = await this.kuChairmenService.isMemberOfBranch(
const isMember = await this.kuChairmanService.isMemberOfBranch(
coopname,
data.braname,
member.username
@@ -9,9 +9,9 @@ import { MarketplaceRoleGuard } from '../guards/marketplace-role.guard';
import { canAccess } from '../access/marketplace-access-matrix';
import type { MarketplaceRole } from '../membership/marketplace-roles.mapper';
import {
MARKETPLACE_KU_CHAIRMEN_SERVICE,
type MarketplaceKuChairmenService,
} from '../services/marketplace-ku-chairmen.service';
MARKETPLACE_KU_CHAIRMAN_SERVICE,
type MarketplaceKuChairmanService,
} from '../services/marketplace-ku-chairman.service';
import type { IMarketplaceCurrentMember } from '../dto/marketplace-current-member.dto';
import {
MarketplaceInventoryItemDTO,
@@ -45,8 +45,8 @@ export class MarketplaceInventoryResolver {
private readonly labelService: MarketplaceInventoryLabelService,
@Inject(MARKETPLACE_INVENTORY_REPOSITORY)
private readonly inventoryRepo: MarketplaceInventoryDomainRepository,
@Inject(MARKETPLACE_KU_CHAIRMEN_SERVICE)
private readonly kuChairmenService: MarketplaceKuChairmenService
@Inject(MARKETPLACE_KU_CHAIRMAN_SERVICE)
private readonly kuChairmanService: MarketplaceKuChairmanService
) {}
@Mutation(() => MarketplaceLabelInventoryResultDTO, {
@@ -122,7 +122,7 @@ export class MarketplaceInventoryResolver {
// `read:own-KU` (оператор/председатель КУ) ограничивается своими КУ.
let branameFilter: string | string[] | undefined = data?.braname;
if (!canAccess(roles, 'Warehouse', 'read:all')) {
const ownBranames = await this.kuChairmenService.listBranamesForMember(
const ownBranames = await this.kuChairmanService.listBranamesForMember(
coopname,
member.username
);
@@ -9,9 +9,9 @@ import { MarketplaceRoleGuard } from '../guards/marketplace-role.guard';
import { canAccess } from '../access/marketplace-access-matrix';
import type { MarketplaceRole } from '../membership/marketplace-roles.mapper';
import {
MARKETPLACE_KU_CHAIRMEN_SERVICE,
type MarketplaceKuChairmenService,
} from '../services/marketplace-ku-chairmen.service';
MARKETPLACE_KU_CHAIRMAN_SERVICE,
type MarketplaceKuChairmanService,
} from '../services/marketplace-ku-chairman.service';
import type { IMarketplaceCurrentMember } from '../dto/marketplace-current-member.dto';
import {
MarketplaceFinalizeIssuanceInputDTO,
@@ -51,8 +51,8 @@ export class MarketplaceIssuanceResolver {
private readonly service: MarketplaceIssuanceService,
@Inject(MARKETPLACE_ORDER_REPOSITORY)
private readonly orderRepo: MarketplaceOrderDomainRepository,
@Inject(MARKETPLACE_KU_CHAIRMEN_SERVICE)
private readonly kuChairmenService: MarketplaceKuChairmenService
@Inject(MARKETPLACE_KU_CHAIRMAN_SERVICE)
private readonly kuChairmanService: MarketplaceKuChairmanService
) {}
@Mutation(() => MarketplaceIssuanceResultDTO, {
@@ -126,7 +126,7 @@ export class MarketplaceIssuanceResolver {
if (!order || order.coopname !== coopname) {
throw new NotFoundException('Заказ не найден.');
}
const isMember = await this.kuChairmenService.isMemberOfBranch(
const isMember = await this.kuChairmanService.isMemberOfBranch(
coopname,
order.delivery_braname,
member.username
@@ -202,7 +202,7 @@ export class MarketplaceIssuanceResolver {
// только с `read:own-KU` (оператор/председатель КУ) обязана быть членом
// запрашиваемого участка, иначе утечёт лента выдач чужого КУ.
if (!canAccess(roles, 'Issuance', 'read:all')) {
const isMember = await this.kuChairmenService.isMemberOfBranch(
const isMember = await this.kuChairmanService.isMemberOfBranch(
coopname,
data.delivery_braname,
member.username
@@ -9,9 +9,9 @@ import { MarketplaceCurrentMemberDTO } from '../dto/marketplace-current-member.d
import type { IMarketplaceCurrentMember } from '../dto/marketplace-current-member.dto';
import { MarketplaceMembershipGuard } from '../guards/marketplace-membership.guard';
import {
MARKETPLACE_KU_CHAIRMEN_SERVICE,
type MarketplaceKuChairmenService,
} from '../services/marketplace-ku-chairmen.service';
MARKETPLACE_KU_CHAIRMAN_SERVICE,
type MarketplaceKuChairmanService,
} from '../services/marketplace-ku-chairman.service';
/**
* Story 1.3: тестовый whoami-эндпоинт расширения marketplace.
@@ -25,8 +25,8 @@ import {
@Injectable()
export class MarketplaceMembershipResolver {
constructor(
@Inject(MARKETPLACE_KU_CHAIRMEN_SERVICE)
private readonly kuChairmenService: MarketplaceKuChairmenService
@Inject(MARKETPLACE_KU_CHAIRMAN_SERVICE)
private readonly kuChairmanService: MarketplaceKuChairmanService
) {}
@Query(() => MarketplaceCurrentMemberDTO, {
@@ -37,7 +37,7 @@ export class MarketplaceMembershipResolver {
async marketplaceWhoAmI(
@CurrentMarketplaceMember() currentMember: IMarketplaceCurrentMember
): Promise<MarketplaceCurrentMemberDTO> {
const branches = await this.kuChairmenService.listBranamesForMember(
const branches = await this.kuChairmanService.listBranamesForMember(
config.coopname,
currentMember.username
);
@@ -8,8 +8,9 @@ import { MarketplaceMembershipGuard } from '../guards/marketplace-membership.gua
import { MarketplaceRoleGuard } from '../guards/marketplace-role.guard';
import type { IMarketplaceCurrentMember } from '../dto/marketplace-current-member.dto';
import {
MarketplaceListOutgoingPaymentsAsSupplierFilterInputDTO,
MarketplaceListOutgoingPaymentsFilterInputDTO,
MarketplaceOutgoingPaymentRequestDTO,
MarketplaceOutgoingPaymentRequestStatusEnum,
toMarketplaceOutgoingPaymentRequestDTO,
} from '../dto/marketplace-outgoing-payment.dto';
import {
@@ -40,16 +41,13 @@ export class MarketplaceOutgoingPaymentResolver {
@UseGuards(GqlJwtAuthGuard, MarketplaceMembershipGuard)
async marketplaceListOutgoingPaymentsAsSupplier(
@CurrentMarketplaceMember() member: IMarketplaceCurrentMember,
@Args('statuses', {
type: () => [MarketplaceOutgoingPaymentRequestStatusEnum],
nullable: true,
})
statuses?: MarketplaceOutgoingPaymentRequestStatusEnum[]
@Args('filter', { nullable: true })
filter?: MarketplaceListOutgoingPaymentsAsSupplierFilterInputDTO
): Promise<MarketplaceOutgoingPaymentRequestDTO[]> {
const list = await this.paymentRepo.listByPayee(
config.coopname,
member.username,
statuses as unknown as MarketplaceOutgoingPaymentRequestStatus[] | undefined
filter?.statuses as MarketplaceOutgoingPaymentRequestStatus[] | undefined
);
return list.map(toMarketplaceOutgoingPaymentRequestDTO);
}
@@ -62,16 +60,12 @@ export class MarketplaceOutgoingPaymentResolver {
@UseGuards(GqlJwtAuthGuard, MarketplaceMembershipGuard, MarketplaceRoleGuard)
@RequireMarketplaceAccess('Payment', 'read:all')
async marketplaceListOutgoingPayments(
@Args('supplier_account', { nullable: true }) supplier_account?: string,
@Args('statuses', {
type: () => [MarketplaceOutgoingPaymentRequestStatusEnum],
nullable: true,
})
statuses?: MarketplaceOutgoingPaymentRequestStatusEnum[]
@Args('filter', { nullable: true })
filter?: MarketplaceListOutgoingPaymentsFilterInputDTO
): Promise<MarketplaceOutgoingPaymentRequestDTO[]> {
const list = await this.paymentRepo.listAll(config.coopname, {
payee_account: supplier_account ?? undefined,
statuses: statuses as unknown as MarketplaceOutgoingPaymentRequestStatus[] | undefined,
payee_account: filter?.supplier_account ?? undefined,
statuses: filter?.statuses as MarketplaceOutgoingPaymentRequestStatus[] | undefined,
});
return list.map(toMarketplaceOutgoingPaymentRequestDTO);
}
@@ -22,9 +22,9 @@ import {
} from '../dto/marketplace-return-claim.dto';
import { MarketplaceReturnClaimService } from '../services/marketplace-return-claim.service';
import {
MARKETPLACE_KU_CHAIRMEN_SERVICE,
type MarketplaceKuChairmenService,
} from '../services/marketplace-ku-chairmen.service';
MARKETPLACE_KU_CHAIRMAN_SERVICE,
type MarketplaceKuChairmanService,
} from '../services/marketplace-ku-chairman.service';
import { Inject } from '@nestjs/common';
import { toMarketplaceReturnClaimDTO } from './marketplace-return-claim.mapper';
@@ -53,8 +53,8 @@ function toGeneratedDocumentDTO(e: DocumentDomainEntity): GeneratedDocumentDTO {
export class MarketplaceReturnClaimResolver {
constructor(
private readonly service: MarketplaceReturnClaimService,
@Inject(MARKETPLACE_KU_CHAIRMEN_SERVICE)
private readonly kuChairmenService: MarketplaceKuChairmenService
@Inject(MARKETPLACE_KU_CHAIRMAN_SERVICE)
private readonly kuChairmanService: MarketplaceKuChairmanService
) {}
@Query(() => GeneratedDocumentDTO, {
@@ -220,8 +220,8 @@ export class MarketplaceReturnClaimResolver {
): Promise<MarketplaceReturnClaimDTO[]> {
// Ownership `:own-KU` — пайщик должен быть trustee или trusted
// именно того КУ, к которому привязано заявление. Проверка идёт
// через единый источник истины состава КУ (MarketplaceKuChairmenService).
const isMember = await this.kuChairmenService.isMemberOfBranch(
// через единый источник истины состава КУ (MarketplaceKuChairmanService).
const isMember = await this.kuChairmanService.isMemberOfBranch(
config.coopname,
data.delivery_braname,
member.username
@@ -256,7 +256,7 @@ export class MarketplaceReturnClaimResolver {
const isOwnerOrderer = claim.orderer_account === member.username;
const isOperatorOfDeliveryKu =
member.marketplace_roles.includes('operator') &&
(await this.kuChairmenService.isMemberOfBranch(
(await this.kuChairmanService.isMemberOfBranch(
config.coopname,
claim.delivery_braname,
member.username
@@ -9,9 +9,9 @@ import { MarketplaceRoleGuard } from '../guards/marketplace-role.guard';
import { canAccess } from '../access/marketplace-access-matrix';
import type { MarketplaceRole } from '../membership/marketplace-roles.mapper';
import {
MARKETPLACE_KU_CHAIRMEN_SERVICE,
type MarketplaceKuChairmenService,
} from '../services/marketplace-ku-chairmen.service';
MARKETPLACE_KU_CHAIRMAN_SERVICE,
type MarketplaceKuChairmanService,
} from '../services/marketplace-ku-chairman.service';
import type { IMarketplaceCurrentMember } from '../dto/marketplace-current-member.dto';
import {
MarketplaceCreateShipmentInputDTO,
@@ -45,8 +45,8 @@ export class MarketplaceShipmentResolver {
private readonly createService: MarketplaceShipmentCreateService,
@Inject(MARKETPLACE_SHIPMENT_REPOSITORY)
private readonly shipmentRepo: MarketplaceShipmentDomainRepository,
@Inject(MARKETPLACE_KU_CHAIRMEN_SERVICE)
private readonly kuChairmenService: MarketplaceKuChairmenService
@Inject(MARKETPLACE_KU_CHAIRMAN_SERVICE)
private readonly kuChairmanService: MarketplaceKuChairmanService
) {}
@Mutation(() => MarketplaceCreateShipmentResultDTO, {
@@ -119,7 +119,7 @@ export class MarketplaceShipmentResolver {
// только с `read:own-KU` (оператор/председатель КУ) обязана быть членом
// запрашиваемого участка, иначе утечёт лента поставок чужого КУ.
if (!canAccess(roles, 'Shipment', 'read:all')) {
const isMember = await this.kuChairmenService.isMemberOfBranch(
const isMember = await this.kuChairmanService.isMemberOfBranch(
coopname,
data.braname,
member.username
@@ -1,4 +1,4 @@
import { MarketplaceKuChairmenService } from './marketplace-ku-chairmen.service';
import { MarketplaceKuChairmanService } from './marketplace-ku-chairman.service';
import type { BranchBlockchainPort } from '~/domain/branch/interfaces/branch-blockchain.port';
function buildPort(branches: Array<{ braname: string; trustee: string; trusted: string[] }>) {
@@ -10,14 +10,14 @@ function buildPort(branches: Array<{ braname: string; trustee: string; trusted:
return port as BranchBlockchainPort & { getBranches: jest.Mock };
}
describe('MarketplaceKuChairmenService', () => {
describe('MarketplaceKuChairmanService', () => {
describe('isKuChairman', () => {
it('возвращает true для trustee любого КУ', async () => {
const port = buildPort([
{ braname: 'krg', trustee: 'chairkrg', trusted: ['opkrg'] },
{ braname: 'myt', trustee: 'chairmyt', trusted: [] },
]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
expect(await service.isKuChairman('voskhod', 'chairkrg')).toBe(true);
expect(await service.isKuChairman('voskhod', 'chairmyt')).toBe(true);
@@ -27,7 +27,7 @@ describe('MarketplaceKuChairmenService', () => {
const port = buildPort([
{ braname: 'krg', trustee: 'chairkrg', trusted: ['opkrg', 'trustedkrg'] },
]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
expect(await service.isKuChairman('voskhod', 'opkrg')).toBe(true);
expect(await service.isKuChairman('voskhod', 'trustedkrg')).toBe(true);
@@ -37,7 +37,7 @@ describe('MarketplaceKuChairmenService', () => {
const port = buildPort([
{ braname: 'krg', trustee: 'chairkrg', trusted: ['opkrg'] },
]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
expect(await service.isKuChairman('voskhod', 'ivanpetrov')).toBe(false);
});
@@ -46,7 +46,7 @@ describe('MarketplaceKuChairmenService', () => {
const port = buildPort([
{ braname: 'krg', trustee: 'chairkrg', trusted: [] },
]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
await service.isKuChairman('voskhod', 'chairkrg');
await service.isKuChairman('voskhod', 'someoneelse');
@@ -59,7 +59,7 @@ describe('MarketplaceKuChairmenService', () => {
const port = buildPort([
{ braname: 'krg', trustee: 'chairkrg', trusted: [] },
]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
await service.isKuChairman('voskhod', 'chairkrg');
service.invalidate('voskhod');
@@ -75,7 +75,7 @@ describe('MarketplaceKuChairmenService', () => {
{ braname: 'krg', trustee: 'chairkrg', trusted: ['opkrg'] },
{ braname: 'myt', trustee: 'chairmyt', trusted: [] },
]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
expect(await service.isMemberOfBranch('voskhod', 'krg', 'chairkrg')).toBe(true);
expect(await service.isMemberOfBranch('voskhod', 'krg', 'opkrg')).toBe(true);
@@ -90,7 +90,7 @@ describe('MarketplaceKuChairmenService', () => {
const port = buildPort([
{ braname: 'krg', trustee: 'chairkrg', trusted: ['opkrg', 'trustedkrg'] },
]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
expect(await service.listOperatorsOfBranch('voskhod', 'krg')).toEqual([
'chairkrg',
@@ -101,7 +101,7 @@ describe('MarketplaceKuChairmenService', () => {
it('пустой список для несуществующего КУ', async () => {
const port = buildPort([]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
expect(await service.listOperatorsOfBranch('voskhod', 'nope')).toEqual([]);
});
@@ -114,7 +114,7 @@ describe('MarketplaceKuChairmenService', () => {
{ braname: 'myt', trustee: 'chairmyt', trusted: [] },
{ braname: 'odn', trustee: 'chairodn', trusted: ['ekaterina'] },
]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
expect(await service.listBranamesForMember('voskhod', 'ekaterina')).toEqual(['krg', 'odn']);
expect(await service.listBranamesForMember('voskhod', 'chairmyt')).toEqual(['myt']);
@@ -127,14 +127,14 @@ describe('MarketplaceKuChairmenService', () => {
const port = buildPort([
{ braname: 'krg', trustee: 'chairkrg', trusted: ['opkrg'] },
]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
expect(await service.getTrusteeOfBranch('voskhod', 'krg')).toBe('chairkrg');
});
it('возвращает null для несуществующего', async () => {
const port = buildPort([]);
const service = new MarketplaceKuChairmenService(port);
const service = new MarketplaceKuChairmanService(port);
expect(await service.getTrusteeOfBranch('voskhod', 'nope')).toBeNull();
});
@@ -7,7 +7,7 @@ import {
type BranchBlockchainPort,
} from '~/domain/branch/interfaces/branch-blockchain.port';
export const MARKETPLACE_KU_CHAIRMEN_SERVICE = Symbol('MARKETPLACE_KU_CHAIRMEN_SERVICE');
export const MARKETPLACE_KU_CHAIRMAN_SERVICE = Symbol('MARKETPLACE_KU_CHAIRMAN_SERVICE');
interface IBranchesCacheEntry {
branches: BranchContract.Tables.Branches.IBranch[];
@@ -41,7 +41,7 @@ interface IBranchesCacheEntry {
* состав КУ, дёргают `invalidate(coopname)`.
*/
@Injectable()
export class MarketplaceKuChairmenService {
export class MarketplaceKuChairmanService {
private static readonly BRANCHES_CACHE_TTL_MS = 60_000;
private readonly branchesCache = new Map<string, IBranchesCacheEntry>();
@@ -114,7 +114,7 @@ export class MarketplaceKuChairmenService {
const branches = await this.branchPort.getBranches(coopname);
this.branchesCache.set(coopname, {
branches,
expires_at: now + MarketplaceKuChairmenService.BRANCHES_CACHE_TTL_MS,
expires_at: now + MarketplaceKuChairmanService.BRANCHES_CACHE_TTL_MS,
});
return branches;
}
@@ -7,9 +7,9 @@ import { NOVU_WORKFLOW_PORT, type NovuWorkflowPort } from '~/domain/notification
import { ACCOUNT_DATA_PORT, type AccountDataPort } from '~/domain/account/ports/account-data.port';
import type { WorkflowTriggerDomainInterface } from '~/domain/notification/interfaces/workflow-trigger-domain.interface';
import {
MARKETPLACE_KU_CHAIRMEN_SERVICE,
type MarketplaceKuChairmenService,
} from './marketplace-ku-chairmen.service';
MARKETPLACE_KU_CHAIRMAN_SERVICE,
type MarketplaceKuChairmanService,
} from './marketplace-ku-chairman.service';
import {
MARKETPLACE_APL_SUPPLIER_SIGN_REQUEST_EVENT,
MARKETPLACE_CASHIER_NEW_PAYMENT_EVENT,
@@ -51,8 +51,8 @@ export class MarketplaceNotificationService implements OnModuleInit {
private readonly novuWorkflowPort: NovuWorkflowPort,
@Inject(ACCOUNT_DATA_PORT)
private readonly accountPort: AccountDataPort,
@Inject(MARKETPLACE_KU_CHAIRMEN_SERVICE)
private readonly kuChairmenService: MarketplaceKuChairmenService,
@Inject(MARKETPLACE_KU_CHAIRMAN_SERVICE)
private readonly kuChairmanService: MarketplaceKuChairmanService,
private readonly logger: WinstonLoggerService
) {
this.logger.setContext(MarketplaceNotificationService.name);
@@ -236,7 +236,7 @@ export class MarketplaceNotificationService implements OnModuleInit {
// равны в правах по столу ПВЗ, новый return-claim видят все. Veerom
// (несколько Novu-триггеров) сохраняет инвариант «никто не пропустил»
// без ввода понятия «главный получатель уведомления».
const operators = await this.kuChairmenService.listOperatorsOfBranch(
const operators = await this.kuChairmanService.listOperatorsOfBranch(
event.coopname,
event.delivery_braname
);
@@ -13,6 +13,7 @@ import {
type MarketplaceCanonicalBlockchainPort,
} from '../../domain/ports/marketplace-canonical-blockchain.port';
import type { MarketplaceOrderDomainEntity } from '../../domain/entities/marketplace-order.entity';
import { normalizeChainTxHash, rethrowChainError } from '../shared/chain-tx.util';
export interface MarketplaceOrderCancelInputDto {
/** coopname кооператива. Берётся из core-сессии в resolver'е. */
@@ -108,13 +109,16 @@ export class MarketplaceOrderCancelService {
orderer: input.orderer_account,
order_hash: order.order_hash,
});
txHash = this.normalizeTxHash(tx);
txHash = normalizeChainTxHash(
tx,
'Отмена заказа: цепь не вернула tx_hash. Повторите попытку.'
);
} catch (error: any) {
this.logger.error(
`MarketplaceOrderCancelService: chain.cancelOrder fail для Order ${order.id} (orderer=${input.orderer_account}): ${error.message}`,
error.stack
);
this.rethrowChainError(error);
rethrowChainError(error);
}
// ── 3. Counter onOrderUnblocked (best-effort) ───────────────────
@@ -139,41 +143,6 @@ export class MarketplaceOrderCancelService {
return { order: updated, tx_hash: txHash! };
}
private normalizeTxHash(tx: unknown): string {
// wharfkit TransactResult держит Antelope push_transaction response под
// `response` (response.processed.id / response.transaction_id). Для
// совместимости со spec-тестами поддерживаем и плоский processed.id.
const t = tx as {
transaction?: { id?: string };
processed?: { id?: string };
response?: {
transaction_id?: string;
processed?: { id?: string };
};
};
const hash =
t?.response?.processed?.id ??
t?.response?.transaction_id ??
t?.processed?.id ??
t?.transaction?.id;
if (!hash) {
// fail-fast: цепь приняла action, но не вернула tx_hash — audit-trail
// станет фантомным ('unknown') без возможности cross-reference. Лучше
// отбить отмену пайщику и попросить retry, чем записать «unknown».
throw new BadRequestException(
'Отмена заказа: цепь не вернула tx_hash. Повторите попытку.'
);
}
return hash;
}
private rethrowChainError(error: any): never {
const raw: string = error?.message ?? String(error);
const match = raw.match(/assertion failure with message: (.+?)(?:\n|$)/);
const clean = match ? match[1].trim() : raw;
throw new BadRequestException(clean);
}
}
export const MARKETPLACE_ORDER_CANCEL_SERVICE = Symbol('MARKETPLACE_ORDER_CANCEL_SERVICE');
@@ -28,6 +28,7 @@ import {
import type { MarketplaceOrderDomainEntity } from '../../domain/entities/marketplace-order.entity';
import type { MarketplaceOrderCreateTxSnapshot } from '../../domain/entities/marketplace-order.types';
import { toChainCycleType } from '../../domain/entities/marketplace-offer.types';
import { rethrowChainError } from '../shared/chain-tx.util';
export interface MarketplaceOrderCreateInputDto {
/** coopname кооператива. Берётся из core-сессии в resolver'е. */
@@ -180,7 +181,7 @@ export class MarketplaceOrderCreateService {
compErr.stack
);
}
this.rethrowChainError(error);
rethrowChainError(error);
}
// ── 5. Persist PG row Order с tx snapshot ──────────────────────
@@ -356,19 +357,6 @@ export class MarketplaceOrderCreateService {
const block_num = t?.response?.processed?.block_num ?? 0;
return { tx_hash, block_num };
}
/**
* Антелопе chain exception приходит в exception.message с трейсом и
* `assertion failure with message: <текст>`. Эта функция вытаскивает
* чистое сообщение и оборачивает в BadRequestException для clean
* пользовательского ответа.
*/
private rethrowChainError(error: any): never {
const raw: string = error?.message ?? String(error);
const match = raw.match(/assertion failure with message: (.+?)(?:\n|$)/);
const clean = match ? match[1].trim() : raw;
throw new BadRequestException(clean);
}
}
export const MARKETPLACE_ORDER_CREATE_SERVICE = Symbol('MARKETPLACE_ORDER_CREATE_SERVICE');
@@ -26,6 +26,7 @@ import {
MarketplaceOrderCycleTypes,
MarketplaceOrderStatuses,
} from '../../domain/entities/marketplace-order.types';
import { normalizeChainTxHash, rethrowChainError } from '../shared/chain-tx.util';
export interface MarketplaceSupplierAcceptInput {
coopname: string;
@@ -83,13 +84,16 @@ export class MarketplaceOrderSupplierActionService {
offerer: input.offerer_account,
order_hash: order.order_hash,
});
txHash = this.normalizeTxHash(tx);
txHash = normalizeChainTxHash(
tx,
'Действие поставщика: цепь не вернула tx_hash. Повторите попытку.'
);
} catch (error: any) {
this.logger.error(
`MarketplaceOrderSupplierActionService.acceptIndividual: chain.acceptOrder fail для Order ${order.id}: ${error.message}`,
error.stack
);
this.rethrowChainError(error);
rethrowChainError(error);
}
const updated = await this.orderRepo.applyStatusTransition(
@@ -221,13 +225,16 @@ export class MarketplaceOrderSupplierActionService {
offerer: offerer_account,
order_hash: order.order_hash,
});
txHash = this.normalizeTxHash(tx);
txHash = normalizeChainTxHash(
tx,
'Действие поставщика: цепь не вернула tx_hash. Повторите попытку.'
);
} catch (error: any) {
this.logger.error(
`MarketplaceOrderSupplierActionService: chain.declineOrder fail для Order ${order.id}: ${error.message}`,
error.stack
);
this.rethrowChainError(error);
rethrowChainError(error);
}
try {
@@ -265,41 +272,6 @@ export class MarketplaceOrderSupplierActionService {
}
return order;
}
private normalizeTxHash(tx: unknown): string {
// wharfkit TransactResult держит Antelope push_transaction response под
// `response` (response.processed.id / response.transaction_id). Для
// совместимости со spec-тестами поддерживаем и плоский processed.id.
const t = tx as {
transaction?: { id?: string };
processed?: { id?: string };
response?: {
transaction_id?: string;
processed?: { id?: string };
};
};
const hash =
t?.response?.processed?.id ??
t?.response?.transaction_id ??
t?.processed?.id ??
t?.transaction?.id;
if (!hash) {
// fail-fast: цепь приняла action, но не вернула tx_hash —
// лучше отбить поставщику и попросить retry, чем записать
// 'unknown' в audit-trail.
throw new BadRequestException(
'Действие поставщика: цепь не вернула tx_hash. Повторите попытку.'
);
}
return hash;
}
private rethrowChainError(error: any): never {
const raw: string = error?.message ?? String(error);
const match = raw.match(/assertion failure with message: (.+?)(?:\n|$)/);
const clean = match ? match[1].trim() : raw;
throw new BadRequestException(clean);
}
}
export const MARKETPLACE_ORDER_SUPPLIER_ACTION_SERVICE = Symbol(
@@ -0,0 +1,43 @@
import { BadRequestException } from '@nestjs/common';
/**
* Извлекает tx_hash из ответа цепи (wharfkit TransactResult).
*
* wharfkit держит Antelope push_transaction response под `response`
* (`response.processed.id` / `response.transaction_id`). Для совместимости
* со spec-тестами поддерживаем и плоский `processed.id` / `transaction.id`.
*
* fail-fast: цепь приняла action, но не вернула tx_hash — audit-trail станет
* фантомным ('unknown') без возможности cross-reference. Лучше отбить операцию
* пайщику с просьбой о retry (`txHashMissingMessage`), чем записать «unknown».
*/
export function normalizeChainTxHash(tx: unknown, txHashMissingMessage: string): string {
const t = tx as {
transaction?: { id?: string };
processed?: { id?: string };
response?: {
transaction_id?: string;
processed?: { id?: string };
};
};
const hash =
t?.response?.processed?.id ??
t?.response?.transaction_id ??
t?.processed?.id ??
t?.transaction?.id;
if (!hash) {
throw new BadRequestException(txHashMissingMessage);
}
return hash;
}
/**
* Чистит `assertion failure with message: ...` из `eosio::check` и пробрасывает
* пайщику как BadRequest с человекочитаемым текстом (без EOSIO-обёртки).
*/
export function rethrowChainError(error: unknown): never {
const raw: string = (error as { message?: string })?.message ?? String(error);
const match = raw.match(/assertion failure with message: (.+?)(?:\n|$)/);
const clean = match ? match[1].trim() : raw;
throw new BadRequestException(clean);
}
@@ -30,11 +30,11 @@ const makeResolver = (reception: any, isMember: boolean) => {
findById: jest.fn().mockResolvedValue(reception),
listByBraname: jest.fn().mockResolvedValue([]),
} as any;
const kuChairmenService = {
const kuChairmanService = {
isMemberOfBranch: jest.fn().mockResolvedValue(isMember),
} as any;
const resolver = new MarketplaceAplReceptionResolver(service, receptionRepo, kuChairmenService);
return { resolver, service, receptionRepo, kuChairmenService };
const resolver = new MarketplaceAplReceptionResolver(service, receptionRepo, kuChairmanService);
return { resolver, service, receptionRepo, kuChairmanService };
};
const asMember = (roles: string[]) =>
@@ -42,11 +42,11 @@ const asMember = (roles: string[]) =>
describe('marketplaceListAplReceptionsByBraname ownership-scoping', () => {
it('operator-член запрашиваемого КУ → лента отдаётся', async () => {
const { resolver, receptionRepo, kuChairmenService } = makeResolver(receptionOf({}), true);
const { resolver, receptionRepo, kuChairmanService } = makeResolver(receptionOf({}), true);
await resolver.marketplaceListAplReceptionsByBraname(asMember(['operator']), {
braname: 'krg',
} as any);
expect(kuChairmenService.isMemberOfBranch).toHaveBeenCalledWith('voskhod', 'krg', 'op');
expect(kuChairmanService.isMemberOfBranch).toHaveBeenCalledWith('voskhod', 'krg', 'op');
expect(receptionRepo.listByBraname).toHaveBeenCalledWith('voskhod', 'krg');
});
@@ -83,11 +83,11 @@ describe('marketplaceAplReceptionSupplierSignablePayloads ownership-scoping', ()
describe('marketplaceAplReceptionChairmanSignablePayloads ownership-scoping', () => {
it('operator-член КУ приёмки → превью отдаётся', async () => {
const { resolver, service, kuChairmenService } = makeResolver(receptionOf({}), true);
const { resolver, service, kuChairmanService } = makeResolver(receptionOf({}), true);
await resolver.marketplaceAplReceptionChairmanSignablePayloads(asMember(['operator']), {
apl_reception_id: 'r1',
} as any);
expect(kuChairmenService.isMemberOfBranch).toHaveBeenCalledWith('voskhod', 'krg', 'op');
expect(kuChairmanService.isMemberOfBranch).toHaveBeenCalledWith('voskhod', 'krg', 'op');
expect(service.getChairmanSignablePayloads).toHaveBeenCalledWith('voskhod', 'r1', 'op');
});
@@ -21,15 +21,15 @@ import { MarketplaceInventoryResolver } from '~/extensions/marketplace/applicati
const makeResolver = (ownBranames: string[]) => {
const inventoryRepo = { list: jest.fn().mockResolvedValue([]) } as any;
const labelService = {} as any;
const kuChairmenService = {
const kuChairmanService = {
listBranamesForMember: jest.fn().mockResolvedValue(ownBranames),
} as any;
const resolver = new MarketplaceInventoryResolver(
labelService,
inventoryRepo,
kuChairmenService
kuChairmanService
);
return { resolver, inventoryRepo, kuChairmenService };
return { resolver, inventoryRepo, kuChairmanService };
};
const asMember = (roles: string[]) =>
@@ -37,20 +37,20 @@ const asMember = (roles: string[]) =>
describe('marketplaceListInventory ownership-scoping', () => {
it('admin (read:all) без braname → НЕ скоупит, braname=undefined, КУ-сервис не дёргается', async () => {
const { resolver, inventoryRepo, kuChairmenService } = makeResolver(['krg']);
const { resolver, inventoryRepo, kuChairmanService } = makeResolver(['krg']);
await resolver.marketplaceListInventory(asMember(['admin']), undefined);
expect(kuChairmenService.listBranamesForMember).not.toHaveBeenCalled();
expect(kuChairmanService.listBranamesForMember).not.toHaveBeenCalled();
expect(inventoryRepo.list).toHaveBeenCalledWith(
expect.objectContaining({ coopname: 'voskhod', braname: undefined })
);
});
it('board_readonly (read:all) с braname → braname как пришёл, без скоупа', async () => {
const { resolver, inventoryRepo, kuChairmenService } = makeResolver(['krg']);
const { resolver, inventoryRepo, kuChairmanService } = makeResolver(['krg']);
await resolver.marketplaceListInventory(asMember(['board_readonly']), {
braname: 'krg',
} as any);
expect(kuChairmenService.listBranamesForMember).not.toHaveBeenCalled();
expect(kuChairmanService.listBranamesForMember).not.toHaveBeenCalled();
expect(inventoryRepo.list).toHaveBeenCalledWith(
expect.objectContaining({ braname: 'krg' })
);
@@ -19,11 +19,11 @@ import { MarketplaceIssuanceResolver } from '~/extensions/marketplace/applicatio
const makeResolver = (isMember: boolean) => {
const service = {} as any;
const orderRepo = { listForIssuanceByBraname: jest.fn().mockResolvedValue([]) } as any;
const kuChairmenService = {
const kuChairmanService = {
isMemberOfBranch: jest.fn().mockResolvedValue(isMember),
} as any;
const resolver = new MarketplaceIssuanceResolver(service, orderRepo, kuChairmenService);
return { resolver, orderRepo, kuChairmenService };
const resolver = new MarketplaceIssuanceResolver(service, orderRepo, kuChairmanService);
return { resolver, orderRepo, kuChairmanService };
};
const asMember = (roles: string[]) =>
@@ -31,11 +31,11 @@ const asMember = (roles: string[]) =>
describe('marketplaceListIssuancesByBraname ownership-scoping', () => {
it('operator-член запрашиваемого КУ → лента отдаётся', async () => {
const { resolver, orderRepo, kuChairmenService } = makeResolver(true);
const { resolver, orderRepo, kuChairmanService } = makeResolver(true);
await resolver.marketplaceListIssuancesByBraname(asMember(['operator']), {
delivery_braname: 'krg',
} as any);
expect(kuChairmenService.isMemberOfBranch).toHaveBeenCalledWith('voskhod', 'krg', 'op');
expect(kuChairmanService.isMemberOfBranch).toHaveBeenCalledWith('voskhod', 'krg', 'op');
expect(orderRepo.listForIssuanceByBraname).toHaveBeenCalledWith('voskhod', 'krg');
});
@@ -68,18 +68,18 @@ const makePayloadResolver = (order: any, isMember: boolean) => {
}),
} as any;
const orderRepo = { findById: jest.fn().mockResolvedValue(order) } as any;
const kuChairmenService = { isMemberOfBranch: jest.fn().mockResolvedValue(isMember) } as any;
const resolver = new MarketplaceIssuanceResolver(service, orderRepo, kuChairmenService);
return { resolver, service, orderRepo, kuChairmenService };
const kuChairmanService = { isMemberOfBranch: jest.fn().mockResolvedValue(isMember) } as any;
const resolver = new MarketplaceIssuanceResolver(service, orderRepo, kuChairmanService);
return { resolver, service, orderRepo, kuChairmanService };
};
describe('marketplaceIssueActChairmanSignablePayload ownership-scoping', () => {
it('operator-член КУ заказа → превью отдаётся', async () => {
const { resolver, service, kuChairmenService } = makePayloadResolver(orderOf({}), true);
const { resolver, service, kuChairmanService } = makePayloadResolver(orderOf({}), true);
await resolver.marketplaceIssueActChairmanSignablePayload(asMember(['operator']), {
order_id: 'o1',
} as any);
expect(kuChairmenService.isMemberOfBranch).toHaveBeenCalledWith('voskhod', 'krg', 'op');
expect(kuChairmanService.isMemberOfBranch).toHaveBeenCalledWith('voskhod', 'krg', 'op');
expect(service.getOpenIssuanceSignablePayload).toHaveBeenCalledWith('voskhod', 'o1', 'op');
});
@@ -16,7 +16,7 @@
import { ForbiddenException, UnauthorizedException } from '@nestjs/common';
import { MarketplaceMembershipGuard } from '~/extensions/marketplace/application/guards/marketplace-membership.guard';
import type { MarketplaceKuChairmenService } from '~/extensions/marketplace/application/services/marketplace-ku-chairmen.service';
import type { MarketplaceKuChairmanService } from '~/extensions/marketplace/application/services/marketplace-ku-chairman.service';
import type { MarketplaceWhitelistService } from '~/extensions/marketplace/application/services/marketplace-whitelist.service';
jest.mock('~/config/config', () => ({
@@ -48,15 +48,15 @@ function makeWhitelistService(isOffererResult: boolean): MarketplaceWhitelistSer
} as unknown as MarketplaceWhitelistService;
}
function makeKuChairmenService(isKuChairmanResult: boolean): MarketplaceKuChairmenService {
function makeKuChairmanService(isKuChairmanResult: boolean): MarketplaceKuChairmanService {
return {
isKuChairman: jest.fn().mockResolvedValue(isKuChairmanResult),
} as unknown as MarketplaceKuChairmenService;
} as unknown as MarketplaceKuChairmanService;
}
describe('MarketplaceMembershipGuard', () => {
it('user.role=user, status=active, whitelist пуст → ctx [User] + marketplace_roles [orderer, offerer]', async () => {
const guard = new MarketplaceMembershipGuard(makeWhitelistService(true), makeKuChairmenService(false));
const guard = new MarketplaceMembershipGuard(makeWhitelistService(true), makeKuChairmanService(false));
const req = { user: { username: 'alice', role: 'user', status: 'active' }, headers: {} };
const ctx = makeCtx(req);
@@ -70,7 +70,7 @@ describe('MarketplaceMembershipGuard', () => {
});
it('user.role=user, whitelist непустой и пайщика там нет → marketplace_roles [orderer] (без offerer)', async () => {
const guard = new MarketplaceMembershipGuard(makeWhitelistService(false), makeKuChairmenService(false));
const guard = new MarketplaceMembershipGuard(makeWhitelistService(false), makeKuChairmanService(false));
const req = { user: { username: 'alice', role: 'user', status: 'active' }, headers: {} };
const ctx = makeCtx(req);
@@ -79,7 +79,7 @@ describe('MarketplaceMembershipGuard', () => {
});
it('user.role=member, status=active → core_roles [User, Member] + marketplace_roles [orderer, offerer, board_readonly]', async () => {
const guard = new MarketplaceMembershipGuard(makeWhitelistService(true), makeKuChairmenService(false));
const guard = new MarketplaceMembershipGuard(makeWhitelistService(true), makeKuChairmanService(false));
const req = { user: { username: 'bob', role: 'member', status: 'active' }, headers: {} };
const ctx = makeCtx(req);
await expect(guard.canActivate(ctx as any)).resolves.toBe(true);
@@ -92,7 +92,7 @@ describe('MarketplaceMembershipGuard', () => {
});
it('user.role=chairman, status=active → marketplace_roles полный набор', async () => {
const guard = new MarketplaceMembershipGuard(makeWhitelistService(true), makeKuChairmenService(false));
const guard = new MarketplaceMembershipGuard(makeWhitelistService(true), makeKuChairmanService(false));
const req = { user: { username: 'chair', role: 'chairman', status: 'active' }, headers: {} };
const ctx = makeCtx(req);
await expect(guard.canActivate(ctx as any)).resolves.toBe(true);
@@ -107,7 +107,7 @@ describe('MarketplaceMembershipGuard', () => {
});
it('status != active → 403 Forbidden «Доступ только для пайщиков кооператива»', async () => {
const guard = new MarketplaceMembershipGuard(makeWhitelistService(false), makeKuChairmenService(false));
const guard = new MarketplaceMembershipGuard(makeWhitelistService(false), makeKuChairmanService(false));
const req = { user: { username: 'alice', role: 'user', status: '4_Registered' }, headers: {} };
const ctx = makeCtx(req);
await expect(guard.canActivate(ctx as any)).rejects.toThrow(ForbiddenException);
@@ -117,7 +117,7 @@ describe('MarketplaceMembershipGuard', () => {
});
it('нет user (нет JWT) → 401 Unauthorized', async () => {
const guard = new MarketplaceMembershipGuard(makeWhitelistService(false), makeKuChairmenService(false));
const guard = new MarketplaceMembershipGuard(makeWhitelistService(false), makeKuChairmanService(false));
const req = { headers: {} };
const ctx = makeCtx(req);
await expect(guard.canActivate(ctx as any)).rejects.toThrow(UnauthorizedException);
@@ -125,7 +125,7 @@ describe('MarketplaceMembershipGuard', () => {
it('server-secret bypass → true, currentMember не выставляется, whitelist/branches не дёргаются', async () => {
const ws = makeWhitelistService(true);
const ku = makeKuChairmenService(false);
const ku = makeKuChairmanService(false);
const guard = new MarketplaceMembershipGuard(ws, ku);
const req = { headers: { 'server-secret': 'test-secret' } };
const ctx = makeCtx(req);
@@ -136,7 +136,7 @@ describe('MarketplaceMembershipGuard', () => {
});
it('isKuChairman=true → marketplace_roles содержит operator', async () => {
const guard = new MarketplaceMembershipGuard(makeWhitelistService(false), makeKuChairmenService(true));
const guard = new MarketplaceMembershipGuard(makeWhitelistService(false), makeKuChairmanService(true));
const req = { user: { username: 'chairkrg', role: 'user', status: 'active' }, headers: {} };
const ctx = makeCtx(req);
await expect(guard.canActivate(ctx as any)).resolves.toBe(true);
+8 -2
View File
@@ -754,6 +754,12 @@ export const AllTypesProps: Record<string,any> = {
MarketplaceListOrdersInput:{
statuses:"MarketplaceOrderStatus"
},
MarketplaceListOutgoingPaymentsAsSupplierFilterInput:{
statuses:"MarketplaceOutgoingPaymentRequestStatus"
},
MarketplaceListOutgoingPaymentsFilterInput:{
statuses:"MarketplaceOutgoingPaymentRequestStatus"
},
MarketplaceListPendingOffersInput:{
},
@@ -1980,10 +1986,10 @@ export const AllTypesProps: Record<string,any> = {
options:"PaginationInput"
},
marketplaceListOutgoingPayments:{
statuses:"MarketplaceOutgoingPaymentRequestStatus"
filter:"MarketplaceListOutgoingPaymentsFilterInput"
},
marketplaceListOutgoingPaymentsAsSupplier:{
statuses:"MarketplaceOutgoingPaymentRequestStatus"
filter:"MarketplaceListOutgoingPaymentsAsSupplierFilterInput"
},
marketplaceListPendingOffers:{
input:"MarketplaceListPendingOffersInput"
+46 -4
View File
@@ -6866,6 +6866,16 @@ export type ValueTypes = {
statuses?: Array<ValueTypes["MarketplaceOrderStatus"]> | undefined | null | Variable<any, string>,
/** Фильтр по аккаунту поставщика. */
supplier_account?: string | undefined | null | Variable<any, string>
};
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>
};
["MarketplaceListOutgoingPaymentsFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>,
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
supplier_account?: string | undefined | null | Variable<any, string>
};
["MarketplaceListPendingOffersInput"]: {
/** Количество элементов на странице */
@@ -9594,8 +9604,8 @@ marketplaceListMyOrders?: [{ input?: ValueTypes["MarketplaceListOrdersInput"] |
marketplaceListMyReadyToReceive?:ValueTypes["MarketplaceOrder"],
/** Все заявления текущего пайщика на гарантийный возврат — активные и архивные. */
marketplaceListMyReturnClaims?:ValueTypes["MarketplaceReturnClaim"],
marketplaceListOutgoingPayments?: [{ statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>, supplier_account?: string | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPaymentsAsSupplier?: [{ statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPayments?: [{ filter?: ValueTypes["MarketplaceListOutgoingPaymentsFilterInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPaymentsAsSupplier?: [{ filter?: ValueTypes["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListPendingOffers?: [{ input?: ValueTypes["MarketplaceListPendingOffersInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOfferPaginationResult"]],
marketplaceListReturnClaimsByBraname?: [{ data: ValueTypes["MarketplaceListReturnClaimsByBranameInput"] | Variable<any, string>},ValueTypes["MarketplaceReturnClaim"]],
marketplaceListShipments?: [{ data?: ValueTypes["MarketplaceListShipmentsInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceShipment"]],
@@ -16818,6 +16828,16 @@ export type ResolverInputTypes = {
statuses?: Array<ResolverInputTypes["MarketplaceOrderStatus"]> | undefined | null,
/** Фильтр по аккаунту поставщика. */
supplier_account?: string | undefined | null
};
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null
};
["MarketplaceListOutgoingPaymentsFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null,
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
supplier_account?: string | undefined | null
};
["MarketplaceListPendingOffersInput"]: {
/** Количество элементов на странице */
@@ -19449,8 +19469,8 @@ marketplaceListMyOrders?: [{ input?: ResolverInputTypes["MarketplaceListOrdersIn
marketplaceListMyReadyToReceive?:ResolverInputTypes["MarketplaceOrder"],
/** Все заявления текущего пайщика на гарантийный возврат — активные и архивные. */
marketplaceListMyReturnClaims?:ResolverInputTypes["MarketplaceReturnClaim"],
marketplaceListOutgoingPayments?: [{ statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null, supplier_account?: string | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPaymentsAsSupplier?: [{ statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPayments?: [{ filter?: ResolverInputTypes["MarketplaceListOutgoingPaymentsFilterInput"] | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPaymentsAsSupplier?: [{ filter?: ResolverInputTypes["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"] | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListPendingOffers?: [{ input?: ResolverInputTypes["MarketplaceListPendingOffersInput"] | undefined | null},ResolverInputTypes["MarketplaceOfferPaginationResult"]],
marketplaceListReturnClaimsByBraname?: [{ data: ResolverInputTypes["MarketplaceListReturnClaimsByBranameInput"]},ResolverInputTypes["MarketplaceReturnClaim"]],
marketplaceListShipments?: [{ data?: ResolverInputTypes["MarketplaceListShipmentsInput"] | undefined | null},ResolverInputTypes["MarketplaceShipment"]],
@@ -26449,6 +26469,16 @@ export type ModelTypes = {
statuses?: Array<ModelTypes["MarketplaceOrderStatus"]> | undefined | null,
/** Фильтр по аккаунту поставщика. */
supplier_account?: string | undefined | null
};
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ModelTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null
};
["MarketplaceListOutgoingPaymentsFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ModelTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null,
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
supplier_account?: string | undefined | null
};
["MarketplaceListPendingOffersInput"]: {
/** Количество элементов на странице */
@@ -36996,6 +37026,16 @@ export type GraphQLTypes = {
statuses?: Array<GraphQLTypes["MarketplaceOrderStatus"]> | undefined | null,
/** Фильтр по аккаунту поставщика. */
supplier_account?: string | undefined | null
};
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<GraphQLTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null
};
["MarketplaceListOutgoingPaymentsFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<GraphQLTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null,
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
supplier_account?: string | undefined | null
};
["MarketplaceListPendingOffersInput"]: {
/** Количество элементов на странице */
@@ -42933,6 +42973,8 @@ type ZEUS_VARIABLES = {
["MarketplaceListIssuancesByBranameInput"]: ValueTypes["MarketplaceListIssuancesByBranameInput"];
["MarketplaceListMyOffersInput"]: ValueTypes["MarketplaceListMyOffersInput"];
["MarketplaceListOrdersInput"]: ValueTypes["MarketplaceListOrdersInput"];
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: ValueTypes["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"];
["MarketplaceListOutgoingPaymentsFilterInput"]: ValueTypes["MarketplaceListOutgoingPaymentsFilterInput"];
["MarketplaceListPendingOffersInput"]: ValueTypes["MarketplaceListPendingOffersInput"];
["MarketplaceListReturnClaimsByBranameInput"]: ValueTypes["MarketplaceListReturnClaimsByBranameInput"];
["MarketplaceListShipmentsByBranameInput"]: ValueTypes["MarketplaceListShipmentsByBranameInput"];
@@ -0,0 +1,22 @@
import * as Permissions from '../../../common/permissions'
import type * as Wallet from '../../../interfaces/wallet'
import { Actors } from '../../../common'
/**
* Имя действия
* Требуется авторизация {@link Actors._chairman | председателя} авторизацию
* выплаты по заявке на возврат паевого взноса выполняет совет.
*/
export const authorizations = [{ permissions: [Permissions.active], actor: Actors._chairman }] as const
/**
* Имя действия
*/
export const actionName = 'authwthd'
/**
* @interface
* Действие авторизации советом выплаты по заявке на возврат паевого взноса
* из кошелька (переводит заявку в статус, видимый кассиру).
*/
export type IAuthWithdraw = Wallet.IAuthwthd
@@ -0,0 +1,22 @@
import * as Permissions from '../../../common/permissions'
import type * as Wallet from '../../../interfaces/wallet'
import { Actors } from '../../../common'
/**
* Имя действия
* Требуется авторизация {@link Actors._chairman | председателя} отказ в
* выплате по заявке на возврат паевого взноса выполняет совет.
*/
export const authorizations = [{ permissions: [Permissions.active], actor: Actors._chairman }] as const
/**
* Имя действия
*/
export const actionName = 'declinewthd'
/**
* @interface
* Действие отказа советом в выплате по заявке на возврат паевого взноса
* из кошелька (переводит заявку в статус «отклонено»).
*/
export type IDeclineWithdraw = Wallet.IDeclinewthd
@@ -2,6 +2,9 @@ export * as CreateDeposit from './createDeposit'
export * as CreateWithdraw from './createWithdraw'
export * as AuthWithdraw from './authWithdraw'
export * as DeclineWithdraw from './declineWithdraw'
export * as SignAgreement from './signAgreement'
export * as RevokeAgreement from './revokeAgreement'
export * as Migrate3 from './migrate3'
@@ -427,7 +427,7 @@ export default async function (): Promise<IWorkspaceConfig[]> {
// Стол ПВЗ открыт двум профилям пайщиков:
// - cooperative chairman (core-роль `chairman`) — видит все КУ;
// - председатель КУ или его доверенное лицо (marketplace-роль
// `operator`, выдаётся через `MarketplaceKuChairmenService`).
// `operator`, выдаётся через `MarketplaceKuChairmanService`).
// OR-логика подключается router-guard'ом при наличии обоих
// ограничений (см. processes/navigation-guard-setup).
meta: {
@@ -1,14 +1,13 @@
import { Queries } from '@coopenomics/sdk';
import { client } from 'src/shared/api/client';
export interface MarketplaceCurrentMemberView {
username: string;
core_roles: string[];
marketplace_roles: string[];
branches: string[];
}
export type MarketplaceCurrentMemberView =
Queries.Marketplace.WhoAmI.IOutput['marketplaceWhoAmI'];
export async function fetchWhoAmI(): Promise<MarketplaceCurrentMemberView> {
const result = await client.Query(Queries.Marketplace.WhoAmI.query, { variables: {} });
return result[Queries.Marketplace.WhoAmI.name] as unknown as MarketplaceCurrentMemberView;
const { [Queries.Marketplace.WhoAmI.name]: result } = await client.Query(
Queries.Marketplace.WhoAmI.query,
{ variables: {} },
);
return result;
}
@@ -23,7 +23,7 @@ import type { MarketplaceReturnClaimView } from '../../OperatorReturnClaims/api'
*
* Поле braname автозаполняется из `marketplaceWhoAmI.branches` список КУ,
* где пайщик доверенное лицо (on-chain branch.trustee/trusted через
* MarketplaceKuChairmenService.listBranamesForMember). Один участок
* MarketplaceKuChairmanService.listBranamesForMember). Один участок
* подставляется сразу; несколько берётся первый, поле остаётся редактируемым
* для ручного переключения.
*/
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { Notify } from 'quasar';
import { FailAlert, SuccessAlert } from 'src/shared/api';
import { useSystemStore } from 'src/entities/System/model';
import { fetchBranchOptions, submitCreateOrder } from '../api';
import type { BranchOption, MarketplaceOfferView } from '../types';
@@ -81,8 +81,7 @@ watch(
branch.value = branches.value[0]!.braname;
}
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
Notify.create({ type: 'negative', message: `Не удалось загрузить ПВЗ: ${message}` });
FailAlert(e, 'Не удалось загрузить ПВЗ');
} finally {
loadingBranches.value = false;
}
@@ -92,26 +91,6 @@ watch(
},
);
function extractErrorMessage(e: unknown): string {
if (e instanceof Error && e.message) return e.message;
if (Array.isArray(e) && e.length > 0) return extractErrorMessage(e[0]);
if (e && typeof e === 'object') {
const err = e as Record<string, unknown>;
if (typeof err.message === 'string') return err.message;
const gql = err.graphQLErrors;
if (Array.isArray(gql) && gql.length > 0) return extractErrorMessage(gql[0]);
const resp = err.response as Record<string, unknown> | undefined;
if (resp && Array.isArray(resp.errors) && resp.errors.length > 0) {
return extractErrorMessage(resp.errors[0]);
}
}
try {
return JSON.stringify(e);
} catch {
return String(e);
}
}
async function onSubmit(): Promise<void> {
if (!props.offer || !branch.value) return;
submitting.value = true;
@@ -121,11 +100,11 @@ async function onSubmit(): Promise<void> {
quantity: Number(quantity.value),
delivery_braname: branch.value,
});
Notify.create({ type: 'positive', message: 'Заказ создан' });
SuccessAlert('Заказ создан');
emit('created');
open.value = false;
} catch (e) {
Notify.create({ type: 'negative', message: extractErrorMessage(e) });
FailAlert(e);
} finally {
submitting.value = false;
}
@@ -14,39 +14,43 @@ export interface ListMyOrdersVariables {
export async function fetchMyOrders(variables: ListMyOrdersVariables = {}): Promise<MarketplaceOrderPage> {
const { page, limit, sortBy, sortOrder, statuses, supplier_account, offer_id } = variables;
const result = await client.Query(Queries.Marketplace.ListMyOrders.query, {
variables: {
input: {
supplier_account,
offer_id,
statuses,
},
options: {
page: page ?? 1,
limit: limit ?? 50,
sortBy: sortBy ?? 'updated_at',
sortOrder: sortOrder ?? 'DESC',
const { [Queries.Marketplace.ListMyOrders.name]: result } = await client.Query(
Queries.Marketplace.ListMyOrders.query,
{
variables: {
input: {
supplier_account,
offer_id,
statuses,
},
options: {
page: page ?? 1,
limit: limit ?? 50,
sortBy: sortBy ?? 'updated_at',
sortOrder: sortOrder ?? 'DESC',
},
},
},
});
return result[Queries.Marketplace.ListMyOrders.name] as unknown as MarketplaceOrderPage;
);
// Zeus отдаёт DateTime как unknown; сужаем скалярную дату до строки во view-типе.
return result as MarketplaceOrderPage;
}
export async function fetchOrder(order_id: string): Promise<MarketplaceOrderView> {
const result = await client.Query(Queries.Marketplace.GetOrder.query, {
variables: { input: { order_id } },
});
return result[Queries.Marketplace.GetOrder.name] as unknown as MarketplaceOrderView;
const { [Queries.Marketplace.GetOrder.name]: result } = await client.Query(
Queries.Marketplace.GetOrder.query,
{ variables: { input: { order_id } } },
);
return result as MarketplaceOrderView;
}
export interface CancelOrderResult {
order: MarketplaceOrderView;
tx_hash: string;
}
export type CancelOrderResult =
Mutations.Marketplace.CancelOrder.IOutput['marketplaceCancelOrder'];
export async function cancelOrder(order_id: string): Promise<CancelOrderResult> {
const result = await client.Mutation(Mutations.Marketplace.CancelOrder.mutation, {
variables: { input: { order_id } },
});
return result[Mutations.Marketplace.CancelOrder.name] as unknown as CancelOrderResult;
const { [Mutations.Marketplace.CancelOrder.name]: result } = await client.Mutation(
Mutations.Marketplace.CancelOrder.mutation,
{ variables: { input: { order_id } } },
);
return result;
}
@@ -1,58 +1,33 @@
/**
* Story 4.6: типы orderer-стола «Мои заказы».
* Источник истины backend MarketplaceOrderDTO / Story 4.1/4.4 mutations.
* Ручная типизация техдолг до регенерации Zeus в `@coopenomics/sdk`.
* Источник истины Zeus-вывод операции marketplaceListMyOrders из @coopenomics/sdk.
*/
export type MarketplaceOrderStatusView =
| 'ACTIVE'
| 'ACCEPTED_PENDING_SUPPLIER'
| 'ACCEPTED_PENDING_SUPPLIER_INDIVIDUAL'
| 'ACCEPTED'
| 'SUPPLY_PREPARED'
| 'ACCEPTED_TO_COOP'
| 'READY_TO_RECEIVE'
| 'RECEIVED'
| 'RETURNED'
| 'CANCELLED_BY_ORDERER'
| 'CANCELLED_BY_SUPPLIER'
| 'EXPIRED_NO_THRESHOLD'
| 'EXPIRED_NO_VOLUME';
import { Queries } from '@coopenomics/sdk';
export type MarketplaceOrderCycleTypeView =
| 'time_based'
| 'volume_based'
| 'open_subscription'
| 'individual';
type _RawOrderPage =
Queries.Marketplace.ListMyOrders.IOutput['marketplaceListMyOrders'];
export interface MarketplaceOrderView {
id: string;
coopname: string;
order_hash: string;
orderer_account: string;
offer_id: string;
offer_hash: string;
supplier_account: string;
delivery_braname: string;
quantity: number;
price_per_unit: string;
total_cost: string;
cycle_type: MarketplaceOrderCycleTypeView;
cycle_id: string | null;
warranty_period_secs: number;
warranty_until: string | null;
status: MarketplaceOrderStatusView;
last_status_reason: string | null;
blocked_at: string | null;
accepted_at: string | null;
received_at: string | null;
cancelled_at: string | null;
type _RawOrder = _RawOrderPage['items'][number];
/**
* Zeus маппит DateTime в `unknown`. Структуру/enum'ы оставляем из Zeus, но
* скалярную дату создания переопределяем на строку для форматирования в UI.
*/
export type MarketplaceOrderView = Omit<_RawOrder, 'created_at'> & {
created_at: string;
updated_at: string;
}
};
export interface MarketplaceOrderPage {
/** Страница заказов: обёртка из Zeus, но items со строгой датой (см. MarketplaceOrderView). */
export type MarketplaceOrderPage = Omit<_RawOrderPage, 'items'> & {
items: MarketplaceOrderView[];
totalCount: number;
totalPages: number;
currentPage: number;
}
};
/**
* Источник статусов/типов цикла Zeus-enum'ы из @coopenomics/sdk. Берём
* строковое значение поля Zeus-вывода (`${...}`), чтобы UI-карты статусов и
* фильтры одинаково принимали и строковые литералы, и enum-значения из
* самих заказов. Никаких параллельных строковых union'ов.
*/
export type MarketplaceOrderStatusView = `${MarketplaceOrderView['status']}`;
export type MarketplaceOrderCycleTypeView = `${MarketplaceOrderView['cycle_type']}`;
@@ -28,22 +28,26 @@ export async function fetchSupplierOrders(
variables: ListSupplierOrdersVariables = {},
): Promise<MarketplaceOrderPage> {
const { page, limit, sortBy, sortOrder, statuses, orderer_account, offer_id } = variables;
const result = await client.Query(Queries.Marketplace.ListSupplierOrders.query, {
variables: {
input: {
orderer_account,
offer_id,
statuses,
},
options: {
page: page ?? 1,
limit: limit ?? 50,
sortBy: sortBy ?? 'updated_at',
sortOrder: sortOrder ?? 'DESC',
const { [Queries.Marketplace.ListSupplierOrders.name]: result } = await client.Query(
Queries.Marketplace.ListSupplierOrders.query,
{
variables: {
input: {
orderer_account,
offer_id,
statuses,
},
options: {
page: page ?? 1,
limit: limit ?? 50,
sortBy: sortBy ?? 'updated_at',
sortOrder: sortOrder ?? 'DESC',
},
},
},
});
return result[Queries.Marketplace.ListSupplierOrders.name] as unknown as MarketplaceOrderPage;
);
// Zeus отдаёт DateTime как unknown; сужаем скалярную дату до строки во view-типе.
return result as MarketplaceOrderPage;
}
export async function acceptIndividualOrder(order_id: string): Promise<void> {
@@ -23,15 +23,18 @@ export async function fetchMyOffers(
variables: ListMyOffersVariables = {},
): Promise<MarketplaceOfferPage> {
const { page, limit, sortBy, sortOrder } = variables;
const result = await client.Query(Queries.Marketplace.ListMyOffers.query, {
variables: {
input: {
page: page ?? 1,
limit: limit ?? 50,
sortBy: sortBy ?? 'updated_at',
sortOrder: sortOrder ?? 'DESC',
const { [Queries.Marketplace.ListMyOffers.name]: result } = await client.Query(
Queries.Marketplace.ListMyOffers.query,
{
variables: {
input: {
page: page ?? 1,
limit: limit ?? 50,
sortBy: sortBy ?? 'updated_at',
sortOrder: sortOrder ?? 'DESC',
},
},
},
});
return result[Queries.Marketplace.ListMyOffers.name] as unknown as MarketplaceOfferPage;
);
return result;
}
@@ -1,55 +1,21 @@
/**
* Эпик 3 / Story 3.4: типы offerer-стола «Мои предложения».
* Источник истины backend MarketplaceOfferDTO + MarketplaceOfferStatuses.
* Источник истины Zeus-вывод операции marketplaceListMyOffers из @coopenomics/sdk.
*/
export type MarketplaceOfferStatusView =
| 'PENDING_MODERATION'
| 'ACTIVE'
| 'REJECTED'
| 'WITHDRAWN';
import { Queries } from '@coopenomics/sdk';
export type MarketplaceOfferCycleTypeView =
| 'time_based'
| 'volume_based'
| 'open_subscription'
| 'individual';
export type MarketplaceOfferPage =
Queries.Marketplace.ListMyOffers.IOutput['marketplaceListMyOffers'];
export interface MarketplaceOfferView {
id: string;
coopname: string;
supplier_account: string;
vitrine_id: string | null;
product_name: string;
description: string | null;
category_id: string | null;
price_per_unit: string;
unit_of_measure: string;
quantity_available: number;
quantity_blocked: number;
quantity_consumed: number;
unlimited_flag: boolean;
cycle_type: MarketplaceOfferCycleTypeView;
cycle_days: number | null;
target_volume: number | null;
max_wait_days: number | null;
min_threshold: number | null;
warranty_days: number;
barcode_strategy: string;
pack_size: number | null;
status: MarketplaceOfferStatusView;
approved_by: string | null;
approved_at: string | null;
rejected_by: string | null;
rejected_at: string | null;
reject_reason: string | null;
created_at: string;
updated_at: string;
}
export type MarketplaceOfferView = MarketplaceOfferPage['items'][number];
export interface MarketplaceOfferPage {
items: MarketplaceOfferView[];
totalCount: number;
totalPages: number;
currentPage: number;
}
/**
* Источник статусов/типов цикла Zeus-enum'ы из @coopenomics/sdk. Берём
* строковое значение поля Zeus-вывода (`${...}`), чтобы UI-карты статусов и
* фильтры одинаково принимали и строковые литералы, и enum-значения из
* самих предложений.
*/
export type MarketplaceOfferStatusView = `${MarketplaceOfferView['status']}`;
export type MarketplaceOfferCycleTypeView = `${MarketplaceOfferView['cycle_type']}`;
@@ -70,7 +70,7 @@ const cards = computed<Array<CatalogOffer & { domainStatus: MarketplaceOfferStat
unitLabel: o.unit_of_measure,
status: STATUS_TO_CARD[o.status],
domainStatus: o.status,
rejectReason: o.reject_reason,
rejectReason: o.reject_reason ?? null,
})),
);
@@ -1,29 +1,13 @@
import { Queries } from '@coopenomics/sdk';
import { client } from 'src/shared/api/client';
export interface MarketplaceOutgoingPaymentRequestView {
id: string;
coopname: string;
apl_reception_id: string;
payee_account: string;
related_order_ids: string[];
amount: string;
symbol: string;
purpose: string;
status: string;
confirmed_at: string | null;
payment_reference: string | null;
bank_statement_ref: string | null;
blocked_reason: string | null;
payout_tx_hash: string | null;
core_payment_id: string | null;
created_at: string;
updated_at: string;
}
export type MarketplaceOutgoingPaymentRequestView =
Queries.Marketplace.ListOutgoingPaymentsAsSupplier.IOutput['marketplaceListOutgoingPaymentsAsSupplier'][number];
export async function listMyPayments(): Promise<MarketplaceOutgoingPaymentRequestView[]> {
const result = await client.Query(Queries.Marketplace.ListOutgoingPaymentsAsSupplier.query, {});
return result[
Queries.Marketplace.ListOutgoingPaymentsAsSupplier.name
] as unknown as MarketplaceOutgoingPaymentRequestView[];
const { [Queries.Marketplace.ListOutgoingPaymentsAsSupplier.name]: result } = await client.Query(
Queries.Marketplace.ListOutgoingPaymentsAsSupplier.query,
{},
);
return result;
}
@@ -1,52 +1,58 @@
import { Mutations, Queries, type Types } from '@coopenomics/sdk';
import { client } from 'src/shared/api/client';
export interface MarketplaceAplReceptionView {
type _RawAplReception =
Queries.Marketplace.ListAplReceptionsAsSupplier.IOutput['marketplaceListAplReceptionsAsSupplier'][number];
/**
* Zeus маппит GraphQL-скаляры ID/DateTime в `unknown`. Структуру и enum'ы
* (status/variant) оставляем из Zeus-вывода, но скалярные поля-идентификаторы
* и даты переопределяем на строгие строковые типы, чтобы потребители в UI
* (slice по id, форматирование дат) типизировались без unknown.
*/
export type MarketplaceAplReceptionView = Omit<
_RawAplReception,
'id' | 'shipment_id' | 'cycle_id' | 'created_at' | 'updated_at' | 'supplier_signed_at' | 'chairman_signed_at'
> & {
id: string;
coopname: string;
shipment_id: string;
cycle_id: string;
braname: string;
offerer_account: string;
variant: 'A' | 'B';
status: string;
fact_quantity_per_order: Array<{ order_id: string; fact_quantity: number }>;
ttn_number: string | null;
total_amount: string;
created_at: string;
updated_at: string;
supplier_signed_at: string | null;
chairman_signed_at: string | null;
}
};
export type SignedDocumentInput = Types.Document.ISignedDocumentInput;
export type MarketplaceAplReceptionDocumentView = Types.Document.IGeneratedDocument;
export async function listAplReceptionsAsSupplier(): Promise<MarketplaceAplReceptionView[]> {
const result = await client.Query(Queries.Marketplace.ListAplReceptionsAsSupplier.query, {});
return result[
Queries.Marketplace.ListAplReceptionsAsSupplier.name
] as unknown as MarketplaceAplReceptionView[];
const { [Queries.Marketplace.ListAplReceptionsAsSupplier.name]: result } = await client.Query(
Queries.Marketplace.ListAplReceptionsAsSupplier.query,
{},
);
// Zeus отдаёт ID/DateTime как unknown; сужаем скаляры до строк во view-типе.
return result as MarketplaceAplReceptionView[];
}
export async function fetchSupplierSignablePayloads(
apl_reception_id: string,
): Promise<MarketplaceAplReceptionDocumentView[]> {
const result = await client.Query(
Queries.Marketplace.AplReceptionSupplierSignablePayloads.query,
{ variables: { data: { apl_reception_id } } },
);
return result[
Queries.Marketplace.AplReceptionSupplierSignablePayloads.name
] as unknown as MarketplaceAplReceptionDocumentView[];
const { [Queries.Marketplace.AplReceptionSupplierSignablePayloads.name]: result } =
await client.Query(Queries.Marketplace.AplReceptionSupplierSignablePayloads.query, {
variables: { data: { apl_reception_id } },
});
return result;
}
export async function signAsSupplier(
apl_reception_id: string,
signed_documents: SignedDocumentInput[],
): Promise<{ apl_reception: MarketplaceAplReceptionView }> {
const result = await client.Mutation(Mutations.Marketplace.SignAplReceptionAsSupplier.mutation, {
variables: { data: { apl_reception_id, signed_documents } },
});
return result[Mutations.Marketplace.SignAplReceptionAsSupplier.name] as unknown as {
apl_reception: MarketplaceAplReceptionView;
};
const { [Mutations.Marketplace.SignAplReceptionAsSupplier.name]: result } =
await client.Mutation(Mutations.Marketplace.SignAplReceptionAsSupplier.mutation, {
variables: { data: { apl_reception_id, signed_documents } },
});
// Zeus отдаёт ID/DateTime как unknown; сужаем скаляры до строк во view-типе.
return result as { apl_reception: MarketplaceAplReceptionView };
}
@@ -1,54 +1,27 @@
import { Mutations, Queries } from '@coopenomics/sdk';
import { client } from 'src/shared/api/client';
export interface MarketplaceShipmentTTNDataView {
expeditor_full_name: string;
expeditor_id_doc: string;
expeditor_phone: string;
vehicle_number: string;
loading_address: string;
loading_datetime: string;
delivery_datetime_estimate: string;
}
export type MarketplaceShipmentView =
Queries.Marketplace.ListShipments.IOutput['marketplaceListShipments'][number];
export interface MarketplaceShipmentView {
id: string;
coopname: string;
cycle_id: string;
braname: string;
offerer_account: string;
delivery_variant: 'A' | 'B';
status: string;
total_amount: string;
ttn_number: string | null;
ttn_data: MarketplaceShipmentTTNDataView | null;
ttn_document_id: string | null;
}
export type MarketplaceShipmentTTNDataView = NonNullable<MarketplaceShipmentView['ttn_data']>;
export async function listShipments(cycle_id?: string): Promise<MarketplaceShipmentView[]> {
const result = await client.Query(Queries.Marketplace.ListShipments.query, {
variables: { data: { cycle_id } },
});
return result[Queries.Marketplace.ListShipments.name] as unknown as MarketplaceShipmentView[];
const { [Queries.Marketplace.ListShipments.name]: result } = await client.Query(
Queries.Marketplace.ListShipments.query,
{ variables: { data: { cycle_id } } },
);
return result;
}
export interface CreateShipmentVariables {
cycle_id: string;
groups: Array<{
braname: string;
delivery_variant: 'A' | 'B';
order_ids: string[];
ttn_data?: MarketplaceShipmentTTNDataView;
}>;
}
export type CreateShipmentVariables = Mutations.Marketplace.CreateShipment.IInput['data'];
export async function createShipment(
data: CreateShipmentVariables,
): Promise<{ shipments: MarketplaceShipmentView[] }> {
const result = await client.Mutation(Mutations.Marketplace.CreateShipment.mutation, {
variables: { data },
});
return result[Mutations.Marketplace.CreateShipment.name] as unknown as {
shipments: MarketplaceShipmentView[];
};
): Promise<Mutations.Marketplace.CreateShipment.IOutput['marketplaceCreateShipment']> {
const { [Mutations.Marketplace.CreateShipment.name]: result } = await client.Mutation(
Mutations.Marketplace.CreateShipment.mutation,
{ variables: { data } },
);
return result;
}
@@ -17,12 +17,13 @@ export async function listShipmentsByBraname(
return result;
}
export interface MarketplaceOperatorBranchesView {
username: string;
branches: string[];
}
export type MarketplaceOperatorBranchesView =
Queries.Marketplace.WhoAmI.IOutput['marketplaceWhoAmI'];
export async function fetchOperatorBranches(): Promise<MarketplaceOperatorBranchesView> {
const result = await client.Query(Queries.Marketplace.WhoAmI.query, { variables: {} });
return result[Queries.Marketplace.WhoAmI.name] as unknown as MarketplaceOperatorBranchesView;
const { [Queries.Marketplace.WhoAmI.name]: result } = await client.Query(
Queries.Marketplace.WhoAmI.query,
{ variables: {} },
);
return result;
}
@@ -10,44 +10,26 @@ export interface MarketplaceOrderForLabeling {
status: string;
}
export interface MarketplaceInventoryItemView {
id: string;
barcode_value: string;
barcode_format: 'CODE128' | 'EAN13';
order_id: string;
shipment_id: string;
braname: string;
status: 'LABELED' | 'ISSUED' | 'RETURNED' | 'WRITTEN_OFF';
product_name_snapshot: string;
quantity_per_label: number;
orderer_account_snapshot: string;
labeled_at: string;
}
// Zeus маппит скаляр ID в `unknown`; идентификатор переопределяем на строку
// (используется как :key и для slice в UI).
type _RawInventoryItem =
Queries.Marketplace.ListInventory.IOutput['marketplaceListInventory'][number];
export type MarketplaceInventoryItemView = Omit<_RawInventoryItem, 'id'> & { id: string };
export interface MarketplaceShipmentView {
id: string;
coopname: string;
cycle_id: string;
braname: string;
offerer_account: string;
delivery_variant: 'SELF_PICKUP' | 'EXPEDITOR' | string;
status: string;
total_amount: string;
ttn_number: string | null;
created_at: string;
}
type _RawShipment =
Queries.Marketplace.ListShipments.IOutput['marketplaceListShipments'][number];
export type MarketplaceShipmentView = Omit<_RawShipment, 'id'> & { id: string };
export interface MarketplaceLabelShipmentInventoryResultView {
inventory: MarketplaceInventoryItemView[];
labeled_order_ids: string[];
skipped_order_ids: string[];
}
export type MarketplaceLabelShipmentInventoryResultView =
Mutations.Marketplace.LabelShipmentInventory.IOutput['marketplaceLabelShipmentInventory'];
export async function fetchInventoryByBraname(braname: string): Promise<MarketplaceInventoryItemView[]> {
const result = await client.Query(Queries.Marketplace.ListInventory.query, {
variables: { data: { braname } },
})
return result[Queries.Marketplace.ListInventory.name] as unknown as MarketplaceInventoryItemView[]
const { [Queries.Marketplace.ListInventory.name]: result } = await client.Query(
Queries.Marketplace.ListInventory.query,
{ variables: { data: { braname } } },
)
// Zeus отдаёт ID как unknown; сужаем идентификатор до строки во view-типе.
return result as MarketplaceInventoryItemView[]
}
const SHIPMENT_STATUSES_FOR_LABELING: Zeus.MarketplaceShipmentStatus[] = [
@@ -60,26 +42,30 @@ export async function fetchShipmentsForLabeling(braname?: string): Promise<Marke
statuses: SHIPMENT_STATUSES_FOR_LABELING,
}
if (braname) data.braname = braname
const result = await client.Query(Queries.Marketplace.ListShipments.query, {
variables: { data },
})
return result[Queries.Marketplace.ListShipments.name] as unknown as MarketplaceShipmentView[]
const { [Queries.Marketplace.ListShipments.name]: result } = await client.Query(
Queries.Marketplace.ListShipments.query,
{ variables: { data } },
)
// Zeus отдаёт ID как unknown; сужаем идентификатор до строки во view-типе.
return result as MarketplaceShipmentView[]
}
export async function labelInventory(data: Mutations.Marketplace.LabelInventory.IInput['data']): Promise<{ inventory: MarketplaceInventoryItemView[] }> {
const result = await client.Mutation(Mutations.Marketplace.LabelInventory.mutation, {
variables: { data },
})
return result[Mutations.Marketplace.LabelInventory.name] as unknown as {
inventory: MarketplaceInventoryItemView[];
}
export async function labelInventory(
data: Mutations.Marketplace.LabelInventory.IInput['data'],
): Promise<Mutations.Marketplace.LabelInventory.IOutput['marketplaceLabelInventory']> {
const { [Mutations.Marketplace.LabelInventory.name]: result } = await client.Mutation(
Mutations.Marketplace.LabelInventory.mutation,
{ variables: { data } },
)
return result
}
export async function labelShipmentInventory(
data: Mutations.Marketplace.LabelShipmentInventory.IInput['data'],
): Promise<MarketplaceLabelShipmentInventoryResultView> {
const result = await client.Mutation(Mutations.Marketplace.LabelShipmentInventory.mutation, {
variables: { data },
})
return result[Mutations.Marketplace.LabelShipmentInventory.name] as unknown as MarketplaceLabelShipmentInventoryResultView
const { [Mutations.Marketplace.LabelShipmentInventory.name]: result } = await client.Mutation(
Mutations.Marketplace.LabelShipmentInventory.mutation,
{ variables: { data } },
)
return result
}
@@ -13,9 +13,13 @@ export type MarketplaceOrderIssuanceFactView =
* SDK через select из orderSelector (полный набор колонок Order вместе
* с полями выдачи Эпика 6).
*/
export type MarketplaceOrderIssuanceView =
type _RawOrderIssuance =
Queries.Marketplace.ListMyReadyToReceive.IOutput['marketplaceListMyReadyToReceive'][number];
export type MarketplaceOrderIssuanceView = Omit<_RawOrderIssuance, 'created_at'> & {
created_at: string;
};
export type MarketplaceIssuanceResultView =
Mutations.Marketplace.OpenIssuance.IOutput['marketplaceOpenIssuance'];
@@ -27,11 +31,18 @@ export type MarketplaceGeneratedDocumentView = Types.Document.IGeneratedDocument
* уже наложенной подписью председателя (document). Заказчик накладывает
* финальную подпись поверх, не перегенерируя документ.
*/
export interface MarketplaceIssuanceAggregateView {
hash: string;
rawDocument: Types.Document.IGeneratedDocument;
document: Types.Document.ISignedDocument;
}
type _RawIssuanceAggregate =
Queries.Marketplace.IssueActOrdererSignablePayload.IOutput['marketplaceIssueActOrdererSignablePayload'];
/**
* Backend всегда возвращает исходный документ (`rawDocument`) в агрегате
* подписываемой выдачи заказчик накладывает финальную подпись поверх него.
* В Zeus поле помечено опциональным; фиксируем как обязательное и
* ненулевое для строгой типизации подписи.
*/
export type MarketplaceIssuanceAggregateView = Omit<_RawIssuanceAggregate, 'rawDocument'> & {
rawDocument: NonNullable<_RawIssuanceAggregate['rawDocument']>;
};
export async function listIssuancesByBraname(
delivery_braname: string,
@@ -40,7 +51,8 @@ export async function listIssuancesByBraname(
Queries.Marketplace.ListIssuancesByBraname.query,
{ variables: { data: { delivery_braname } } },
);
return result;
// Zeus отдаёт DateTime как unknown; сужаем скалярную дату до строки во view-типе.
return result as MarketplaceOrderIssuanceView[];
}
export async function listMyReadyToReceive(): Promise<MarketplaceOrderIssuanceView[]> {
@@ -48,7 +60,7 @@ export async function listMyReadyToReceive(): Promise<MarketplaceOrderIssuanceVi
Queries.Marketplace.ListMyReadyToReceive.query,
{},
);
return result;
return result as MarketplaceOrderIssuanceView[];
}
export async function getChairmanSignablePayload(
@@ -68,7 +80,8 @@ export async function getOrdererSignablePayload(
await client.Query(Queries.Marketplace.IssueActOrdererSignablePayload.query, {
variables: { data: { order_id } },
});
return result as unknown as MarketplaceIssuanceAggregateView;
// Backend всегда возвращает rawDocument; в Zeus оно опционально — фиксируем как обязательное.
return result as MarketplaceIssuanceAggregateView;
}
export async function openIssuance(
@@ -1,65 +1,61 @@
import { Mutations, Queries, type Types } from '@coopenomics/sdk';
import { Mutations, Queries } from '@coopenomics/sdk';
import { client } from 'src/shared/api/client';
import type { MarketplaceAplReceptionView, SignedDocumentInput } from '../../OffererPendingAplReceptions/api';
export type { MarketplaceAplReceptionView } from '../../OffererPendingAplReceptions/api';
export type { MarketplaceAplReceptionView, SignedDocumentInput } from '../../OffererPendingAplReceptions/api';
/**
* Агрегат документа: исходный документ (rawDocument) + документ с уже
* наложенной подписью поставщика (document). Председатель накладывает
* закрывающую подпись поверх, не перегенерируя документ.
*/
export interface MarketplaceDocumentAggregateView {
hash: string;
rawDocument: Types.Document.IGeneratedDocument;
document: Types.Document.ISignedDocument;
}
type _RawDocumentAggregate =
Queries.Marketplace.AplReceptionChairmanSignablePayloads.IOutput['marketplaceAplReceptionChairmanSignablePayloads'][number];
export type MarketplaceDocumentAggregateView = Omit<_RawDocumentAggregate, 'rawDocument'> & {
rawDocument: NonNullable<_RawDocumentAggregate['rawDocument']>;
};
export async function fetchChairmanSignablePayloads(
apl_reception_id: string,
): Promise<MarketplaceDocumentAggregateView[]> {
const result = await client.Query(
Queries.Marketplace.AplReceptionChairmanSignablePayloads.query,
{ variables: { data: { apl_reception_id } } },
);
return result[
Queries.Marketplace.AplReceptionChairmanSignablePayloads.name
] as unknown as MarketplaceDocumentAggregateView[];
const { [Queries.Marketplace.AplReceptionChairmanSignablePayloads.name]: result } =
await client.Query(Queries.Marketplace.AplReceptionChairmanSignablePayloads.query, {
variables: { data: { apl_reception_id } },
});
// Backend всегда возвращает rawDocument; в Zeus оно опционально — фиксируем как обязательное.
return result as MarketplaceDocumentAggregateView[];
}
export async function listAplReceptionsByBraname(braname: string): Promise<MarketplaceAplReceptionView[]> {
const result = await client.Query(Queries.Marketplace.ListAplReceptionsByBraname.query, {
variables: { data: { braname } },
});
return result[
Queries.Marketplace.ListAplReceptionsByBraname.name
] as unknown as MarketplaceAplReceptionView[];
const { [Queries.Marketplace.ListAplReceptionsByBraname.name]: result } = await client.Query(
Queries.Marketplace.ListAplReceptionsByBraname.query,
{ variables: { data: { braname } } },
);
// Zeus отдаёт ID/DateTime как unknown; сужаем скаляры до строк во view-типе.
return result as MarketplaceAplReceptionView[];
}
export interface CreateAplReceptionVariables {
shipment_id: string;
fact_quantity_per_order?: Array<{ order_id: string; fact_quantity: number }>;
}
export type CreateAplReceptionVariables =
Mutations.Marketplace.CreateAplReception.IInput['data'];
export async function createAplReception(
data: CreateAplReceptionVariables,
): Promise<{ apl_reception: MarketplaceAplReceptionView }> {
const result = await client.Mutation(Mutations.Marketplace.CreateAplReception.mutation, {
variables: { data },
});
return result[Mutations.Marketplace.CreateAplReception.name] as unknown as {
apl_reception: MarketplaceAplReceptionView;
};
): Promise<Mutations.Marketplace.CreateAplReception.IOutput['marketplaceCreateAplReception']> {
const { [Mutations.Marketplace.CreateAplReception.name]: result } = await client.Mutation(
Mutations.Marketplace.CreateAplReception.mutation,
{ variables: { data } },
);
return result;
}
export async function signAsChairman(
apl_reception_id: string,
signed_documents: SignedDocumentInput[],
): Promise<{ apl_reception: MarketplaceAplReceptionView }> {
const result = await client.Mutation(Mutations.Marketplace.SignAplReceptionAsChairman.mutation, {
variables: { data: { apl_reception_id, signed_documents } },
});
return result[Mutations.Marketplace.SignAplReceptionAsChairman.name] as unknown as {
apl_reception: MarketplaceAplReceptionView;
};
): Promise<Mutations.Marketplace.SignAplReceptionAsChairman.IOutput['marketplaceSignAplReceptionAsChairman']> {
const { [Mutations.Marketplace.SignAplReceptionAsChairman.name]: result } = await client.Mutation(
Mutations.Marketplace.SignAplReceptionAsChairman.mutation,
{ variables: { data: { apl_reception_id, signed_documents } } },
);
return result;
}
@@ -16,9 +16,14 @@ export function defectCategoryLabel(category?: string | null): string {
return DEFECT_CATEGORY_LABELS[category] ?? category;
}
export type MarketplaceReturnClaimView =
type _RawReturnClaim =
Queries.Marketplace.ListReturnClaimsByBraname.IOutput['marketplaceListReturnClaimsByBraname'][number];
/** DateTime из Zeus приходит как `unknown`; дату создания переопределяем на строку для UI. */
export type MarketplaceReturnClaimView = Omit<_RawReturnClaim, 'created_at'> & {
created_at: string;
};
export type MarketplaceReturnClaimResultView =
Mutations.Marketplace.ApproveReturnVisit.IOutput['marketplaceApproveReturnVisit'];
@@ -44,7 +49,8 @@ export async function listReturnClaimsByBraname(
Queries.Marketplace.ListReturnClaimsByBraname.query,
{ variables: { data } },
);
return result;
// Zeus отдаёт DateTime как unknown; сужаем дату создания до строки во view-типе.
return result as MarketplaceReturnClaimView[];
}
export async function approveReturnVisit(
@@ -1,5 +1,6 @@
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { Zeus } from '@coopenomics/sdk';
import { FailAlert } from 'src/shared/api';
import {
listReturnClaimsByBraname,
@@ -29,17 +30,17 @@ const onSiteDialog = ref(false);
const selectedClaim = ref<MarketplaceReturnClaimView | null>(null);
const pendingClaims = computed(() =>
items.value.filter((c) => c.status === 'PENDING_CHAIRMAN_REVIEW'),
items.value.filter((c) => c.status === Zeus.MarketplaceReturnClaimStatus.PENDING_CHAIRMAN_REVIEW),
);
const approvedClaims = computed(() =>
items.value.filter((c) => c.status === 'APPROVED_FOR_VISIT'),
items.value.filter((c) => c.status === Zeus.MarketplaceReturnClaimStatus.APPROVED_FOR_VISIT),
);
const archiveClaims = computed(() =>
items.value.filter(
(c) =>
c.status === 'ACCEPTED_AT_VISIT' ||
c.status === 'REJECTED_REMOTELY' ||
c.status === 'REJECTED_AT_VISIT',
c.status === Zeus.MarketplaceReturnClaimStatus.ACCEPTED_AT_VISIT ||
c.status === Zeus.MarketplaceReturnClaimStatus.REJECTED_REMOTELY ||
c.status === Zeus.MarketplaceReturnClaimStatus.REJECTED_AT_VISIT,
),
);
@@ -84,15 +85,15 @@ function formatDateTime(value: unknown): string {
function humanStatus(status: MarketplaceReturnClaimView['status']): string {
switch (status) {
case 'PENDING_CHAIRMAN_REVIEW':
case Zeus.MarketplaceReturnClaimStatus.PENDING_CHAIRMAN_REVIEW:
return 'Ждёт удалённого рассмотрения';
case 'APPROVED_FOR_VISIT':
case Zeus.MarketplaceReturnClaimStatus.APPROVED_FOR_VISIT:
return 'Очный визит одобрен';
case 'ACCEPTED_AT_VISIT':
case Zeus.MarketplaceReturnClaimStatus.ACCEPTED_AT_VISIT:
return 'Возврат принят';
case 'REJECTED_REMOTELY':
case Zeus.MarketplaceReturnClaimStatus.REJECTED_REMOTELY:
return 'Отказано удалённо';
case 'REJECTED_AT_VISIT':
case Zeus.MarketplaceReturnClaimStatus.REJECTED_AT_VISIT:
return 'Отказано на месте';
default:
return status;
@@ -34,17 +34,17 @@ const loading = ref(false);
let pollTimer: ReturnType<typeof setInterval> | null = null;
const CYCLE_TYPE_LABEL: Record<MarketplaceOrderCycleTypeView, string> = {
time_based: 'По времени',
volume_based: 'По объёму',
open_subscription: 'Открытая подписка',
individual: 'Индивидуальный',
TIME_BASED: 'По времени',
VOLUME_BASED: 'По объёму',
OPEN_SUBSCRIPTION: 'Открытая подписка',
INDIVIDUAL: 'Индивидуальный',
};
const CYCLE_TYPE_HINT: Record<MarketplaceOrderCycleTypeView, string> = {
time_based: 'Партия закрывается по таймеру; заказы внутри окна объединяются для одной поставки.',
volume_based: 'Партия закрывается при достижении порога объёма заказов.',
open_subscription: 'Открытый пул заказов; партии формируются по решению поставщика.',
individual: 'Каждый заказ обслуживается поставщиком отдельно.',
TIME_BASED: 'Партия закрывается по таймеру; заказы внутри окна объединяются для одной поставки.',
VOLUME_BASED: 'Партия закрывается при достижении порога объёма заказов.',
OPEN_SUBSCRIPTION: 'Открытый пул заказов; партии формируются по решению поставщика.',
INDIVIDUAL: 'Каждый заказ обслуживается поставщиком отдельно.',
};
const STATUS_LABEL: Record<MarketplaceOrderStatusView, string> = {
@@ -121,7 +121,7 @@ const groups = computed<ConsolidatedGroup[]>(() => {
if (!g) {
g = {
key,
cycle_id: o.cycle_id,
cycle_id: o.cycle_id ?? null,
cycle_type: o.cycle_type,
offer_id: o.offer_id,
supplier_account: o.supplier_account,
@@ -12,5 +12,6 @@ export async function listMyReadyToReceive(): Promise<MarketplaceOrderIssuanceVi
Queries.Marketplace.ListMyReadyToReceive.query,
{},
);
return result;
// Zeus отдаёт DateTime как unknown; сужаем скалярную дату до строки во view-типе.
return result as MarketplaceOrderIssuanceView[];
}
@@ -1,5 +1,6 @@
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { Zeus } from '@coopenomics/sdk';
import { FailAlert } from 'src/shared/api';
import { listMyReturnClaims, type MarketplaceReturnClaimView } from '../api';
import { fetchMyOrders } from '../../MyOrders/api';
@@ -35,7 +36,11 @@ const selectedClaim = ref<MarketplaceReturnClaimView | null>(null);
const orderOptions = computed(() => {
const openOrderIds = new Set(
items.value
.filter((c) => c.status === 'PENDING_CHAIRMAN_REVIEW' || c.status === 'APPROVED_FOR_VISIT')
.filter(
(c) =>
c.status === Zeus.MarketplaceReturnClaimStatus.PENDING_CHAIRMAN_REVIEW ||
c.status === Zeus.MarketplaceReturnClaimStatus.APPROVED_FOR_VISIT,
)
.map((c) => c.order_id),
);
return eligibleOrders.value
@@ -48,15 +53,17 @@ const orderOptions = computed(() => {
const activeClaims = computed(() =>
items.value.filter(
(c) => c.status === 'PENDING_CHAIRMAN_REVIEW' || c.status === 'APPROVED_FOR_VISIT',
(c) =>
c.status === Zeus.MarketplaceReturnClaimStatus.PENDING_CHAIRMAN_REVIEW ||
c.status === Zeus.MarketplaceReturnClaimStatus.APPROVED_FOR_VISIT,
),
);
const archiveClaims = computed(() =>
items.value.filter(
(c) =>
c.status === 'ACCEPTED_AT_VISIT' ||
c.status === 'REJECTED_REMOTELY' ||
c.status === 'REJECTED_AT_VISIT',
c.status === Zeus.MarketplaceReturnClaimStatus.ACCEPTED_AT_VISIT ||
c.status === Zeus.MarketplaceReturnClaimStatus.REJECTED_REMOTELY ||
c.status === Zeus.MarketplaceReturnClaimStatus.REJECTED_AT_VISIT,
),
);
@@ -104,15 +111,15 @@ function onSubmitted(): void {
function humanStatus(status: MarketplaceReturnClaimView['status']): string {
switch (status) {
case 'PENDING_CHAIRMAN_REVIEW':
case Zeus.MarketplaceReturnClaimStatus.PENDING_CHAIRMAN_REVIEW:
return 'На рассмотрении председателя';
case 'APPROVED_FOR_VISIT':
case Zeus.MarketplaceReturnClaimStatus.APPROVED_FOR_VISIT:
return 'Очный визит одобрен';
case 'ACCEPTED_AT_VISIT':
case Zeus.MarketplaceReturnClaimStatus.ACCEPTED_AT_VISIT:
return 'Возврат принят';
case 'REJECTED_REMOTELY':
case Zeus.MarketplaceReturnClaimStatus.REJECTED_REMOTELY:
return 'Отказано удалённо';
case 'REJECTED_AT_VISIT':
case Zeus.MarketplaceReturnClaimStatus.REJECTED_AT_VISIT:
return 'Отказано на месте';
default:
return status;
@@ -128,14 +135,14 @@ function formatDate(value: unknown): string {
function statusColor(status: MarketplaceReturnClaimView['status']): string {
switch (status) {
case 'PENDING_CHAIRMAN_REVIEW':
case Zeus.MarketplaceReturnClaimStatus.PENDING_CHAIRMAN_REVIEW:
return 'primary';
case 'APPROVED_FOR_VISIT':
case Zeus.MarketplaceReturnClaimStatus.APPROVED_FOR_VISIT:
return 'info';
case 'ACCEPTED_AT_VISIT':
case Zeus.MarketplaceReturnClaimStatus.ACCEPTED_AT_VISIT:
return 'positive';
case 'REJECTED_REMOTELY':
case 'REJECTED_AT_VISIT':
case Zeus.MarketplaceReturnClaimStatus.REJECTED_REMOTELY:
case Zeus.MarketplaceReturnClaimStatus.REJECTED_AT_VISIT:
return 'negative';
default:
return 'grey';
@@ -5,10 +5,7 @@ export const name = 'marketplaceListOutgoingPayments'
export const query = Selector('Query')({
[name]: [
{
supplier_account: $('supplier_account', 'String'),
statuses: $('statuses', '[MarketplaceOutgoingPaymentRequestStatus!]'),
},
{ filter: $('filter', 'MarketplaceListOutgoingPaymentsFilterInput') },
marketplaceOutgoingPaymentRequestSelector,
],
})
@@ -19,8 +16,7 @@ export interface IInput {
*/
[key: string]: unknown
supplier_account?: string
statuses?: ModelTypes['MarketplaceOutgoingPaymentRequestStatus'][]
filter?: ModelTypes['MarketplaceListOutgoingPaymentsFilterInput']
}
export type IOutput = InputType<GraphQLTypes['Query'], typeof query>
@@ -5,7 +5,7 @@ export const name = 'marketplaceListOutgoingPaymentsAsSupplier'
export const query = Selector('Query')({
[name]: [
{ statuses: $('statuses', '[MarketplaceOutgoingPaymentRequestStatus!]') },
{ filter: $('filter', 'MarketplaceListOutgoingPaymentsAsSupplierFilterInput') },
marketplaceOutgoingPaymentRequestSelector,
],
})
@@ -16,7 +16,7 @@ export interface IInput {
*/
[key: string]: unknown
statuses?: ModelTypes['MarketplaceOutgoingPaymentRequestStatus'][]
filter?: ModelTypes['MarketplaceListOutgoingPaymentsAsSupplierFilterInput']
}
export type IOutput = InputType<GraphQLTypes['Query'], typeof query>
+8 -2
View File
@@ -754,6 +754,12 @@ export const AllTypesProps: Record<string,any> = {
MarketplaceListOrdersInput:{
statuses:"MarketplaceOrderStatus"
},
MarketplaceListOutgoingPaymentsAsSupplierFilterInput:{
statuses:"MarketplaceOutgoingPaymentRequestStatus"
},
MarketplaceListOutgoingPaymentsFilterInput:{
statuses:"MarketplaceOutgoingPaymentRequestStatus"
},
MarketplaceListPendingOffersInput:{
},
@@ -1980,10 +1986,10 @@ export const AllTypesProps: Record<string,any> = {
options:"PaginationInput"
},
marketplaceListOutgoingPayments:{
statuses:"MarketplaceOutgoingPaymentRequestStatus"
filter:"MarketplaceListOutgoingPaymentsFilterInput"
},
marketplaceListOutgoingPaymentsAsSupplier:{
statuses:"MarketplaceOutgoingPaymentRequestStatus"
filter:"MarketplaceListOutgoingPaymentsAsSupplierFilterInput"
},
marketplaceListPendingOffers:{
input:"MarketplaceListPendingOffersInput"
+46 -4
View File
@@ -6866,6 +6866,16 @@ export type ValueTypes = {
statuses?: Array<ValueTypes["MarketplaceOrderStatus"]> | undefined | null | Variable<any, string>,
/** Фильтр по аккаунту поставщика. */
supplier_account?: string | undefined | null | Variable<any, string>
};
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>
};
["MarketplaceListOutgoingPaymentsFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>,
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
supplier_account?: string | undefined | null | Variable<any, string>
};
["MarketplaceListPendingOffersInput"]: {
/** Количество элементов на странице */
@@ -9594,8 +9604,8 @@ marketplaceListMyOrders?: [{ input?: ValueTypes["MarketplaceListOrdersInput"] |
marketplaceListMyReadyToReceive?:ValueTypes["MarketplaceOrder"],
/** Все заявления текущего пайщика на гарантийный возврат — активные и архивные. */
marketplaceListMyReturnClaims?:ValueTypes["MarketplaceReturnClaim"],
marketplaceListOutgoingPayments?: [{ statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>, supplier_account?: string | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPaymentsAsSupplier?: [{ statuses?: Array<ValueTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPayments?: [{ filter?: ValueTypes["MarketplaceListOutgoingPaymentsFilterInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPaymentsAsSupplier?: [{ filter?: ValueTypes["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListPendingOffers?: [{ input?: ValueTypes["MarketplaceListPendingOffersInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceOfferPaginationResult"]],
marketplaceListReturnClaimsByBraname?: [{ data: ValueTypes["MarketplaceListReturnClaimsByBranameInput"] | Variable<any, string>},ValueTypes["MarketplaceReturnClaim"]],
marketplaceListShipments?: [{ data?: ValueTypes["MarketplaceListShipmentsInput"] | undefined | null | Variable<any, string>},ValueTypes["MarketplaceShipment"]],
@@ -16818,6 +16828,16 @@ export type ResolverInputTypes = {
statuses?: Array<ResolverInputTypes["MarketplaceOrderStatus"]> | undefined | null,
/** Фильтр по аккаунту поставщика. */
supplier_account?: string | undefined | null
};
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null
};
["MarketplaceListOutgoingPaymentsFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null,
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
supplier_account?: string | undefined | null
};
["MarketplaceListPendingOffersInput"]: {
/** Количество элементов на странице */
@@ -19449,8 +19469,8 @@ marketplaceListMyOrders?: [{ input?: ResolverInputTypes["MarketplaceListOrdersIn
marketplaceListMyReadyToReceive?:ResolverInputTypes["MarketplaceOrder"],
/** Все заявления текущего пайщика на гарантийный возврат — активные и архивные. */
marketplaceListMyReturnClaims?:ResolverInputTypes["MarketplaceReturnClaim"],
marketplaceListOutgoingPayments?: [{ statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null, supplier_account?: string | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPaymentsAsSupplier?: [{ statuses?: Array<ResolverInputTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPayments?: [{ filter?: ResolverInputTypes["MarketplaceListOutgoingPaymentsFilterInput"] | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListOutgoingPaymentsAsSupplier?: [{ filter?: ResolverInputTypes["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"] | undefined | null},ResolverInputTypes["MarketplaceOutgoingPaymentRequest"]],
marketplaceListPendingOffers?: [{ input?: ResolverInputTypes["MarketplaceListPendingOffersInput"] | undefined | null},ResolverInputTypes["MarketplaceOfferPaginationResult"]],
marketplaceListReturnClaimsByBraname?: [{ data: ResolverInputTypes["MarketplaceListReturnClaimsByBranameInput"]},ResolverInputTypes["MarketplaceReturnClaim"]],
marketplaceListShipments?: [{ data?: ResolverInputTypes["MarketplaceListShipmentsInput"] | undefined | null},ResolverInputTypes["MarketplaceShipment"]],
@@ -26449,6 +26469,16 @@ export type ModelTypes = {
statuses?: Array<ModelTypes["MarketplaceOrderStatus"]> | undefined | null,
/** Фильтр по аккаунту поставщика. */
supplier_account?: string | undefined | null
};
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ModelTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null
};
["MarketplaceListOutgoingPaymentsFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<ModelTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null,
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
supplier_account?: string | undefined | null
};
["MarketplaceListPendingOffersInput"]: {
/** Количество элементов на странице */
@@ -36996,6 +37026,16 @@ export type GraphQLTypes = {
statuses?: Array<GraphQLTypes["MarketplaceOrderStatus"]> | undefined | null,
/** Фильтр по аккаунту поставщика. */
supplier_account?: string | undefined | null
};
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<GraphQLTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null
};
["MarketplaceListOutgoingPaymentsFilterInput"]: {
/** Фильтр по статусам выплат. Пусто — показывать все статусы. */
statuses?: Array<GraphQLTypes["MarketplaceOutgoingPaymentRequestStatus"]> | undefined | null,
/** Поставщик-получатель выплаты. Пусто — по всем поставщикам. */
supplier_account?: string | undefined | null
};
["MarketplaceListPendingOffersInput"]: {
/** Количество элементов на странице */
@@ -42933,6 +42973,8 @@ type ZEUS_VARIABLES = {
["MarketplaceListIssuancesByBranameInput"]: ValueTypes["MarketplaceListIssuancesByBranameInput"];
["MarketplaceListMyOffersInput"]: ValueTypes["MarketplaceListMyOffersInput"];
["MarketplaceListOrdersInput"]: ValueTypes["MarketplaceListOrdersInput"];
["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"]: ValueTypes["MarketplaceListOutgoingPaymentsAsSupplierFilterInput"];
["MarketplaceListOutgoingPaymentsFilterInput"]: ValueTypes["MarketplaceListOutgoingPaymentsFilterInput"];
["MarketplaceListPendingOffersInput"]: ValueTypes["MarketplaceListPendingOffersInput"];
["MarketplaceListReturnClaimsByBranameInput"]: ValueTypes["MarketplaceListReturnClaimsByBranameInput"];
["MarketplaceListShipmentsByBranameInput"]: ValueTypes["MarketplaceListShipmentsByBranameInput"];