docs(marketplace): переписать README как продуктовое описание для председателя
Убраны технические детали (имена резолверов/сервисов, пути к файлам, GraphQL-примеры, слои архитектуры) — описание процесса выдачи и возможностей расширения теперь на языке пользователя, а не разработчика. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,255 +1,77 @@
|
|||||||
# Marketplace — расширение «Стол заказов»
|
# 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)
|
### Выдача и возвраты
|
||||||
|
- Выдача имущества пайщику по акту с подписью на месте (см. выше).
|
||||||
|
- Докладка из остатка склада участка прямо у стойки выдачи.
|
||||||
|
- Гарантийный возврат и претензии по качеству.
|
||||||
|
- Списание просроченного товара по решению совета кооператива.
|
||||||
|
|
||||||
### 🗂️ Дерево категорий
|
### Экономика участка
|
||||||
- Получение полного дерева категорий с иерархией
|
- Членский взнос, включённый в стоимость заказа.
|
||||||
- Поиск категорий по названию
|
- Распределение взноса между доверенными лицами участка.
|
||||||
- Фильтрация по доступности
|
- Материальная помощь доверенным лицам из общего кошелька участка.
|
||||||
- Получение листовых категорий (где можно создавать товары)
|
|
||||||
- Статистика по категориям
|
|
||||||
|
|
||||||
### 🏷️ Типы товаров
|
## Настройки при установке
|
||||||
- Получение типов товаров для категорий
|
|
||||||
- Фильтрация доступных типов
|
|
||||||
- Связь типов с категориями
|
|
||||||
|
|
||||||
### ⚙️ Атрибуты товаров
|
При установке расширения председателю доступны настройки списания
|
||||||
- Получение атрибутов для конкретной категории и типа
|
просроченного товара:
|
||||||
- Группировка атрибутов
|
|
||||||
- Обязательные и аспектные атрибуты
|
|
||||||
- Валидация значений атрибутов
|
|
||||||
|
|
||||||
### 📚 Словари значений
|
- **Автоматическое формирование проекта списания** — включено по
|
||||||
- Поиск значений в словарях
|
умолчанию; кооператив сам готовит предложение о списании, когда товар
|
||||||
- Атрибуты со справочными значениями
|
просрочен, и выносит его на решение совета.
|
||||||
- Поддержка изображений в значениях
|
- **Через сколько дней после истечения срока годности списывать** — по
|
||||||
|
умолчанию 7 дней; даёт время продать товар с уценкой до списания.
|
||||||
## 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 выше.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user