docs(marketplace): переписать README как продуктовое описание для председателя
Убраны технические детали (имена резолверов/сервисов, пути к файлам, GraphQL-примеры, слои архитектуры) — описание процесса выдачи и возможностей расширения теперь на языке пользователя, а не разработчика. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,255 +1,77 @@
|
||||
# Marketplace — расширение «Стол заказов»
|
||||
|
||||
Бэкенд кооперативной закупки/распределения имущества (членская модель): каталог
|
||||
оферт, коллективные заказы, приёмка на склад КУ (ПВЗ), **выдача пайщику**,
|
||||
возвраты, списание скоропорта, экономика КУ (членский взнос/распределение).
|
||||
Ниже — быстрый контекст по самому нетривиальному потоку (выдача); внизу файла
|
||||
осталась справка по дереву категорий (донорская часть Ozon).
|
||||
«Стол заказов» — сервис коллективных закупок для участников кооператива.
|
||||
Пайщики оформляют заказы на товары и услуги у поставщиков, кооператив
|
||||
принимает поставку на своём участке (пункте выдачи), а затем выдаёт
|
||||
имущество пайщикам. Расширение отвечает за весь этот путь:
|
||||
|
||||
---
|
||||
- каталог товаров и услуг (категории, карточки, поиск);
|
||||
- коллективные заказы участников одного кооперативного участка;
|
||||
- приёмку поставки на склад пункта выдачи;
|
||||
- выдачу имущества пайщику;
|
||||
- возвраты и претензии по качеству;
|
||||
- списание просроченного товара;
|
||||
- экономику участка — членский взнос с заказов и его распределение между
|
||||
доверенными лицами участка.
|
||||
|
||||
## ⭐ Выдача имущества — ЕДИНЫЙ ПУТЬ (бандл-пропозал)
|
||||
## Как устроена выдача имущества пайщику
|
||||
|
||||
Главный инвариант: **до подписи пайщика в блокчейне по выдаче не происходит
|
||||
ничего.** И обычные заказы, и докладку со склада оператор кладёт в ОДИН
|
||||
оффчейн-бандл (PG), подписывая свою (первую) подпись акта. Связка подписей
|
||||
уходит на цепь только когда пайщик у стойки контрподписывает акт. Поэтому
|
||||
отмена пайщиком = отказ от бандла (оффчейн), без он-чейн отката, и нет двух
|
||||
веток «уже в цепи / ещё в БД».
|
||||
Это самый ответственный момент всего процесса, поэтому у него простое и
|
||||
надёжное устройство.
|
||||
|
||||
```
|
||||
ОПЕРАТОР у стойки (IssueActOpenDialog):
|
||||
по выдаваемым заказам И докладке со склада подписывает АПП-выдачи первой
|
||||
подписью (signiss1) → marketplaceCreateStockProposal (бандл в PG).
|
||||
Заказы остаются ACCEPTED_TO_COOP; в цепи — ничего.
|
||||
│ (пайщику realtime-сигнал → гейт «подпись на месте»)
|
||||
▼
|
||||
ПАЙЩИК в OnsiteSignatureGate — одна карточка-акт, две кнопки:
|
||||
├─ «Подписать и получить» → marketplaceFinalizeStockIssuance:
|
||||
│ (0) контрольная сверка: контрподписанный акт байт-в-байт = выданный
|
||||
│ оператором signiss1 (защита от подмены тела «вагон алюминия»);
|
||||
│ (1) при дефиците членских — ОДНА конвертация паевого по Заявлению;
|
||||
│ (2) докладка → createStockOrder (обычный заказ уже существует);
|
||||
│ (3) по строке: openIssuance(signiss1 оператора) → finalizeIssuance(signiss2 пайщика).
|
||||
│ Заказы → RECEIVED.
|
||||
└─ «Отменить» → marketplaceDeclineStockProposal (оффчейн).
|
||||
Заказы остаются ACCEPTED_TO_COOP — оператор
|
||||
переоткрывает выдачу и формирует акт заново.
|
||||
```
|
||||
Пока пайщик не поставил свою подпись на месте, у стойки выдачи — **ничего
|
||||
не считается свершившимся**. Оператор пункта выдачи готовит акт передачи:
|
||||
собирает в него как заказанные пайщиком позиции, так и то, что кооператив
|
||||
предлагает докупить дополнительно (например, остаток со склада участка).
|
||||
Этот акт — черновик, он нигде не фиксируется окончательно, пока пайщик его
|
||||
не увидит и не подтвердит.
|
||||
|
||||
**Экономика по типу строки бандла** (`order_id` в `MarketplaceStockProposalItem`
|
||||
— дискриминатор): докладка (нет `order_id`) фондируется целиком с членского
|
||||
(lock на stockorder, дефицит закрывает конвертация); обычный заказ (есть
|
||||
`order_id`) уже профондирован с паевого на `createorder` — конвертация ему не
|
||||
нужна, доплату при факт>заказ берёт сам `signiss2` на цепи и фейлится при
|
||||
нехватке членских.
|
||||
Когда пайщик подходит к стойке, ему показывается один документ — акт
|
||||
приёма-передачи — с двумя вариантами действия:
|
||||
|
||||
**`READY_TO_RECEIVE` теперь транзитный**: `signiss1` ставит его, `signiss2`
|
||||
сразу читает и переводит в `RECEIVED` внутри одной finalize-связки. Отдельного
|
||||
member-видимого состояния «готов к получению» НЕТ (поэтому в «Мои заказы» нет
|
||||
подписи получения и нет вкладки «Готовы к выдаче»).
|
||||
- **«Подписать и получить»** — пайщик подтверждает получение своей
|
||||
подписью, оплата и передача имущества фиксируются, заказ закрывается.
|
||||
- **«Отменить»** — пайщик отказывается (например, не устроило качество
|
||||
докладки); ничего не списывается и не фиксируется, оператор может
|
||||
заново подготовить акт.
|
||||
|
||||
### Ключевые файлы выдачи
|
||||
Благодаря этому нет «зависших» промежуточных состояний: либо выдача
|
||||
подтверждена подписью пайщика и полностью учтена, либо её как будто не
|
||||
было. Отменить получение до подписи можно без каких-либо последствий для
|
||||
пайщика или кооператива.
|
||||
|
||||
| Слой | Файл | Роль |
|
||||
|---|---|---|
|
||||
| Сервис-оркестратор | `application/services/marketplace-stock-proposal.service.ts` | бандл: createProposal / getAcceptSignablePayloads / **finalizeStockIssuance** / decline / cancel + контрольная сверка `assertCountersignMatchesStored` |
|
||||
| Сервис выдачи | `application/services/marketplace-issuance.service.ts` | `openIssuance`/`finalizeIssuance` (signiss1/signiss2 на цепь) + генерация АПП (1105); зовутся ИЗНУТРИ бандла, не из GraphQL |
|
||||
| Резолвер бандла | `application/resolvers/marketplace-stock.resolver.ts` | `marketplaceCreateStockProposal` / `marketplaceStockProposalSignablePayloads` / `marketplaceFinalizeStockIssuance` / decline / cancel |
|
||||
| Резолвер выдачи | `application/resolvers/marketplace-issuance.resolver.ts` | `marketplaceIssueActChairmanSignablePayload` (signiss1-превью оператору) + `marketplaceListIssuancesByBraname` (лента оператора) |
|
||||
| Домен-строка | `domain/entities/marketplace-stock-proposal.types.ts` | `MarketplaceStockProposalItem` (+ `order_id`/`order_hash`/`signiss1_act`) |
|
||||
| Фронт оператора | `desktop/.../OperatorIssuance/ui/IssueActOpenDialog.vue` | сборка одного бандла (order_items + докладка), подпись signiss1 |
|
||||
| Фронт пайщика | `desktop/.../OnsiteSignatureGate/` | единая карточка-акт: «Подписать и получить» / «Отменить» |
|
||||
## Возможности
|
||||
|
||||
> Контракт не меняли: используются существующие `signiss1`/`signiss2`/`stockorder`/
|
||||
> `convert`. Старого немедленного он-чейн-пути выдачи (`marketplaceOpenIssuance`/
|
||||
> `marketplaceFinalizeIssuance`/`marketplaceListMyReadyToReceive`/orderer-payload)
|
||||
> больше нет — он удалён как мёртвый после унификации.
|
||||
### Каталог
|
||||
- Дерево категорий товаров и услуг с поиском и фильтрацией.
|
||||
- Карточки товаров с характеристиками (атрибутами), обязательными и
|
||||
справочными полями.
|
||||
- Публикация предложений (оферт) поставщиками, модерация кооперативом.
|
||||
|
||||
---
|
||||
### Заказы и приёмка
|
||||
- Коллективный заказ участников участка у одного поставщика.
|
||||
- Разные схемы формирования заказа — по мере набора нужного объёма или
|
||||
по установленному сроку.
|
||||
- Приёмка поставки на склад пункта выдачи, приходование, маркировка партии.
|
||||
|
||||
## Возможности (дерево категорий — донорская часть Ozon)
|
||||
### Выдача и возвраты
|
||||
- Выдача имущества пайщику по акту с подписью на месте (см. выше).
|
||||
- Докладка из остатка склада участка прямо у стойки выдачи.
|
||||
- Гарантийный возврат и претензии по качеству.
|
||||
- Списание просроченного товара по решению совета кооператива.
|
||||
|
||||
### 🗂️ Дерево категорий
|
||||
- Получение полного дерева категорий с иерархией
|
||||
- Поиск категорий по названию
|
||||
- Фильтрация по доступности
|
||||
- Получение листовых категорий (где можно создавать товары)
|
||||
- Статистика по категориям
|
||||
### Экономика участка
|
||||
- Членский взнос, включённый в стоимость заказа.
|
||||
- Распределение взноса между доверенными лицами участка.
|
||||
- Материальная помощь доверенным лицам из общего кошелька участка.
|
||||
|
||||
### 🏷️ Типы товаров
|
||||
- Получение типов товаров для категорий
|
||||
- Фильтрация доступных типов
|
||||
- Связь типов с категориями
|
||||
## Настройки при установке
|
||||
|
||||
### ⚙️ Атрибуты товаров
|
||||
- Получение атрибутов для конкретной категории и типа
|
||||
- Группировка атрибутов
|
||||
- Обязательные и аспектные атрибуты
|
||||
- Валидация значений атрибутов
|
||||
При установке расширения председателю доступны настройки списания
|
||||
просроченного товара:
|
||||
|
||||
### 📚 Словари значений
|
||||
- Поиск значений в словарях
|
||||
- Атрибуты со справочными значениями
|
||||
- Поддержка изображений в значениях
|
||||
|
||||
## GraphQL API
|
||||
|
||||
### Запросы категорий
|
||||
|
||||
```graphql
|
||||
# Получить дерево категорий
|
||||
query GetCategoryTree {
|
||||
getMarketplaceCategoryTree(input: {
|
||||
onlyAvailable: true
|
||||
includeTypes: true
|
||||
maxDepth: 3
|
||||
}) {
|
||||
descriptionCategoryId
|
||||
categoryName
|
||||
disabled
|
||||
isLeafCategory
|
||||
children {
|
||||
descriptionCategoryId
|
||||
categoryName
|
||||
types {
|
||||
typeId
|
||||
typeName
|
||||
isAvailable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Поиск категорий
|
||||
query SearchCategories {
|
||||
searchMarketplaceCategories(input: {
|
||||
searchTerm: "канцелярские"
|
||||
onlyAvailable: true
|
||||
limit: 10
|
||||
}) {
|
||||
descriptionCategoryId
|
||||
categoryName
|
||||
fullPath
|
||||
}
|
||||
}
|
||||
|
||||
# Статистика категорий
|
||||
query GetCategoryStats {
|
||||
getMarketplaceCategoryTreeStats {
|
||||
totalCategories
|
||||
rootCategories
|
||||
leafCategories
|
||||
totalTypes
|
||||
availableTypes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Запросы атрибутов
|
||||
|
||||
```graphql
|
||||
# Получить атрибуты для категории и типа
|
||||
query GetCategoryAttributes {
|
||||
getMarketplaceCategoryAttributes(input: {
|
||||
categoryId: 17029016
|
||||
typeId: 970778135
|
||||
includeDictionaryValues: true
|
||||
onlyRequired: false
|
||||
}) {
|
||||
attributeId
|
||||
name
|
||||
description
|
||||
type
|
||||
isRequired
|
||||
isAspect
|
||||
dictionary {
|
||||
dictionaryId
|
||||
name
|
||||
values {
|
||||
dictionaryValueId
|
||||
value
|
||||
info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Группированные атрибуты
|
||||
query GetGroupedAttributes {
|
||||
getMarketplaceCategoryAttributesGrouped(input: {
|
||||
categoryId: 17029016
|
||||
typeId: 970778135
|
||||
}) {
|
||||
groupName
|
||||
attributesCount
|
||||
attributes {
|
||||
attributeId
|
||||
name
|
||||
isRequired
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Поиск атрибутов
|
||||
query SearchAttributes {
|
||||
searchMarketplaceAttributes(input: {
|
||||
searchTerm: "бренд"
|
||||
onlyRequired: true
|
||||
limit: 5
|
||||
}) {
|
||||
attributeId
|
||||
name
|
||||
description
|
||||
isRequired
|
||||
isAspect
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Архитектура
|
||||
|
||||
Расширение построено по чистой архитектуре с разделением на слои:
|
||||
|
||||
### 📋 Доменный слой (`domain/`)
|
||||
- **Entities** - доменные сущности (Category, Type, Attribute, Dictionary)
|
||||
- **Repositories** - интерфейсы репозиториев
|
||||
- **Services** - доменные сервисы с бизнес-логикой
|
||||
|
||||
### 🔧 Слой приложения (`application/`)
|
||||
- **DTO** - GraphQL DTO для входных и выходных данных
|
||||
- **Resolvers** - GraphQL резолверы
|
||||
- **Services** - сервисы приложения
|
||||
|
||||
### 🏗️ Инфраструктурный слой (`infrastructure/`)
|
||||
- **Adapters** - адаптеры для подключения к базе данных ozon-categories
|
||||
|
||||
## Конфигурация
|
||||
|
||||
```typescript
|
||||
interface IConfig {
|
||||
// Системное состояние принятия положения ЦПП советом (скрыто из формы установки).
|
||||
coopAcceptance: {
|
||||
accepted: boolean;
|
||||
document_registry_id: number;
|
||||
accepted_at: string;
|
||||
accepted_by_board_decision_id: string;
|
||||
};
|
||||
// Настройки автосписания скоропорта (видимы в форме установки).
|
||||
writeoff: {
|
||||
auto_proposal_enabled: boolean; // Автоформирование проекта списания (по умолчанию вкл.)
|
||||
post_expiry_grace_days: number; // Списывать спустя N дней после истечения срока (по умолчанию 7)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
В форме установки расширения показываются только пользовательские настройки
|
||||
(блок `writeoff`); `coopAcceptance` — внутреннее состояние, помечено
|
||||
`visible: false`. Человекочитаемые подписи полей задаются через `describeField`
|
||||
в `Schema` (см. `types.ts`), как в расширении `capital`.
|
||||
|
||||
## Использование
|
||||
|
||||
После установки и активации расширения, GraphQL API становится доступен по эндпоинту `/v1/graphql`.
|
||||
|
||||
Примеры использования см. в секции GraphQL API выше.
|
||||
- **Автоматическое формирование проекта списания** — включено по
|
||||
умолчанию; кооператив сам готовит предложение о списании, когда товар
|
||||
просрочен, и выносит его на решение совета.
|
||||
- **Через сколько дней после истечения срока годности списывать** — по
|
||||
умолчанию 7 дней; даёт время продать товар с уценкой до списания.
|
||||
|
||||
Reference in New Issue
Block a user