returnByMoneyDecision Document
This commit is contained in:
Vendored
+1
-1
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.guides.bracketPairs": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.tabCompletion": "onlySnippets",
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.addMissingImports.ts": "explicit",
|
||||
"source.fixAll.ts": "explicit",
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -114,6 +114,9 @@ static const std::set<eosio::name> soviet_actions = {
|
||||
"capwthdrprog"_n, //произвести возврат накопленных членских взносов по программе на капиталиста
|
||||
"capwthdrproj"_n, //произвести возврат накопленных членских взносов по проекту на актора
|
||||
"capwthdrres"_n, //произвести возврат из задания
|
||||
|
||||
//WALLET
|
||||
"createwthd"_n, //создать заявление на возврат паевого взноса
|
||||
};
|
||||
|
||||
static const std::set<eosio::name> gateway_income_actions = {
|
||||
|
||||
@@ -14,7 +14,7 @@ void wallet::authwthd(eosio::name coopname, checksum256 withdraw_hash) {
|
||||
});
|
||||
|
||||
// создаём объект исходящего платежа в gateway с коллбэком после обработки
|
||||
action(permission_level{ _capital, "active"_n}, _gateway, "createoutpay"_n,
|
||||
action(permission_level{ _wallet, "active"_n}, _gateway, "createoutpay"_n,
|
||||
std::make_tuple(
|
||||
coopname,
|
||||
withdraw -> username,
|
||||
|
||||
@@ -28,22 +28,20 @@ void wallet::createwthd(eosio::name coopname, eosio::name username, checksum256
|
||||
d.created_at = eosio::time_point_sec(eosio::current_time_point().sec_since_epoch());
|
||||
});
|
||||
|
||||
//TODO: перевести approve на hash-идентификатор???
|
||||
action(
|
||||
permission_level{_capital, "active"_n}, // кто вызывает
|
||||
Action::send<createagenda_interface>(
|
||||
_soviet,
|
||||
"createapprv"_n,
|
||||
std::make_tuple(
|
||||
coopname,
|
||||
username,
|
||||
statement,
|
||||
withdraw_hash,
|
||||
_wallet, // callback_contract (текущий контракт)
|
||||
"approvewthd"_n, // callback_action_approve
|
||||
"declinewthd"_n, // callback_action_decline
|
||||
std::string("")
|
||||
)
|
||||
).send();
|
||||
"createagenda"_n,
|
||||
_wallet,
|
||||
coopname,
|
||||
username,
|
||||
get_valid_soviet_action("createwthd"_n),
|
||||
withdraw_hash,
|
||||
_wallet, // callback_contract (текущий контракт)
|
||||
"approvewthd"_n, // callback_action_approve
|
||||
"declinewthd"_n, // callback_action_decline
|
||||
statement,
|
||||
std::string("")
|
||||
);
|
||||
|
||||
std::string memo_in = "Возврат части целевого паевого взноса по ЦПП 'Цифровой Кошелёк'";
|
||||
Wallet::block_funds(_wallet, coopname, username, quantity, _wallet_program, memo_in);
|
||||
|
||||
@@ -1640,6 +1640,34 @@ input CreateProjectFreeDecisionInput {
|
||||
question: String!
|
||||
}
|
||||
|
||||
input CreateWithdrawInput {
|
||||
"""Имя аккаунта кооператива"""
|
||||
coopname: String!
|
||||
|
||||
"""Дополнительная информация"""
|
||||
memo: String
|
||||
|
||||
"""ID метода платежа"""
|
||||
method_id: String!
|
||||
|
||||
"""Количество средств"""
|
||||
quantity: String!
|
||||
|
||||
"""Подписанное заявление на возврат средств"""
|
||||
statement: SignedDigitalDocumentInput!
|
||||
|
||||
"""Символ валюты"""
|
||||
symbol: String!
|
||||
|
||||
"""Имя пользователя"""
|
||||
username: String!
|
||||
}
|
||||
|
||||
type CreateWithdrawResponse {
|
||||
"""Хеш созданной заявки на вывод"""
|
||||
withdraw_hash: String!
|
||||
}
|
||||
|
||||
type CreatedProjectFreeDecision {
|
||||
"""Проект решения, которое предлагается принять"""
|
||||
decision: String!
|
||||
@@ -2070,6 +2098,68 @@ input FreeDecisionGenerateDocumentInput {
|
||||
version: String
|
||||
}
|
||||
|
||||
type GatewayPayment {
|
||||
"""Данные из блокчейна"""
|
||||
blockchain_data: JSON
|
||||
|
||||
"""Может ли статус быть изменен"""
|
||||
can_change_status: Boolean!
|
||||
|
||||
"""Название кооператива"""
|
||||
coopname: String!
|
||||
|
||||
"""Дата создания"""
|
||||
created_at: DateTime!
|
||||
|
||||
"""Форматированная сумма"""
|
||||
formatted_amount: String!
|
||||
|
||||
"""Универсальный хеш платежа"""
|
||||
hash: String!
|
||||
|
||||
"""Уникальный идентификатор платежа"""
|
||||
id: ID
|
||||
|
||||
"""Хеш входящего платежа"""
|
||||
income_hash: String
|
||||
|
||||
"""Дополнительная информация"""
|
||||
memo: String
|
||||
|
||||
"""ID платежного метода"""
|
||||
method_id: String!
|
||||
|
||||
"""Хеш исходящего платежа"""
|
||||
outcome_hash: String
|
||||
|
||||
"""Детали платежа"""
|
||||
payment_details: String
|
||||
|
||||
"""Количество"""
|
||||
quantity: String!
|
||||
|
||||
"""Статус платежа"""
|
||||
status: gatewayPaymentStatus!
|
||||
|
||||
"""Человекочитаемый статус"""
|
||||
status_label: String!
|
||||
|
||||
"""Символ валюты"""
|
||||
symbol: String!
|
||||
|
||||
"""Тип платежа: входящий или исходящий"""
|
||||
type: gatewayPaymentType!
|
||||
|
||||
"""Человекочитаемый тип платежа"""
|
||||
type_label: String!
|
||||
|
||||
"""Дата последнего обновления"""
|
||||
updated_at: DateTime
|
||||
|
||||
"""Имя пользователя"""
|
||||
username: String!
|
||||
}
|
||||
|
||||
input GenerateDocumentInput {
|
||||
"""Номер блока, на котором был создан документ"""
|
||||
block_num: Int
|
||||
@@ -2182,6 +2272,12 @@ input GetMeetsInput {
|
||||
coopname: String!
|
||||
}
|
||||
|
||||
input GetOutgoingPaymentsInput {
|
||||
coopname: String
|
||||
status: String
|
||||
username: String
|
||||
}
|
||||
|
||||
input GetPaymentMethodsInput {
|
||||
"""Количество элементов на странице"""
|
||||
limit: Int! = 10
|
||||
@@ -2681,6 +2777,9 @@ type Mutation {
|
||||
"""
|
||||
createProjectOfFreeDecision(data: CreateProjectFreeDecisionInput!): CreatedProjectFreeDecision!
|
||||
|
||||
"""Создать заявку на вывод средств"""
|
||||
createWithdraw(input: CreateWithdrawInput!): CreateWithdrawResponse!
|
||||
|
||||
"""Отклонить заявку"""
|
||||
declineRequest(data: DeclineRequestInput!): Transaction!
|
||||
|
||||
@@ -2749,6 +2848,12 @@ type Mutation {
|
||||
"""Сгенерировать документ заявления о возврате имущества."""
|
||||
generateReturnByAssetStatement(data: ReturnByAssetStatementGenerateDocumentInput!, options: GenerateDocumentOptionsInput): GeneratedDocument!
|
||||
|
||||
"""Сгенерировать документ решения совета о возврате паевого взноса"""
|
||||
generateReturnByMoneyDecisionDocument(data: ReturnByMoneyDecisionGenerateDocumentInput!, options: GenerateDocumentOptionsInput): GeneratedDocument!
|
||||
|
||||
"""Сгенерировать документ заявления на возврат паевого взноса"""
|
||||
generateReturnByMoneyStatementDocument(data: ReturnByMoneyGenerateDocumentInput!, options: GenerateDocumentOptionsInput): GeneratedDocument!
|
||||
|
||||
"""Сгенерировать документ, подтверждающий выбор кооперативного участка"""
|
||||
generateSelectBranchDocument(data: SelectBranchGenerateDocumentInput!, options: GenerateDocumentOptionsInput): GeneratedDocument!
|
||||
|
||||
@@ -2872,6 +2977,9 @@ type Mutation {
|
||||
"""Обновить расширение"""
|
||||
updateExtension(data: ExtensionInput!): Extension!
|
||||
|
||||
"""Обновить статус платежа."""
|
||||
updatePaymentStatus(input: UpdatePaymentStatusInput!): OutgoingPayment!
|
||||
|
||||
"""Обновить заявку"""
|
||||
updateRequest(data: UpdateRequestInput!): Transaction!
|
||||
|
||||
@@ -2975,6 +3083,73 @@ enum OrganizationType {
|
||||
ZAO
|
||||
}
|
||||
|
||||
type OutgoingPayment {
|
||||
"""Данные из блокчейна"""
|
||||
blockchain_data: JSON
|
||||
|
||||
"""Может ли статус быть изменен"""
|
||||
can_change_status: Boolean!
|
||||
|
||||
"""Название кооператива"""
|
||||
coopname: String!
|
||||
|
||||
"""Дата создания"""
|
||||
created_at: DateTime!
|
||||
|
||||
"""Форматированная сумма"""
|
||||
formatted_amount: String!
|
||||
|
||||
"""Хеш исходящего платежа"""
|
||||
hash: String!
|
||||
|
||||
"""Уникальный идентификатор платежа"""
|
||||
id: ID
|
||||
|
||||
"""Дополнительная информация"""
|
||||
memo: String
|
||||
|
||||
"""ID платежного метода"""
|
||||
method_id: String!
|
||||
|
||||
"""Детали платежа"""
|
||||
payment_details: String
|
||||
|
||||
"""Количество"""
|
||||
quantity: String!
|
||||
|
||||
"""Статус платежа"""
|
||||
status: gatewayPaymentStatus!
|
||||
|
||||
"""Человекочитаемый статус"""
|
||||
status_label: String!
|
||||
|
||||
"""Символ валюты"""
|
||||
symbol: String!
|
||||
|
||||
"""Тип платежа (всегда outgoing)"""
|
||||
type: gatewayPaymentType!
|
||||
|
||||
"""Дата последнего обновления"""
|
||||
updated_at: DateTime
|
||||
|
||||
"""Имя пользователя"""
|
||||
username: String!
|
||||
}
|
||||
|
||||
type PaginatedGatewayPaymentsPaginationResult {
|
||||
"""Текущая страница"""
|
||||
currentPage: Int!
|
||||
|
||||
"""Элементы текущей страницы"""
|
||||
items: [GatewayPayment!]!
|
||||
|
||||
"""Общее количество элементов"""
|
||||
totalCount: Int!
|
||||
|
||||
"""Общее количество страниц"""
|
||||
totalPages: Int!
|
||||
}
|
||||
|
||||
input PaginationInput {
|
||||
"""Количество элементов на странице"""
|
||||
limit: Int! = 10
|
||||
@@ -3538,6 +3713,11 @@ type Query {
|
||||
"""Получить список расширений"""
|
||||
getExtensions(data: GetExtensionsInput): [Extension!]!
|
||||
|
||||
"""
|
||||
Получить список всех платежей (универсальный метод, поддержка входящих платежей в будущем).
|
||||
"""
|
||||
getGatewayPayments(filters: GetOutgoingPaymentsInput!, options: PaginationInput!): PaginatedGatewayPaymentsPaginationResult!
|
||||
|
||||
"""Получить данные собрания по хешу"""
|
||||
getMeet(data: GetMeetInput!): MeetAggregate!
|
||||
|
||||
@@ -3749,6 +3929,17 @@ input RepresentedByInput {
|
||||
position: String!
|
||||
}
|
||||
|
||||
input RequestInput {
|
||||
"""Сумма к возврату"""
|
||||
amount: String!
|
||||
|
||||
"""Валюта"""
|
||||
currency: String!
|
||||
|
||||
"""ID платежного метода"""
|
||||
method_id: String!
|
||||
}
|
||||
|
||||
input ResetKeyInput {
|
||||
"""Публичный ключ для замены"""
|
||||
public_key: String!
|
||||
@@ -4051,6 +4242,85 @@ input ReturnByAssetStatementSignedMetaDocumentInput {
|
||||
version: String!
|
||||
}
|
||||
|
||||
input ReturnByMoneyDecisionGenerateDocumentInput {
|
||||
"""Сумма к возврату"""
|
||||
amount: String!
|
||||
|
||||
"""Номер блока, на котором был создан документ"""
|
||||
block_num: Int
|
||||
|
||||
"""Название кооператива, связанное с документом"""
|
||||
coopname: String!
|
||||
|
||||
"""Дата и время создания документа"""
|
||||
created_at: String
|
||||
|
||||
"""Валюта"""
|
||||
currency: String!
|
||||
|
||||
"""ID решения совета"""
|
||||
decision_id: Float!
|
||||
|
||||
"""Имя генератора, использованного для создания документа"""
|
||||
generator: String
|
||||
|
||||
"""Язык документа"""
|
||||
lang: String
|
||||
|
||||
"""Ссылки, связанные с документом"""
|
||||
links: [String!]
|
||||
|
||||
"""Хэш платежа"""
|
||||
payment_hash: String!
|
||||
|
||||
"""Часовой пояс, в котором был создан документ"""
|
||||
timezone: String
|
||||
|
||||
"""Название документа"""
|
||||
title: String
|
||||
|
||||
"""Имя пользователя, создавшего документ"""
|
||||
username: String!
|
||||
|
||||
"""Версия генератора, использованного для создания документа"""
|
||||
version: String
|
||||
}
|
||||
|
||||
input ReturnByMoneyGenerateDocumentInput {
|
||||
"""Номер блока, на котором был создан документ"""
|
||||
block_num: Int
|
||||
|
||||
"""Название кооператива, связанное с документом"""
|
||||
coopname: String!
|
||||
|
||||
"""Дата и время создания документа"""
|
||||
created_at: String
|
||||
|
||||
"""Имя генератора, использованного для создания документа"""
|
||||
generator: String
|
||||
|
||||
"""Язык документа"""
|
||||
lang: String
|
||||
|
||||
"""Ссылки, связанные с документом"""
|
||||
links: [String!]
|
||||
|
||||
"""Данные запроса на возврат денежных средств"""
|
||||
request: RequestInput!
|
||||
|
||||
"""Часовой пояс, в котором был создан документ"""
|
||||
timezone: String
|
||||
|
||||
"""Название документа"""
|
||||
title: String
|
||||
|
||||
"""Имя пользователя, создавшего документ"""
|
||||
username: String!
|
||||
|
||||
"""Версия генератора, использованного для создания документа"""
|
||||
version: String
|
||||
}
|
||||
|
||||
type SbpAccount {
|
||||
"""Мобильный телефон получателя"""
|
||||
phone: String!
|
||||
@@ -4572,6 +4842,23 @@ input UpdateOrganizationDataInput {
|
||||
username: String!
|
||||
}
|
||||
|
||||
input UpdatePaymentStatusInput {
|
||||
"""Название кооператива"""
|
||||
coopname: String!
|
||||
|
||||
"""Универсальный хеш платежа"""
|
||||
hash: String!
|
||||
|
||||
"""Причина изменения статуса (для статуса failed)"""
|
||||
reason: String
|
||||
|
||||
"""Новый статус платежа"""
|
||||
status: String!
|
||||
|
||||
"""Тип платежа"""
|
||||
type: String!
|
||||
}
|
||||
|
||||
input UpdateRequestInput {
|
||||
"""Имя аккаунта кооператива"""
|
||||
coopname: String!
|
||||
@@ -4734,4 +5021,19 @@ type WaitWeight {
|
||||
|
||||
"""Вес"""
|
||||
weight: Int!
|
||||
}
|
||||
|
||||
"""Статус платежа в системе Gateway"""
|
||||
enum gatewayPaymentStatus {
|
||||
CANCELLED
|
||||
COMPLETED
|
||||
FAILED
|
||||
PENDING
|
||||
PROCESSING
|
||||
}
|
||||
|
||||
"""Тип платежа в системе Gateway"""
|
||||
enum gatewayPaymentType {
|
||||
INCOMING
|
||||
OUTGOING
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import swaggerJsdoc from 'swagger-jsdoc';
|
||||
import swaggerDefinition from '../src/docs/swaggerDef';
|
||||
import fs from 'fs'
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const specs = swaggerJsdoc({
|
||||
@@ -8,5 +8,4 @@ const specs = swaggerJsdoc({
|
||||
apis: ['src/docs/*.yml', 'src/**/*.ts'],
|
||||
});
|
||||
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, '../src/docs/swagger.json'), JSON.stringify(specs, null, 2));
|
||||
|
||||
@@ -68,6 +68,18 @@ const envVarsSchema = z.object({
|
||||
REDIS_PASSWORD: z.string(),
|
||||
BLOCKCHAIN_RPC: z.string().min(1, { message: 'Не должно быть пустым' }),
|
||||
CHAIN_ID: z.string().min(1, { message: 'Не должно быть пустым' }),
|
||||
|
||||
// Параметры блокчейна
|
||||
ROOT_SYMBOL: z.string().default('AXON'),
|
||||
ROOT_GOVERN_SYMBOL: z.string().default('RUB'),
|
||||
ROOT_PRECISION: z
|
||||
.string()
|
||||
.default('4')
|
||||
.transform((val) => parseInt(val, 10)),
|
||||
ROOT_GOVERN_PRECISION: z
|
||||
.string()
|
||||
.default('4')
|
||||
.transform((val) => parseInt(val, 10)),
|
||||
});
|
||||
|
||||
// Валидация переменных окружения
|
||||
@@ -95,6 +107,10 @@ export default {
|
||||
blockchain: {
|
||||
url: envVars.data.BLOCKCHAIN_RPC,
|
||||
id: envVars.data.CHAIN_ID,
|
||||
root_symbol: envVars.data.ROOT_SYMBOL,
|
||||
root_govern_symbol: envVars.data.ROOT_GOVERN_SYMBOL,
|
||||
root_precision: envVars.data.ROOT_PRECISION,
|
||||
root_govern_precision: envVars.data.ROOT_GOVERN_PRECISION,
|
||||
},
|
||||
mongoose: {
|
||||
url: envVars.data.MONGODB_URL + (envVars.data.NODE_ENV === 'test' ? '-test' : ''),
|
||||
|
||||
@@ -18,6 +18,8 @@ import { DesktopDomainModule } from './desktop/desktop-domain.module';
|
||||
import { MeetDomainModule } from './meet/meet-domain.module';
|
||||
import { ControllerWsMeetModule } from './meet/controllers-ws-meet.module';
|
||||
import { UserCertificateDomainModule } from './user-certificate/user-certificate.module';
|
||||
import { GatewayDomainModule } from './gateway/gateway-domain.module';
|
||||
import { WalletDomainModule } from './wallet/wallet-domain.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -39,6 +41,8 @@ import { UserCertificateDomainModule } from './user-certificate/user-certificate
|
||||
MeetDomainModule,
|
||||
ControllerWsMeetModule,
|
||||
UserCertificateDomainModule,
|
||||
GatewayDomainModule,
|
||||
WalletDomainModule,
|
||||
],
|
||||
exports: [
|
||||
AuthDomainModule,
|
||||
@@ -58,6 +62,8 @@ import { UserCertificateDomainModule } from './user-certificate/user-certificate
|
||||
CooplaceDomainModule,
|
||||
MeetDomainModule,
|
||||
UserCertificateDomainModule,
|
||||
GatewayDomainModule,
|
||||
WalletDomainModule,
|
||||
],
|
||||
})
|
||||
export class DomainModule {}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import type { OutgoingPaymentDomainInterface } from '../interfaces/outgoing-payment-domain.interface';
|
||||
import { GatewayPaymentStatusEnum } from '../enums/gateway-payment-status.enum';
|
||||
import { GatewayPaymentTypeEnum } from '../enums/gateway-payment-type.enum';
|
||||
|
||||
/**
|
||||
* Доменная сущность исходящего платежа
|
||||
*/
|
||||
export class OutgoingPaymentDomainEntity implements OutgoingPaymentDomainInterface {
|
||||
id?: string;
|
||||
coopname: string;
|
||||
username: string;
|
||||
hash: string;
|
||||
quantity: string;
|
||||
symbol: string;
|
||||
method_id: string;
|
||||
status: GatewayPaymentStatusEnum;
|
||||
type: GatewayPaymentTypeEnum.OUTGOING = GatewayPaymentTypeEnum.OUTGOING;
|
||||
created_at: Date;
|
||||
updated_at?: Date;
|
||||
memo?: string;
|
||||
payment_details?: string;
|
||||
blockchain_data?: any;
|
||||
|
||||
constructor(data: OutgoingPaymentDomainInterface) {
|
||||
this.id = data.id;
|
||||
this.coopname = data.coopname;
|
||||
this.username = data.username;
|
||||
this.hash = data.hash;
|
||||
this.quantity = data.quantity;
|
||||
this.symbol = data.symbol;
|
||||
this.method_id = data.method_id;
|
||||
this.status = data.status as GatewayPaymentStatusEnum;
|
||||
this.created_at = data.created_at;
|
||||
this.updated_at = data.updated_at;
|
||||
this.memo = data.memo;
|
||||
this.payment_details = data.payment_details;
|
||||
this.blockchain_data = data.blockchain_data;
|
||||
this.type = GatewayPaymentTypeEnum.OUTGOING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить количество и символ в формате строки
|
||||
*/
|
||||
getFormattedAmount(): string {
|
||||
return `${this.quantity} ${this.symbol}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, может ли статус быть изменен
|
||||
*/
|
||||
canChangeStatus(): boolean {
|
||||
return this.status !== GatewayPaymentStatusEnum.COMPLETED && this.status !== GatewayPaymentStatusEnum.CANCELLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить человекочитаемый статус
|
||||
*/
|
||||
getStatusLabel(): string {
|
||||
const statusLabels: Record<GatewayPaymentStatusEnum, string> = {
|
||||
[GatewayPaymentStatusEnum.PENDING]: 'Ожидает обработки',
|
||||
[GatewayPaymentStatusEnum.PROCESSING]: 'В процессе',
|
||||
[GatewayPaymentStatusEnum.COMPLETED]: 'Завершен',
|
||||
[GatewayPaymentStatusEnum.FAILED]: 'Ошибка',
|
||||
[GatewayPaymentStatusEnum.CANCELLED]: 'Отменен',
|
||||
};
|
||||
return statusLabels[this.status] || this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразовать в DTO для GraphQL
|
||||
*/
|
||||
toDTO(): any {
|
||||
return {
|
||||
id: this.id,
|
||||
coopname: this.coopname,
|
||||
username: this.username,
|
||||
hash: this.hash,
|
||||
quantity: this.quantity,
|
||||
symbol: this.symbol,
|
||||
method_id: this.method_id,
|
||||
status: this.status,
|
||||
type: this.type,
|
||||
created_at: this.created_at,
|
||||
updated_at: this.updated_at,
|
||||
memo: this.memo,
|
||||
payment_details: this.payment_details,
|
||||
blockchain_data: this.blockchain_data,
|
||||
formatted_amount: this.getFormattedAmount(),
|
||||
status_label: this.getStatusLabel(),
|
||||
can_change_status: this.canChangeStatus(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { PaymentDomainInterface } from '../interfaces/payment-domain.interface';
|
||||
|
||||
/**
|
||||
* Универсальная доменная сущность платежа
|
||||
* Работает как для входящих, так и для исходящих платежей
|
||||
*/
|
||||
export class PaymentDomainEntity implements PaymentDomainInterface {
|
||||
id?: string;
|
||||
coopname: string;
|
||||
username: string;
|
||||
hash: string;
|
||||
quantity: string;
|
||||
symbol: string;
|
||||
method_id: string;
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
|
||||
type: 'incoming' | 'outgoing';
|
||||
created_at: Date;
|
||||
updated_at?: Date;
|
||||
memo?: string;
|
||||
payment_details?: string;
|
||||
blockchain_data?: any;
|
||||
|
||||
constructor(data: PaymentDomainInterface) {
|
||||
this.id = data.id;
|
||||
this.coopname = data.coopname;
|
||||
this.username = data.username;
|
||||
this.hash = data.hash;
|
||||
this.quantity = data.quantity;
|
||||
this.symbol = data.symbol;
|
||||
this.method_id = data.method_id;
|
||||
this.status = data.status;
|
||||
this.type = data.type;
|
||||
this.created_at = data.created_at;
|
||||
this.updated_at = data.updated_at;
|
||||
this.memo = data.memo;
|
||||
this.payment_details = data.payment_details;
|
||||
this.blockchain_data = data.blockchain_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить количество и символ в формате строки
|
||||
*/
|
||||
getFormattedAmount(): string {
|
||||
return `${this.quantity} ${this.symbol}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, может ли статус быть изменен
|
||||
*/
|
||||
canChangeStatus(): boolean {
|
||||
return this.status !== 'completed' && this.status !== 'cancelled';
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить человекочитаемый статус
|
||||
*/
|
||||
getStatusLabel(): string {
|
||||
const statusLabels = {
|
||||
pending: 'Ожидает обработки',
|
||||
processing: 'В процессе',
|
||||
completed: 'Завершен',
|
||||
failed: 'Ошибка',
|
||||
cancelled: 'Отменен',
|
||||
};
|
||||
return statusLabels[this.status] || this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить человекочитаемый тип платежа
|
||||
*/
|
||||
getTypeLabel(): string {
|
||||
const typeLabels = {
|
||||
incoming: 'Входящий платеж',
|
||||
outgoing: 'Исходящий платеж',
|
||||
};
|
||||
return typeLabels[this.type] || this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить соответствующий хеш для типа платежа
|
||||
*/
|
||||
getSpecificHash(): { [key: string]: string } {
|
||||
if (this.type === 'outgoing') {
|
||||
return { outcome_hash: this.hash };
|
||||
} else {
|
||||
return { income_hash: this.hash };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразовать в DTO для GraphQL
|
||||
*/
|
||||
toDTO(): any {
|
||||
return {
|
||||
id: this.id,
|
||||
coopname: this.coopname,
|
||||
username: this.username,
|
||||
hash: this.hash,
|
||||
...this.getSpecificHash(), // Добавляем конкретный хеш
|
||||
quantity: this.quantity,
|
||||
symbol: this.symbol,
|
||||
method_id: this.method_id,
|
||||
status: this.status,
|
||||
type: this.type,
|
||||
created_at: this.created_at,
|
||||
updated_at: this.updated_at,
|
||||
memo: this.memo,
|
||||
payment_details: this.payment_details,
|
||||
blockchain_data: this.blockchain_data,
|
||||
formatted_amount: this.getFormattedAmount(),
|
||||
status_label: this.getStatusLabel(),
|
||||
type_label: this.getTypeLabel(),
|
||||
can_change_status: this.canChangeStatus(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Статус платежа в системе Gateway
|
||||
*/
|
||||
export enum GatewayPaymentStatusEnum {
|
||||
PENDING = 'pending',
|
||||
PROCESSING = 'processing',
|
||||
COMPLETED = 'completed',
|
||||
FAILED = 'failed',
|
||||
CANCELLED = 'cancelled',
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Тип платежа в системе Gateway
|
||||
*/
|
||||
export enum GatewayPaymentTypeEnum {
|
||||
INCOMING = 'incoming',
|
||||
OUTGOING = 'outgoing',
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { GatewayInteractor } from './interactors/gateway.interactor';
|
||||
import { InfrastructureModule } from '~/infrastructure/infrastructure.module';
|
||||
|
||||
@Module({
|
||||
imports: [InfrastructureModule],
|
||||
providers: [GatewayInteractor],
|
||||
exports: [GatewayInteractor],
|
||||
})
|
||||
export class GatewayDomainModule {}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Injectable, Inject, Logger } from '@nestjs/common';
|
||||
import type { OutgoingPaymentDomainInterface } from '../interfaces/outgoing-payment-domain.interface';
|
||||
import type { UpdatePaymentStatusInputDomainInterface } from '../interfaces/update-payment-status-input-domain.interface';
|
||||
import type { GetOutgoingPaymentsInputDomainInterface } from '../interfaces/get-outgoing-payments-input-domain.interface';
|
||||
import type {
|
||||
PaginationInputDomainInterface,
|
||||
PaginationResultDomainInterface,
|
||||
} from '~/domain/common/interfaces/pagination.interface';
|
||||
import { OutgoingPaymentDomainEntity } from '../entities/outgoing-payment-domain.entity';
|
||||
import { GatewayBlockchainPort, GATEWAY_BLOCKCHAIN_PORT } from '../ports/gateway-blockchain.port';
|
||||
import { PaymentRepository, PAYMENT_REPOSITORY } from '../repositories/payment.repository';
|
||||
|
||||
/**
|
||||
* Интерактор домена gateway для управления исходящими платежами
|
||||
*/
|
||||
@Injectable()
|
||||
export class GatewayInteractor {
|
||||
private readonly logger = new Logger(GatewayInteractor.name);
|
||||
|
||||
constructor(
|
||||
@Inject(GATEWAY_BLOCKCHAIN_PORT)
|
||||
private readonly gatewayBlockchainPort: GatewayBlockchainPort,
|
||||
@Inject(PAYMENT_REPOSITORY)
|
||||
private readonly paymentRepository: PaymentRepository
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Получить список исходящих платежей
|
||||
*/
|
||||
async getOutgoingPayments(
|
||||
filters: GetOutgoingPaymentsInputDomainInterface,
|
||||
options: PaginationInputDomainInterface
|
||||
): Promise<PaginationResultDomainInterface<OutgoingPaymentDomainEntity>> {
|
||||
// Получаем данные из локальной БД через репозиторий
|
||||
const result = await this.paymentRepository.getOutgoingPayments(filters, options);
|
||||
|
||||
// Обогащаем каждый платеж данными из блокчейна
|
||||
const enrichedItems = await Promise.all(
|
||||
result.items.map(async (payment) => {
|
||||
try {
|
||||
// Получаем данные из блокчейна
|
||||
const blockchainData = await this.gatewayBlockchainPort.getOutcome(payment.coopname, payment.hash);
|
||||
|
||||
// Создаем доменную сущность с обогащенными данными
|
||||
const enrichedPayment = new OutgoingPaymentDomainEntity({
|
||||
...payment,
|
||||
blockchain_data: blockchainData,
|
||||
});
|
||||
|
||||
return enrichedPayment;
|
||||
} catch (error: any) {
|
||||
this.logger.warn(`Не удалось получить данные из блокчейна для платежа ${payment.hash}: ${error.message}`);
|
||||
// Возвращаем платеж без данных блокчейна
|
||||
return new OutgoingPaymentDomainEntity(payment);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
items: enrichedItems,
|
||||
totalCount: result.totalCount,
|
||||
totalPages: result.totalPages,
|
||||
currentPage: result.currentPage,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить статус платежа
|
||||
*/
|
||||
async updatePaymentStatus(data: UpdatePaymentStatusInputDomainInterface): Promise<OutgoingPaymentDomainEntity> {
|
||||
try {
|
||||
if (data.status === 'completed') {
|
||||
// Завершаем платеж в gateway
|
||||
await this.gatewayBlockchainPort.completeOutcome({
|
||||
coopname: data.coopname,
|
||||
outcome_hash: data.hash,
|
||||
});
|
||||
this.logger.log(`Платеж ${data.hash} завершен в блокчейне`);
|
||||
} else if (data.status === 'failed') {
|
||||
// Отклоняем платеж в gateway
|
||||
await this.gatewayBlockchainPort.declineOutcome({
|
||||
coopname: data.coopname,
|
||||
outcome_hash: data.hash,
|
||||
reason: data.reason || 'Платеж отклонен',
|
||||
});
|
||||
this.logger.log(`Платеж ${data.hash} отклонен в блокчейне`);
|
||||
}
|
||||
|
||||
// Обновляем статус в локальной БД через репозиторий
|
||||
const updatedPayment = await this.paymentRepository.updatePaymentStatus(data.hash, data.status, data.reason);
|
||||
if (!updatedPayment) {
|
||||
throw new Error(`Не удалось найти платеж с хешем ${data.hash}`);
|
||||
}
|
||||
|
||||
return new OutgoingPaymentDomainEntity(updatedPayment as OutgoingPaymentDomainInterface);
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Ошибка при обновлении статуса платежа ${data.hash}: ${error.message}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
|
||||
|
||||
/**
|
||||
* Доменный интерфейс для создания исходящего платежа
|
||||
*/
|
||||
export interface CreateOutgoingPaymentInputDomainInterface {
|
||||
coopname: string;
|
||||
username: string;
|
||||
quantity: string;
|
||||
symbol: string;
|
||||
method_id: string;
|
||||
memo?: string;
|
||||
statement: ISignedDocumentDomainInterface;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Доменный интерфейс для получения исходящих платежей
|
||||
*/
|
||||
export interface GetOutgoingPaymentsInputDomainInterface {
|
||||
coopname?: string;
|
||||
username?: string;
|
||||
status?: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import type { PaymentDomainInterface } from './payment-domain.interface';
|
||||
import { GatewayPaymentTypeEnum } from '../enums/gateway-payment-type.enum';
|
||||
|
||||
/**
|
||||
* Доменный интерфейс исходящего платежа
|
||||
* Расширяет универсальный интерфейс платежа
|
||||
*/
|
||||
export interface OutgoingPaymentDomainInterface extends Omit<PaymentDomainInterface, 'type'> {
|
||||
type: GatewayPaymentTypeEnum.OUTGOING;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
|
||||
import { GatewayPaymentStatusEnum } from '../enums/gateway-payment-status.enum';
|
||||
import { GatewayPaymentTypeEnum } from '../enums/gateway-payment-type.enum';
|
||||
|
||||
/**
|
||||
* Универсальный доменный интерфейс платежа
|
||||
* Работает как для входящих, так и для исходящих платежей
|
||||
*/
|
||||
export interface PaymentDomainInterface {
|
||||
id?: string;
|
||||
coopname: string;
|
||||
username: string;
|
||||
hash: string; // Универсальный хеш (income_hash или outcome_hash)
|
||||
quantity: string;
|
||||
symbol: string;
|
||||
method_id: string;
|
||||
status: GatewayPaymentStatusEnum;
|
||||
type: GatewayPaymentTypeEnum;
|
||||
created_at: Date;
|
||||
updated_at?: Date;
|
||||
memo?: string;
|
||||
payment_details?: string;
|
||||
blockchain_data?: any;
|
||||
statement?: ISignedDocumentDomainInterface;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { GatewayPaymentStatusEnum } from '../enums/gateway-payment-status.enum';
|
||||
import { GatewayPaymentTypeEnum } from '../enums/gateway-payment-type.enum';
|
||||
|
||||
/**
|
||||
* Доменный интерфейс для обновления статуса платежа
|
||||
*/
|
||||
export interface UpdatePaymentStatusInputDomainInterface {
|
||||
hash: string; // Универсальный хеш (income_hash или outcome_hash)
|
||||
coopname: string;
|
||||
type?: GatewayPaymentTypeEnum; // Тип платежа
|
||||
status: GatewayPaymentStatusEnum; // Любой статус из enum
|
||||
reason?: string; // для статуса failed
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { TransactionResult } from '~/domain/blockchain/types/transaction-result.type';
|
||||
import type { GatewayContract } from 'cooptypes';
|
||||
|
||||
/**
|
||||
* Доменные интерфейсы для управления gateway
|
||||
*/
|
||||
export interface CompleteOutcomeDomainInterface {
|
||||
coopname: string;
|
||||
outcome_hash: string;
|
||||
}
|
||||
|
||||
export interface DeclineOutcomeDomainInterface {
|
||||
coopname: string;
|
||||
outcome_hash: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Порт для взаимодействия с gateway блокчейном
|
||||
* Только управление уже созданными outcomes - создание происходит автоматически другими контрактами
|
||||
*/
|
||||
export interface GatewayBlockchainPort {
|
||||
// Завершение исходящего платежа
|
||||
completeOutcome(data: CompleteOutcomeDomainInterface): Promise<TransactionResult>;
|
||||
|
||||
// Отклонение исходящего платежа
|
||||
declineOutcome(data: DeclineOutcomeDomainInterface): Promise<TransactionResult>;
|
||||
|
||||
// Получение исходящих платежей из блокчейна
|
||||
getOutcomes(coopname: string): Promise<GatewayContract.Tables.Outcomes.IOutcome[]>;
|
||||
|
||||
// Получение конкретного исходящего платежа
|
||||
getOutcome(coopname: string, outcome_hash: string): Promise<GatewayContract.Tables.Outcomes.IOutcome | null>;
|
||||
}
|
||||
|
||||
export const GATEWAY_BLOCKCHAIN_PORT = Symbol('GATEWAY_BLOCKCHAIN_PORT');
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { PaymentDomainInterface } from '../interfaces/payment-domain.interface';
|
||||
import type { OutgoingPaymentDomainInterface } from '../interfaces/outgoing-payment-domain.interface';
|
||||
import type {
|
||||
PaginationInputDomainInterface,
|
||||
PaginationResultDomainInterface,
|
||||
} from '~/domain/common/interfaces/pagination.interface';
|
||||
import type { GetOutgoingPaymentsInputDomainInterface } from '../interfaces/get-outgoing-payments-input-domain.interface';
|
||||
|
||||
export interface PaymentRepository {
|
||||
findByHash(hash: string): Promise<PaymentDomainInterface | null>;
|
||||
create(data: PaymentDomainInterface): Promise<PaymentDomainInterface>;
|
||||
updatePaymentStatus(hash: string, status: string, reason?: string): Promise<PaymentDomainInterface | null>;
|
||||
updatePaymentWithBlockchainData(hash: string, blockchain_data: any): Promise<PaymentDomainInterface | null>;
|
||||
getOutgoingPayments(
|
||||
filters: GetOutgoingPaymentsInputDomainInterface,
|
||||
options: PaginationInputDomainInterface
|
||||
): Promise<PaginationResultDomainInterface<OutgoingPaymentDomainInterface>>;
|
||||
}
|
||||
|
||||
export const PAYMENT_REPOSITORY = Symbol('PaymentRepository');
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Injectable, Inject, Logger } from '@nestjs/common';
|
||||
import { Cooperative } from 'cooptypes';
|
||||
import { DocumentDomainService } from '~/domain/document/services/document-domain.service';
|
||||
import { DocumentDomainEntity } from '~/domain/document/entity/document-domain.entity';
|
||||
import { WalletBlockchainPort, WALLET_BLOCKCHAIN_PORT } from '../ports/wallet-blockchain.port';
|
||||
import { generateUniqueHash } from '~/utils/generate-hash.util';
|
||||
import type { CreateWithdrawInputDomainInterface } from '../interfaces/create-withdraw-input-domain.interface';
|
||||
|
||||
/**
|
||||
* Интерактор домена wallet для управления выводом средств и генерацией документов
|
||||
*/
|
||||
@Injectable()
|
||||
export class WalletDomainInteractor {
|
||||
private readonly logger = new Logger(WalletDomainInteractor.name);
|
||||
|
||||
constructor(
|
||||
private readonly documentDomainService: DocumentDomainService,
|
||||
@Inject(WALLET_BLOCKCHAIN_PORT)
|
||||
private readonly walletBlockchainPort: WalletBlockchainPort
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Генерация документа заявления на возврат паевого взноса (900)
|
||||
*/
|
||||
async generateReturnByMoneyStatementDocument(
|
||||
data: Cooperative.Registry.ReturnByMoney.Action,
|
||||
options: Cooperative.Document.IGenerationOptions
|
||||
): Promise<DocumentDomainEntity> {
|
||||
// Устанавливаем registry_id для документа заявления
|
||||
data.registry_id = Cooperative.Registry.ReturnByMoney.registry_id;
|
||||
return await this.documentDomainService.generateDocument({ data, options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация документа решения совета о возврате паевого взноса (901)
|
||||
*/
|
||||
async generateReturnByMoneyDecisionDocument(
|
||||
data: Cooperative.Registry.ReturnByMoneyDecision.Action,
|
||||
options: Cooperative.Document.IGenerationOptions
|
||||
): Promise<DocumentDomainEntity> {
|
||||
// Устанавливаем registry_id для документа решения
|
||||
data.registry_id = Cooperative.Registry.ReturnByMoneyDecision.registry_id;
|
||||
return await this.documentDomainService.generateDocument({ data, options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание заявки на вывод средств (createWithdraw)
|
||||
* Данный метод создает withdraw в blockchain и автоматически создается outcome в gateway
|
||||
*/
|
||||
async createWithdraw(data: CreateWithdrawInputDomainInterface): Promise<{ withdraw_hash: string }> {
|
||||
const withdraw_hash = generateUniqueHash();
|
||||
|
||||
try {
|
||||
// Создаем withdraw в wallet контракте
|
||||
// wallet контракт автоматически создаст outcome в gateway контракте
|
||||
await this.walletBlockchainPort.createWithdraw({
|
||||
coopname: data.coopname,
|
||||
username: data.username,
|
||||
withdraw_hash,
|
||||
quantity: `${data.quantity} ${data.symbol}`,
|
||||
statement: data.statement,
|
||||
});
|
||||
|
||||
this.logger.log(`Создан withdraw в wallet: ${withdraw_hash}, outcome будет создан автоматически в gateway`);
|
||||
|
||||
return { withdraw_hash };
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Ошибка при создании withdraw: ${error.message}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Injectable, Inject, Logger } from '@nestjs/common';
|
||||
import { Cooperative } from 'cooptypes';
|
||||
import { DocumentDomainService } from '~/domain/document/services/document-domain.service';
|
||||
import { DocumentDomainEntity } from '~/domain/document/entity/document-domain.entity';
|
||||
import { WalletBlockchainPort, WALLET_BLOCKCHAIN_PORT } from '../ports/wallet-blockchain.port';
|
||||
import { generateUniqueHash } from '~/utils/generate-hash.util';
|
||||
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
|
||||
import type { CreateWithdrawInputDomainInterface } from '../interfaces/create-withdraw-input-domain.interface';
|
||||
|
||||
/**
|
||||
* Интерактор домена wallet для управления выводом средств и генерацией документов
|
||||
*/
|
||||
@Injectable()
|
||||
export class WalletDomainInteractor {
|
||||
private readonly logger = new Logger(WalletDomainInteractor.name);
|
||||
|
||||
constructor(
|
||||
private readonly documentDomainService: DocumentDomainService,
|
||||
@Inject(WALLET_BLOCKCHAIN_PORT)
|
||||
private readonly walletBlockchainPort: WalletBlockchainPort
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Генерация документа заявления на возврат паевого взноса (900)
|
||||
*/
|
||||
async generateReturnByMoneyStatementDocument(
|
||||
data: Cooperative.Registry.ReturnByMoney.Action,
|
||||
options: Cooperative.Document.IGenerationOptions
|
||||
): Promise<DocumentDomainEntity> {
|
||||
// Устанавливаем registry_id для документа заявления
|
||||
data.registry_id = Cooperative.Registry.ReturnByMoney.registry_id;
|
||||
return await this.documentDomainService.generateDocument({ data, options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация документа решения совета о возврате паевого взноса (901)
|
||||
*/
|
||||
async generateReturnByMoneyDecisionDocument(
|
||||
data: Cooperative.Registry.ReturnByMoneyDecision.Action,
|
||||
options: Cooperative.Document.IGenerationOptions
|
||||
): Promise<DocumentDomainEntity> {
|
||||
// Устанавливаем registry_id для документа решения
|
||||
data.registry_id = Cooperative.Registry.ReturnByMoneyDecision.registry_id;
|
||||
return await this.documentDomainService.generateDocument({ data, options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание заявки на вывод средств (createWithdraw)
|
||||
* Данный метод создает withdraw в blockchain и автоматически создается outcome в gateway
|
||||
*/
|
||||
async createWithdraw(data: CreateWithdrawInputDomainInterface): Promise<{ withdraw_hash: string }> {
|
||||
const withdraw_hash = generateUniqueHash();
|
||||
|
||||
try {
|
||||
// Создаем withdraw в wallet контракте
|
||||
// wallet контракт автоматически создаст outcome в gateway контракте
|
||||
await this.walletBlockchainPort.createWithdraw({
|
||||
coopname: data.coopname,
|
||||
username: data.username,
|
||||
withdraw_hash,
|
||||
quantity: `${data.quantity} ${data.symbol}`,
|
||||
statement: data.statement,
|
||||
});
|
||||
|
||||
this.logger.log(`Создан withdraw в wallet: ${withdraw_hash}, outcome будет создан автоматически в gateway`);
|
||||
|
||||
return { withdraw_hash };
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Ошибка при создании withdraw: ${error.message}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
|
||||
|
||||
/**
|
||||
* Доменный интерфейс для создания вывода средств (withdraw)
|
||||
*/
|
||||
export interface CreateWithdrawInputDomainInterface {
|
||||
coopname: string;
|
||||
username: string;
|
||||
quantity: string;
|
||||
symbol: string;
|
||||
method_id: string;
|
||||
memo?: string;
|
||||
statement: ISignedDocumentDomainInterface;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { TransactionResult } from '~/domain/blockchain/types/transaction-result.type';
|
||||
import type { WalletContract } from 'cooptypes';
|
||||
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
|
||||
|
||||
/**
|
||||
* Доменные интерфейсы для входных данных wallet блокчейн адаптера
|
||||
*/
|
||||
export interface CreateWithdrawDomainInterface {
|
||||
coopname: string;
|
||||
username: string;
|
||||
withdraw_hash: string;
|
||||
quantity: string;
|
||||
statement: ISignedDocumentDomainInterface;
|
||||
}
|
||||
|
||||
export interface GenerateReturnStatementDomainInterface {
|
||||
coopname: string;
|
||||
username: string;
|
||||
quantity: string;
|
||||
symbol: string;
|
||||
method_id: string;
|
||||
memo?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Порт для взаимодействия с wallet блокчейном
|
||||
*/
|
||||
export interface WalletBlockchainPort {
|
||||
// Создание заявки на вывод средств в wallet контракте
|
||||
createWithdraw(data: CreateWithdrawDomainInterface): Promise<TransactionResult>;
|
||||
|
||||
// Генерация заявления на возврат паевого взноса
|
||||
generateReturnStatement(data: GenerateReturnStatementDomainInterface): Promise<ISignedDocumentDomainInterface>;
|
||||
|
||||
// Получение данных из wallet контракта
|
||||
getWithdraw(coopname: string, withdraw_hash: string): Promise<WalletContract.Tables.Withdraws.IWithdraws | null>;
|
||||
|
||||
// Получение всех withdrawals для кооператива
|
||||
getWithdraws(coopname: string): Promise<WalletContract.Tables.Withdraws.IWithdraws[]>;
|
||||
}
|
||||
|
||||
export const WALLET_BLOCKCHAIN_PORT = Symbol('WALLET_BLOCKCHAIN_PORT');
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { WalletDomainInteractor } from './interactors/wallet.interactor';
|
||||
import { DocumentDomainModule } from '~/domain/document/document.module';
|
||||
import { InfrastructureModule } from '~/infrastructure/infrastructure.module';
|
||||
|
||||
/**
|
||||
* Доменный модуль Wallet
|
||||
*/
|
||||
@Module({
|
||||
imports: [DocumentDomainModule, InfrastructureModule],
|
||||
providers: [WalletDomainInteractor],
|
||||
exports: [WalletDomainInteractor],
|
||||
})
|
||||
export class WalletDomainModule {}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { BlockchainService } from '../blockchain.service';
|
||||
import { GatewayContract } from 'cooptypes';
|
||||
import { TransactResult } from '@wharfkit/session';
|
||||
import Vault from '~/models/vault.model';
|
||||
import httpStatus from 'http-status';
|
||||
import { HttpApiError } from '~/errors/http-api-error';
|
||||
import type { TransactionResult } from '~/domain/blockchain/types/transaction-result.type';
|
||||
import type {
|
||||
GatewayBlockchainPort,
|
||||
CompleteOutcomeDomainInterface,
|
||||
DeclineOutcomeDomainInterface,
|
||||
} from '~/domain/gateway/ports/gateway-blockchain.port';
|
||||
|
||||
/**
|
||||
* Блокчейн адаптер для gateway
|
||||
* Реализует только управление уже созданными outcomes
|
||||
*/
|
||||
@Injectable()
|
||||
export class GatewayBlockchainAdapter implements GatewayBlockchainPort {
|
||||
private readonly logger = new Logger(GatewayBlockchainAdapter.name);
|
||||
|
||||
constructor(private readonly blockchainService: BlockchainService) {}
|
||||
|
||||
/**
|
||||
* Завершение исходящего платежа
|
||||
*/
|
||||
async completeOutcome(data: CompleteOutcomeDomainInterface): Promise<TransactionResult> {
|
||||
const wif = await Vault.getWif(data.coopname);
|
||||
if (!wif) throw new HttpApiError(httpStatus.BAD_GATEWAY, 'Не найден приватный ключ для совершения операции');
|
||||
|
||||
this.blockchainService.initialize(data.coopname, wif);
|
||||
|
||||
// Преобразуем доменные данные в формат cooptypes контракта
|
||||
const blockchainData: GatewayContract.Actions.CompleteOutcome.ICompleteOutcome = {
|
||||
coopname: data.coopname,
|
||||
outcome_hash: data.outcome_hash,
|
||||
};
|
||||
|
||||
const result = (await this.blockchainService.transact({
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.CompleteOutcome.actionName,
|
||||
authorization: [{ actor: data.coopname, permission: 'active' }],
|
||||
data: blockchainData,
|
||||
})) as TransactResult;
|
||||
|
||||
this.logger.log(`Завершен исходящий платеж: ${data.outcome_hash}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отклонение исходящего платежа
|
||||
*/
|
||||
async declineOutcome(data: DeclineOutcomeDomainInterface): Promise<TransactionResult> {
|
||||
const wif = await Vault.getWif(data.coopname);
|
||||
if (!wif) throw new HttpApiError(httpStatus.BAD_GATEWAY, 'Не найден приватный ключ для совершения операции');
|
||||
|
||||
this.blockchainService.initialize(data.coopname, wif);
|
||||
|
||||
// Преобразуем доменные данные в формат cooptypes контракта
|
||||
const blockchainData: GatewayContract.Actions.DeclineOutcome.IOutDecline = {
|
||||
coopname: data.coopname,
|
||||
outcome_hash: data.outcome_hash,
|
||||
reason: data.reason,
|
||||
};
|
||||
|
||||
const result = (await this.blockchainService.transact({
|
||||
account: GatewayContract.contractName.production,
|
||||
name: GatewayContract.Actions.DeclineOutcome.actionName,
|
||||
authorization: [{ actor: data.coopname, permission: 'active' }],
|
||||
data: blockchainData,
|
||||
})) as TransactResult;
|
||||
|
||||
this.logger.log(`Отклонен исходящий платеж: ${data.outcome_hash}, причина: ${data.reason}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение исходящих платежей из блокчейна
|
||||
*/
|
||||
async getOutcomes(coopname: string): Promise<GatewayContract.Tables.Outcomes.IOutcome[]> {
|
||||
return this.blockchainService.getAllRows(
|
||||
GatewayContract.contractName.production,
|
||||
coopname,
|
||||
GatewayContract.Tables.Outcomes.tableName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение конкретного исходящего платежа
|
||||
*/
|
||||
async getOutcome(coopname: string, outcome_hash: string): Promise<GatewayContract.Tables.Outcomes.IOutcome | null> {
|
||||
try {
|
||||
const outcomes = await this.blockchainService.query(
|
||||
GatewayContract.contractName.production,
|
||||
coopname,
|
||||
GatewayContract.Tables.Outcomes.tableName,
|
||||
{
|
||||
indexPosition: 'secondary',
|
||||
from: outcome_hash,
|
||||
to: outcome_hash,
|
||||
}
|
||||
);
|
||||
|
||||
return outcomes.length > 0 ? outcomes[0] : null;
|
||||
} catch (error: any) {
|
||||
this.logger.warn(`Не удалось получить outcome ${outcome_hash} для ${coopname}: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { BlockchainService } from '../blockchain.service';
|
||||
import { WalletContract } from 'cooptypes';
|
||||
import { TransactResult } from '@wharfkit/session';
|
||||
import Vault from '~/models/vault.model';
|
||||
import httpStatus from 'http-status';
|
||||
import { HttpApiError } from '~/errors/http-api-error';
|
||||
import type { TransactionResult } from '~/domain/blockchain/types/transaction-result.type';
|
||||
import type {
|
||||
WalletBlockchainPort,
|
||||
CreateWithdrawDomainInterface,
|
||||
GenerateReturnStatementDomainInterface,
|
||||
} from '~/domain/wallet/ports/wallet-blockchain.port';
|
||||
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
|
||||
import { DomainToBlockchainUtils } from '../utils/domain-to-blockchain.utils';
|
||||
|
||||
/**
|
||||
* Блокчейн адаптер для wallet
|
||||
* Реализует взаимодействие с контрактом wallet
|
||||
*/
|
||||
@Injectable()
|
||||
export class WalletBlockchainAdapter implements WalletBlockchainPort {
|
||||
private readonly logger = new Logger(WalletBlockchainAdapter.name);
|
||||
|
||||
constructor(
|
||||
private readonly blockchainService: BlockchainService,
|
||||
private readonly domainToBlockchainUtils: DomainToBlockchainUtils
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Создание заявки на вывод средств в контракте wallet
|
||||
*/
|
||||
async createWithdraw(data: CreateWithdrawDomainInterface): Promise<TransactionResult> {
|
||||
const wif = await Vault.getWif(data.coopname);
|
||||
if (!wif) throw new HttpApiError(httpStatus.BAD_GATEWAY, 'Не найден приватный ключ для совершения операции');
|
||||
|
||||
this.blockchainService.initialize(data.coopname, wif);
|
||||
|
||||
// Форматируем quantity к необходимой точности для блокчейна
|
||||
const formattedQuantity = this.domainToBlockchainUtils.formatQuantityWithPrecision(data.quantity);
|
||||
|
||||
// Преобразуем доменные данные в формат cooptypes контракта
|
||||
const blockchainData: WalletContract.Actions.CreateWithdraw.ICreateWithdraw = {
|
||||
coopname: data.coopname,
|
||||
username: data.username,
|
||||
withdraw_hash: data.withdraw_hash,
|
||||
quantity: formattedQuantity,
|
||||
statement: this.domainToBlockchainUtils.convertSignedDocumentToBlockchainFormat(data.statement),
|
||||
};
|
||||
|
||||
const result = (await this.blockchainService.transact({
|
||||
account: WalletContract.contractName.production,
|
||||
name: WalletContract.Actions.CreateWithdraw.actionName,
|
||||
authorization: [{ actor: data.coopname, permission: 'active' }],
|
||||
data: blockchainData,
|
||||
})) as TransactResult;
|
||||
|
||||
this.logger.log(`Создана заявка на вывод средств: ${data.withdraw_hash}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация заявления на возврат паевого взноса
|
||||
*/
|
||||
async generateReturnStatement(_data: GenerateReturnStatementDomainInterface): Promise<ISignedDocumentDomainInterface> {
|
||||
// TODO: Реализовать генерацию заявления через документный сервис
|
||||
// Пока возвращаем заглушку
|
||||
throw new Error('Метод generateReturnStatement еще не реализован');
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение данных из wallet контракта
|
||||
*/
|
||||
async getWithdraw(coopname: string, withdraw_hash: string): Promise<WalletContract.Tables.Withdraws.IWithdraws | null> {
|
||||
try {
|
||||
const withdraws = await this.blockchainService.query(
|
||||
WalletContract.contractName.production,
|
||||
coopname,
|
||||
WalletContract.Tables.Withdraws.tableName,
|
||||
{
|
||||
indexPosition: 'secondary',
|
||||
from: withdraw_hash,
|
||||
to: withdraw_hash,
|
||||
}
|
||||
);
|
||||
|
||||
return withdraws.length > 0 ? withdraws[0] : null;
|
||||
} catch (error: any) {
|
||||
this.logger.warn(`Не удалось получить withdraw ${withdraw_hash} для ${coopname}: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение всех withdrawals для кооператива
|
||||
*/
|
||||
async getWithdraws(coopname: string): Promise<WalletContract.Tables.Withdraws.IWithdraws[]> {
|
||||
return this.blockchainService.getAllRows(
|
||||
WalletContract.contractName.production,
|
||||
coopname,
|
||||
WalletContract.Tables.Withdraws.tableName
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,10 @@ import { MEET_BLOCKCHAIN_PORT } from '~/domain/meet/ports/meet-blockchain.port';
|
||||
import { MeetBlockchainAdapter } from './adapters/meet-blockchain.adapter';
|
||||
import { DomainToBlockchainUtils } from './utils/domain-to-blockchain.utils';
|
||||
import { DomainModule } from '~/domain/domain.module';
|
||||
import { GatewayBlockchainAdapter } from './adapters/gateway-blockchain.adapter';
|
||||
import { WALLET_BLOCKCHAIN_PORT } from '~/domain/wallet/ports/wallet-blockchain.port';
|
||||
import { GATEWAY_BLOCKCHAIN_PORT } from '~/domain/gateway/ports/gateway-blockchain.port';
|
||||
import { WalletBlockchainAdapter } from './adapters/wallet-blockchain.adapter';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
@@ -49,9 +53,20 @@ import { DomainModule } from '~/domain/domain.module';
|
||||
provide: MEET_BLOCKCHAIN_PORT,
|
||||
useClass: MeetBlockchainAdapter,
|
||||
},
|
||||
// Провайдеры для gateway
|
||||
{
|
||||
provide: GATEWAY_BLOCKCHAIN_PORT,
|
||||
useClass: GatewayBlockchainAdapter,
|
||||
},
|
||||
// Провайдеры для wallet
|
||||
{
|
||||
provide: WALLET_BLOCKCHAIN_PORT,
|
||||
useClass: WalletBlockchainAdapter,
|
||||
},
|
||||
DomainToBlockchainUtils,
|
||||
],
|
||||
exports: [
|
||||
BlockchainService,
|
||||
BLOCKCHAIN_PORT,
|
||||
BRANCH_BLOCKCHAIN_PORT,
|
||||
SYSTEM_BLOCKCHAIN_PORT,
|
||||
@@ -59,6 +74,8 @@ import { DomainModule } from '~/domain/domain.module';
|
||||
SOVIET_BLOCKCHAIN_PORT,
|
||||
COOPLACE_BLOCKCHAIN_PORT,
|
||||
MEET_BLOCKCHAIN_PORT,
|
||||
GATEWAY_BLOCKCHAIN_PORT,
|
||||
WALLET_BLOCKCHAIN_PORT,
|
||||
DomainToBlockchainUtils,
|
||||
],
|
||||
})
|
||||
|
||||
+57
-4
@@ -1,6 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Cooperative } from 'cooptypes';
|
||||
import moment from 'moment';
|
||||
import config from '~/config/config';
|
||||
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
|
||||
|
||||
/**
|
||||
* Утилитарный класс для конвертации доменных объектов в инфраструктурные типы блокчейна
|
||||
@@ -12,9 +14,7 @@ export class DomainToBlockchainUtils {
|
||||
* @param document Доменный объект подписанного документа
|
||||
* @returns Объект документа в формате блокчейна
|
||||
*/
|
||||
convertSignedDocumentToBlockchainFormat(
|
||||
document: Cooperative.Document.ISignedDocument2
|
||||
): Cooperative.Document.IChainDocument2 {
|
||||
convertSignedDocumentToBlockchainFormat(document: ISignedDocumentDomainInterface): Cooperative.Document.IChainDocument2 {
|
||||
return {
|
||||
version: document.version,
|
||||
hash: document.hash,
|
||||
@@ -25,6 +25,22 @@ export class DomainToBlockchainUtils {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразует документ из блокчейна в доменный интерфейс
|
||||
* @param chainDoc Документ из блокчейна
|
||||
* @returns Документ в доменном формате
|
||||
*/
|
||||
convertBlockchainDocumentToDomainFormat(chainDoc: Cooperative.Document.IChainDocument2): ISignedDocumentDomainInterface {
|
||||
return {
|
||||
version: chainDoc.version,
|
||||
hash: chainDoc.hash,
|
||||
doc_hash: chainDoc.doc_hash,
|
||||
meta_hash: chainDoc.meta_hash,
|
||||
meta: typeof chainDoc.meta === 'string' ? (chainDoc.meta === '' ? {} : JSON.parse(chainDoc.meta)) : chainDoc.meta,
|
||||
signatures: chainDoc.signatures,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразует Date в формат EOSIO time_point_sec для блокчейна
|
||||
* @param date Дата или строка с датой
|
||||
@@ -35,7 +51,44 @@ export class DomainToBlockchainUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразует объект IChainDocument2 в ISignedDocument2
|
||||
* Форматирование quantity с проверкой символа и использованием precision из конфига
|
||||
* @param quantity Строка в формате "число символ" (например, "100.0 AXON")
|
||||
* @returns Отформатированная строка quantity для блокчейна
|
||||
*/
|
||||
formatQuantityWithPrecision(quantity: string): string {
|
||||
// Извлекаем число и символ
|
||||
const parts = quantity.split(' ');
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(`Неверный формат quantity: ${quantity}. Ожидается "число символ"`);
|
||||
}
|
||||
|
||||
const [amount, symbol] = parts;
|
||||
const numericAmount = parseFloat(amount);
|
||||
|
||||
if (isNaN(numericAmount)) {
|
||||
throw new Error(`Некорректное числовое значение в quantity: ${amount}`);
|
||||
}
|
||||
|
||||
// Проверяем символ на соответствие с конфигом
|
||||
let precision: number;
|
||||
if (symbol === config.blockchain.root_symbol) {
|
||||
precision = config.blockchain.root_precision;
|
||||
} else if (symbol === config.blockchain.root_govern_symbol) {
|
||||
precision = config.blockchain.root_govern_precision;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Неподдерживаемый символ: ${symbol}. Поддерживаются только: ${config.blockchain.root_symbol}, ${config.blockchain.root_govern_symbol}`
|
||||
);
|
||||
}
|
||||
|
||||
// Форматируем к необходимому количеству знаков после запятой
|
||||
const formattedAmount = numericAmount.toFixed(precision);
|
||||
|
||||
return `${formattedAmount} ${symbol}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразует объект IChainDocument2 в ISignedDocument2 (для совместимости)
|
||||
* @param chainDoc Документ из блокчейна
|
||||
* @returns Документ в формате ISignedDocument2
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Column, CreateDateColumn, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm';
|
||||
import type { ISignedDocumentDomainInterface } from '~/domain/document/interfaces/signed-document-domain.interface';
|
||||
|
||||
@Entity('payments')
|
||||
export class PaymentEntity {
|
||||
@PrimaryColumn()
|
||||
hash!: string;
|
||||
|
||||
@Column()
|
||||
coopname!: string;
|
||||
|
||||
@Column()
|
||||
username!: string;
|
||||
|
||||
@Column()
|
||||
quantity!: string;
|
||||
|
||||
@Column()
|
||||
symbol!: string;
|
||||
|
||||
@Column()
|
||||
method_id!: string;
|
||||
|
||||
@Column()
|
||||
status!: string;
|
||||
|
||||
@Column()
|
||||
type!: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at!: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
memo?: string;
|
||||
|
||||
@Column('json', { nullable: true })
|
||||
payment_details?: any;
|
||||
|
||||
@Column('json', { nullable: true })
|
||||
blockchain_data?: any;
|
||||
|
||||
@Column('json', { nullable: true })
|
||||
statement?: ISignedDocumentDomainInterface;
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { PaymentEntity } from '../entities/payment.entity';
|
||||
import { GatewayPaymentStatusEnum } from '~/domain/gateway/enums/gateway-payment-status.enum';
|
||||
import { GatewayPaymentTypeEnum } from '~/domain/gateway/enums/gateway-payment-type.enum';
|
||||
import type { PaymentRepository } from '~/domain/gateway/repositories/payment.repository';
|
||||
import type { PaymentDomainInterface } from '~/domain/gateway/interfaces/payment-domain.interface';
|
||||
import type { OutgoingPaymentDomainInterface } from '~/domain/gateway/interfaces/outgoing-payment-domain.interface';
|
||||
import type {
|
||||
PaginationInputDomainInterface,
|
||||
PaginationResultDomainInterface,
|
||||
} from '~/domain/common/interfaces/pagination.interface';
|
||||
import type { GetOutgoingPaymentsInputDomainInterface } from '~/domain/gateway/interfaces/get-outgoing-payments-input-domain.interface';
|
||||
|
||||
@Injectable()
|
||||
export class TypeOrmPaymentRepository implements PaymentRepository {
|
||||
constructor(
|
||||
@InjectRepository(PaymentEntity)
|
||||
private readonly paymentRepository: Repository<PaymentEntity>
|
||||
) {}
|
||||
|
||||
async findByHash(hash: string): Promise<PaymentDomainInterface | null> {
|
||||
const payment = await this.paymentRepository.findOneBy({ hash });
|
||||
if (!payment) return null;
|
||||
return this.mapToDomainEntity(payment);
|
||||
}
|
||||
|
||||
async create(data: PaymentDomainInterface): Promise<PaymentDomainInterface> {
|
||||
const payment = new PaymentEntity();
|
||||
payment.hash = data.hash;
|
||||
payment.coopname = data.coopname;
|
||||
payment.username = data.username;
|
||||
payment.quantity = data.quantity;
|
||||
payment.symbol = data.symbol;
|
||||
payment.method_id = data.method_id;
|
||||
payment.status = data.status;
|
||||
payment.type = data.type;
|
||||
payment.created_at = data.created_at;
|
||||
payment.memo = data.memo;
|
||||
payment.payment_details = data.payment_details;
|
||||
payment.blockchain_data = data.blockchain_data;
|
||||
payment.statement = data.statement;
|
||||
|
||||
const createdEntity = await this.paymentRepository.save(payment);
|
||||
return this.mapToDomainEntity(createdEntity);
|
||||
}
|
||||
|
||||
async updatePaymentStatus(
|
||||
hash: string,
|
||||
status: GatewayPaymentStatusEnum,
|
||||
reason?: string
|
||||
): Promise<PaymentDomainInterface | null> {
|
||||
const payment = await this.paymentRepository.findOneBy({ hash });
|
||||
if (!payment) return null;
|
||||
|
||||
payment.status = status;
|
||||
payment.updated_at = new Date();
|
||||
if (reason) {
|
||||
payment.memo = reason;
|
||||
}
|
||||
|
||||
const updatedEntity = await this.paymentRepository.save(payment);
|
||||
return this.mapToDomainEntity(updatedEntity);
|
||||
}
|
||||
|
||||
async updatePaymentWithBlockchainData(hash: string, blockchain_data: any): Promise<PaymentDomainInterface | null> {
|
||||
const payment = await this.paymentRepository.findOneBy({ hash });
|
||||
if (!payment) return null;
|
||||
|
||||
payment.blockchain_data = blockchain_data;
|
||||
payment.updated_at = new Date();
|
||||
|
||||
const updatedEntity = await this.paymentRepository.save(payment);
|
||||
return this.mapToDomainEntity(updatedEntity);
|
||||
}
|
||||
|
||||
async getOutgoingPayments(
|
||||
filters: GetOutgoingPaymentsInputDomainInterface,
|
||||
options: PaginationInputDomainInterface
|
||||
): Promise<PaginationResultDomainInterface<OutgoingPaymentDomainInterface>> {
|
||||
const queryBuilder = this.paymentRepository.createQueryBuilder('payment');
|
||||
|
||||
queryBuilder.where('payment.type = :type', { type: GatewayPaymentTypeEnum.OUTGOING });
|
||||
|
||||
if (filters.coopname) {
|
||||
queryBuilder.andWhere('payment.coopname = :coopname', { coopname: filters.coopname });
|
||||
}
|
||||
|
||||
if (filters.username) {
|
||||
queryBuilder.andWhere('payment.username = :username', { username: filters.username });
|
||||
}
|
||||
|
||||
if (filters.status) {
|
||||
queryBuilder.andWhere('payment.status = :status', { status: filters.status });
|
||||
}
|
||||
|
||||
queryBuilder.orderBy('payment.created_at', 'DESC');
|
||||
|
||||
const limit = options.limit || 10;
|
||||
const page = options.page || 1;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
queryBuilder.take(limit).skip(skip);
|
||||
|
||||
const [payments, totalCount] = await queryBuilder.getManyAndCount();
|
||||
|
||||
const items = payments.map((payment) => this.mapToDomainEntity(payment) as OutgoingPaymentDomainInterface);
|
||||
|
||||
return {
|
||||
items,
|
||||
totalCount,
|
||||
totalPages: Math.ceil(totalCount / limit),
|
||||
currentPage: page,
|
||||
};
|
||||
}
|
||||
|
||||
private mapToDomainEntity(entity: PaymentEntity): PaymentDomainInterface {
|
||||
return {
|
||||
hash: entity.hash,
|
||||
coopname: entity.coopname,
|
||||
username: entity.username,
|
||||
quantity: entity.quantity,
|
||||
symbol: entity.symbol,
|
||||
method_id: entity.method_id,
|
||||
status: entity.status as GatewayPaymentStatusEnum,
|
||||
type: entity.type as GatewayPaymentTypeEnum,
|
||||
created_at: entity.created_at,
|
||||
updated_at: entity.updated_at,
|
||||
memo: entity.memo,
|
||||
payment_details: entity.payment_details,
|
||||
blockchain_data: entity.blockchain_data,
|
||||
statement: entity.statement,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@ import { TypeOrmCandidateRepository } from './repositories/typeorm-candidate.rep
|
||||
import { MeetProcessedEntity } from './entities/meet-processed.entity';
|
||||
import { MEET_PROCESSED_REPOSITORY } from '~/domain/meet/repositories/meet-processed.repository';
|
||||
import { TypeOrmMeetProcessedRepository } from './repositories/typeorm-meet-processed.repository';
|
||||
import { PaymentEntity } from './entities/payment.entity';
|
||||
import { PAYMENT_REPOSITORY } from '~/domain/gateway/repositories/payment.repository';
|
||||
import { TypeOrmPaymentRepository } from './repositories/typeorm-payment.repository';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
@@ -43,6 +46,7 @@ import { TypeOrmMeetProcessedRepository } from './repositories/typeorm-meet-proc
|
||||
MeetProcessedEntity,
|
||||
MigrationEntity,
|
||||
CandidateEntity,
|
||||
PaymentEntity,
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
@@ -70,6 +74,10 @@ import { TypeOrmMeetProcessedRepository } from './repositories/typeorm-meet-proc
|
||||
provide: CANDIDATE_REPOSITORY,
|
||||
useClass: TypeOrmCandidateRepository,
|
||||
},
|
||||
{
|
||||
provide: PAYMENT_REPOSITORY,
|
||||
useClass: TypeOrmPaymentRepository,
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
NestTypeOrmModule,
|
||||
@@ -79,6 +87,7 @@ import { TypeOrmMeetProcessedRepository } from './repositories/typeorm-meet-proc
|
||||
MEET_PROCESSED_REPOSITORY,
|
||||
MIGRATION_REPOSITORY,
|
||||
CANDIDATE_REPOSITORY,
|
||||
PAYMENT_REPOSITORY,
|
||||
],
|
||||
})
|
||||
export class TypeOrmModule {}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// app.module.ts
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DatabaseModule } from './database/database.module';
|
||||
import { GraphqlModule } from './graphql/graphql.module';
|
||||
|
||||
@@ -18,6 +18,8 @@ import { PaymentModule } from './payment/payment.module';
|
||||
import { CooplaceModule } from './cooplace/cooplace.module';
|
||||
import { DesktopModule } from './desktop/desktop.module';
|
||||
import { MeetModule } from './meet/meet.module';
|
||||
import { GatewayModule } from './gateway/gateway.module';
|
||||
import { WalletModule } from './wallet/wallet.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -39,6 +41,8 @@ import { MeetModule } from './meet/meet.module';
|
||||
ParticipantModule,
|
||||
CooplaceModule,
|
||||
MeetModule,
|
||||
GatewayModule,
|
||||
WalletModule,
|
||||
],
|
||||
exports: [
|
||||
AccountModule,
|
||||
@@ -59,6 +63,8 @@ import { MeetModule } from './meet/meet.module';
|
||||
ParticipantModule,
|
||||
CooplaceModule,
|
||||
MeetModule,
|
||||
GatewayModule,
|
||||
WalletModule,
|
||||
],
|
||||
})
|
||||
export class ApplicationModule {}
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { InputType, Field, IntersectionType, OmitType } from '@nestjs/graphql';
|
||||
import { IsString, IsNumber } from 'class-validator';
|
||||
import { Cooperative } from 'cooptypes';
|
||||
import { GenerateMetaDocumentInputDTO } from '~/modules/document/dto/generate-meta-document-input.dto';
|
||||
import { MetaDocumentInputDTO } from '~/modules/document/dto/meta-document-input.dto';
|
||||
import { SignedDigitalDocumentInputDTO } from '~/modules/document/dto/signed-digital-document-input.dto';
|
||||
|
||||
type ExcludeCommonProps<T> = Omit<T, 'coopname' | 'username' | 'registry_id'>;
|
||||
|
||||
type action = Cooperative.Registry.ReturnByMoneyDecision.Action;
|
||||
|
||||
@InputType('BaseReturnByMoneyDecisionMetaDocumentInput')
|
||||
class BaseReturnByMoneyDecisionMetaDocumentInputDTO implements ExcludeCommonProps<action> {
|
||||
@Field({ description: 'ID решения совета' })
|
||||
@IsNumber()
|
||||
decision_id!: number;
|
||||
|
||||
@Field({ description: 'Хэш платежа' })
|
||||
@IsString()
|
||||
payment_hash!: string;
|
||||
|
||||
@Field({ description: 'Сумма к возврату' })
|
||||
@IsString()
|
||||
amount!: string;
|
||||
|
||||
@Field({ description: 'Валюта' })
|
||||
@IsString()
|
||||
currency!: string;
|
||||
}
|
||||
|
||||
@InputType('ReturnByMoneyDecisionGenerateDocumentInput')
|
||||
export class ReturnByMoneyDecisionGenerateDocumentInputDTO
|
||||
extends IntersectionType(
|
||||
BaseReturnByMoneyDecisionMetaDocumentInputDTO,
|
||||
OmitType(GenerateMetaDocumentInputDTO, ['registry_id'] as const)
|
||||
)
|
||||
implements action
|
||||
{
|
||||
registry_id!: number;
|
||||
}
|
||||
|
||||
@InputType('ReturnByMoneyDecisionSignedMetaDocumentInput')
|
||||
export class ReturnByMoneyDecisionSignedMetaDocumentInputDTO
|
||||
extends IntersectionType(BaseReturnByMoneyDecisionMetaDocumentInputDTO, MetaDocumentInputDTO)
|
||||
implements action {}
|
||||
|
||||
@InputType('ReturnByMoneyDecisionSignedDocumentInput')
|
||||
export class ReturnByMoneyDecisionSignedDocumentInputDTO extends SignedDigitalDocumentInputDTO {
|
||||
@Field(() => ReturnByMoneyDecisionSignedMetaDocumentInputDTO, {
|
||||
description: 'Метаинформация для документа решения совета о возврате паевого взноса',
|
||||
})
|
||||
public readonly meta!: ReturnByMoneyDecisionSignedMetaDocumentInputDTO;
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { InputType, Field, IntersectionType, OmitType } from '@nestjs/graphql';
|
||||
import { IsString } from 'class-validator';
|
||||
import { Cooperative } from 'cooptypes';
|
||||
import { GenerateMetaDocumentInputDTO } from '~/modules/document/dto/generate-meta-document-input.dto';
|
||||
import { MetaDocumentInputDTO } from '~/modules/document/dto/meta-document-input.dto';
|
||||
import { SignedDigitalDocumentInputDTO } from '~/modules/document/dto/signed-digital-document-input.dto';
|
||||
|
||||
// утилита для выборки повторяющихся параметров из базовых интерфейсов
|
||||
type ExcludeCommonProps<T> = Omit<T, 'coopname' | 'username' | 'registry_id'>;
|
||||
|
||||
// интерфейс параметров для генерации
|
||||
type action = Cooperative.Registry.ReturnByMoney.Action;
|
||||
|
||||
@InputType('RequestInput')
|
||||
class RequestInputDTO implements Cooperative.Registry.ReturnByMoney.IMoneyReturnRequest {
|
||||
@Field({ description: 'ID платежного метода' })
|
||||
@IsString()
|
||||
method_id!: string;
|
||||
|
||||
@Field({ description: 'Сумма к возврату' })
|
||||
@IsString()
|
||||
amount!: string;
|
||||
|
||||
@Field({ description: 'Валюта' })
|
||||
@IsString()
|
||||
currency!: string;
|
||||
}
|
||||
|
||||
@InputType(`BaseReturnByMoneyMetaDocumentInput`)
|
||||
class BaseReturnByMoneyMetaDocumentInputDTO implements ExcludeCommonProps<action> {
|
||||
@Field(() => RequestInputDTO, { description: 'Данные запроса на возврат денежных средств' })
|
||||
request!: RequestInputDTO;
|
||||
}
|
||||
|
||||
@InputType(`ReturnByMoneyGenerateDocumentInput`)
|
||||
export class ReturnByMoneyGenerateDocumentInputDTO
|
||||
extends IntersectionType(
|
||||
BaseReturnByMoneyMetaDocumentInputDTO,
|
||||
OmitType(GenerateMetaDocumentInputDTO, ['registry_id'] as const)
|
||||
)
|
||||
implements action
|
||||
{
|
||||
registry_id!: number;
|
||||
}
|
||||
|
||||
@InputType(`ReturnByMoneySignedMetaDocumentInput`)
|
||||
export class ReturnByMoneySignedMetaDocumentInputDTO
|
||||
extends IntersectionType(BaseReturnByMoneyMetaDocumentInputDTO, MetaDocumentInputDTO)
|
||||
implements action {}
|
||||
|
||||
@InputType(`ReturnByMoneySignedDocumentInput`)
|
||||
export class ReturnByMoneySignedDocumentInputDTO extends SignedDigitalDocumentInputDTO {
|
||||
@Field(() => ReturnByMoneySignedMetaDocumentInputDTO, {
|
||||
description: 'Метаинформация для документа заявления на возврат паевого взноса денежными средствами',
|
||||
})
|
||||
public readonly meta!: ReturnByMoneySignedMetaDocumentInputDTO;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { IsString, IsOptional, IsNotEmpty, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import type { CreateOutgoingPaymentInputDomainInterface } from '~/domain/gateway/interfaces/create-outgoing-payment-input-domain.interface';
|
||||
import { SignedDigitalDocumentInputDTO } from '~/modules/document/dto/signed-digital-document-input.dto';
|
||||
|
||||
/**
|
||||
* DTO для создания исходящего платежа
|
||||
*/
|
||||
@InputType('CreateOutgoingPaymentInput')
|
||||
export class CreateOutgoingPaymentInputDTO implements CreateOutgoingPaymentInputDomainInterface {
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
coopname!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
username!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
quantity!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
symbol!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
method_id!: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
memo?: string;
|
||||
|
||||
@Field(() => SignedDigitalDocumentInputDTO, {
|
||||
description: 'Подписанный документ заявления на возврат денежными средствами',
|
||||
})
|
||||
@ValidateNested()
|
||||
@Type(() => SignedDigitalDocumentInputDTO)
|
||||
statement!: SignedDigitalDocumentInputDTO;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { ObjectType, Field, ID, registerEnumType } from '@nestjs/graphql';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import { GatewayPaymentStatusEnum } from '~/domain/gateway/enums/gateway-payment-status.enum';
|
||||
import { GatewayPaymentTypeEnum } from '~/domain/gateway/enums/gateway-payment-type.enum';
|
||||
|
||||
// Регистрируем enum'ы для GraphQL
|
||||
registerEnumType(GatewayPaymentStatusEnum, {
|
||||
name: 'gatewayPaymentStatus',
|
||||
description: 'Статус платежа в системе Gateway',
|
||||
});
|
||||
|
||||
registerEnumType(GatewayPaymentTypeEnum, {
|
||||
name: 'gatewayPaymentType',
|
||||
description: 'Тип платежа в системе Gateway',
|
||||
});
|
||||
|
||||
/**
|
||||
* Универсальное DTO платежа для GraphQL
|
||||
* Работает как для входящих, так и для исходящих платежей
|
||||
*/
|
||||
@ObjectType('GatewayPayment')
|
||||
export class GatewayPaymentDTO {
|
||||
@Field(() => ID, { nullable: true, description: 'Уникальный идентификатор платежа' })
|
||||
id?: string;
|
||||
|
||||
@Field({ description: 'Название кооператива' })
|
||||
coopname!: string;
|
||||
|
||||
@Field({ description: 'Имя пользователя' })
|
||||
username!: string;
|
||||
|
||||
@Field({ description: 'Универсальный хеш платежа' })
|
||||
hash!: string;
|
||||
|
||||
@Field({ description: 'Хеш исходящего платежа', nullable: true })
|
||||
outcome_hash?: string;
|
||||
|
||||
@Field({ description: 'Хеш входящего платежа', nullable: true })
|
||||
income_hash?: string;
|
||||
|
||||
@Field({ description: 'Количество' })
|
||||
quantity!: string;
|
||||
|
||||
@Field({ description: 'Символ валюты' })
|
||||
symbol!: string;
|
||||
|
||||
@Field({ description: 'ID платежного метода' })
|
||||
method_id!: string;
|
||||
|
||||
@Field(() => GatewayPaymentStatusEnum, { description: 'Статус платежа' })
|
||||
status!: GatewayPaymentStatusEnum;
|
||||
|
||||
@Field(() => GatewayPaymentTypeEnum, { description: 'Тип платежа: входящий или исходящий' })
|
||||
type!: GatewayPaymentTypeEnum;
|
||||
|
||||
@Field({ description: 'Дата создания' })
|
||||
created_at!: Date;
|
||||
|
||||
@Field({ description: 'Дата последнего обновления', nullable: true })
|
||||
updated_at?: Date;
|
||||
|
||||
@Field({ description: 'Дополнительная информация', nullable: true })
|
||||
memo?: string;
|
||||
|
||||
@Field({ description: 'Детали платежа', nullable: true })
|
||||
payment_details?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { description: 'Данные из блокчейна', nullable: true })
|
||||
blockchain_data?: any;
|
||||
|
||||
// Вычисляемые поля
|
||||
@Field({ description: 'Форматированная сумма' })
|
||||
formatted_amount!: string;
|
||||
|
||||
@Field({ description: 'Человекочитаемый статус' })
|
||||
status_label!: string;
|
||||
|
||||
@Field({ description: 'Человекочитаемый тип платежа' })
|
||||
type_label!: string;
|
||||
|
||||
@Field({ description: 'Может ли статус быть изменен' })
|
||||
can_change_status!: boolean;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { IsString, IsEnum, IsOptional, IsNotEmpty } from 'class-validator';
|
||||
import type { GetOutgoingPaymentsInputDomainInterface } from '~/domain/gateway/interfaces/get-outgoing-payments-input-domain.interface';
|
||||
|
||||
/**
|
||||
* DTO для получения исходящих платежей
|
||||
*/
|
||||
@InputType('GetOutgoingPaymentsInput')
|
||||
export class GetOutgoingPaymentsInputDTO implements GetOutgoingPaymentsInputDomainInterface {
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
coopname?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
username?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsEnum(['pending', 'processing', 'completed', 'failed', 'cancelled'])
|
||||
@IsOptional()
|
||||
status?: 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { ObjectType, Field, ID } from '@nestjs/graphql';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import { GatewayPaymentStatusEnum } from '~/domain/gateway/enums/gateway-payment-status.enum';
|
||||
import { GatewayPaymentTypeEnum } from '~/domain/gateway/enums/gateway-payment-type.enum';
|
||||
|
||||
/**
|
||||
* DTO исходящего платежа для GraphQL
|
||||
*/
|
||||
@ObjectType('OutgoingPayment')
|
||||
export class OutgoingGatewayPaymentDTO {
|
||||
@Field(() => ID, { nullable: true, description: 'Уникальный идентификатор платежа' })
|
||||
id?: string;
|
||||
|
||||
@Field({ description: 'Название кооператива' })
|
||||
coopname!: string;
|
||||
|
||||
@Field({ description: 'Имя пользователя' })
|
||||
username!: string;
|
||||
|
||||
@Field({ description: 'Хеш исходящего платежа' })
|
||||
hash!: string;
|
||||
|
||||
@Field({ description: 'Количество' })
|
||||
quantity!: string;
|
||||
|
||||
@Field({ description: 'Символ валюты' })
|
||||
symbol!: string;
|
||||
|
||||
@Field({ description: 'ID платежного метода' })
|
||||
method_id!: string;
|
||||
|
||||
@Field(() => GatewayPaymentStatusEnum, { description: 'Статус платежа' })
|
||||
status!: GatewayPaymentStatusEnum;
|
||||
|
||||
@Field(() => GatewayPaymentTypeEnum, { description: 'Тип платежа (всегда outgoing)' })
|
||||
type!: GatewayPaymentTypeEnum;
|
||||
|
||||
@Field({ description: 'Дата создания' })
|
||||
created_at!: Date;
|
||||
|
||||
@Field({ description: 'Дата последнего обновления', nullable: true })
|
||||
updated_at?: Date;
|
||||
|
||||
@Field({ description: 'Дополнительная информация', nullable: true })
|
||||
memo?: string;
|
||||
|
||||
@Field({ description: 'Детали платежа', nullable: true })
|
||||
payment_details?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { description: 'Данные из блокчейна', nullable: true })
|
||||
blockchain_data?: any;
|
||||
|
||||
// Вычисляемые поля
|
||||
@Field({ description: 'Форматированная сумма' })
|
||||
formatted_amount!: string;
|
||||
|
||||
@Field({ description: 'Человекочитаемый статус' })
|
||||
status_label!: string;
|
||||
|
||||
@Field({ description: 'Может ли статус быть изменен' })
|
||||
can_change_status!: boolean;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { InputType, Field } from '@nestjs/graphql';
|
||||
import { IsString, IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { GatewayPaymentStatusEnum } from '~/domain/gateway/enums/gateway-payment-status.enum';
|
||||
import { GatewayPaymentTypeEnum } from '~/domain/gateway/enums/gateway-payment-type.enum';
|
||||
import type { UpdatePaymentStatusInputDomainInterface } from '~/domain/gateway/interfaces/update-payment-status-input-domain.interface';
|
||||
|
||||
/**
|
||||
* DTO для обновления статуса платежа
|
||||
*/
|
||||
@InputType('UpdatePaymentStatusInput')
|
||||
export class UpdatePaymentStatusInputDTO implements UpdatePaymentStatusInputDomainInterface {
|
||||
@Field({ description: 'Универсальный хеш платежа' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
hash!: string;
|
||||
|
||||
@Field({ description: 'Название кооператива' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
coopname!: string;
|
||||
|
||||
@Field({ description: 'Тип платежа' })
|
||||
@IsEnum(GatewayPaymentTypeEnum)
|
||||
type!: GatewayPaymentTypeEnum;
|
||||
|
||||
@Field({ description: 'Новый статус платежа' })
|
||||
@IsEnum(GatewayPaymentStatusEnum)
|
||||
status!: GatewayPaymentStatusEnum;
|
||||
|
||||
@Field({ description: 'Причина изменения статуса (для статуса failed)', nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
reason?: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { GatewayResolver } from './resolvers/gateway.resolver';
|
||||
import { GatewayService } from './services/gateway.service';
|
||||
import { GatewayDomainModule } from '~/domain/gateway/gateway-domain.module';
|
||||
|
||||
@Module({
|
||||
imports: [GatewayDomainModule],
|
||||
providers: [GatewayResolver, GatewayService],
|
||||
exports: [GatewayService],
|
||||
})
|
||||
export class GatewayModule {}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { GqlJwtAuthGuard } from '~/modules/auth/guards/graphql-jwt-auth.guard';
|
||||
import { RolesGuard } from '~/modules/auth/guards/roles.guard';
|
||||
import { AuthRoles } from '~/modules/auth/decorators/auth.decorator';
|
||||
import { GatewayService } from '../services/gateway.service';
|
||||
import { UpdatePaymentStatusInputDTO } from '../dto/update-payment-status-input.dto';
|
||||
import { GetOutgoingPaymentsInputDTO } from '../dto/get-outgoing-payments-input.dto';
|
||||
import { OutgoingGatewayPaymentDTO } from '../dto/outgoing-payment.dto';
|
||||
import { GatewayPaymentDTO } from '../dto/gateway-payment.dto';
|
||||
import { createPaginationResult, PaginationInputDTO, PaginationResult } from '~/modules/common/dto/pagination.dto';
|
||||
|
||||
const paginatedPaymentsResult = createPaginationResult(GatewayPaymentDTO, 'PaginatedGatewayPayments');
|
||||
|
||||
/**
|
||||
* GraphQL резолвер для gateway
|
||||
* Обеспечивает API для управления исходящими платежами
|
||||
*/
|
||||
@Resolver()
|
||||
export class GatewayResolver {
|
||||
constructor(private readonly gatewayService: GatewayService) {}
|
||||
|
||||
/**
|
||||
* Query: Получить список всех платежей (универсальный)
|
||||
* Готовится для будущей поддержки входящих платежей
|
||||
*/
|
||||
@Query(() => paginatedPaymentsResult, {
|
||||
name: 'getGatewayPayments',
|
||||
description: 'Получить список всех платежей (универсальный метод, поддержка входящих платежей в будущем).',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard, RolesGuard)
|
||||
@AuthRoles(['chairman', 'member'])
|
||||
async getGatewayPayments(
|
||||
@Args('filters') filters: GetOutgoingPaymentsInputDTO,
|
||||
@Args('options') options: PaginationInputDTO
|
||||
): Promise<PaginationResult<GatewayPaymentDTO>> {
|
||||
return this.gatewayService.getPayments(filters, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutation: Обновить статус платежа
|
||||
*/
|
||||
@Mutation(() => OutgoingGatewayPaymentDTO, {
|
||||
name: 'updatePaymentStatus',
|
||||
description: 'Обновить статус платежа.',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard, RolesGuard)
|
||||
@AuthRoles(['chairman', 'member'])
|
||||
async updatePaymentStatus(@Args('input') input: UpdatePaymentStatusInputDTO): Promise<OutgoingGatewayPaymentDTO> {
|
||||
return this.gatewayService.updatePaymentStatus(input);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GatewayInteractor } from '~/domain/gateway/interactors/gateway.interactor';
|
||||
import type { UpdatePaymentStatusInputDTO } from '../dto/update-payment-status-input.dto';
|
||||
import type { GetOutgoingPaymentsInputDTO } from '../dto/get-outgoing-payments-input.dto';
|
||||
import type { OutgoingGatewayPaymentDTO } from '../dto/outgoing-payment.dto';
|
||||
import type { GatewayPaymentDTO } from '../dto/gateway-payment.dto';
|
||||
import type { PaginationInputDTO, PaginationResult } from '~/modules/common/dto/pagination.dto';
|
||||
|
||||
@Injectable()
|
||||
export class GatewayService {
|
||||
constructor(private readonly gatewayInteractor: GatewayInteractor) {}
|
||||
|
||||
/**
|
||||
* Получить список всех платежей (универсальный метод)
|
||||
*/
|
||||
async getPayments(
|
||||
filters: GetOutgoingPaymentsInputDTO,
|
||||
options: PaginationInputDTO
|
||||
): Promise<PaginationResult<GatewayPaymentDTO>> {
|
||||
const result = await this.gatewayInteractor.getOutgoingPayments(filters, options);
|
||||
|
||||
return {
|
||||
items: result.items.map((item) => {
|
||||
const dto = item.toDTO();
|
||||
return {
|
||||
...dto,
|
||||
type: 'outgoing',
|
||||
} as GatewayPaymentDTO;
|
||||
}),
|
||||
totalCount: result.totalCount,
|
||||
totalPages: result.totalPages,
|
||||
currentPage: result.currentPage,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить статус платежа (универсальный метод)
|
||||
*/
|
||||
async updatePaymentStatus(input: UpdatePaymentStatusInputDTO): Promise<OutgoingGatewayPaymentDTO> {
|
||||
if (input.type !== 'outgoing') {
|
||||
throw new Error('В настоящее время поддерживаются только исходящие платежи');
|
||||
}
|
||||
|
||||
const result = await this.gatewayInteractor.updatePaymentStatus(input);
|
||||
return result.toDTO() as OutgoingGatewayPaymentDTO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { IsString, IsOptional, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { SignedDigitalDocumentInputDTO } from '~/modules/document/dto/signed-digital-document-input.dto';
|
||||
import type { CreateWithdrawInputDomainInterface } from '~/domain/wallet/interfaces/create-withdraw-input-domain.interface';
|
||||
|
||||
/**
|
||||
* DTO для создания заявки на вывод средств
|
||||
*/
|
||||
@InputType('CreateWithdrawInput')
|
||||
export class CreateWithdrawInputDTO implements CreateWithdrawInputDomainInterface {
|
||||
@Field(() => String, { description: 'Имя аккаунта кооператива' })
|
||||
@IsString()
|
||||
coopname!: string;
|
||||
|
||||
@Field(() => String, { description: 'Имя пользователя' })
|
||||
@IsString()
|
||||
username!: string;
|
||||
|
||||
@Field(() => String, { description: 'Количество средств' })
|
||||
@IsString()
|
||||
quantity!: string;
|
||||
|
||||
@Field(() => String, { description: 'Символ валюты' })
|
||||
@IsString()
|
||||
symbol!: string;
|
||||
|
||||
@Field(() => String, { description: 'ID метода платежа' })
|
||||
@IsString()
|
||||
method_id!: string;
|
||||
|
||||
@Field(() => String, { description: 'Дополнительная информация', nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
memo?: string;
|
||||
|
||||
@Field(() => SignedDigitalDocumentInputDTO, { description: 'Подписанное заявление на возврат средств' })
|
||||
@ValidateNested()
|
||||
@Type(() => SignedDigitalDocumentInputDTO)
|
||||
statement!: SignedDigitalDocumentInputDTO;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
/**
|
||||
* DTO для ответа создания заявки на вывод средств
|
||||
*/
|
||||
@ObjectType('CreateWithdrawResponse')
|
||||
export class CreateWithdrawResponseDTO {
|
||||
@Field(() => String, { description: 'Хеш созданной заявки на вывод' })
|
||||
withdraw_hash!: string;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Resolver, Mutation, Args } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Throttle } from '@nestjs/throttler';
|
||||
import { GqlJwtAuthGuard } from '~/modules/auth/guards/graphql-jwt-auth.guard';
|
||||
import { RolesGuard } from '~/modules/auth/guards/roles.guard';
|
||||
import { AuthRoles } from '~/modules/auth/decorators/auth.decorator';
|
||||
import { WalletService } from '../services/wallet.service';
|
||||
import { GenerateDocumentOptionsInputDTO } from '~/modules/document/dto/generate-document-options-input.dto';
|
||||
import { GeneratedDocumentDTO } from '~/modules/document/dto/generated-document.dto';
|
||||
import { ReturnByMoneyGenerateDocumentInputDTO } from '~/modules/document/documents-dto/return-by-money-statement.dto';
|
||||
import { ReturnByMoneyDecisionGenerateDocumentInputDTO } from '~/modules/document/documents-dto/return-by-money-decision.dto';
|
||||
import { CreateWithdrawInputDTO } from '../dto/create-withdraw-input.dto';
|
||||
import { CreateWithdrawResponseDTO } from '../dto/create-withdraw-response.dto';
|
||||
|
||||
/**
|
||||
* GraphQL резолвер для wallet
|
||||
* Обеспечивает API для управления выводом средств и генерацией документов
|
||||
*/
|
||||
@Resolver()
|
||||
export class WalletResolver {
|
||||
constructor(private readonly walletService: WalletService) {}
|
||||
|
||||
/**
|
||||
* Mutation: Генерация документа заявления на возврат паевого взноса (900)
|
||||
*/
|
||||
@Mutation(() => GeneratedDocumentDTO, {
|
||||
name: 'generateReturnByMoneyStatementDocument',
|
||||
description: 'Сгенерировать документ заявления на возврат паевого взноса',
|
||||
})
|
||||
@Throttle({ default: { limit: 3, ttl: 60000 } })
|
||||
@UseGuards(GqlJwtAuthGuard, RolesGuard)
|
||||
@AuthRoles(['chairman', 'member'])
|
||||
async generateReturnByMoneyStatementDocument(
|
||||
@Args('data', { type: () => ReturnByMoneyGenerateDocumentInputDTO })
|
||||
data: ReturnByMoneyGenerateDocumentInputDTO,
|
||||
@Args('options', { type: () => GenerateDocumentOptionsInputDTO, nullable: true })
|
||||
options: GenerateDocumentOptionsInputDTO
|
||||
): Promise<GeneratedDocumentDTO> {
|
||||
return this.walletService.generateReturnByMoneyStatementDocument(data, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutation: Генерация документа решения совета о возврате паевого взноса (901)
|
||||
*/
|
||||
@Mutation(() => GeneratedDocumentDTO, {
|
||||
name: 'generateReturnByMoneyDecisionDocument',
|
||||
description: 'Сгенерировать документ решения совета о возврате паевого взноса',
|
||||
})
|
||||
@Throttle({ default: { limit: 3, ttl: 60000 } })
|
||||
@UseGuards(GqlJwtAuthGuard, RolesGuard)
|
||||
@AuthRoles(['chairman', 'member'])
|
||||
async generateReturnByMoneyDecisionDocument(
|
||||
@Args('data', { type: () => ReturnByMoneyDecisionGenerateDocumentInputDTO })
|
||||
data: ReturnByMoneyDecisionGenerateDocumentInputDTO,
|
||||
@Args('options', { type: () => GenerateDocumentOptionsInputDTO, nullable: true })
|
||||
options: GenerateDocumentOptionsInputDTO
|
||||
): Promise<GeneratedDocumentDTO> {
|
||||
return this.walletService.generateReturnByMoneyDecisionDocument(data, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutation: Создание заявки на вывод средств
|
||||
*/
|
||||
@Mutation(() => CreateWithdrawResponseDTO, {
|
||||
name: 'createWithdraw',
|
||||
description: 'Создать заявку на вывод средств',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard, RolesGuard)
|
||||
@AuthRoles(['chairman', 'member'])
|
||||
async createWithdraw(@Args('input') input: CreateWithdrawInputDTO): Promise<CreateWithdrawResponseDTO> {
|
||||
return this.walletService.createWithdraw(input);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { WalletDomainInteractor } from '~/domain/wallet/interactors/wallet.interactor';
|
||||
import { GenerateDocumentOptionsInputDTO } from '~/modules/document/dto/generate-document-options-input.dto';
|
||||
import { GeneratedDocumentDTO } from '~/modules/document/dto/generated-document.dto';
|
||||
import { ReturnByMoneyGenerateDocumentInputDTO } from '~/modules/document/documents-dto/return-by-money-statement.dto';
|
||||
import { ReturnByMoneyDecisionGenerateDocumentInputDTO } from '~/modules/document/documents-dto/return-by-money-decision.dto';
|
||||
import { CreateWithdrawInputDTO } from '../dto/create-withdraw-input.dto';
|
||||
import { CreateWithdrawResponseDTO } from '../dto/create-withdraw-response.dto';
|
||||
import { Cooperative } from 'cooptypes';
|
||||
|
||||
/**
|
||||
* Сервис для работы с wallet модулем
|
||||
*/
|
||||
@Injectable()
|
||||
export class WalletService {
|
||||
constructor(private readonly walletDomainInteractor: WalletDomainInteractor) {}
|
||||
|
||||
/**
|
||||
* Генерация документа заявления на возврат паевого взноса (900)
|
||||
*/
|
||||
public async generateReturnByMoneyStatementDocument(
|
||||
data: ReturnByMoneyGenerateDocumentInputDTO,
|
||||
options?: GenerateDocumentOptionsInputDTO
|
||||
): Promise<GeneratedDocumentDTO> {
|
||||
// Устанавливаем registry_id для документа заявления
|
||||
data.registry_id = Cooperative.Registry.ReturnByMoney.registry_id;
|
||||
|
||||
const document = await this.walletDomainInteractor.generateReturnByMoneyStatementDocument(data, options || {});
|
||||
//TODO чтобы избавиться от unknown необходимо строго типизировать ответ фабрики документов
|
||||
return document as unknown as GeneratedDocumentDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация документа решения совета о возврате паевого взноса (901)
|
||||
*/
|
||||
public async generateReturnByMoneyDecisionDocument(
|
||||
data: ReturnByMoneyDecisionGenerateDocumentInputDTO,
|
||||
options?: GenerateDocumentOptionsInputDTO
|
||||
): Promise<GeneratedDocumentDTO> {
|
||||
// Устанавливаем registry_id для документа решения
|
||||
data.registry_id = Cooperative.Registry.ReturnByMoneyDecision.registry_id;
|
||||
|
||||
const document = await this.walletDomainInteractor.generateReturnByMoneyDecisionDocument(data, options || {});
|
||||
//TODO чтобы избавиться от unknown необходимо строго типизировать ответ фабрики документов
|
||||
return document as unknown as GeneratedDocumentDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание заявки на вывод средств
|
||||
*/
|
||||
public async createWithdraw(data: CreateWithdrawInputDTO): Promise<CreateWithdrawResponseDTO> {
|
||||
const result = await this.walletDomainInteractor.createWithdraw(data);
|
||||
|
||||
const response = new CreateWithdrawResponseDTO();
|
||||
response.withdraw_hash = result.withdraw_hash;
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { WalletResolver } from './resolvers/wallet.resolver';
|
||||
import { WalletService } from './services/wallet.service';
|
||||
import { WalletDomainInteractor } from '~/domain/wallet/interactors/wallet.interactor';
|
||||
|
||||
/**
|
||||
* Модуль wallet для управления выводом средств и генерацией документов
|
||||
*/
|
||||
@Module({
|
||||
providers: [WalletResolver, WalletService, WalletDomainInteractor],
|
||||
exports: [WalletService, WalletDomainInteractor],
|
||||
})
|
||||
export class WalletModule {}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Do not modify this file manually
|
||||
*/
|
||||
|
||||
export type IInstall = ({
|
||||
export type IInstall = {
|
||||
individual_data: {
|
||||
birthdate: string;
|
||||
email: string;
|
||||
@@ -14,10 +14,10 @@ export type IInstall = ({
|
||||
phone: string;
|
||||
};
|
||||
role: 'chairman' | 'member';
|
||||
})[];
|
||||
}[];
|
||||
|
||||
export interface RInstall {
|
||||
body: ({
|
||||
body: {
|
||||
individual_data: {
|
||||
birthdate: string;
|
||||
email: string;
|
||||
@@ -28,5 +28,5 @@ export interface RInstall {
|
||||
phone: string;
|
||||
};
|
||||
role: 'chairman' | 'member';
|
||||
})[];
|
||||
}[];
|
||||
}
|
||||
|
||||
@@ -116,19 +116,21 @@ export interface IRecieveIPN {
|
||||
}
|
||||
|
||||
export interface ISavePaymentMethod {
|
||||
data: {
|
||||
phone: string;
|
||||
} | ({
|
||||
account_number: string;
|
||||
bank_name: string;
|
||||
card_number?: string;
|
||||
currency: 'RUB' | 'Other';
|
||||
details: {
|
||||
bik: string;
|
||||
corr: string;
|
||||
kpp: string;
|
||||
data:
|
||||
| {
|
||||
phone: string;
|
||||
}
|
||||
| {
|
||||
account_number: string;
|
||||
bank_name: string;
|
||||
card_number?: string;
|
||||
currency: 'RUB' | 'Other';
|
||||
details: {
|
||||
bik: string;
|
||||
corr: string;
|
||||
kpp: string;
|
||||
};
|
||||
};
|
||||
});
|
||||
method_id: number;
|
||||
method_type: 'sbp' | 'bank_transfer';
|
||||
username: string;
|
||||
@@ -155,19 +157,21 @@ export interface RGetListPaymentMethods {
|
||||
|
||||
export interface RSavePaymentMethod {
|
||||
body: {
|
||||
data: {
|
||||
phone: string;
|
||||
} | ({
|
||||
account_number: string;
|
||||
bank_name: string;
|
||||
card_number?: string;
|
||||
currency: 'RUB' | 'Other';
|
||||
details: {
|
||||
bik: string;
|
||||
corr: string;
|
||||
kpp: string;
|
||||
data:
|
||||
| {
|
||||
phone: string;
|
||||
}
|
||||
| {
|
||||
account_number: string;
|
||||
bank_name: string;
|
||||
card_number?: string;
|
||||
currency: 'RUB' | 'Other';
|
||||
details: {
|
||||
bik: string;
|
||||
corr: string;
|
||||
kpp: string;
|
||||
};
|
||||
};
|
||||
});
|
||||
method_id: number;
|
||||
method_type: 'sbp' | 'bank_transfer';
|
||||
username: string;
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
const getActiveTemplateForAction = (registry_id: string, drafts: any, translations: any) => {
|
||||
if (drafts && translations) {
|
||||
const draft: any = Object.values(drafts).find(
|
||||
(el: any) => el.registry_id == registry_id
|
||||
)
|
||||
const translation:any = Object.values(translations).find(
|
||||
(el: any) => el.id == draft.default_translation_id
|
||||
)
|
||||
if (drafts && translations) {
|
||||
const draft: any = Object.values(drafts).find((el: any) => el.registry_id == registry_id);
|
||||
const translation: any = Object.values(translations).find((el: any) => el.id == draft.default_translation_id);
|
||||
|
||||
return { context: draft.context, translation: JSON.parse(translation.data) }
|
||||
} else return {}
|
||||
}
|
||||
return { context: draft.context, translation: JSON.parse(translation.data) };
|
||||
} else return {};
|
||||
};
|
||||
|
||||
export default getActiveTemplateForAction
|
||||
export default getActiveTemplateForAction;
|
||||
|
||||
@@ -2,7 +2,6 @@ function getInternalAction(data, actionName) {
|
||||
// Вспомогательная функция для рекурсивного поиска
|
||||
function searchTraces(traces) {
|
||||
for (const trace of traces) {
|
||||
|
||||
if (trace.act.name === actionName) {
|
||||
return trace.act.data; // Предполагается, что вам нужны данные, а не сам act
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ const { verify, sha256 } = eosjsecc;
|
||||
|
||||
async function verifySignature(doc: DocumentInterface, hash) {
|
||||
let isValidSignature = false;
|
||||
let isValidHash = hash === doc.hash;
|
||||
let reason = "";
|
||||
const isValidHash = hash === doc.hash;
|
||||
let reason = '';
|
||||
|
||||
try {
|
||||
isValidSignature = await verify(doc.signature, doc.hash, doc.public_key);
|
||||
} catch (e) {
|
||||
reason = "wrong signature";
|
||||
reason = 'wrong signature';
|
||||
}
|
||||
|
||||
if (!isValidHash) {
|
||||
reason = "hash is not equal doc hash";
|
||||
reason = 'hash is not equal doc hash';
|
||||
}
|
||||
|
||||
return {
|
||||
is_valid_signature: isValidSignature,
|
||||
is_valid_hash: isValidHash,
|
||||
reason: reason,
|
||||
is_valid: isValidSignature && isValidHash
|
||||
is_valid: isValidSignature && isValidHash,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('Проверка данных', () => {
|
||||
skip_save: false,
|
||||
};
|
||||
|
||||
let res = await request(app)
|
||||
const res = await request(app)
|
||||
.post('/v1/documents/generate')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(options);
|
||||
@@ -135,7 +135,7 @@ describe('Проверка данных', () => {
|
||||
skip_save: false,
|
||||
};
|
||||
|
||||
let res = await request(app)
|
||||
const res = await request(app)
|
||||
.post('/v1/documents/generate')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(options);
|
||||
@@ -147,7 +147,7 @@ describe('Проверка данных', () => {
|
||||
expect(res.body.html).toBeDefined();
|
||||
expect(res.body.hash).toBeDefined();
|
||||
|
||||
let res_regenerated = await request(app)
|
||||
const res_regenerated = await request(app)
|
||||
.post('/v1/documents/generate')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(res.body.meta);
|
||||
@@ -175,7 +175,7 @@ describe('Проверка данных', () => {
|
||||
signature: 'any imaged signature',
|
||||
skip_save: false,
|
||||
};
|
||||
let res = await request(app)
|
||||
const res = await request(app)
|
||||
.post('/v1/documents/generate')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(options);
|
||||
@@ -195,7 +195,7 @@ describe('Проверка данных', () => {
|
||||
statement: signedDocument,
|
||||
};
|
||||
|
||||
let joincoop_result = await request(app)
|
||||
const joincoop_result = await request(app)
|
||||
.post('/v1/users/join-cooperative')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(joinCoopData);
|
||||
@@ -220,7 +220,7 @@ describe('Проверка данных', () => {
|
||||
signature: 'any imaged signature',
|
||||
skip_save: false,
|
||||
};
|
||||
let res = await request(app)
|
||||
const res = await request(app)
|
||||
.post('/v1/documents/generate')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(options);
|
||||
@@ -246,7 +246,7 @@ describe('Проверка данных', () => {
|
||||
statement: signedDocument,
|
||||
};
|
||||
|
||||
let joincoop_result = await request(app)
|
||||
const joincoop_result = await request(app)
|
||||
.post('/v1/users/join-cooperative')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(joinCoopData);
|
||||
@@ -276,7 +276,7 @@ describe('Проверка данных', () => {
|
||||
skip_save: false,
|
||||
};
|
||||
|
||||
let res = await request(app)
|
||||
const res = await request(app)
|
||||
.post('/v1/documents/generate')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(options);
|
||||
@@ -288,7 +288,7 @@ describe('Проверка данных', () => {
|
||||
expect(res.body.html).toBeDefined();
|
||||
expect(res.body.hash).toBeDefined();
|
||||
|
||||
let res_regenerated = await request(app)
|
||||
const res_regenerated = await request(app)
|
||||
.post('/v1/documents/generate')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(res.body.meta);
|
||||
@@ -315,7 +315,7 @@ describe('Проверка данных', () => {
|
||||
statement: signedDocument,
|
||||
};
|
||||
|
||||
let joincoop_result = await request(app)
|
||||
const joincoop_result = await request(app)
|
||||
.post('/v1/users/join-cooperative')
|
||||
.set('Authorization', `Bearer ${registeredUser.body.tokens.access.token}`)
|
||||
.send(joinCoopData);
|
||||
|
||||
@@ -5,7 +5,7 @@ export const registry_id = 900
|
||||
|
||||
// Интерфейс запроса без чувствительных данных
|
||||
export interface IMoneyReturnRequest {
|
||||
method_id: string // ID платежного метода вместо банковских реквизитов
|
||||
method_id: string // ID платежного метода
|
||||
amount: string
|
||||
currency: string
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export interface Model {
|
||||
|
||||
export const title = 'Заявление на возврат паевого взноса денежными средствами'
|
||||
export const description = 'Форма заявления на возврат паевого взноса денежными средствами'
|
||||
export const context = '<div class="digital-document"><div style="text-align: right; margin:">\n<p style="margin: 0px !important">{% trans \'v_soviet\' %} {{ vars.full_abbr_genitive}} "{{vars.name}}"</p>\n<p style="margin: 0px !important">{% trans \'from_participant\' %}</p>\n<p style="margin: 0px !important">{{ user.full_name_or_short_name}}</p>\n</div>\n<div style="text-align: center">\n<h1 class="header">{% trans \'statement\' %}</h1>\n</div>\n<p>{% trans \'money_return_request\', coop.short_name %}</p>\n<table>\n<tbody>\n<tr>\n<th>{% trans \'asset_form\' %}</th>\n<td>{% trans \'money_assets\' %}</td>\n</tr>\n<tr>\n<th>{% trans \'name_requisites\' %}</th>\n<td>{% trans \'return_part_contribution\' %}</td>\n</tr>\n<tr>\n<th>№</th>\n<td>1</td>\n</tr>\n<tr>\n<th>{% trans \'payment_details_field\' %}</th>\n<td style="white-space: pre-line;">{{ request.payment_details }}</td>\n</tr>\n<tr>\n<th>{% trans \'article\' %}</th>\n<td>na</td>\n</tr>\n<tr>\n<th>{% trans \'unit_measurement\' %}</th>\n<td>{{ request.currency }}</td>\n</tr>\n<tr>\n<th>{% trans \'quantity\' %}</th>\n<td>{{ request.amount }}</td>\n</tr>\n<tr>\n<th>{% trans \'unit_cost\' %}</th>\n<td>na</td>\n</tr>\n<tr>\n<th>{% trans \'total_cost\' %}</th>\n<td>{{ request.amount }} {{ request.currency }}</td>\n</tr>\n</tbody>\n</table>\n<p>{% trans \'signature\' %}</p>\n<p>{{ user.full_name_or_short_name }}</p>\n<p>{{ meta.created_at }}</p>\n\n<style>\n.digital-document {\npadding: 20px;\nwhite-space: pre-wrap;\n};\ntable {\n width: 100%;\n border-collapse: collapse;\n}\nth, td {\n border: 1px solid #ccc;\n padding: 8px;\n text-align: left;\n word-wrap: break-word; \n overflow-wrap: break-word; \n}\nth {\n background-color: #f4f4f4;\n width: 30%;\n}\n</style>\n\n'
|
||||
export const context = `<div class="digital-document"><div style="text-align: right; margin:"><p style="margin: 0px !important">{% trans 'v_soviet' %} {{ vars.full_abbr_genitive}} "{{vars.name}}"</p><p style="margin: 0px !important">{% trans 'from_participant' %}</p><p style="margin: 0px !important">{{ user.full_name_or_short_name}}</p></div><div style="text-align: center; padding-top: 20px; padding-bottom: 20px;"><h1 class="header">{% trans 'statement' %}</h1></div><p style="padding-bottom: 20px;">{% trans 'money_return_request', coop.short_name %}</p><table style="margin-bottom: 20px;"><tbody><tr><th>{% trans 'asset_form' %}</th><td>{% trans 'money_assets' %}</td></tr><tr><th>{% trans 'name_requisites' %}</th><td>{% trans 'return_part_contribution' %}</td></tr><tr><th>№</th><td>1</td></tr><tr><th>{% trans 'payment_details_field' %}</th><td style="white-space: pre-line;">{{ request.payment_details }}</td></tr><tr><th>{% trans 'article' %}</th><td>na</td></tr><tr><th>{% trans 'unit_measurement' %}</th><td>{{ request.currency }}</td></tr><tr><th>{% trans 'quantity' %}</th><td>{{ request.amount }}</td></tr><tr><th>{% trans 'unit_cost' %}</th><td>na</td></tr><tr><th>{% trans 'total_cost' %}</th><td>{{ request.amount }} {{ request.currency }}</td></tr></tbody></table><p style="padding-top: 20px;">{% trans 'signature' %}</p><p>{{ user.full_name_or_short_name }}</p><p>{{ meta.created_at }}</p><style>.digital-document {padding: 20px;white-space: pre-wrap;};table {width: 100%;border-collapse: collapse;}th, td {border: 1px solid #ccc;padding: 8px;text-align: left;word-wrap: break-word; overflow-wrap: break-word; }th {background-color: #f4f4f4;width: 30%;}</style>`
|
||||
|
||||
export const translations = {
|
||||
ru: {
|
||||
@@ -53,7 +53,6 @@ export const translations = {
|
||||
money_assets: 'Денежные средства',
|
||||
signature: 'Подписано электронной подписью.',
|
||||
},
|
||||
// ... другие переводы
|
||||
}
|
||||
|
||||
export const exampleData = {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import type { IDecisionData, IGenerate, IMetaDocument } from '../../document'
|
||||
import type { ICommonUser, ICooperativeData, IVars } from '../../model'
|
||||
|
||||
export const registry_id = 901
|
||||
|
||||
/**
|
||||
* Интерфейс генерации решения совета по возврату паевого взноса
|
||||
*/
|
||||
export interface Action extends IGenerate {
|
||||
decision_id: number
|
||||
payment_hash: string // Хэш платежа
|
||||
amount: string
|
||||
currency: string
|
||||
}
|
||||
|
||||
export type Meta = IMetaDocument & Action
|
||||
|
||||
// Модель данных
|
||||
export interface Model {
|
||||
meta: IMetaDocument
|
||||
coop: ICooperativeData
|
||||
decision: IDecisionData
|
||||
user: ICommonUser
|
||||
amount: string
|
||||
currency: string
|
||||
vars: IVars
|
||||
}
|
||||
|
||||
export const title = 'Решение совета о возврате паевого взноса'
|
||||
export const description = 'Форма решения совета о возврате паевого взноса денежными средствами'
|
||||
export const context = `<style> h1 {margin: 0px; text-align:center;}h3{margin: 0px;padding-top: 15px;}.about {padding: 20px;}.digital-document {padding: 20px;white-space: pre-wrap;}.subheader {padding-bottom: 20px; }table {width: 100%;border-collapse: collapse;}th, td {border: 1px solid #ccc;padding: 8px;text-align: left;word-wrap: break-word; overflow-wrap: break-word; }th {background-color: #f4f4f4;width: 30%;}</style><div class="digital-document"><h1 class="header">{% trans 'protocol_number', decision.id %}</h1><p style="text-align:center" class="subheader">{% trans 'council_meeting_name' %} {{vars.full_abbr_genitive}} "{{vars.name}}"</p><p style="text-align: right; padding-bottom: 20px;"> {{ meta.created_at }}, {{ coop.city }}</p><table class="about" style="margin-bottom: 20px;"><tbody><tr><th>{% trans 'meeting_format' %}</th><td>{% trans 'meeting_format_value' %}</td></tr><tr><th>{% trans 'meeting_place' %}</th><td>{{ coop.full_address }}</td></tr><tr><th>{% trans 'meeting_date' %}</th><td>{{ decision.date }}</td></tr><tr><th>{% trans 'opening_time' %}</th><td>{{ decision.time }}</td></tr></tbody></table><h3 style="padding-top: 30px; padding-bottom: 10px;">{% trans 'council_members' %}</h3><table style="margin-bottom: 20px;"><tbody>{% for member in coop.members %}<tr><th>{% if member.is_chairman %}{% trans 'chairman_of_the_council' %}{% else %}{% trans 'member_of_the_council' %}{% endif %}</th><td>{{ member.last_name }} {{ member.first_name }} {{ member.middle_name }}</td></tr>{% endfor %}</tbody></table><h3 style="padding-top: 30px; padding-bottom: 10px;">{% trans 'meeting_legality' %}</h3><p style="padding-bottom: 20px;">{% trans 'voting_results', decision.voters_percent %} {% trans 'quorum' %} {% trans 'chairman_of_the_meeting', coop.chairman.last_name, coop.chairman.first_name, coop.chairman.middle_name %}.</p><h3 style="padding-top: 30px; padding-bottom: 10px;">{% trans 'agenda' %}</h3><p style="padding-bottom: 10px;">{% trans 'return_application_consideration' %}:</p><p style="padding-bottom: 20px;">- {{ user.full_name_or_short_name }}, {{ amount }} {{ currency }}</p><h3 style="padding-top: 30px; padding-bottom: 10px;">{% trans 'voting' %}</h3><p style="padding-bottom: 20px;">{% trans 'vote_results' %} {% trans 'votes_for_label' %} – {{ decision.votes_for }}; {% trans 'votes_against_label' %} - {{ decision.votes_against }}; {% trans 'votes_abstained_label' %} - {{ decision.votes_abstained }}.</p><h3 style="padding-top: 30px; padding-bottom: 10px;">{% trans 'decision_made' %}</h3><p style="padding-bottom: 10px;">{% trans 'return_decision_text' %}:</p><p style="padding-bottom: 20px;">- {{ user.full_name_or_short_name }}, {{ amount }} {{ currency }}</p><hr><p style="padding-top: 20px;">{% trans 'closing_time', decision.time %}</p><div class="signature" style="padding-top: 20px;"><p>{% trans 'signature' %}</p><p>{% trans 'chairman' %} {{ coop.chairman.last_name }} {{ coop.chairman.first_name }} {{ coop.chairman.middle_name }}</p></div></div>`
|
||||
|
||||
export const translations = {
|
||||
ru: {
|
||||
meeting_format: 'Форма',
|
||||
meeting_date: 'Дата',
|
||||
meeting_place: 'Место',
|
||||
opening_time: 'Время открытия',
|
||||
council_members: 'ЧЛЕНЫ СОВЕТА',
|
||||
voting_results: 'Количество голосов составляет {0}% от общего числа членов Совета.',
|
||||
meeting_legality: 'СОБРАНИЕ ПРАВОМОЧНО',
|
||||
chairman_of_the_meeting: 'Председатель собрания совета: {0} {1} {2}',
|
||||
agenda: 'ПОВЕСТКА ДНЯ',
|
||||
decision_made: 'РЕШИЛИ',
|
||||
closing_time: 'Время закрытия собрания совета: {0}.',
|
||||
protocol_number: 'ПРОТОКОЛ № {0}',
|
||||
council_meeting_name: 'Собрания Совета',
|
||||
chairman_of_the_council: 'Председатель совета',
|
||||
signature: 'Документ подписан электронной подписью.',
|
||||
chairman: 'Председатель',
|
||||
quorum: 'Кворум для решения поставленных на повестку дня вопросов имеется.',
|
||||
voting: 'ГОЛОСОВАНИЕ',
|
||||
meeting_format_value: 'заочная',
|
||||
member_of_the_council: 'Член совета',
|
||||
return_application_consideration: 'Рассмотрение заявления на возврат паевого взноса пайщика',
|
||||
vote_results: 'По первому вопросу повестки дня проголосовали:',
|
||||
votes_for_label: '«За»',
|
||||
votes_against_label: '«Против»',
|
||||
votes_abstained_label: '«Воздержался»',
|
||||
return_decision_text: 'Вернуть паевой взнос пайщика по реквизитам, указанным в заявлении',
|
||||
},
|
||||
}
|
||||
|
||||
export const exampleData = {
|
||||
meta: {
|
||||
created_at: '18.03.2025 14:30',
|
||||
},
|
||||
coop: {
|
||||
city: 'Москва',
|
||||
full_address: 'г. Москва, ул. Центральная, д. 1',
|
||||
chairman: {
|
||||
last_name: 'Петров',
|
||||
first_name: 'Петр',
|
||||
middle_name: 'Петрович',
|
||||
},
|
||||
},
|
||||
decision: {
|
||||
time: '14:30',
|
||||
date: '18.03.2025',
|
||||
votes_for: '5',
|
||||
votes_against: '0',
|
||||
votes_abstained: '1',
|
||||
voters_percent: '100',
|
||||
id: '5',
|
||||
},
|
||||
user: {
|
||||
full_name_or_short_name: 'Иванов Иван Иванович',
|
||||
},
|
||||
vars: {
|
||||
full_abbr_genitive: 'Потребительского Кооператива',
|
||||
name: 'ВОСХОД',
|
||||
},
|
||||
member: {
|
||||
is_chairman: '',
|
||||
last_name: '',
|
||||
first_name: '',
|
||||
middle_name: '',
|
||||
},
|
||||
amount: '',
|
||||
currency: '',
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export * as ReturnByAssetDecision from './801.ReturnByAssetDecision'
|
||||
export * as ReturnByAssetAct from './802.ReturnByAssetAct'
|
||||
|
||||
export * as ReturnByMoney from './900.ReturnByMoney'
|
||||
export * as ReturnByMoneyDecision from './901.ReturnByMoneyDecision'
|
||||
|
||||
export * as InvestmentAgreement from './1000.InvestmentAgreement'
|
||||
export * as InvestByResultStatement from './1001.InvestByResultStatement'
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import { Cooperative, SovietContract } from 'cooptypes'
|
||||
import { useGenerateFreeDecision } from 'src/features/FreeDecision/GenerateDecision'
|
||||
import { useGenerateParticipantApplicationDecision } from 'src/features/Decision/ParticipantApplication'
|
||||
import { useGenerateSovietDecisionOnAnnualMeet } from 'src/features/Meet/GenerateSovietDecision/model'
|
||||
import { useSystemStore } from 'src/entities/System/model'
|
||||
import { useSessionStore } from 'src/entities/Session'
|
||||
import { useGlobalStore } from 'src/shared/store'
|
||||
import { useVoteForDecision } from 'src/features/Decision/VoteForDecision'
|
||||
import { useVoteAgainstDecision } from 'src/features/Decision/VoteAgainstDecision'
|
||||
import { computed } from 'vue'
|
||||
import { useAgendaStore } from 'src/entities/Agenda/model'
|
||||
import type { IAgenda } from 'src/entities/Agenda/model'
|
||||
import { DigitalDocument } from 'src/shared/lib/document'
|
||||
import type { IUserCertificateUnion } from 'src/shared/lib/types/certificate'
|
||||
import { getNameFromCertificate } from 'src/shared/lib/utils/getNameFromCertificate'
|
||||
import { Cooperative, SovietContract } from 'cooptypes';
|
||||
import { useGenerateFreeDecision } from 'src/features/FreeDecision/GenerateDecision';
|
||||
import { useGenerateParticipantApplicationDecision } from 'src/features/Decision/ParticipantApplication';
|
||||
import { useGenerateSovietDecisionOnAnnualMeet } from 'src/features/Meet/GenerateSovietDecision/model';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
import { useGlobalStore } from 'src/shared/store';
|
||||
import { useVoteForDecision } from 'src/features/Decision/VoteForDecision';
|
||||
import { useVoteAgainstDecision } from 'src/features/Decision/VoteAgainstDecision';
|
||||
import { computed } from 'vue';
|
||||
import { useAgendaStore } from 'src/entities/Agenda/model';
|
||||
import type { IAgenda } from 'src/entities/Agenda/model';
|
||||
import { DigitalDocument } from 'src/shared/lib/document';
|
||||
import type { IUserCertificateUnion } from 'src/shared/lib/types/certificate';
|
||||
import { getNameFromCertificate } from 'src/shared/lib/utils/getNameFromCertificate';
|
||||
|
||||
/**
|
||||
* Процесс обработки решений
|
||||
* Координирует работу различных фич для генерации и обработки решений различных типов
|
||||
*/
|
||||
export function useDecisionProcessor() {
|
||||
const { info } = useSystemStore()
|
||||
const session = useSessionStore()
|
||||
const agendaStore = useAgendaStore()
|
||||
const { info } = useSystemStore();
|
||||
const session = useSessionStore();
|
||||
const agendaStore = useAgendaStore();
|
||||
|
||||
// Данные повестки и состояние загрузки
|
||||
const decisions = computed(() => agendaStore.agenda)
|
||||
const loading = computed(() => agendaStore.loading)
|
||||
const decisions = computed(() => agendaStore.agenda);
|
||||
const loading = computed(() => agendaStore.loading);
|
||||
|
||||
/**
|
||||
* Форматирует заголовок вопроса
|
||||
*/
|
||||
function formatDecisionTitle(title: string, cert: IUserCertificateUnion) {
|
||||
let result = 'Вопрос на голосование'
|
||||
let result = 'Вопрос на голосование';
|
||||
|
||||
const name = getNameFromCertificate(cert)
|
||||
result = `${title} от ${name}`
|
||||
const name = getNameFromCertificate(cert);
|
||||
result = `${title} от ${name}`;
|
||||
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,24 +45,27 @@ export function useDecisionProcessor() {
|
||||
function getDocumentTitle(row: IAgenda) {
|
||||
// Используем только агрегаты документов
|
||||
if (row.documents?.statement?.documentAggregate?.rawDocument?.full_title) {
|
||||
return row.documents.statement.documentAggregate.rawDocument.full_title
|
||||
return row.documents.statement.documentAggregate.rawDocument.full_title;
|
||||
}
|
||||
|
||||
if (row.documents?.decision?.documentAggregate?.rawDocument?.full_title) {
|
||||
return row.documents.decision.documentAggregate.rawDocument.full_title
|
||||
return row.documents.decision.documentAggregate.rawDocument.full_title;
|
||||
}
|
||||
|
||||
// Поддержка исходного формата для таблицы
|
||||
if (row.table?.statement?.meta && typeof row.table.statement.meta === 'string') {
|
||||
if (
|
||||
row.table?.statement?.meta &&
|
||||
typeof row.table.statement.meta === 'string'
|
||||
) {
|
||||
try {
|
||||
const meta = JSON.parse(row.table.statement.meta)
|
||||
if (meta.title) return meta.title
|
||||
const meta = JSON.parse(row.table.statement.meta);
|
||||
if (meta.title) return meta.title;
|
||||
} catch (e) {
|
||||
// Игнорируем ошибку парсинга JSON
|
||||
}
|
||||
}
|
||||
|
||||
return 'Вопрос без заголовка'
|
||||
return 'Вопрос без заголовка';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,10 +73,10 @@ export function useDecisionProcessor() {
|
||||
*/
|
||||
async function loadDecisions(coopname: string, hidden = false) {
|
||||
try {
|
||||
const result = await agendaStore.loadAgenda({ coopname }, hidden)
|
||||
return result
|
||||
const result = await agendaStore.loadAgenda({ coopname }, hidden);
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,19 +86,19 @@ export function useDecisionProcessor() {
|
||||
function getDocumentHash(row: IAgenda) {
|
||||
// Используем только агрегаты документов
|
||||
if (row.documents?.statement?.documentAggregate?.rawDocument?.hash) {
|
||||
return row.documents.statement.documentAggregate.rawDocument.hash
|
||||
return row.documents.statement.documentAggregate.rawDocument.hash;
|
||||
}
|
||||
|
||||
if (row.documents?.decision?.documentAggregate?.rawDocument?.hash) {
|
||||
return row.documents.decision.documentAggregate.rawDocument.hash
|
||||
return row.documents.decision.documentAggregate.rawDocument.hash;
|
||||
}
|
||||
|
||||
// Поддержка исходного формата для таблицы
|
||||
if (row.table?.statement?.hash) {
|
||||
return row.table.statement.hash
|
||||
return row.table.statement.hash;
|
||||
}
|
||||
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,58 +106,76 @@ export function useDecisionProcessor() {
|
||||
*/
|
||||
async function generateDecisionDocument(row: IAgenda) {
|
||||
if (!row.table?.id || !row.table?.username || !row.table?.type) {
|
||||
throw new Error('Некорректные данные решения')
|
||||
throw new Error('Некорректные данные решения');
|
||||
}
|
||||
|
||||
const decision_id = Number(row.table.id)
|
||||
const username = row.table.username
|
||||
const type = row.table.type
|
||||
const registry_id = Cooperative.Document.decisionsRegistry[type]
|
||||
const decision_id = Number(row.table.id);
|
||||
const username = row.table.username;
|
||||
const type = row.table.type;
|
||||
const registry_id = Cooperative.Document.decisionsRegistry[type];
|
||||
|
||||
// Генерация документа в зависимости от типа решения
|
||||
let document
|
||||
let document;
|
||||
|
||||
if (registry_id === Cooperative.Registry.FreeDecision.registry_id) {
|
||||
const unparsedDocumentMeta = row.table.statement.meta == '' ? '{}' : row.table.statement.meta
|
||||
const parsedDocumentMeta = JSON.parse(unparsedDocumentMeta) as Cooperative.Registry.FreeDecision.Action
|
||||
const project_id = parsedDocumentMeta.project_id
|
||||
const unparsedDocumentMeta =
|
||||
row.table.statement.meta == '' ? '{}' : row.table.statement.meta;
|
||||
const parsedDocumentMeta = JSON.parse(
|
||||
unparsedDocumentMeta,
|
||||
) as Cooperative.Registry.FreeDecision.Action;
|
||||
const project_id = parsedDocumentMeta.project_id;
|
||||
|
||||
const { generateFreeDecision } = useGenerateFreeDecision()
|
||||
const { generateFreeDecision } = useGenerateFreeDecision();
|
||||
document = await generateFreeDecision({
|
||||
username: username,
|
||||
decision_id: decision_id,
|
||||
project_id: project_id
|
||||
})
|
||||
}
|
||||
else if (registry_id === Cooperative.Registry.DecisionOfParticipantApplication.registry_id) {
|
||||
const { generateParticipantApplicationDecision } = useGenerateParticipantApplicationDecision()
|
||||
project_id: project_id,
|
||||
});
|
||||
} else if (
|
||||
registry_id ===
|
||||
Cooperative.Registry.DecisionOfParticipantApplication.registry_id
|
||||
) {
|
||||
const { generateParticipantApplicationDecision } =
|
||||
useGenerateParticipantApplicationDecision();
|
||||
document = await generateParticipantApplicationDecision({
|
||||
username: username,
|
||||
decision_id: decision_id
|
||||
})
|
||||
}
|
||||
else if (registry_id === Cooperative.Registry.AnnualGeneralMeetingSovietDecision.registry_id) {
|
||||
const { generateSovietDecisionOnAnnualMeet } = useGenerateSovietDecisionOnAnnualMeet()
|
||||
const unparsedDocumentMeta = row.table.statement.meta == '' ? '{}' : row.table.statement.meta
|
||||
const parsedDocumentMeta = JSON.parse(unparsedDocumentMeta) as Cooperative.Registry.AnnualGeneralMeetingAgenda.Action
|
||||
const is_repeated = parsedDocumentMeta.is_repeated || false
|
||||
decision_id: decision_id,
|
||||
});
|
||||
} else if (
|
||||
registry_id ===
|
||||
Cooperative.Registry.AnnualGeneralMeetingSovietDecision.registry_id
|
||||
) {
|
||||
const { generateSovietDecisionOnAnnualMeet } =
|
||||
useGenerateSovietDecisionOnAnnualMeet();
|
||||
const unparsedDocumentMeta =
|
||||
row.table.statement.meta == '' ? '{}' : row.table.statement.meta;
|
||||
const parsedDocumentMeta = JSON.parse(
|
||||
unparsedDocumentMeta,
|
||||
) as Cooperative.Registry.AnnualGeneralMeetingAgenda.Action;
|
||||
const is_repeated = parsedDocumentMeta.is_repeated || false;
|
||||
|
||||
document = await generateSovietDecisionOnAnnualMeet({
|
||||
username: username,
|
||||
decision_id: decision_id,
|
||||
meet_hash: row.table.hash as string,
|
||||
is_repeated
|
||||
})
|
||||
}
|
||||
else {
|
||||
throw new Error('Неизвестный тип решения')
|
||||
is_repeated,
|
||||
});
|
||||
} else if (registry_id === Cooperative.Registry.ReturnByMoney.registry_id) {
|
||||
// const { generateReturnByMoneyDecision } = useGenerateReturnByMoneyDecision()
|
||||
// document = await generateReturnByMoneyDecision({
|
||||
// username: username,
|
||||
// decision_id: decision_id
|
||||
// })
|
||||
} else {
|
||||
console.log('Неизвестный тип решения', registry_id);
|
||||
throw new Error('Неизвестный тип решения');
|
||||
}
|
||||
|
||||
if (!document) {
|
||||
throw new Error('Ошибка при генерации документа решения')
|
||||
throw new Error('Ошибка при генерации документа решения');
|
||||
}
|
||||
|
||||
return document
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,31 +183,35 @@ export function useDecisionProcessor() {
|
||||
*/
|
||||
async function authorizeAndExecuteDecision(row: IAgenda) {
|
||||
if (!row.table?.id) {
|
||||
throw new Error('Некорректные данные решения')
|
||||
throw new Error('Некорректные данные решения');
|
||||
}
|
||||
|
||||
const decision_id = Number(row.table.id)
|
||||
const decision_id = Number(row.table.id);
|
||||
|
||||
// Генерируем документ решения
|
||||
const document = await generateDecisionDocument(row)
|
||||
const document = await generateDecisionDocument(row);
|
||||
|
||||
// Создаем экземпляр класса DigitalDocument и подписываем документ
|
||||
const digitalDocument = new DigitalDocument(document)
|
||||
const signedDocument = await digitalDocument.sign(session.username)
|
||||
const digitalDocument = new DigitalDocument(document);
|
||||
const signedDocument = await digitalDocument.sign(session.username);
|
||||
|
||||
// Подготавливаем данные для транзакций
|
||||
const authorizeData: SovietContract.Actions.Decisions.Authorize.IAuthorize = {
|
||||
coopname: info.coopname,
|
||||
chairman: session.username,
|
||||
decision_id,
|
||||
document: {...signedDocument, meta: JSON.stringify(signedDocument.meta)},
|
||||
}
|
||||
const authorizeData: SovietContract.Actions.Decisions.Authorize.IAuthorize =
|
||||
{
|
||||
coopname: info.coopname,
|
||||
chairman: session.username,
|
||||
decision_id,
|
||||
document: {
|
||||
...signedDocument,
|
||||
meta: JSON.stringify(signedDocument.meta),
|
||||
},
|
||||
};
|
||||
|
||||
const execData: SovietContract.Actions.Decisions.Exec.IExec = {
|
||||
executer: session.username,
|
||||
coopname: info.coopname,
|
||||
decision_id: decision_id,
|
||||
}
|
||||
};
|
||||
|
||||
// Выполняем транзакции
|
||||
await useGlobalStore().transact([
|
||||
@@ -212,9 +237,9 @@ export function useDecisionProcessor() {
|
||||
],
|
||||
data: execData,
|
||||
},
|
||||
])
|
||||
]);
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,21 +274,21 @@ export function useDecisionProcessor() {
|
||||
* Проверяет, проголосовал ли текущий пользователь "за" решение
|
||||
*/
|
||||
function isVotedFor(decision: SovietContract.Tables.Decisions.IDecision) {
|
||||
return decision.votes_for.includes(session.username)
|
||||
return decision.votes_for.includes(session.username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, проголосовал ли текущий пользователь "против" решения
|
||||
*/
|
||||
function isVotedAgainst(decision: SovietContract.Tables.Decisions.IDecision) {
|
||||
return decision.votes_against.includes(session.username)
|
||||
return decision.votes_against.includes(session.username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, проголосовал ли текущий пользователь за решение каким-либо образом
|
||||
*/
|
||||
function isVotedAny(decision: SovietContract.Tables.Decisions.IDecision) {
|
||||
return isVotedAgainst(decision) || isVotedFor(decision)
|
||||
return isVotedAgainst(decision) || isVotedFor(decision);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -279,6 +304,6 @@ export function useDecisionProcessor() {
|
||||
isVotedAny,
|
||||
formatDecisionTitle,
|
||||
getDocumentTitle,
|
||||
getDocumentHash
|
||||
}
|
||||
getDocumentHash,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,189 +1,230 @@
|
||||
<template lang="pug">
|
||||
div.scroll-area(style="height: calc(100% - $toolbar-min-height); overflow-y: auto;")
|
||||
q-table(
|
||||
ref="tableRef"
|
||||
flat
|
||||
:grid="isMobile"
|
||||
:rows="decisions"
|
||||
:columns="columns"
|
||||
:table-colspan="9"
|
||||
row-key="table.id"
|
||||
:pagination="pagination"
|
||||
virtual-scroll
|
||||
:virtual-scroll-item-size="48"
|
||||
:rows-per-page-options="[10]"
|
||||
:loading="loading"
|
||||
:no-data-label="'У совета нет вопросов на повестке для голосования. Вопросы на повестку добавляются автоматически при участии пайщиков в цифровых целевых потребительских программах кооператива. Также, вопрос на повестку можно добавить вручную, нажав на кнопку `ПРЕДЛОЖИТЬ ПОВЕСТКУ`.'"
|
||||
:virtual-scroll-target="'.scroll-area'"
|
||||
).full-width
|
||||
.scroll-area(style='height: calc(100% - $toolbar-min-height); overflow-y: auto')
|
||||
q-table.full-width(
|
||||
ref='tableRef',
|
||||
flat,
|
||||
:grid='isMobile',
|
||||
:rows='decisions',
|
||||
:columns='columns',
|
||||
:table-colspan='9',
|
||||
row-key='table.id',
|
||||
:pagination='pagination',
|
||||
virtual-scroll,
|
||||
:virtual-scroll-item-size='48',
|
||||
:rows-per-page-options='[10]',
|
||||
:loading='loading',
|
||||
:no-data-label='"У совета нет вопросов на повестке для голосования. Вопросы на повестку добавляются автоматически при участии пайщиков в цифровых целевых потребительских программах кооператива. Также, вопрос на повестку можно добавить вручную, нажав на кнопку `ПРЕДЛОЖИТЬ ПОВЕСТКУ`."',
|
||||
:virtual-scroll-target='".scroll-area"'
|
||||
)
|
||||
template(#top, v-if='$slots.top')
|
||||
slot(name='top')
|
||||
|
||||
template(#top v-if="$slots.top")
|
||||
slot(name="top")
|
||||
|
||||
template(#item="props")
|
||||
template(#item='props')
|
||||
QuestionCard(
|
||||
:agenda="props.row"
|
||||
:expanded="expanded.get(props.row.table.id)"
|
||||
:is-processing="isProcessing(props.row.table.id)"
|
||||
:is-voted-for="isVotedFor"
|
||||
:is-voted-against="isVotedAgainst"
|
||||
:is-voted-any="isVotedAny"
|
||||
@toggle-expand="toggleExpand(props.row.table.id)"
|
||||
@authorize="onAuthorizeDecision(props.row)"
|
||||
@vote-for="onVoteFor(props.row)"
|
||||
@vote-against="onVoteAgainst(props.row)"
|
||||
:agenda='props.row',
|
||||
:expanded='expanded.get(props.row.table.id)',
|
||||
:is-processing='isProcessing(props.row.table.id)',
|
||||
:is-voted-for='isVotedFor',
|
||||
:is-voted-against='isVotedAgainst',
|
||||
:is-voted-any='isVotedAny',
|
||||
@toggle-expand='toggleExpand(props.row.table.id)',
|
||||
@authorize='onAuthorizeDecision(props.row)',
|
||||
@vote-for='onVoteFor(props.row)',
|
||||
@vote-against='onVoteAgainst(props.row)'
|
||||
)
|
||||
|
||||
template(#header="props")
|
||||
q-tr(:props="props")
|
||||
template(#header='props')
|
||||
q-tr(:props='props')
|
||||
q-th(auto-width)
|
||||
q-th(
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
) {{ col.label }}
|
||||
q-th(v-for='col in props.cols', :key='col.name', :props='props') {{ col.label }}
|
||||
|
||||
template(#body="props")
|
||||
q-tr(:key="`m_${props.row.table.id}`" :props="props")
|
||||
template(#body='props')
|
||||
q-tr(:key='`m_${props.row.table.id}`', :props='props')
|
||||
q-td(auto-width)
|
||||
q-btn(
|
||||
size="sm"
|
||||
color="primary"
|
||||
dense
|
||||
:icon="expanded.get(props.row.table.id) ? 'expand_more' : 'chevron_right'"
|
||||
round
|
||||
@click="toggleExpand(props.row.table.id)"
|
||||
size='sm',
|
||||
color='primary',
|
||||
dense,
|
||||
:icon='expanded.get(props.row.table.id) ? "expand_more" : "chevron_right"',
|
||||
round,
|
||||
@click='toggleExpand(props.row.table.id)'
|
||||
)
|
||||
|
||||
q-td {{ props.row.table.id }}
|
||||
q-td {{ getShortNameFromCertificate(props.row.table.username_certificate) || props.row.table.username }}
|
||||
q-td(style="max-width: 200px; word-wrap: break-word; white-space: normal;") {{ getDecisionTitle(props.row) }}
|
||||
q-td(
|
||||
style='max-width: 200px; word-wrap: break-word; white-space: normal'
|
||||
) {{ getDecisionTitle(props.row) }}
|
||||
|
||||
q-td {{formatToFromNow(props.row.table.expired_at)}}
|
||||
q-td {{ formatToFromNow(props.row.table.expired_at) }}
|
||||
q-td
|
||||
VotingButtons(
|
||||
:decision="props.row.table"
|
||||
:is-voted-for="isVotedFor"
|
||||
:is-voted-against="isVotedAgainst"
|
||||
:is-voted-any="isVotedAny"
|
||||
@vote-for="onVoteFor(props.row)"
|
||||
@vote-against="onVoteAgainst(props.row)"
|
||||
:decision='props.row.table',
|
||||
:is-voted-for='isVotedFor',
|
||||
:is-voted-against='isVotedAgainst',
|
||||
:is-voted-any='isVotedAny',
|
||||
@vote-for='onVoteFor(props.row)',
|
||||
@vote-against='onVoteAgainst(props.row)'
|
||||
)
|
||||
q-td
|
||||
q-btn(
|
||||
size="sm"
|
||||
color="teal"
|
||||
push
|
||||
v-if="isChairman"
|
||||
:loading="isProcessing(props.row.table.id)"
|
||||
@click="onAuthorizeDecision(props.row)"
|
||||
size='sm',
|
||||
color='teal',
|
||||
push,
|
||||
v-if='isChairman',
|
||||
:loading='isProcessing(props.row.table.id)',
|
||||
@click='onAuthorizeDecision(props.row)'
|
||||
) утвердить
|
||||
|
||||
q-tr(no-hover v-if="expanded.get(props.row.table.id)" :key="`e_${props.row.table.id}`" :props="props" class="q-virtual-scroll--with-prev")
|
||||
q-td(colspan="100%")
|
||||
ComplexDocument(:documents="props.row.documents")
|
||||
q-tr.q-virtual-scroll--with-prev(
|
||||
no-hover,
|
||||
v-if='expanded.get(props.row.table.id)',
|
||||
:key='`e_${props.row.table.id}`',
|
||||
:props='props'
|
||||
)
|
||||
q-td(colspan='100%')
|
||||
ComplexDocument(:documents='props.row.documents')
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ComplexDocument } from 'src/shared/ui/ComplexDocument'
|
||||
import { Cooperative } from 'cooptypes'
|
||||
import { formatToFromNow } from 'src/shared/lib/utils/dates/formatToFromNow'
|
||||
import { QuestionCard } from '../QuestionCard'
|
||||
import { VotingButtons } from '../VotingButtons'
|
||||
import { useWindowSize } from 'src/shared/hooks'
|
||||
import type { IAgenda } from 'src/entities/Agenda/model'
|
||||
import { getShortNameFromCertificate } from 'src/shared/lib/utils/getNameFromCertificate'
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ComplexDocument } from 'src/shared/ui/ComplexDocument';
|
||||
import { Cooperative } from 'cooptypes';
|
||||
import { formatToFromNow } from 'src/shared/lib/utils/dates/formatToFromNow';
|
||||
import { QuestionCard } from '../QuestionCard';
|
||||
import { VotingButtons } from '../VotingButtons';
|
||||
import { useWindowSize } from 'src/shared/hooks';
|
||||
import type { IAgenda } from 'src/entities/Agenda/model';
|
||||
import { getShortNameFromCertificate } from 'src/shared/lib/utils/getNameFromCertificate';
|
||||
|
||||
const props = defineProps({
|
||||
decisions: {
|
||||
type: Array,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
isChairman: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
formatDecisionTitle: {
|
||||
type: Function,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
isVotedFor: {
|
||||
type: Function,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
isVotedAgainst: {
|
||||
type: Function,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
isVotedAny: {
|
||||
type: Function,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
processingDecisions: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['authorize', 'vote-for', 'vote-against'])
|
||||
const emit = defineEmits(['authorize', 'vote-for', 'vote-against']);
|
||||
|
||||
const { isMobile } = useWindowSize()
|
||||
const { isMobile } = useWindowSize();
|
||||
|
||||
// Получение заголовка для решения с поддержкой агрегатов документов
|
||||
function getDecisionTitle(row: IAgenda) {
|
||||
console.log('agenda', row)
|
||||
// Используем только агрегаты документа
|
||||
const title = (row.documents?.statement?.documentAggregate?.rawDocument?.meta as any)?.title
|
||||
const title = (
|
||||
row.documents?.statement?.documentAggregate?.rawDocument?.meta as any
|
||||
)?.title;
|
||||
|
||||
if (title) {
|
||||
const actor_certificate = row.documents?.statement?.action?.actor_certificate
|
||||
return props.formatDecisionTitle(title, actor_certificate)
|
||||
const actor_certificate =
|
||||
row.documents?.statement?.action?.actor_certificate;
|
||||
return props.formatDecisionTitle(title, actor_certificate);
|
||||
}
|
||||
|
||||
// Запасной вариант
|
||||
return 'Вопрос на голосование'
|
||||
return 'Вопрос на голосование';
|
||||
}
|
||||
|
||||
// Настройка таблицы
|
||||
const columns = [
|
||||
{ name: 'id', align: 'left', label: '№', field: row => row.table.id, sortable: true },
|
||||
{ name: 'username', align: 'left', label: 'Заявитель', field: row => row.table.username, sortable: true },
|
||||
{ name: 'caption', align: 'left', label: 'Пункт', field: row => getDecisionTitle(row), sortable: true },
|
||||
{ name: 'expired_at', align: 'left', label: 'Истекает', field: row => row.table.expired_at, format: val => formatToFromNow(val), sortable: false },
|
||||
{ name: 'approved', align: 'left', label: 'Голосование', field: row => row.table.approved, sortable: true },
|
||||
{ name: 'authorized', align: 'left', label: '', field: row => row.table.authorized, sortable: true },
|
||||
] as any
|
||||
{
|
||||
name: 'id',
|
||||
align: 'left',
|
||||
label: '№',
|
||||
field: (row) => row.table.id,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
align: 'left',
|
||||
label: 'Заявитель',
|
||||
field: (row) => row.table.username,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'caption',
|
||||
align: 'left',
|
||||
label: 'Пункт',
|
||||
field: (row) => getDecisionTitle(row),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'expired_at',
|
||||
align: 'left',
|
||||
label: 'Истекает',
|
||||
field: (row) => row.table.expired_at,
|
||||
format: (val) => formatToFromNow(val),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
name: 'approved',
|
||||
align: 'left',
|
||||
label: 'Голосование',
|
||||
field: (row) => row.table.approved,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'authorized',
|
||||
align: 'left',
|
||||
label: '',
|
||||
field: (row) => row.table.authorized,
|
||||
sortable: true,
|
||||
},
|
||||
] as any;
|
||||
|
||||
// Состояние UI
|
||||
const expanded = reactive(new Map()) // Map для отслеживания состояния развертывания каждой записи
|
||||
const tableRef = ref(null)
|
||||
const pagination = ref({ rowsPerPage: 10 })
|
||||
const expanded = reactive(new Map()); // Map для отслеживания состояния развертывания каждой записи
|
||||
const tableRef = ref(null);
|
||||
const pagination = ref({ rowsPerPage: 10 });
|
||||
|
||||
// UI методы
|
||||
const toggleExpand = (id: any) => {
|
||||
expanded.set(id, !expanded.get(id))
|
||||
}
|
||||
expanded.set(id, !expanded.get(id));
|
||||
};
|
||||
|
||||
const isProcessing = (decisionId: number) => {
|
||||
// Используем только processingDecisions из props
|
||||
return Boolean(props.processingDecisions[decisionId])
|
||||
}
|
||||
return Boolean(props.processingDecisions[decisionId]);
|
||||
};
|
||||
|
||||
// Обработчики событий
|
||||
const onAuthorizeDecision = (row: Cooperative.Document.IComplexAgenda) => {
|
||||
emit('authorize', row)
|
||||
}
|
||||
emit('authorize', row);
|
||||
};
|
||||
|
||||
const onVoteFor = (row: Cooperative.Document.IComplexAgenda) => {
|
||||
emit('vote-for', row)
|
||||
}
|
||||
emit('vote-for', row);
|
||||
};
|
||||
|
||||
const onVoteAgainst = (row: Cooperative.Document.IComplexAgenda) => {
|
||||
emit('vote-against', row)
|
||||
}
|
||||
emit('vote-against', row);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { DraftContract } from 'cooptypes'
|
||||
import { DocFactory } from '../Factory'
|
||||
import type {
|
||||
IGeneratedDocument,
|
||||
IGenerationOptions,
|
||||
IMetaDocument,
|
||||
ITemplate,
|
||||
} from '../Interfaces'
|
||||
import type { MongoDBConnector } from '../Services/Databazor'
|
||||
|
||||
import { ReturnByMoneyDecision } from '../Templates'
|
||||
|
||||
export { ReturnByMoneyDecision as Template } from '../Templates'
|
||||
|
||||
export class Factory extends DocFactory<ReturnByMoneyDecision.Action> {
|
||||
constructor(storage: MongoDBConnector) {
|
||||
super(storage)
|
||||
}
|
||||
|
||||
async generateDocument(
|
||||
data: ReturnByMoneyDecision.Action,
|
||||
options?: IGenerationOptions,
|
||||
): Promise<IGeneratedDocument> {
|
||||
let template: ITemplate<ReturnByMoneyDecision.Model>
|
||||
|
||||
if (process.env.SOURCE === 'local') {
|
||||
template = ReturnByMoneyDecision.Template
|
||||
}
|
||||
else {
|
||||
template = await this.getTemplate(DraftContract.contractName.production, ReturnByMoneyDecision.registry_id, data.block_num)
|
||||
}
|
||||
|
||||
const user = await super.getUser(data.username, data.block_num)
|
||||
const coop = await super.getCooperative(data.coopname, data.block_num)
|
||||
const vars = await super.getVars(data.coopname, data.block_num)
|
||||
|
||||
// TODO необходимо строго типизировать мета-данные документов друг под друга!
|
||||
const meta: IMetaDocument = await super.getMeta({
|
||||
title: template.title,
|
||||
...data,
|
||||
}) // Генерируем мета-данные
|
||||
|
||||
const decision = await super.getDecision(
|
||||
coop,
|
||||
data.coopname,
|
||||
data.decision_id,
|
||||
meta.created_at,
|
||||
)
|
||||
|
||||
const combinedData: ReturnByMoneyDecision.Model = {
|
||||
vars,
|
||||
meta,
|
||||
coop,
|
||||
decision,
|
||||
user: super.getCommonUser(user),
|
||||
amount: data.amount,
|
||||
currency: data.currency,
|
||||
}
|
||||
|
||||
// валидируем скомбинированные данные
|
||||
await super.validate(combinedData, template.model)
|
||||
|
||||
// получаем комплекс перевода
|
||||
const translation = template.translations[meta.lang]
|
||||
|
||||
// генерируем документ
|
||||
const document: IGeneratedDocument = await super.generatePDF(
|
||||
user.data,
|
||||
template.context,
|
||||
combinedData,
|
||||
translation,
|
||||
meta,
|
||||
options?.skip_save,
|
||||
)
|
||||
|
||||
// возвращаем
|
||||
return document
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export * as AssetContributionAct from './702.AssetContributionAct'
|
||||
export * as ReturnByAssetDecision from './801.ReturnByAssetDecision'
|
||||
export * as ReturnByAssetAct from './802.ReturnByAssetAct'
|
||||
export * as ReturnByMoney from './900.ReturnByMoney'
|
||||
export * as ReturnByMoneyDecision from './901.ReturnByMoneyDecision'
|
||||
|
||||
export * as InvestmentAgreement from './1000.InvestmentAgreement'
|
||||
export * as InvestByResultStatement from './1001.InvestByResultStatement'
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { JSONSchemaType } from 'ajv'
|
||||
import { Cooperative } from 'cooptypes'
|
||||
import type { ITemplate } from '../Interfaces'
|
||||
import { IMetaJSONSchema } from '../Schema/MetaSchema'
|
||||
import { CooperativeSchema, VarsSchema } from '../Schema'
|
||||
import { decisionSchema } from '../Schema/DecisionSchema'
|
||||
import { CommonUserSchema } from '../Schema/CommonUserSchema'
|
||||
|
||||
export const registry_id = Cooperative.Registry.ReturnByMoneyDecision.registry_id
|
||||
|
||||
// Модель действия для генерации
|
||||
export type Action = Cooperative.Registry.ReturnByMoneyDecision.Action
|
||||
|
||||
// Модель данных
|
||||
export type Model = Cooperative.Registry.ReturnByMoneyDecision.Model
|
||||
|
||||
// Схема для сверки
|
||||
export const Schema: JSONSchemaType<Model> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
meta: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...IMetaJSONSchema.properties,
|
||||
},
|
||||
required: [...IMetaJSONSchema.required],
|
||||
additionalProperties: true,
|
||||
},
|
||||
coop: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...CooperativeSchema.properties,
|
||||
},
|
||||
required: [...CooperativeSchema.required],
|
||||
additionalProperties: true,
|
||||
},
|
||||
decision: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...decisionSchema.properties,
|
||||
},
|
||||
required: [...decisionSchema.required],
|
||||
additionalProperties: true,
|
||||
},
|
||||
user: CommonUserSchema,
|
||||
amount: { type: 'string' },
|
||||
currency: { type: 'string' },
|
||||
vars: VarsSchema,
|
||||
},
|
||||
required: ['meta', 'coop', 'decision', 'user', 'amount', 'currency', 'vars'],
|
||||
additionalProperties: false,
|
||||
}
|
||||
|
||||
export const Template: ITemplate<Model> = {
|
||||
title: Cooperative.Registry.ReturnByMoneyDecision.title,
|
||||
description: Cooperative.Registry.ReturnByMoneyDecision.description,
|
||||
model: Schema,
|
||||
context: Cooperative.Registry.ReturnByMoneyDecision.context,
|
||||
translations: Cooperative.Registry.ReturnByMoneyDecision.translations,
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export * as ReturnByAssetDecision from './801.returnByAssetDecision'
|
||||
export * as AssetContributionAct from './702.assetContributionAct'
|
||||
export * as ReturnByAssetAct from './802.returnByAssetAct'
|
||||
export * as ReturnByMoney from './900.returnByMoney'
|
||||
export * as ReturnByMoneyDecision from './901.ReturnByMoneyDecision'
|
||||
export * as InvestmentAgreement from './1000.InvestmentAgreement'
|
||||
export * as InvestByResultStatement from './1001.InvestByResultStatement'
|
||||
export * as InvestByResultAct from './1002.InvestByResultAct'
|
||||
|
||||
@@ -16,6 +16,7 @@ import * as ReturnByAssetDecision from './801.returnByAssetDecision'
|
||||
import * as AssetContributionAct from './702.assetContributionAct'
|
||||
import * as ReturnByAssetAct from './802.returnByAssetAct'
|
||||
import * as ReturnByMoney from './900.returnByMoney'
|
||||
import * as ReturnByMoneyDecision from './901.ReturnByMoneyDecision'
|
||||
import * as InvestmentAgreement from './1000.InvestmentAgreement'
|
||||
import * as InvestByResultStatement from './1001.InvestByResultStatement'
|
||||
import * as InvestByResultAct from './1002.InvestByResultAct'
|
||||
@@ -54,6 +55,7 @@ export const Registry = {
|
||||
801: ReturnByAssetDecision,
|
||||
802: ReturnByAssetAct,
|
||||
900: ReturnByMoney,
|
||||
901: ReturnByMoneyDecision,
|
||||
1000: InvestmentAgreement,
|
||||
1001: InvestByResultStatement,
|
||||
1002: InvestByResultAct,
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
import { matchMock } from '../matchMock'
|
||||
import { voteforActionMock } from './votefor'
|
||||
import { returnByMoneyDecisionActionMock, voteAgainstDecision9001Mock, voteForDecision9001Mock } from './returnByMoneyDecision'
|
||||
|
||||
voteforActionMock.match = function (url: string, params?: URLSearchParams): boolean {
|
||||
return matchMock(this, url, params)
|
||||
}
|
||||
|
||||
export const actionsMocks = [voteforActionMock as any]
|
||||
returnByMoneyDecisionActionMock.match = function (url: string, params?: URLSearchParams): boolean {
|
||||
return matchMock(this, url, params)
|
||||
}
|
||||
|
||||
voteForDecision9001Mock.match = function (url: string, params?: URLSearchParams): boolean {
|
||||
return matchMock(this, url, params)
|
||||
}
|
||||
|
||||
voteAgainstDecision9001Mock.match = function (url: string, params?: URLSearchParams): boolean {
|
||||
return matchMock(this, url, params)
|
||||
}
|
||||
|
||||
export const actionsMocks = [
|
||||
voteforActionMock as any,
|
||||
returnByMoneyDecisionActionMock as any,
|
||||
voteForDecision9001Mock as any,
|
||||
voteAgainstDecision9001Mock as any,
|
||||
]
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
const returnByMoneyDecisionAction = {
|
||||
_id: '6852b0389bd56240ad71b998',
|
||||
chain_id: 'f50256680336ee6daaeee93915b945c1166b5dfc98977adcb717403ae225c559',
|
||||
block_id: '000CBF518A8998507069881FF00EE74E1E2E5345CD9B3D1F34E09DA732AC1D3D',
|
||||
block_num: 835409,
|
||||
transaction_id: 'F79ED54A8D37076C3ADC73D3A10EF2A3933089D7B2B3D87F46D8703BCB34DA5B',
|
||||
global_sequence: '836089',
|
||||
receipt: {
|
||||
receiver: 'soviet',
|
||||
act_digest: '642C266D89D4FB33ACB2A41E4294A382A451F361FCD040F5D202F8D1816FBFF2',
|
||||
global_sequence: '836089',
|
||||
recv_sequence: '163',
|
||||
auth_sequence: [{ account: 'ant', sequence: '3' }],
|
||||
code_sequence: 1,
|
||||
abi_sequence: 1,
|
||||
},
|
||||
account: 'soviet',
|
||||
name: 'returnbymoneydecision',
|
||||
authorization: [{ actor: 'ant', permission: 'active' }],
|
||||
data: {
|
||||
version: '1.0.0',
|
||||
coopname: 'voskhod',
|
||||
username: 'ant',
|
||||
decision_id: '9001',
|
||||
payment_hash: 'abc123def456hash789',
|
||||
amount: '50000',
|
||||
currency: 'руб.',
|
||||
signed_at: '2025-06-18T12:25:23.000',
|
||||
signed_hash: '940734E565D3FA78527CC85C90D8601D7801F896EC5B40F6D1CC1DF0C6600948',
|
||||
signature: 'SIG_K1_KkSjs8tZtmDmTyLvaCEETVZ2QQQpshMF3ZyEjVXadNXivsAQfjbkrN6smKRoApqXjBhc5vHhL8vbSt4itAzYRbrvHF18WF',
|
||||
public_key: 'PUB_K1_52YPV66yKaju9WTXKuk8xWRSsCZ18Ewt9Z6SW24yMsRySPi2mZ',
|
||||
},
|
||||
action_ordinal: 1,
|
||||
creator_action_ordinal: 0,
|
||||
receiver: 'soviet',
|
||||
context_free: false,
|
||||
elapsed: '0',
|
||||
console: '',
|
||||
account_ram_deltas: [{ account: 'soviet', delta: '8' }],
|
||||
return_value: '',
|
||||
}
|
||||
|
||||
// Мок для голосов "за" решение
|
||||
const voteForAction9001_1 = {
|
||||
_id: '6852b0389bd56240ad71b990',
|
||||
chain_id: 'f50256680336ee6daaeee93915b945c1166b5dfc98977adcb717403ae225c559',
|
||||
block_id: '000CBF518A8998507069881FF00EE74E1E2E5345CD9B3D1F34E09DA732AC1D3D',
|
||||
block_num: 835400,
|
||||
transaction_id: 'F79ED54A8D37076C3ADC73D3A10EF2A3933089D7B2B3D87F46D8703BCB34DA5A',
|
||||
global_sequence: '836080',
|
||||
receipt: {
|
||||
receiver: 'soviet',
|
||||
act_digest: '642C266D89D4FB33ACB2A41E4294A382A451F361FCD040F5D202F8D1816FBFF1',
|
||||
global_sequence: '836080',
|
||||
recv_sequence: '160',
|
||||
auth_sequence: [{ account: 'member1', sequence: '1' }],
|
||||
code_sequence: 1,
|
||||
abi_sequence: 1,
|
||||
},
|
||||
account: 'soviet',
|
||||
name: 'votefor',
|
||||
authorization: [{ actor: 'member1', permission: 'active' }],
|
||||
data: {
|
||||
version: '1.0.0',
|
||||
coopname: 'voskhod',
|
||||
username: 'member1',
|
||||
decision_id: '9001',
|
||||
signed_at: '2025-06-18T12:20:00.000',
|
||||
signed_hash: '940734E565D3FA78527CC85C90D8601D7801F896EC5B40F6D1CC1DF0C6600940',
|
||||
signature: 'SIG_K1_KkSjs8tZtmDmTyLvaCEETVZ2QQQpshMF3ZyEjVXadNXivsAQfjbkrN6smKRoApqXjBhc5vHhL8vbSt4itAzYRbrvHF18WA',
|
||||
public_key: 'PUB_K1_52YPV66yKaju9WTXKuk8xWRSsCZ18Ewt9Z6SW24yMsRySPi2mA',
|
||||
},
|
||||
action_ordinal: 1,
|
||||
creator_action_ordinal: 0,
|
||||
receiver: 'soviet',
|
||||
context_free: false,
|
||||
elapsed: '0',
|
||||
console: '',
|
||||
account_ram_deltas: [{ account: 'soviet', delta: '8' }],
|
||||
return_value: '',
|
||||
}
|
||||
|
||||
// Мок для голосов "против" решения (пустой массив - никто не голосовал против)
|
||||
const voteAgainstAction9001: any[] = []
|
||||
|
||||
export const returnByMoneyDecisionActionMock = {
|
||||
code: 'soviet',
|
||||
actionName: 'returnbymoneydecision',
|
||||
value: { 'data.decision_id': '9001', 'data.coopname': 'voskhod' },
|
||||
data: {
|
||||
results: [returnByMoneyDecisionAction],
|
||||
},
|
||||
match: ((_url: string, _params?: URLSearchParams) => false) as (url: string, params?: URLSearchParams) => boolean,
|
||||
}
|
||||
|
||||
// Мок для голосов "за"
|
||||
export const voteForDecision9001Mock = {
|
||||
code: 'soviet',
|
||||
actionName: 'votefor',
|
||||
value: { 'data.decision_id': '9001', 'data.coopname': 'voskhod' },
|
||||
data: {
|
||||
results: [voteForAction9001_1],
|
||||
},
|
||||
match: ((_url: string, _params?: URLSearchParams) => false) as (url: string, params?: URLSearchParams) => boolean,
|
||||
}
|
||||
|
||||
// Мок для голосов "против"
|
||||
export const voteAgainstDecision9001Mock = {
|
||||
code: 'soviet',
|
||||
actionName: 'voteagainst',
|
||||
value: { 'data.decision_id': '9001', 'data.coopname': 'voskhod' },
|
||||
data: {
|
||||
results: voteAgainstAction9001,
|
||||
},
|
||||
match: ((_url: string, _params?: URLSearchParams) => false) as (url: string, params?: URLSearchParams) => boolean,
|
||||
}
|
||||
@@ -97,6 +97,7 @@ export class Generator implements IGenerator {
|
||||
[Actions.ReturnByAssetDecision.Template.registry_id]: new Actions.ReturnByAssetDecision.Factory(this.storage), // 801
|
||||
[Actions.ReturnByAssetAct.Template.registry_id]: new Actions.ReturnByAssetAct.Factory(this.storage), // 802
|
||||
[Actions.ReturnByMoney.Template.registry_id]: new Actions.ReturnByMoney.Factory(this.storage), // 900
|
||||
[Actions.ReturnByMoneyDecision.Template.registry_id]: new Actions.ReturnByMoneyDecision.Factory(this.storage), // 901
|
||||
[Actions.AssetContributionDecision.Template.registry_id]: new Actions.AssetContributionDecision.Factory(this.storage), // 701
|
||||
[Actions.AssetContributionAct.Template.registry_id]: new Actions.AssetContributionAct.Factory(this.storage), // 702
|
||||
[Actions.InvestmentAgreement.Template.registry_id]: new Actions.InvestmentAgreement.Factory(this.storage), // 1000
|
||||
|
||||
@@ -45,4 +45,17 @@ describe('тестируем цифровой кошелёк', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('генерация решения о возврате паевого взноса (ReturnByMoneyDecision)', async () => {
|
||||
await testDocumentGeneration({
|
||||
registry_id: Cooperative.Registry.ReturnByMoneyDecision.registry_id,
|
||||
coopname: 'voskhod',
|
||||
username: 'ant',
|
||||
lang: 'ru',
|
||||
decision_id: 9001,
|
||||
payment_hash: 'abc123def456hash789',
|
||||
amount: '50000',
|
||||
currency: 'руб.',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -64,6 +64,7 @@ export const subsribedActions: IActionConfig[] = [
|
||||
{ code: 'soviet', action: 'updateboard', notify: true },
|
||||
{ code: 'soviet', action: 'createboard', notify: true },
|
||||
{ code: 'meet', action: 'newgdecision', notify: true },
|
||||
{ code: 'wallet', action: 'createwthd', notify: true },
|
||||
]
|
||||
|
||||
// --------------------------
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
# Примеры использования SDK для Wallet и Gateway модулей
|
||||
|
||||
## Wallet Module
|
||||
|
||||
### Создание заявки на вывод средств
|
||||
```typescript
|
||||
import { Client } from '@cooptypes/sdk'
|
||||
import * as Mutations from '@cooptypes/sdk/mutations'
|
||||
|
||||
const client = Client.getInstance()
|
||||
|
||||
const result = await client.Mutation(
|
||||
Mutations.Wallet.CreateWithdraw.mutation,
|
||||
{
|
||||
variables: {
|
||||
input: {
|
||||
coopname: 'mycoopname',
|
||||
username: 'alice',
|
||||
quantity: '100.0000',
|
||||
symbol: 'SHARES',
|
||||
method_id: 'card_123',
|
||||
memo: 'Возврат паевого взноса',
|
||||
statement: {
|
||||
hash: 'doc_hash_here',
|
||||
signatures: [
|
||||
{
|
||||
signature: 'SIG_...',
|
||||
public_key: 'EOS...',
|
||||
// ... другие поля подписи
|
||||
}
|
||||
],
|
||||
rawDocument: {
|
||||
hash: 'raw_doc_hash',
|
||||
binary: 'base64_content',
|
||||
full_title: 'Заявление на возврат',
|
||||
html: '<html>...</html>',
|
||||
meta: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
console.log('Withdraw hash:', result.createWithdraw.withdraw_hash)
|
||||
```
|
||||
|
||||
### Генерация документа заявления (900)
|
||||
```typescript
|
||||
import * as Mutations from '@cooptypes/sdk/mutations'
|
||||
|
||||
const result = await client.Mutation(
|
||||
Mutations.Wallet.GenerateReturnByMoneyStatementDocument.mutation,
|
||||
{
|
||||
variables: {
|
||||
data: {
|
||||
coopname: 'mycoopname',
|
||||
username: 'alice',
|
||||
full_name: 'Алиса Иванова',
|
||||
quantity: '100.0000',
|
||||
symbol: 'SHARES'
|
||||
},
|
||||
options: {
|
||||
format: 'pdf'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
console.log('Generated document:', result.generateReturnByMoneyStatementDocument)
|
||||
```
|
||||
|
||||
### Генерация документа решения (901)
|
||||
```typescript
|
||||
import * as Mutations from '@cooptypes/sdk/mutations'
|
||||
|
||||
const result = await client.Mutation(
|
||||
Mutations.Wallet.GenerateReturnByMoneyDecisionDocument.mutation,
|
||||
{
|
||||
variables: {
|
||||
data: {
|
||||
coopname: 'mycoopname',
|
||||
username: 'alice',
|
||||
full_name: 'Алиса Иванова',
|
||||
quantity: '100.0000',
|
||||
symbol: 'SHARES',
|
||||
decision_date: new Date(),
|
||||
decision_number: 'Р-001'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
console.log('Generated decision:', result.generateReturnByMoneyDecisionDocument)
|
||||
```
|
||||
|
||||
## Gateway Module
|
||||
|
||||
### Получение списка платежей
|
||||
```typescript
|
||||
import * as Queries from '@cooptypes/sdk/queries'
|
||||
|
||||
const result = await client.Query(
|
||||
Queries.Gateway.GetGatewayPayments.query,
|
||||
{
|
||||
variables: {
|
||||
filters: {
|
||||
coopname: 'mycoopname',
|
||||
username: 'alice',
|
||||
status: 'pending'
|
||||
},
|
||||
options: {
|
||||
limit: 20,
|
||||
offset: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
console.log('Payments:', result.getGatewayPayments.items)
|
||||
console.log('Total count:', result.getGatewayPayments.totalCount)
|
||||
```
|
||||
|
||||
### Обновление статуса платежа
|
||||
```typescript
|
||||
import * as Mutations from '@cooptypes/sdk/mutations'
|
||||
|
||||
const result = await client.Mutation(
|
||||
Mutations.Gateway.UpdatePaymentStatus.mutation,
|
||||
{
|
||||
variables: {
|
||||
input: {
|
||||
hash: 'payment_hash_here',
|
||||
coopname: 'mycoopname',
|
||||
type: 'outgoing',
|
||||
status: 'completed'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
console.log('Updated payment:', result.updatePaymentStatus)
|
||||
```
|
||||
|
||||
## Типизация
|
||||
|
||||
Все селекторы поставляются с типами:
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
CreateWithdrawResponseType,
|
||||
PaymentType,
|
||||
OutgoingPaymentType,
|
||||
PaginatedPaymentsType
|
||||
} from '@cooptypes/sdk/selectors'
|
||||
|
||||
// Строгая типизация результатов
|
||||
const payment: PaymentType = {
|
||||
id: '123',
|
||||
coopname: 'mycoopname',
|
||||
username: 'alice',
|
||||
hash: 'payment_hash',
|
||||
quantity: '100.0000',
|
||||
symbol: 'SHARES',
|
||||
status: 'pending',
|
||||
type: 'outgoing',
|
||||
// ... остальные поля с проверкой типов
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
export * as UpdatePaymentStatus from './updatePaymentStatus'
|
||||
@@ -0,0 +1,19 @@
|
||||
import { $, Selector, type ModelTypes } from '../../zeus/index'
|
||||
import { outgoingPaymentSelector } from '../../selectors/gateway/outgoingPaymentSelector'
|
||||
|
||||
export const name = 'updatePaymentStatus'
|
||||
|
||||
// Селектор мутации
|
||||
export const mutation = Selector('Mutation')({
|
||||
[name]: [{ input: $('input', 'UpdatePaymentStatusInput!') }, outgoingPaymentSelector],
|
||||
})
|
||||
|
||||
// Интерфейс для входных данных
|
||||
export interface IInput {
|
||||
input: ModelTypes['UpdatePaymentStatusInput']
|
||||
}
|
||||
|
||||
// Тип выходных данных
|
||||
export type IOutput = {
|
||||
[name]: ModelTypes['OutgoingPayment']
|
||||
}
|
||||
@@ -5,8 +5,10 @@ export * as Branches from './branches'
|
||||
export * as Cooplace from './cooplace'
|
||||
export * as Extensions from './extensions'
|
||||
export * as FreeDecisions from './freeDecisions'
|
||||
export * as Gateway from './gateway'
|
||||
export * as Meet from './meet'
|
||||
export * as Participants from './participants'
|
||||
export * as PaymentMethods from './paymentMethods'
|
||||
export * as Payments from './payments'
|
||||
export * as System from './system'
|
||||
export * as Wallet from './wallet'
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { $, Selector, type ModelTypes } from '../../zeus/index'
|
||||
import { createWithdrawResponseSelector } from '../../selectors/wallet/createWithdrawResponseSelector'
|
||||
|
||||
export const name = 'createWithdraw'
|
||||
|
||||
// Селектор мутации
|
||||
export const mutation = Selector('Mutation')({
|
||||
[name]: [{ input: $('input', 'CreateWithdrawInput!') }, createWithdrawResponseSelector],
|
||||
})
|
||||
|
||||
// Интерфейс для входных данных
|
||||
export interface IInput {
|
||||
input: ModelTypes['CreateWithdrawInput']
|
||||
}
|
||||
|
||||
// Тип выходных данных
|
||||
export type IOutput = {
|
||||
[name]: ModelTypes['CreateWithdrawResponse']
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { $, Selector, type ModelTypes } from '../../zeus/index'
|
||||
import { rawGeneratedDocumentSelector } from '../../selectors/documents/documentAggregateSelector'
|
||||
|
||||
export const name = 'generateReturnByMoneyDecisionDocument'
|
||||
|
||||
// Селектор мутации
|
||||
export const mutation = Selector('Mutation')({
|
||||
[name]: [
|
||||
{
|
||||
data: $('data', 'ReturnByMoneyDecisionGenerateDocumentInput!'),
|
||||
options: $('options', 'GenerateDocumentOptionsInput'),
|
||||
},
|
||||
rawGeneratedDocumentSelector,
|
||||
],
|
||||
})
|
||||
|
||||
// Интерфейс для входных данных
|
||||
export interface IInput {
|
||||
data: ModelTypes['ReturnByMoneyDecisionGenerateDocumentInput']
|
||||
options?: ModelTypes['GenerateDocumentOptionsInput']
|
||||
}
|
||||
|
||||
// Тип выходных данных
|
||||
export type IOutput = {
|
||||
[name]: ModelTypes['GeneratedDocument']
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { $, Selector, type ModelTypes } from '../../zeus/index'
|
||||
import { rawGeneratedDocumentSelector } from '../../selectors/documents/documentAggregateSelector'
|
||||
|
||||
export const name = 'generateReturnByMoneyStatementDocument'
|
||||
|
||||
// Селектор мутации
|
||||
export const mutation = Selector('Mutation')({
|
||||
[name]: [
|
||||
{
|
||||
data: $('data', 'ReturnByMoneyGenerateDocumentInput!'),
|
||||
options: $('options', 'GenerateDocumentOptionsInput'),
|
||||
},
|
||||
rawGeneratedDocumentSelector,
|
||||
],
|
||||
})
|
||||
|
||||
// Интерфейс для входных данных
|
||||
export interface IInput {
|
||||
data: ModelTypes['ReturnByMoneyGenerateDocumentInput']
|
||||
options?: ModelTypes['GenerateDocumentOptionsInput']
|
||||
}
|
||||
|
||||
// Тип выходных данных
|
||||
export type IOutput = {
|
||||
[name]: ModelTypes['GeneratedDocument']
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * as CreateWithdraw from './createWithdraw'
|
||||
export * as GenerateReturnByMoneyStatementDocument from './generateReturnByMoneyStatementDocument'
|
||||
export * as GenerateReturnByMoneyDecisionDocument from './generateReturnByMoneyDecisionDocument'
|
||||
@@ -0,0 +1,27 @@
|
||||
import { rawOutgoingPaymentSelector } from '../../selectors'
|
||||
import { paginationSelector } from '../../utils/paginationSelector'
|
||||
import { $, Selector, type GraphQLTypes, type InputType, type ModelTypes } from '../../zeus/index'
|
||||
|
||||
const paginatedPaymentsSelector = { ...paginationSelector, items: rawOutgoingPaymentSelector }
|
||||
|
||||
export const name = 'getGatewayPayments'
|
||||
|
||||
// Селектор запроса
|
||||
export const query = Selector('Query')({
|
||||
[name]: [
|
||||
{
|
||||
filters: $('filters', 'GetOutgoingPaymentsInput!'),
|
||||
options: $('options', 'PaginationInput!'),
|
||||
},
|
||||
paginatedPaymentsSelector,
|
||||
],
|
||||
})
|
||||
|
||||
// Интерфейс для входных данных
|
||||
export interface IInput {
|
||||
filters: ModelTypes['GetOutgoingPaymentsInput']
|
||||
options: ModelTypes['PaginationInput']
|
||||
}
|
||||
|
||||
// Тип выходных данных
|
||||
export type IOutput = InputType<GraphQLTypes['Query'], typeof query>
|
||||
@@ -0,0 +1 @@
|
||||
export * as GetGatewayPayments from './getGatewayPayments'
|
||||
@@ -4,6 +4,7 @@ export * as Branches from './branches'
|
||||
export * as Desktop from './desktop'
|
||||
export * as Documents from './documents'
|
||||
export * as Extensions from './extensions'
|
||||
export * as Gateway from './gateway'
|
||||
export * as Meet from './meet'
|
||||
export * as PaymentMethods from './paymentMethods'
|
||||
export * as Payments from './payments'
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './paymentSelector'
|
||||
export * from './outgoingPaymentSelector'
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { MakeAllFieldsRequired } from '../../utils/MakeAllFieldsRequired'
|
||||
import { Selector, type ValueTypes, type ModelTypes } from '../../zeus/index'
|
||||
|
||||
// Сырой селектор для OutgoingPaymentDTO
|
||||
export const rawOutgoingPaymentSelector = {
|
||||
id: true,
|
||||
coopname: true,
|
||||
username: true,
|
||||
hash: true,
|
||||
quantity: true,
|
||||
symbol: true,
|
||||
method_id: true,
|
||||
status: true,
|
||||
type: true,
|
||||
created_at: true,
|
||||
updated_at: true,
|
||||
memo: true,
|
||||
payment_details: true,
|
||||
blockchain_data: true,
|
||||
formatted_amount: true,
|
||||
status_label: true,
|
||||
can_change_status: true,
|
||||
}
|
||||
|
||||
// Валидация селектора
|
||||
const _validate: MakeAllFieldsRequired<ValueTypes['OutgoingPayment']> = rawOutgoingPaymentSelector
|
||||
|
||||
/**
|
||||
* Селектор для исходящего платежа
|
||||
*/
|
||||
export const outgoingPaymentSelector = Selector('OutgoingPayment')(rawOutgoingPaymentSelector)
|
||||
|
||||
export type OutgoingPaymentType = ModelTypes['OutgoingPayment']
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { MakeAllFieldsRequired } from '../../utils/MakeAllFieldsRequired'
|
||||
import { Selector, type ValueTypes, type ModelTypes } from '../../zeus/index'
|
||||
|
||||
// Сырой селектор для PaymentDTO
|
||||
export const rawPaymentSelector = {
|
||||
id: true,
|
||||
coopname: true,
|
||||
username: true,
|
||||
hash: true,
|
||||
outcome_hash: true,
|
||||
income_hash: true,
|
||||
quantity: true,
|
||||
symbol: true,
|
||||
method_id: true,
|
||||
status: true,
|
||||
type: true,
|
||||
created_at: true,
|
||||
updated_at: true,
|
||||
memo: true,
|
||||
payment_details: true,
|
||||
blockchain_data: true,
|
||||
formatted_amount: true,
|
||||
status_label: true,
|
||||
type_label: true,
|
||||
can_change_status: true,
|
||||
}
|
||||
|
||||
// Валидация селектора
|
||||
const _validate: MakeAllFieldsRequired<ValueTypes['GatewayPayment']> = rawPaymentSelector
|
||||
|
||||
/**
|
||||
* Селектор для универсального платежа
|
||||
*/
|
||||
export const paymentSelector = Selector('GatewayPayment')(rawPaymentSelector)
|
||||
|
||||
export type PaymentType = ModelTypes['GatewayPayment']
|
||||
@@ -4,7 +4,9 @@ export * from './common'
|
||||
export * from './cooplace'
|
||||
export * from './extensions'
|
||||
export * from './freeDecisions'
|
||||
export * from './gateway'
|
||||
export * from './meet'
|
||||
export * from './participants'
|
||||
export * from './paymentMethods'
|
||||
export * from './system'
|
||||
export * from './wallet'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { MakeAllFieldsRequired } from '../../utils/MakeAllFieldsRequired'
|
||||
import { Selector, type ValueTypes, type ModelTypes } from '../../zeus/index'
|
||||
|
||||
// Сырой селектор для CreateWithdrawResponse
|
||||
export const rawCreateWithdrawResponseSelector = {
|
||||
withdraw_hash: true,
|
||||
}
|
||||
|
||||
// Валидация селектора
|
||||
const _validate: MakeAllFieldsRequired<ValueTypes['CreateWithdrawResponse']> = rawCreateWithdrawResponseSelector
|
||||
|
||||
/**
|
||||
* Селектор для ответа создания заявки на вывод средств
|
||||
*/
|
||||
export const createWithdrawResponseSelector = Selector('CreateWithdrawResponse')(rawCreateWithdrawResponseSelector)
|
||||
|
||||
export type CreateWithdrawResponseType = ModelTypes['CreateWithdrawResponse']
|
||||
@@ -0,0 +1 @@
|
||||
export * from './createWithdrawResponseSelector'
|
||||
@@ -159,6 +159,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
},
|
||||
CreateProjectFreeDecisionInput:{
|
||||
|
||||
},
|
||||
CreateWithdrawInput:{
|
||||
statement:"SignedDigitalDocumentInput"
|
||||
},
|
||||
DateTime: `scalar.DateTime` as const,
|
||||
DeclineRequestInput:{
|
||||
@@ -220,6 +223,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
},
|
||||
GetMeetsInput:{
|
||||
|
||||
},
|
||||
GetOutgoingPaymentsInput:{
|
||||
|
||||
},
|
||||
GetPaymentMethodsInput:{
|
||||
|
||||
@@ -294,6 +300,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
createProjectOfFreeDecision:{
|
||||
data:"CreateProjectFreeDecisionInput"
|
||||
},
|
||||
createWithdraw:{
|
||||
input:"CreateWithdrawInput"
|
||||
},
|
||||
declineRequest:{
|
||||
data:"DeclineRequestInput"
|
||||
},
|
||||
@@ -375,6 +384,14 @@ export const AllTypesProps: Record<string,any> = {
|
||||
data:"ReturnByAssetStatementGenerateDocumentInput",
|
||||
options:"GenerateDocumentOptionsInput"
|
||||
},
|
||||
generateReturnByMoneyDecisionDocument:{
|
||||
data:"ReturnByMoneyDecisionGenerateDocumentInput",
|
||||
options:"GenerateDocumentOptionsInput"
|
||||
},
|
||||
generateReturnByMoneyStatementDocument:{
|
||||
data:"ReturnByMoneyGenerateDocumentInput",
|
||||
options:"GenerateDocumentOptionsInput"
|
||||
},
|
||||
generateSelectBranchDocument:{
|
||||
data:"SelectBranchGenerateDocumentInput",
|
||||
options:"GenerateDocumentOptionsInput"
|
||||
@@ -479,6 +496,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
updateExtension:{
|
||||
data:"ExtensionInput"
|
||||
},
|
||||
updatePaymentStatus:{
|
||||
input:"UpdatePaymentStatusInput"
|
||||
},
|
||||
updateRequest:{
|
||||
data:"UpdateRequestInput"
|
||||
},
|
||||
@@ -552,6 +572,10 @@ export const AllTypesProps: Record<string,any> = {
|
||||
getExtensions:{
|
||||
data:"GetExtensionsInput"
|
||||
},
|
||||
getGatewayPayments:{
|
||||
filters:"GetOutgoingPaymentsInput",
|
||||
options:"PaginationInput"
|
||||
},
|
||||
getMeet:{
|
||||
data:"GetMeetInput"
|
||||
},
|
||||
@@ -590,6 +614,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
},
|
||||
RepresentedByInput:{
|
||||
|
||||
},
|
||||
RequestInput:{
|
||||
|
||||
},
|
||||
ResetKeyInput:{
|
||||
|
||||
@@ -622,6 +649,12 @@ export const AllTypesProps: Record<string,any> = {
|
||||
ReturnByAssetStatementSignedMetaDocumentInput:{
|
||||
request:"CommonRequestInput"
|
||||
},
|
||||
ReturnByMoneyDecisionGenerateDocumentInput:{
|
||||
|
||||
},
|
||||
ReturnByMoneyGenerateDocumentInput:{
|
||||
request:"RequestInput"
|
||||
},
|
||||
SearchPrivateAccountsInput:{
|
||||
|
||||
},
|
||||
@@ -695,6 +728,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
UpdateOrganizationDataInput:{
|
||||
details:"OrganizationDetailsInput",
|
||||
represented_by:"RepresentedByInput"
|
||||
},
|
||||
UpdatePaymentStatusInput:{
|
||||
|
||||
},
|
||||
UpdateRequestInput:{
|
||||
|
||||
@@ -714,7 +750,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
VoteOnAnnualGeneralMeetInput:{
|
||||
ballot:"AnnualGeneralMeetingVotingBallotSignedDocumentInput",
|
||||
votes:"VoteItemInput"
|
||||
}
|
||||
},
|
||||
gatewayPaymentStatus: "enum" as const,
|
||||
gatewayPaymentType: "enum" as const
|
||||
}
|
||||
|
||||
export const ReturnTypes: Record<string,any> = {
|
||||
@@ -941,6 +979,9 @@ export const ReturnTypes: Record<string,any> = {
|
||||
username:"String",
|
||||
verifications:"Verification"
|
||||
},
|
||||
CreateWithdrawResponse:{
|
||||
withdraw_hash:"String"
|
||||
},
|
||||
CreatedProjectFreeDecision:{
|
||||
decision:"String",
|
||||
id:"String",
|
||||
@@ -1046,6 +1087,28 @@ export const ReturnTypes: Record<string,any> = {
|
||||
title:"String",
|
||||
updated_at:"DateTime"
|
||||
},
|
||||
GatewayPayment:{
|
||||
blockchain_data:"JSON",
|
||||
can_change_status:"Boolean",
|
||||
coopname:"String",
|
||||
created_at:"DateTime",
|
||||
formatted_amount:"String",
|
||||
hash:"String",
|
||||
id:"ID",
|
||||
income_hash:"String",
|
||||
memo:"String",
|
||||
method_id:"String",
|
||||
outcome_hash:"String",
|
||||
payment_details:"String",
|
||||
quantity:"String",
|
||||
status:"gatewayPaymentStatus",
|
||||
status_label:"String",
|
||||
symbol:"String",
|
||||
type:"gatewayPaymentType",
|
||||
type_label:"String",
|
||||
updated_at:"DateTime",
|
||||
username:"String"
|
||||
},
|
||||
GeneratedDocument:{
|
||||
binary:"String",
|
||||
full_title:"String",
|
||||
@@ -1186,6 +1249,7 @@ export const ReturnTypes: Record<string,any> = {
|
||||
createInitialPayment:"Payment",
|
||||
createParentOffer:"Transaction",
|
||||
createProjectOfFreeDecision:"CreatedProjectFreeDecision",
|
||||
createWithdraw:"CreateWithdrawResponse",
|
||||
declineRequest:"Transaction",
|
||||
deleteBranch:"Boolean",
|
||||
deletePaymentMethod:"Boolean",
|
||||
@@ -1208,6 +1272,8 @@ export const ReturnTypes: Record<string,any> = {
|
||||
generateReturnByAssetAct:"GeneratedDocument",
|
||||
generateReturnByAssetDecision:"GeneratedDocument",
|
||||
generateReturnByAssetStatement:"GeneratedDocument",
|
||||
generateReturnByMoneyDecisionDocument:"GeneratedDocument",
|
||||
generateReturnByMoneyStatementDocument:"GeneratedDocument",
|
||||
generateSelectBranchDocument:"GeneratedDocument",
|
||||
generateSignatureAgreement:"GeneratedDocument",
|
||||
generateSovietDecisionOnAnnualMeetDocument:"GeneratedDocument",
|
||||
@@ -1241,6 +1307,7 @@ export const ReturnTypes: Record<string,any> = {
|
||||
updateAccount:"Account",
|
||||
updateBankAccount:"PaymentMethod",
|
||||
updateExtension:"Extension",
|
||||
updatePaymentStatus:"OutgoingPayment",
|
||||
updateRequest:"Transaction",
|
||||
updateSystem:"SystemInfo",
|
||||
voteOnAnnualGeneralMeet:"MeetAggregate"
|
||||
@@ -1272,6 +1339,31 @@ export const ReturnTypes: Record<string,any> = {
|
||||
kpp:"String",
|
||||
ogrn:"String"
|
||||
},
|
||||
OutgoingPayment:{
|
||||
blockchain_data:"JSON",
|
||||
can_change_status:"Boolean",
|
||||
coopname:"String",
|
||||
created_at:"DateTime",
|
||||
formatted_amount:"String",
|
||||
hash:"String",
|
||||
id:"ID",
|
||||
memo:"String",
|
||||
method_id:"String",
|
||||
payment_details:"String",
|
||||
quantity:"String",
|
||||
status:"gatewayPaymentStatus",
|
||||
status_label:"String",
|
||||
symbol:"String",
|
||||
type:"gatewayPaymentType",
|
||||
updated_at:"DateTime",
|
||||
username:"String"
|
||||
},
|
||||
PaginatedGatewayPaymentsPaginationResult:{
|
||||
currentPage:"Int",
|
||||
items:"GatewayPayment",
|
||||
totalCount:"Int",
|
||||
totalPages:"Int"
|
||||
},
|
||||
ParticipantAccount:{
|
||||
braname:"String",
|
||||
created_at:"DateTime",
|
||||
@@ -1386,6 +1478,7 @@ export const ReturnTypes: Record<string,any> = {
|
||||
getDesktop:"Desktop",
|
||||
getDocuments:"DocumentsAggregatePaginationResult",
|
||||
getExtensions:"Extension",
|
||||
getGatewayPayments:"PaginatedGatewayPaymentsPaginationResult",
|
||||
getMeet:"MeetAggregate",
|
||||
getMeets:"MeetAggregate",
|
||||
getPaymentMethods:"PaymentMethodPaginationResult",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
import type { MakeAllFieldsRequired } from '../../utils/MakeAllFieldsRequired'
|
||||
import { Selector, type ValueTypes, type ModelTypes } from '../../zeus/index'
|
||||
|
||||
// Сырой селектор для CreateWithdrawResponse
|
||||
export const rawCreateWithdrawResponseSelector = {
|
||||
withdraw_hash: true,
|
||||
}
|
||||
|
||||
// Валидация селектора
|
||||
const _validate: MakeAllFieldsRequired<ValueTypes['CreateWithdrawResponse']> = rawCreateWithdrawResponseSelector
|
||||
|
||||
/**
|
||||
* Селектор для ответа создания заявки на вывод средств
|
||||
*/
|
||||
export const createWithdrawResponseSelector = Selector('CreateWithdrawResponse')(rawCreateWithdrawResponseSelector)
|
||||
|
||||
export type CreateWithdrawResponseType = ModelTypes['CreateWithdrawResponse']
|
||||
Reference in New Issue
Block a user