Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f2454d4fc | |||
| 9953f7e980 | |||
| fcb816b0e6 | |||
| b294c61b4e | |||
| 6435b50c72 | |||
| 7c804da6b2 | |||
| eaae515860 | |||
| 9bcfd3a654 | |||
| 109f4aaf98 | |||
| beb87b0c00 | |||
| 947077d011 | |||
| 3297616ad1 | |||
| 90c830fdb1 | |||
| 130c544191 | |||
| 63dcdde872 | |||
| e1a496759b | |||
| 99283dfdd7 | |||
| adee9ee032 | |||
| aaf3b5b98f | |||
| 3dbad0ff30 | |||
| 615eba3a84 | |||
| e7de054de2 | |||
| 6f911605d9 |
@@ -1,46 +1,79 @@
|
||||
# TASKS.md — Прогресс выполнения задач
|
||||
|
||||
## Завершённые задачи
|
||||
|
||||
### 1-6. Предыдущие задачи (см. git history)
|
||||
- ✅ Dev-окружение, Security, Тесты, README, AGENTS.md, Setup
|
||||
- ✅ Поисковая система документов (OpenSearch)
|
||||
- ✅ Процессы (Capital extension)
|
||||
- ✅ 1. Dev-окружение
|
||||
- ✅ 2. Security updates
|
||||
- ✅ 3. Unified test pipeline (162/163)
|
||||
- ✅ 4. README + описания компонентов
|
||||
- ✅ 5. AGENTS.md для всех компонентов
|
||||
- ✅ 6. Setup — профессиональный установщик
|
||||
- ✅ 7. Поисковая система документов (OpenSearch)
|
||||
- ✅ 8. Процессы (Capital extension) — бэкенд + фронтенд
|
||||
- ✅ 9. Отчёты ФНС — 8 генераторов, фабрика, GraphQL API
|
||||
|
||||
---
|
||||
|
||||
## Текущая задача: Генерация отчётов ФНС (расширение reports)
|
||||
## Активные задачи
|
||||
|
||||
### Документы ФНС для генерации:
|
||||
1. **6-НДФЛ** — ежеквартально (XSD: NO_NDFL6.2)
|
||||
2. **4-ФСС (ЕФС-1)** — ежеквартально
|
||||
3. **РСВ** — ежеквартально (XSD: NO_RASCHSV)
|
||||
4. **ПСВ** — ежемесячно (XSD: NO_PERSSVFL)
|
||||
5. **Бухгалтерский баланс** — ежегодно (XSD: NO_BUHOTCH) — КЛЮЧЕВОЙ
|
||||
6. **ДУСН** — декларация УСН ежегодно (XSD: NO_USN)
|
||||
7. **Уведомление о страховых взносах** — ежемесячно с 2026 (XSD: UT_UVISCHSUMNAL)
|
||||
8. **УУСН** — уведомление УСН
|
||||
### 10. Генерация отчётов ФНС (доработка) ✅
|
||||
- [x] Фабрика генераторов (ReportRegistryService)
|
||||
- [x] 8 генераторов (Бухбаланс, 6-НДФЛ, РСВ, ПСВ, ДУСН, 4-ФСС, Увед. взносы, УУСН)
|
||||
- [x] GraphQL API (getAvailableReports, generateReport)
|
||||
- [x] Генераторы переписаны по XSD — структура соответствует схемам ФНС
|
||||
- [x] 48 unit-тестов для всех генераторов
|
||||
- [x] Desktop UI (страница отчётов) — расширение reports
|
||||
- [x] Интеграция с реальными данными ledger через LedgerInteractor
|
||||
- [x] OrganizationDataInput DTO для передачи данных организации
|
||||
|
||||
### Архитектура:
|
||||
- Расширение `reports` в `components/controller/src/extensions/`
|
||||
- Фабрика XML отчётов: на вход данные за период → на выходе XML
|
||||
- Валидация по XSD схемам
|
||||
- Desktop UI: магазин приложений → установка → рабочий стол отчётов
|
||||
### 11. CASL + гранулированные права доступа + шаринг страниц
|
||||
|
||||
### Подзадачи:
|
||||
#### 11.1 CASL — система прав доступа ✅
|
||||
- [x] @casl/ability установлен
|
||||
- [x] Action enum: manage, read, create, update, delete, execute, share
|
||||
- [x] Subject enum: 30+ subjects для всех ресурсов
|
||||
- [x] CaslAbilityFactory: role-based + granular permissions
|
||||
- [x] CaslGuard + @CheckAbility decorator (обратная совместимость с RolesGuard)
|
||||
- [x] role в user сохранено (chairman, member, user)
|
||||
- [x] Тесты: 22/22 unit-тестов (chairman, member, user, granular permissions)
|
||||
|
||||
- [x] **8.1 Исследование**: Все XSD разобраны, format.nalog.ru изучен
|
||||
- [x] **8.2 Инфраструктура**: Расширение reports, ReportRegistryService (фабрика)
|
||||
- [x] **8.3 XML генератор**: Фабричный подход — IReportGenerator interface
|
||||
- [x] **8.4 Бухбаланс**: BuhotchGenerator — счета 51, 80, 86 из ledger
|
||||
- [x] **8.5 6-НДФЛ**: Ndfl6Generator — нулевая
|
||||
- [x] **8.6 4-ФСС**: Zero generator — нулевая
|
||||
- [x] **8.7 РСВ**: Zero generator — нулевая
|
||||
- [x] **8.8 ПСВ**: Zero generator — нулевая
|
||||
- [x] **8.9 ДУСН**: Zero generator — нулевая
|
||||
- [x] **8.10 Уведомление о взносах**: Zero generator — нулевая
|
||||
- [x] **8.11 УУСН**: Zero generator — нулевая
|
||||
- [x] **8.12 GraphQL API**: getAvailableReports + generateReport
|
||||
- [ ] **8.13 XSD валидация + тесты**: Проверка по схемам
|
||||
- [ ] **8.14 Desktop UI**: Страница отчётов в магазине приложений
|
||||
- [ ] **8.15 Ledger интеграция**: Реальные данные из ledger_operations
|
||||
#### 11.2 Шаринг страниц ✅
|
||||
- [x] ShareTokenEntity с JWT, guest/member targets
|
||||
- [x] ShareService: create/verify/revoke
|
||||
- [x] GraphQL: createShareLink, revokeShareLink, getMyShareLinks, getSharedWithMe
|
||||
- [x] Два уровня: гости (linkName) и пайщики (targetUsername)
|
||||
- [x] Granular allowedActions per share link
|
||||
- [x] Кнопка "Поделиться" в Desktop Header (ShareHeaderAction)
|
||||
- [x] Диалог управления правами (ShareDialog)
|
||||
- [x] Страница "Доступные мне" на рабочем столе Пайщика (SharedWithMePage)
|
||||
|
||||
#### 11.3 Интеграция в desktop ✅
|
||||
- [x] useShareButtonProcess: авто-регистрация на всех страницах
|
||||
- [x] ShareButton + ShareDialog компоненты
|
||||
- [x] Интеграция в init-app process
|
||||
- [x] Валидация share tokens в route guard (frontend) — bypass ролей при наличии share_token
|
||||
|
||||
### 12. API ключи кооператива ✅
|
||||
- [x] ApiKeyEntity: хеш ключа (sha256), префикс, операции, срок
|
||||
- [x] ApiKeyService: create, validate, list, revoke
|
||||
- [x] ApiKeyGuard: аутентификация через x-api-key header
|
||||
- [x] GraphQL: createApiKey, getApiKeys, revokeApiKey (chairman only)
|
||||
- [x] Безопасность: ключ показывается ТОЛЬКО при создании, хранится хеш
|
||||
- [x] Desktop: ApiKeysPage в chairman extension
|
||||
- [x] UI: таблица ключей, создание, отзыв, копирование
|
||||
|
||||
### 13. GraphQL Subscriptions ✅
|
||||
- [x] PubSubModule: глобальный PubSub provider
|
||||
- [x] WebSocket support в GraphQL module (graphql-ws)
|
||||
- [x] 5 Capital subscriptions: issueUpdated/Created, commitCreated/Updated, dataChanged
|
||||
- [x] systemStatusChanged — подписка на статус системы (install/init/update)
|
||||
- [x] sovietDataChanged — подписка на события собраний/решений
|
||||
- [x] Events publishing: GenerationService, ProjectManagementService, VotingService, SystemService, MeetEventService
|
||||
- [x] SDK: Subscriptions namespace (Capital)
|
||||
- [x] Auth через connectionParams.token
|
||||
- [x] `useGraphqlSubscription` composable на фронтенде
|
||||
- [x] **7 страниц Capital** переведены с polling на подписки
|
||||
- [x] **System store** — WebSocket мониторинг вместо setTimeout
|
||||
- [x] **init-wallet** — удалён рекурсивный setTimeout(run, 10_000)
|
||||
- [x] **MeetDetails, ListOfAgenda, WaitingRegistration** — подписки вместо setInterval
|
||||
- [x] **Provider, ConnectionAgreement** — удалён setInterval polling
|
||||
- [x] 0 использований setInterval в прикладном коде (только UI-анимация энергии)
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@a2seven/yoo-checkout": "^1.1.4",
|
||||
"@casl/ability": "^6.8.0",
|
||||
"@coopenomics/factory": "workspace:*",
|
||||
"@coopenomics/notifications": "workspace:*",
|
||||
"@coopenomics/provider-client": "2025.11.12-alpha-1",
|
||||
@@ -126,6 +127,7 @@
|
||||
"graphql": "^16.9.0",
|
||||
"graphql-request": "^7.1.2",
|
||||
"graphql-scalars": "^1.24.0",
|
||||
"graphql-subscriptions": "^3.0.0",
|
||||
"graphql-tools": "^9.0.2",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"graphql-ws": "^5.16.0",
|
||||
|
||||
@@ -788,6 +788,30 @@ input AnswerInput {
|
||||
vote: String!
|
||||
}
|
||||
|
||||
type ApiKeyCreated {
|
||||
allowed_operations: [String!]!
|
||||
created_at: DateTime!
|
||||
expires_at: DateTime
|
||||
id: String!
|
||||
|
||||
"""Полный ключ — показывается ТОЛЬКО при создании"""
|
||||
key: String!
|
||||
key_prefix: String!
|
||||
name: String!
|
||||
}
|
||||
|
||||
type ApiKeyInfo {
|
||||
allowed_operations: [String!]!
|
||||
created_at: DateTime!
|
||||
created_by: String!
|
||||
expires_at: DateTime
|
||||
id: String!
|
||||
is_active: Boolean!
|
||||
key_prefix: String!
|
||||
last_used_at: DateTime
|
||||
name: String!
|
||||
}
|
||||
|
||||
"""Одобрение документа председателем совета"""
|
||||
type Approval {
|
||||
"""Дата создания записи"""
|
||||
@@ -1989,6 +2013,12 @@ input CapitalCycleFilter {
|
||||
status: CycleStatus
|
||||
}
|
||||
|
||||
type CapitalDataChange {
|
||||
action: String!
|
||||
entity: String!
|
||||
id: String
|
||||
}
|
||||
|
||||
"""Долг в системе CAPITAL"""
|
||||
type CapitalDebt {
|
||||
"""Дата создания записи"""
|
||||
@@ -4110,6 +4140,12 @@ input CreateAnnualGeneralMeetInput {
|
||||
secretary: String!
|
||||
}
|
||||
|
||||
input CreateApiKeyInput {
|
||||
allowedOperations: [String!] = ["*"]
|
||||
expiresInDays: Int
|
||||
name: String!
|
||||
}
|
||||
|
||||
input CreateBranchInput {
|
||||
"""
|
||||
Документ, на основании которого действует Уполномоченный (решение совета №СС-.. от ..)
|
||||
@@ -4581,6 +4617,16 @@ input CreateProjectPropertyInput {
|
||||
username: String!
|
||||
}
|
||||
|
||||
input CreateShareLinkInput {
|
||||
allowedActions: [String!]!
|
||||
expiresInDays: Float
|
||||
linkName: String
|
||||
pageName: String!
|
||||
pagePath: String!
|
||||
targetType: ShareTargetType!
|
||||
targetUsername: String
|
||||
}
|
||||
|
||||
input CreateSovietIndividualDataInput {
|
||||
"""Дата рождения"""
|
||||
birthdate: String!
|
||||
@@ -7066,6 +7112,11 @@ type Mutation {
|
||||
"""
|
||||
createAnnualGeneralMeet(data: CreateAnnualGeneralMeetInput!): MeetAggregate!
|
||||
|
||||
"""
|
||||
Создать API ключ кооператива. Полный ключ показывается только при создании.
|
||||
"""
|
||||
createApiKey(data: CreateApiKeyInput!): ApiKeyCreated!
|
||||
|
||||
"""Создать кооперативный участок"""
|
||||
createBranch(data: CreateBranchInput!): Branch!
|
||||
|
||||
@@ -7090,6 +7141,9 @@ type Mutation {
|
||||
"""
|
||||
createProjectOfFreeDecision(data: CreateProjectFreeDecisionInput!): CreatedProjectFreeDecision!
|
||||
|
||||
"""Создать ссылку доступа к странице"""
|
||||
createShareLink(data: CreateShareLinkInput!): ShareLink!
|
||||
|
||||
"""Создать веб-пуш подписку для пользователя"""
|
||||
createWebPushSubscription(data: CreateSubscriptionInput!): CreateSubscriptionResponse!
|
||||
|
||||
@@ -7175,7 +7229,7 @@ type Mutation {
|
||||
generateRegistrationDocuments(data: GenerateRegistrationDocumentsInput!): GenerateRegistrationDocumentsOutput!
|
||||
|
||||
"""Генерация отчёта для ФНС/ФСС"""
|
||||
generateReport(data: GenerateReportInput!): GeneratedReport!
|
||||
generateReport(data: GenerateReportInput!, organization: OrganizationDataInput!): GeneratedReport!
|
||||
|
||||
"""Сгенерировать документ акта возврата имущества."""
|
||||
generateReturnByAssetAct(data: ReturnByAssetActGenerateDocumentInput!, options: GenerateDocumentOptionsInput): GeneratedDocument!
|
||||
@@ -7276,6 +7330,12 @@ type Mutation {
|
||||
"""Перезапуск общего собрания пайщиков"""
|
||||
restartAnnualGeneralMeet(data: RestartAnnualGeneralMeetInput!): MeetAggregate!
|
||||
|
||||
"""Отозвать API ключ"""
|
||||
revokeApiKey(id: String!): Boolean!
|
||||
|
||||
"""Отозвать ссылку доступа"""
|
||||
revokeShareLink(id: String!): Boolean!
|
||||
|
||||
"""Выбрать кооперативный участок"""
|
||||
selectBranch(data: SelectBranchInput!): Boolean!
|
||||
|
||||
@@ -7461,6 +7521,23 @@ type OrganizationCertificate {
|
||||
username: String!
|
||||
}
|
||||
|
||||
input OrganizationDataInput {
|
||||
address: String
|
||||
inn: String!
|
||||
kpp: String!
|
||||
ogrn: String!
|
||||
okfs: String
|
||||
okopf: String
|
||||
oktmo: String!
|
||||
okved: String!
|
||||
orgName: String!
|
||||
phone: String
|
||||
signerFirstName: String!
|
||||
signerLastName: String!
|
||||
signerMiddleName: String
|
||||
signerSnils: String
|
||||
}
|
||||
|
||||
type OrganizationDetails {
|
||||
"""ИНН"""
|
||||
inn: String!
|
||||
@@ -8837,6 +8914,9 @@ type Query {
|
||||
"""Получить список вопросов совета кооператива для голосования"""
|
||||
getAgenda: [AgendaWithDocuments!]!
|
||||
|
||||
"""Получить список API ключей кооператива"""
|
||||
getApiKeys: [ApiKeyInfo!]!
|
||||
|
||||
"""Получить список доступных типов отчётов"""
|
||||
getAvailableReports: [AvailableReport!]!
|
||||
|
||||
@@ -8897,6 +8977,9 @@ type Query {
|
||||
"""Получить список всех собраний кооператива"""
|
||||
getMeets(data: GetMeetsInput!): [MeetAggregate!]!
|
||||
|
||||
"""Получить созданные мной ссылки доступа"""
|
||||
getMyShareLinks: [ShareLink!]!
|
||||
|
||||
"""Получить список методов оплаты"""
|
||||
getPaymentMethods(data: GetPaymentMethodsInput): PaymentMethodPaginationResult!
|
||||
|
||||
@@ -8920,6 +9003,9 @@ type Query {
|
||||
"""Получить конфигурацию программ регистрации для кооператива"""
|
||||
getRegistrationConfig(account_type: AccountType!, coopname: String!): RegistrationConfig!
|
||||
|
||||
"""Получить страницы, к которым мне предоставлен доступ"""
|
||||
getSharedWithMe: [ShareLink!]!
|
||||
|
||||
"""Получить сводную публичную информацию о системе"""
|
||||
getSystemInfo: SystemInfo!
|
||||
|
||||
@@ -10028,6 +10114,25 @@ type Settings {
|
||||
updated_at: DateTime!
|
||||
}
|
||||
|
||||
type ShareLink {
|
||||
allowed_actions: [String!]!
|
||||
created_at: DateTime!
|
||||
expires_at: DateTime
|
||||
id: String!
|
||||
is_active: Boolean!
|
||||
link_name: String
|
||||
page_name: String!
|
||||
page_path: String!
|
||||
target_type: ShareTargetType!
|
||||
target_username: String
|
||||
token: String!
|
||||
}
|
||||
|
||||
enum ShareTargetType {
|
||||
GUEST
|
||||
MEMBER
|
||||
}
|
||||
|
||||
input SignActAsChairmanInput {
|
||||
"""Акт о вкладе результатов"""
|
||||
act: SignedDigitalDocumentInput!
|
||||
@@ -10244,6 +10349,25 @@ input SubmitVoteInput {
|
||||
votes: [VoteDistributionInput!]!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
"""Подписка на создание коммитов"""
|
||||
capitalCommitCreated(project_hash: String): CapitalCommit!
|
||||
|
||||
"""Подписка на обновления коммитов"""
|
||||
capitalCommitUpdated(project_hash: String): CapitalCommit!
|
||||
|
||||
"""
|
||||
Уведомление об изменении данных Capital (проекты, участники, голосования)
|
||||
"""
|
||||
capitalDataChanged: CapitalDataChange!
|
||||
|
||||
"""Подписка на создание задач"""
|
||||
capitalIssueCreated(project_hash: String): CapitalIssue!
|
||||
|
||||
"""Подписка на обновления задач"""
|
||||
capitalIssueUpdated(project_hash: String): CapitalIssue!
|
||||
}
|
||||
|
||||
type SubscriptionStatsDto {
|
||||
"""Количество активных подписок"""
|
||||
active: Int!
|
||||
|
||||
@@ -16,6 +16,12 @@ import { EventsInfrastructureModule } from './infrastructure/events/events.modul
|
||||
import { FreeDecisionInfrastructureModule } from './infrastructure/free-decision/free-decision-infrastructure.module';
|
||||
import { DecisionTrackingInfrastructureModule } from './infrastructure/decision-tracking/decision-tracking-infrastructure.module';
|
||||
import { SearchInfrastructureModule } from './infrastructure/search/search-infrastructure.module';
|
||||
import { CaslModule } from './infrastructure/casl/casl.module';
|
||||
import { PubSubModule } from './infrastructure/pubsub/pubsub.module';
|
||||
import { ShareModule } from './infrastructure/share/share.module';
|
||||
import { ApiKeyModule } from './infrastructure/api-keys/api-key.module';
|
||||
import { ApiKeysAppModule } from './application/api-keys/api-keys.module';
|
||||
import { ShareAppModule } from './application/share/share.module';
|
||||
|
||||
// Domain modules
|
||||
import { AccountDomainModule } from './domain/account/account-domain.module';
|
||||
@@ -95,6 +101,10 @@ import { MutationLoggingInterceptor } from './application/common/interceptors/mu
|
||||
RedisModule,
|
||||
NovuModule,
|
||||
SearchInfrastructureModule,
|
||||
CaslModule,
|
||||
PubSubModule,
|
||||
ShareModule,
|
||||
ApiKeyModule,
|
||||
EventsInfrastructureModule,
|
||||
FreeDecisionInfrastructureModule,
|
||||
DecisionTrackingInfrastructureModule,
|
||||
@@ -152,6 +162,8 @@ import { MutationLoggingInterceptor } from './application/common/interceptors/mu
|
||||
SettingsApplicationModule,
|
||||
RegistrationModule,
|
||||
SearchModule,
|
||||
ShareAppModule,
|
||||
ApiKeysAppModule,
|
||||
ReportsExtensionModule,
|
||||
],
|
||||
providers: [
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ApiKeyResolver } from './resolvers/api-key.resolver';
|
||||
|
||||
@Module({
|
||||
providers: [ApiKeyResolver],
|
||||
})
|
||||
export class ApiKeysAppModule {}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { ObjectType, Field, InputType, Int } from '@nestjs/graphql';
|
||||
import { IsString, IsOptional, IsInt, IsArray } from 'class-validator';
|
||||
|
||||
@InputType('CreateApiKeyInput')
|
||||
export class CreateApiKeyInputDTO {
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
name!: string;
|
||||
|
||||
@Field(() => [String], { nullable: true, defaultValue: ['*'] })
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
allowedOperations?: string[];
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
expiresInDays?: number;
|
||||
}
|
||||
|
||||
@ObjectType('ApiKeyCreated')
|
||||
export class ApiKeyCreatedDTO {
|
||||
@Field(() => String)
|
||||
id!: string;
|
||||
|
||||
@Field(() => String)
|
||||
name!: string;
|
||||
|
||||
@Field(() => String, { description: 'Полный ключ — показывается ТОЛЬКО при создании' })
|
||||
key!: string;
|
||||
|
||||
@Field(() => String)
|
||||
key_prefix!: string;
|
||||
|
||||
@Field(() => [String])
|
||||
allowed_operations!: string[];
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
expires_at?: Date;
|
||||
|
||||
@Field(() => Date)
|
||||
created_at!: Date;
|
||||
}
|
||||
|
||||
@ObjectType('ApiKeyInfo')
|
||||
export class ApiKeyInfoDTO {
|
||||
@Field(() => String)
|
||||
id!: string;
|
||||
|
||||
@Field(() => String)
|
||||
name!: string;
|
||||
|
||||
@Field(() => String)
|
||||
key_prefix!: string;
|
||||
|
||||
@Field(() => String)
|
||||
created_by!: string;
|
||||
|
||||
@Field(() => [String])
|
||||
allowed_operations!: string[];
|
||||
|
||||
@Field(() => Boolean)
|
||||
is_active!: boolean;
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
expires_at?: Date;
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
last_used_at?: Date;
|
||||
|
||||
@Field(() => Date)
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { GqlJwtAuthGuard } from '~/application/auth/guards/graphql-jwt-auth.guard';
|
||||
import { RolesGuard } from '~/application/auth/guards/roles.guard';
|
||||
import { AuthRoles } from '~/application/auth/decorators/auth.decorator';
|
||||
import { CurrentUser } from '~/application/auth/decorators/current-user.decorator';
|
||||
import type { MonoAccountDomainInterface } from '~/domain/account/interfaces/mono-account-domain.interface';
|
||||
import { ApiKeyService } from '~/infrastructure/api-keys/api-key.service';
|
||||
import { CreateApiKeyInputDTO, ApiKeyCreatedDTO, ApiKeyInfoDTO } from '../dto/api-key.dto';
|
||||
import { config } from '~/config';
|
||||
|
||||
@Resolver()
|
||||
export class ApiKeyResolver {
|
||||
constructor(private readonly apiKeyService: ApiKeyService) {}
|
||||
|
||||
@Mutation(() => ApiKeyCreatedDTO, {
|
||||
name: 'createApiKey',
|
||||
description: 'Создать API ключ кооператива. Полный ключ показывается только при создании.',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard, RolesGuard)
|
||||
@AuthRoles(['chairman'])
|
||||
async createApiKey(
|
||||
@Args('data') data: CreateApiKeyInputDTO,
|
||||
@CurrentUser() user: MonoAccountDomainInterface,
|
||||
): Promise<ApiKeyCreatedDTO> {
|
||||
return this.apiKeyService.createKey({
|
||||
coopname: config.coopname,
|
||||
name: data.name,
|
||||
createdBy: user.username,
|
||||
allowedOperations: data.allowedOperations,
|
||||
expiresInDays: data.expiresInDays,
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => [ApiKeyInfoDTO], {
|
||||
name: 'getApiKeys',
|
||||
description: 'Получить список API ключей кооператива',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard, RolesGuard)
|
||||
@AuthRoles(['chairman'])
|
||||
async getApiKeys(): Promise<ApiKeyInfoDTO[]> {
|
||||
return this.apiKeyService.listKeys(config.coopname) as any;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, {
|
||||
name: 'revokeApiKey',
|
||||
description: 'Отозвать API ключ',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard, RolesGuard)
|
||||
@AuthRoles(['chairman'])
|
||||
async revokeApiKey(
|
||||
@Args('id') id: string,
|
||||
@CurrentUser() user: MonoAccountDomainInterface,
|
||||
): Promise<boolean> {
|
||||
await this.apiKeyService.revokeKey(id, user.username);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,16 @@ export class RolesGuard implements CanActivate {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Проверка: есть ли у пользователя делегированные права (через share tokens)
|
||||
if (user.grantedPermissions && Array.isArray(user.grantedPermissions)) {
|
||||
const hasGranted = user.grantedPermissions.some(
|
||||
(p: any) => p.action === 'read' || p.action === 'manage'
|
||||
);
|
||||
if (hasGranted) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnauthorizedException(`Недостаточно прав доступа`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@ import { LedgerDomainModule } from '~/domain/ledger/ledger-domain.module';
|
||||
@Module({
|
||||
imports: [LedgerDomainModule],
|
||||
providers: [LedgerResolver, LedgerService, LedgerEventService, LedgerInteractor],
|
||||
exports: [LedgerInteractor],
|
||||
})
|
||||
export class LedgerModule {}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DocumentModule } from '../document/document.module';
|
||||
import { MeetResolver } from './resolvers/meet.resolver';
|
||||
import { MeetSubscriptionResolver } from './resolvers/meet-subscription.resolver';
|
||||
import { MeetService } from './services/meet.service';
|
||||
import { MeetEventService } from './services/meet-event.service';
|
||||
import { MeetInteractor } from './interactors/meet.interactor';
|
||||
import { AgendaNotificationService } from '../agenda/services/agenda-notification.service';
|
||||
import { DecisionNotificationService } from '../agenda/services/decision-notification.service';
|
||||
import { NovuModule } from '~/infrastructure/novu/novu.module';
|
||||
import { PubSubModule } from '~/infrastructure/pubsub/pubsub.module';
|
||||
import { MeetDomainModule } from '~/domain/meet/meet-domain.module';
|
||||
import { UserDomainModule } from '~/domain/user/user-domain.module';
|
||||
import { UserInfrastructureModule } from '~/infrastructure/user/user-infrastructure.module';
|
||||
@@ -23,6 +25,7 @@ import { DocumentDomainModule } from '~/domain/document/document.module';
|
||||
DatabaseModule,
|
||||
BlockchainModule,
|
||||
NovuModule,
|
||||
PubSubModule,
|
||||
MeetDomainModule,
|
||||
UserInfrastructureModule,
|
||||
UserDomainModule,
|
||||
@@ -32,6 +35,7 @@ import { DocumentDomainModule } from '~/domain/document/document.module';
|
||||
controllers: [],
|
||||
providers: [
|
||||
MeetResolver,
|
||||
MeetSubscriptionResolver,
|
||||
MeetService,
|
||||
MeetEventService,
|
||||
MeetInteractor,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Resolver, Subscription, ObjectType, Field } from '@nestjs/graphql';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import { PUB_SUB } from '~/infrastructure/pubsub/pubsub.module';
|
||||
|
||||
export const SOVIET_EVENTS = {
|
||||
DATA_CHANGED: 'sovietDataChanged',
|
||||
};
|
||||
|
||||
@ObjectType('SovietDataChange')
|
||||
export class SovietDataChangeDTO {
|
||||
@Field(() => String)
|
||||
entity!: string;
|
||||
|
||||
@Field(() => String)
|
||||
action!: string;
|
||||
}
|
||||
|
||||
@Resolver()
|
||||
export class MeetSubscriptionResolver {
|
||||
constructor(@Inject(PUB_SUB) private readonly pubSub: PubSub) {}
|
||||
|
||||
@Subscription(() => SovietDataChangeDTO, {
|
||||
name: 'sovietDataChanged',
|
||||
description: 'Уведомление об изменениях собраний, решений, повестки',
|
||||
})
|
||||
sovietDataChanged() {
|
||||
return this.pubSub.asyncIterableIterator(SOVIET_EVENTS.DATA_CHANGED);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
// application/meet/services/meet-event.service.ts
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import { MeetInteractor } from '../interactors/meet.interactor';
|
||||
import { WinstonLoggerService } from '~/application/logger/logger-app.service';
|
||||
import { DomainToBlockchainUtils } from '~/shared/utils/domain-to-blockchain.utils';
|
||||
import type { MeetDecisionDomainInterface } from '~/domain/meet/interfaces/meet-decision-domain.interface';
|
||||
import { MeetContract } from 'cooptypes';
|
||||
import type { IAction } from '~/types';
|
||||
import { PUB_SUB } from '~/infrastructure/pubsub/pubsub.module';
|
||||
import { SOVIET_EVENTS } from '../resolvers/meet-subscription.resolver';
|
||||
|
||||
/**
|
||||
* Сервис обработки событий собраний
|
||||
@@ -15,10 +18,20 @@ import type { IAction } from '~/types';
|
||||
*/
|
||||
@Injectable()
|
||||
export class MeetEventService {
|
||||
constructor(private readonly meetInteractor: MeetInteractor, private readonly logger: WinstonLoggerService) {
|
||||
constructor(
|
||||
private readonly meetInteractor: MeetInteractor,
|
||||
private readonly logger: WinstonLoggerService,
|
||||
@Inject(PUB_SUB) private readonly pubSub: PubSub,
|
||||
) {
|
||||
this.logger.setContext(MeetEventService.name);
|
||||
}
|
||||
|
||||
private notifySovietChanged(entity: string, action: string) {
|
||||
this.pubSub.publish(SOVIET_EVENTS.DATA_CHANGED, {
|
||||
sovietDataChanged: { entity, action },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик события решения собрания из блокчейна
|
||||
*/
|
||||
@@ -51,6 +64,7 @@ export class MeetEventService {
|
||||
});
|
||||
|
||||
this.logger.info(`Processed meet decision for ${event.data.hash}`);
|
||||
this.notifySovietChanged('decision', 'processed');
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Error processing meet decision: ${error.message}`, error.stack);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { ObjectType, Field, InputType, registerEnumType } from '@nestjs/graphql';
|
||||
import { IsString, IsOptional, IsArray, IsInt, IsEnum } from 'class-validator';
|
||||
import { ShareTargetType } from '~/infrastructure/share/share-token.entity';
|
||||
|
||||
registerEnumType(ShareTargetType, { name: 'ShareTargetType' });
|
||||
|
||||
@InputType('CreateShareLinkInput')
|
||||
export class CreateShareLinkInputDTO {
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
pagePath!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
pageName!: string;
|
||||
|
||||
@Field(() => ShareTargetType)
|
||||
@IsEnum(ShareTargetType)
|
||||
targetType!: ShareTargetType;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
targetUsername?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
linkName?: string;
|
||||
|
||||
@Field(() => [String])
|
||||
@IsArray()
|
||||
allowedActions!: string[];
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
expiresInDays?: number;
|
||||
}
|
||||
|
||||
@ObjectType('ShareLink')
|
||||
export class ShareLinkDTO {
|
||||
@Field(() => String)
|
||||
id!: string;
|
||||
|
||||
@Field(() => String)
|
||||
page_path!: string;
|
||||
|
||||
@Field(() => String)
|
||||
page_name!: string;
|
||||
|
||||
@Field(() => ShareTargetType)
|
||||
target_type!: ShareTargetType;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
target_username?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
link_name?: string;
|
||||
|
||||
@Field(() => [String])
|
||||
allowed_actions!: string[];
|
||||
|
||||
@Field(() => String)
|
||||
token!: string;
|
||||
|
||||
@Field(() => Boolean)
|
||||
is_active!: boolean;
|
||||
|
||||
@Field(() => Date)
|
||||
created_at!: Date;
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
expires_at?: Date;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { GqlJwtAuthGuard } from '~/application/auth/guards/graphql-jwt-auth.guard';
|
||||
import { CurrentUser } from '~/application/auth/decorators/current-user.decorator';
|
||||
import type { MonoAccountDomainInterface } from '~/domain/account/interfaces/mono-account-domain.interface';
|
||||
import { ShareService } from '~/infrastructure/share/share.service';
|
||||
import { ShareLinkDTO, CreateShareLinkInputDTO } from '../dto/share.dto';
|
||||
import { config } from '~/config';
|
||||
|
||||
@Resolver()
|
||||
export class ShareResolver {
|
||||
constructor(private readonly shareService: ShareService) {}
|
||||
|
||||
@Mutation(() => ShareLinkDTO, {
|
||||
name: 'createShareLink',
|
||||
description: 'Создать ссылку доступа к странице',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard)
|
||||
async createShareLink(
|
||||
@Args('data') data: CreateShareLinkInputDTO,
|
||||
@CurrentUser() user: MonoAccountDomainInterface,
|
||||
): Promise<ShareLinkDTO> {
|
||||
return this.shareService.createShareLink({
|
||||
coopname: config.coopname,
|
||||
createdBy: user.username,
|
||||
pagePath: data.pagePath,
|
||||
pageName: data.pageName,
|
||||
targetType: data.targetType,
|
||||
targetUsername: data.targetUsername,
|
||||
linkName: data.linkName,
|
||||
allowedActions: data.allowedActions,
|
||||
expiresInDays: data.expiresInDays,
|
||||
}) as any;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, {
|
||||
name: 'revokeShareLink',
|
||||
description: 'Отозвать ссылку доступа',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard)
|
||||
async revokeShareLink(
|
||||
@Args('id') id: string,
|
||||
@CurrentUser() user: MonoAccountDomainInterface,
|
||||
): Promise<boolean> {
|
||||
await this.shareService.revokeShareLink(id, user.username);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Query(() => [ShareLinkDTO], {
|
||||
name: 'getMyShareLinks',
|
||||
description: 'Получить созданные мной ссылки доступа',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard)
|
||||
async getMyShareLinks(
|
||||
@CurrentUser() user: MonoAccountDomainInterface,
|
||||
): Promise<ShareLinkDTO[]> {
|
||||
return this.shareService.getShareLinks(config.coopname, user.username) as any;
|
||||
}
|
||||
|
||||
@Query(() => [ShareLinkDTO], {
|
||||
name: 'getSharedWithMe',
|
||||
description: 'Получить страницы, к которым мне предоставлен доступ',
|
||||
})
|
||||
@UseGuards(GqlJwtAuthGuard)
|
||||
async getSharedWithMe(
|
||||
@CurrentUser() user: MonoAccountDomainInterface,
|
||||
): Promise<ShareLinkDTO[]> {
|
||||
return this.shareService.getSharedWithMe(config.coopname, user.username) as any;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ShareResolver } from './resolvers/share.resolver';
|
||||
|
||||
@Module({
|
||||
providers: [ShareResolver],
|
||||
})
|
||||
export class ShareAppModule {}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { Resolver, Subscription, ObjectType, Field } from '@nestjs/graphql';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import { PUB_SUB } from '~/infrastructure/pubsub/pubsub.module';
|
||||
|
||||
export const SYSTEM_EVENTS = {
|
||||
STATUS_CHANGED: 'systemStatusChanged',
|
||||
};
|
||||
|
||||
@ObjectType('SystemStatusChange')
|
||||
export class SystemStatusChangeDTO {
|
||||
@Field(() => String)
|
||||
status!: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
message?: string;
|
||||
}
|
||||
|
||||
@Resolver()
|
||||
export class SystemSubscriptionResolver {
|
||||
constructor(@Inject(PUB_SUB) private readonly pubSub: PubSub) {}
|
||||
|
||||
@Subscription(() => SystemStatusChangeDTO, {
|
||||
name: 'systemStatusChanged',
|
||||
description: 'Уведомление об изменении состояния системы (статус, конфигурация)',
|
||||
})
|
||||
systemStatusChanged() {
|
||||
return this.pubSub.asyncIterableIterator(SYSTEM_EVENTS.STATUS_CHANGED);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import { SystemInfoDTO } from '../dto/system.dto';
|
||||
import { SystemInteractor } from '../interactors/system.interactor';
|
||||
import { ProviderService } from '~/application/provider/services/provider.service';
|
||||
@@ -20,6 +21,8 @@ import {
|
||||
AGREEMENT_CONFIGURATION_SERVICE,
|
||||
} from '~/domain/registration/services/agreement-configuration.service';
|
||||
import type { AccountType } from '~/application/account/enum/account-type.enum';
|
||||
import { PUB_SUB } from '~/infrastructure/pubsub/pubsub.module';
|
||||
import { SYSTEM_EVENTS } from '../resolvers/system-subscription.resolver';
|
||||
|
||||
@Injectable()
|
||||
export class SystemService {
|
||||
@@ -27,7 +30,8 @@ export class SystemService {
|
||||
private readonly systemInteractor: SystemInteractor,
|
||||
private readonly providerService: ProviderService,
|
||||
@Inject(AGREEMENT_CONFIGURATION_SERVICE)
|
||||
private readonly agreementConfigService: AgreementConfigurationService
|
||||
private readonly agreementConfigService: AgreementConfigurationService,
|
||||
@Inject(PUB_SUB) private readonly pubSub: PubSub,
|
||||
) {}
|
||||
|
||||
public async getInfo(): Promise<SystemInfoDTO> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SystemService } from './services/system.service';
|
||||
import { SystemResolver } from './resolvers/system.resolver';
|
||||
import { SystemSubscriptionResolver } from './resolvers/system-subscription.resolver';
|
||||
import { SystemDomainModule } from '~/domain/system/system-domain.module';
|
||||
import { ProviderModule } from '~/application/provider/provider.module';
|
||||
import { SystemInteractor } from './interactors/system.interactor';
|
||||
@@ -16,6 +17,7 @@ import { PaymentMethodInfrastructureModule } from '~/infrastructure/payment-meth
|
||||
import { AccountDomainModule } from '~/domain/account/account-domain.module';
|
||||
import { SettingsInfrastructureModule } from '~/infrastructure/settings/settings-infrastructure.module';
|
||||
import { VaultDomainModule } from '~/domain/vault/vault-domain.module';
|
||||
import { PubSubModule } from '~/infrastructure/pubsub/pubsub.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -27,11 +29,13 @@ import { VaultDomainModule } from '~/domain/vault/vault-domain.module';
|
||||
PaymentMethodInfrastructureModule,
|
||||
AccountDomainModule,
|
||||
SettingsInfrastructureModule,
|
||||
PubSubModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [
|
||||
SystemService,
|
||||
SystemResolver,
|
||||
SystemSubscriptionResolver,
|
||||
SystemInteractor,
|
||||
LoadContactsInteractor,
|
||||
WifInteractor,
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { Resolver, Subscription, Args, ObjectType, Field } from '@nestjs/graphql';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import { PUB_SUB } from '~/infrastructure/pubsub/pubsub.module';
|
||||
import { IssueOutputDTO } from '../dto/generation/issue.dto';
|
||||
import { CommitOutputDTO } from '../dto/generation/commit.dto';
|
||||
|
||||
export const CAPITAL_EVENTS = {
|
||||
ISSUE_UPDATED: 'capitalIssueUpdated',
|
||||
ISSUE_CREATED: 'capitalIssueCreated',
|
||||
COMMIT_CREATED: 'capitalCommitCreated',
|
||||
COMMIT_UPDATED: 'capitalCommitUpdated',
|
||||
DATA_CHANGED: 'capitalDataChanged',
|
||||
};
|
||||
|
||||
@ObjectType('CapitalDataChange')
|
||||
export class CapitalDataChangeDTO {
|
||||
@Field(() => String)
|
||||
entity!: string;
|
||||
|
||||
@Field(() => String)
|
||||
action!: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
id?: string;
|
||||
}
|
||||
|
||||
@Resolver()
|
||||
export class CapitalSubscriptionResolver {
|
||||
constructor(@Inject(PUB_SUB) private readonly pubSub: PubSub) {}
|
||||
|
||||
@Subscription(() => IssueOutputDTO, {
|
||||
name: 'capitalIssueUpdated',
|
||||
description: 'Подписка на обновления задач',
|
||||
filter: (payload: any, variables: any) => {
|
||||
if (variables.project_hash && payload.capitalIssueUpdated.project_hash !== variables.project_hash) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
})
|
||||
capitalIssueUpdated(
|
||||
@Args('project_hash', { nullable: true }) _projectHash?: string,
|
||||
) {
|
||||
return this.pubSub.asyncIterableIterator(CAPITAL_EVENTS.ISSUE_UPDATED);
|
||||
}
|
||||
|
||||
@Subscription(() => IssueOutputDTO, {
|
||||
name: 'capitalIssueCreated',
|
||||
description: 'Подписка на создание задач',
|
||||
filter: (payload: any, variables: any) => {
|
||||
if (variables.project_hash && payload.capitalIssueCreated.project_hash !== variables.project_hash) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
})
|
||||
capitalIssueCreated(
|
||||
@Args('project_hash', { nullable: true }) _projectHash?: string,
|
||||
) {
|
||||
return this.pubSub.asyncIterableIterator(CAPITAL_EVENTS.ISSUE_CREATED);
|
||||
}
|
||||
|
||||
@Subscription(() => CommitOutputDTO, {
|
||||
name: 'capitalCommitCreated',
|
||||
description: 'Подписка на создание коммитов',
|
||||
filter: (payload: any, variables: any) => {
|
||||
if (variables.project_hash && payload.capitalCommitCreated.project_hash !== variables.project_hash) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
})
|
||||
capitalCommitCreated(
|
||||
@Args('project_hash', { nullable: true }) _projectHash?: string,
|
||||
) {
|
||||
return this.pubSub.asyncIterableIterator(CAPITAL_EVENTS.COMMIT_CREATED);
|
||||
}
|
||||
|
||||
@Subscription(() => CommitOutputDTO, {
|
||||
name: 'capitalCommitUpdated',
|
||||
description: 'Подписка на обновления коммитов',
|
||||
filter: (payload: any, variables: any) => {
|
||||
if (variables.project_hash && payload.capitalCommitUpdated.project_hash !== variables.project_hash) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
})
|
||||
capitalCommitUpdated(
|
||||
@Args('project_hash', { nullable: true }) _projectHash?: string,
|
||||
) {
|
||||
return this.pubSub.asyncIterableIterator(CAPITAL_EVENTS.COMMIT_UPDATED);
|
||||
}
|
||||
|
||||
@Subscription(() => CapitalDataChangeDTO, {
|
||||
name: 'capitalDataChanged',
|
||||
description: 'Уведомление об изменении данных Capital (проекты, участники, голосования)',
|
||||
})
|
||||
capitalDataChanged() {
|
||||
return this.pubSub.asyncIterableIterator(CAPITAL_EVENTS.DATA_CHANGED);
|
||||
}
|
||||
}
|
||||
+42
-13
@@ -1,4 +1,7 @@
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import { PUB_SUB } from '~/infrastructure/pubsub/pubsub.module';
|
||||
import { CAPITAL_EVENTS } from '../resolvers/capital-subscription.resolver';
|
||||
import { generateUniqueHash } from '~/utils/generate-hash.util';
|
||||
import { GenerationInteractor } from '../use-cases/generation.interactor';
|
||||
import type { CreateCommitInputDTO } from '../dto/generation/create-commit-input.dto';
|
||||
@@ -57,6 +60,7 @@ import type { MonoAccountDomainInterface } from '~/domain/account/interfaces/mon
|
||||
@Injectable()
|
||||
export class GenerationService {
|
||||
constructor(
|
||||
@Inject(PUB_SUB) private readonly pubSub: PubSub,
|
||||
private readonly generationInteractor: GenerationInteractor,
|
||||
@Inject(STORY_REPOSITORY)
|
||||
private readonly storyRepository: StoryRepository,
|
||||
@@ -84,7 +88,13 @@ export class GenerationService {
|
||||
*/
|
||||
async createCommit(data: CreateCommitInputDTO, currentUser: MonoAccountDomainInterface): Promise<CommitOutputDTO> {
|
||||
const commitEntity = await this.generationInteractor.createCommit(data, currentUser);
|
||||
return await this.commitMapperService.toDTO(commitEntity, currentUser);
|
||||
const result = await this.commitMapperService.toDTO(commitEntity, currentUser);
|
||||
|
||||
this.pubSub.publish(CAPITAL_EVENTS.COMMIT_CREATED, {
|
||||
[CAPITAL_EVENTS.COMMIT_CREATED]: result,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +107,13 @@ export class GenerationService {
|
||||
};
|
||||
|
||||
const commitEntity = await this.generationInteractor.approveCommit(domainInput);
|
||||
return await this.commitMapperService.toDTO(commitEntity, currentUser);
|
||||
const result = await this.commitMapperService.toDTO(commitEntity, currentUser);
|
||||
|
||||
this.pubSub.publish(CAPITAL_EVENTS.COMMIT_UPDATED, {
|
||||
[CAPITAL_EVENTS.COMMIT_UPDATED]: result,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,8 +125,15 @@ export class GenerationService {
|
||||
master: currentUser.username,
|
||||
};
|
||||
|
||||
|
||||
const commitEntity = await this.generationInteractor.declineCommit(domainInput);
|
||||
return await this.commitMapperService.toDTO(commitEntity, currentUser);
|
||||
const result = await this.commitMapperService.toDTO(commitEntity, currentUser);
|
||||
|
||||
this.pubSub.publish(CAPITAL_EVENTS.COMMIT_UPDATED, {
|
||||
[CAPITAL_EVENTS.COMMIT_UPDATED]: result,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============ STORY METHODS ============
|
||||
@@ -424,11 +447,14 @@ export class GenerationService {
|
||||
// Рассчитываем права доступа для задачи
|
||||
const permissions = await this.permissionsService.calculateIssuePermissions(savedIssue, currentUser);
|
||||
|
||||
// Возвращаем задачу с правами доступа
|
||||
return {
|
||||
...savedIssue,
|
||||
permissions,
|
||||
} as IssueOutputDTO;
|
||||
const issueResult = { ...savedIssue, permissions } as IssueOutputDTO;
|
||||
|
||||
// Публикуем событие для подписчиков
|
||||
this.pubSub.publish(CAPITAL_EVENTS.ISSUE_CREATED, {
|
||||
[CAPITAL_EVENTS.ISSUE_CREATED]: issueResult,
|
||||
});
|
||||
|
||||
return issueResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -552,11 +578,14 @@ export class GenerationService {
|
||||
// Рассчитываем права доступа для задачи
|
||||
const permissions = await this.permissionsService.calculateIssuePermissions(updatedIssue, currentUser);
|
||||
|
||||
// Возвращаем задачу с правами доступа
|
||||
return {
|
||||
...updatedIssue,
|
||||
permissions,
|
||||
} as IssueOutputDTO;
|
||||
const issueResult = { ...updatedIssue, permissions } as IssueOutputDTO;
|
||||
|
||||
// Публикуем событие для подписчиков
|
||||
this.pubSub.publish(CAPITAL_EVENTS.ISSUE_UPDATED, {
|
||||
[CAPITAL_EVENTS.ISSUE_UPDATED]: issueResult,
|
||||
});
|
||||
|
||||
return issueResult;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+18
-8
@@ -1,4 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import { PUB_SUB } from '~/infrastructure/pubsub/pubsub.module';
|
||||
import { CAPITAL_EVENTS } from '../resolvers/capital-subscription.resolver';
|
||||
import { ProjectManagementInteractor } from '../use-cases/project-management.interactor';
|
||||
import type { CreateProjectInputDTO } from '../dto/project_management';
|
||||
import type { TransactResult } from '@wharfkit/session';
|
||||
@@ -22,30 +25,37 @@ import { DocumentInteractor } from '~/application/document/interactors/document.
|
||||
import { ProjectMapperService } from './project-mapper.service';
|
||||
import type { MonoAccountDomainInterface } from '~/domain/account/interfaces/mono-account-domain.interface';
|
||||
|
||||
/**
|
||||
* Сервис уровня приложения для управления проектами CAPITAL
|
||||
* Обрабатывает запросы от ProjectManagementResolver
|
||||
*/
|
||||
@Injectable()
|
||||
export class ProjectManagementService {
|
||||
constructor(
|
||||
private readonly projectManagementInteractor: ProjectManagementInteractor,
|
||||
private readonly documentInteractor: DocumentInteractor,
|
||||
private readonly projectMapperService: ProjectMapperService
|
||||
private readonly projectMapperService: ProjectMapperService,
|
||||
@Inject(PUB_SUB) private readonly pubSub: PubSub,
|
||||
) {}
|
||||
|
||||
private notifyDataChanged(entity: string, action: string, id?: string) {
|
||||
this.pubSub.publish(CAPITAL_EVENTS.DATA_CHANGED, {
|
||||
[CAPITAL_EVENTS.DATA_CHANGED]: { entity, action, id },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание проекта в CAPITAL контракте
|
||||
*/
|
||||
async createProject(data: CreateProjectInputDTO, currentUser: MonoAccountDomainInterface): Promise<TransactResult> {
|
||||
return await this.projectManagementInteractor.createProject(data, currentUser);
|
||||
const result = await this.projectManagementInteractor.createProject(data, currentUser);
|
||||
this.notifyDataChanged('project', 'created');
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактирование проекта в CAPITAL контракте
|
||||
*/
|
||||
async editProject(data: EditProjectInputDTO): Promise<TransactResult> {
|
||||
return await this.projectManagementInteractor.editProject(data);
|
||||
const result = await this.projectManagementInteractor.editProject(data);
|
||||
this.notifyDataChanged('project', 'updated');
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+24
-9
@@ -1,4 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import { PUB_SUB } from '~/infrastructure/pubsub/pubsub.module';
|
||||
import { CAPITAL_EVENTS } from '../resolvers/capital-subscription.resolver';
|
||||
import { VotingInteractor } from '../use-cases/voting.interactor';
|
||||
import type { StartVotingInputDTO } from '../dto/voting/start-voting-input.dto';
|
||||
import type { SubmitVoteInputDTO } from '../dto/voting/submit-vote-input.dto';
|
||||
@@ -12,33 +15,45 @@ import type { PaginationInputDomainInterface } from '~/domain/common/interfaces/
|
||||
import { SegmentMapper } from '../../infrastructure/mappers/segment.mapper';
|
||||
import { SegmentOutputDTO } from '../dto/segments/segment.dto';
|
||||
|
||||
/**
|
||||
* Сервис уровня приложения для голосования в CAPITAL
|
||||
* Обрабатывает запросы от VotingResolver
|
||||
*/
|
||||
@Injectable()
|
||||
export class VotingService {
|
||||
constructor(private readonly votingInteractor: VotingInteractor, private readonly segmentMapper: SegmentMapper) {}
|
||||
constructor(
|
||||
private readonly votingInteractor: VotingInteractor,
|
||||
private readonly segmentMapper: SegmentMapper,
|
||||
@Inject(PUB_SUB) private readonly pubSub: PubSub,
|
||||
) {}
|
||||
|
||||
private notifyDataChanged(entity: string, action: string) {
|
||||
this.pubSub.publish(CAPITAL_EVENTS.DATA_CHANGED, {
|
||||
[CAPITAL_EVENTS.DATA_CHANGED]: { entity, action },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Запуск голосования в CAPITAL контракте
|
||||
*/
|
||||
async startVoting(data: StartVotingInputDTO): Promise<TransactResult> {
|
||||
return await this.votingInteractor.startVoting(data);
|
||||
const result = await this.votingInteractor.startVoting(data);
|
||||
this.notifyDataChanged('voting', 'started');
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Голосование в CAPITAL контракте
|
||||
*/
|
||||
async submitVote(data: SubmitVoteInputDTO, username: string): Promise<TransactResult> {
|
||||
return await this.votingInteractor.submitVote({ ...data, voter: username });
|
||||
const result = await this.votingInteractor.submitVote({ ...data, voter: username });
|
||||
this.notifyDataChanged('voting', 'voted');
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Завершение голосования в CAPITAL контракте
|
||||
*/
|
||||
async completeVoting(data: CompleteVotingInputDTO): Promise<TransactResult> {
|
||||
return await this.votingInteractor.completeVoting(data);
|
||||
const result = await this.votingInteractor.completeVoting(data);
|
||||
this.notifyDataChanged('voting', 'completed');
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
import { BaseExtModule } from '../base.extension.module';
|
||||
import { PubSubModule } from '~/infrastructure/pubsub/pubsub.module';
|
||||
import { CapitalDatabaseModule } from './infrastructure/database/capital-database.module';
|
||||
import { RegistrationInfrastructureModule } from '~/infrastructure/registration/registration-infrastructure.module';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
@@ -218,6 +219,7 @@ import { ProcessInstanceTypeormRepository } from './infrastructure/repositories/
|
||||
import { PROCESS_TEMPLATE_REPOSITORY, PROCESS_INSTANCE_REPOSITORY } from './domain/repositories/process.repository';
|
||||
import { ProcessService } from './application/services/process.service';
|
||||
import { ProcessResolver } from './application/resolvers/process.resolver';
|
||||
import { CapitalSubscriptionResolver } from './application/resolvers/capital-subscription.resolver';
|
||||
import { ProcessTemplateTypeormEntity } from './infrastructure/entities/process-template.entity';
|
||||
import { ProcessInstanceTypeormEntity } from './infrastructure/entities/process-instance.entity';
|
||||
import { CommentTypeormRepository } from './infrastructure/repositories/comment.typeorm-repository';
|
||||
@@ -520,6 +522,7 @@ export class CapitalPlugin extends BaseExtModule {
|
||||
forwardRef(() => RegistrationModule),
|
||||
RegistrationInfrastructureModule,
|
||||
EventEmitterModule.forRoot(),
|
||||
PubSubModule,
|
||||
],
|
||||
providers: [
|
||||
// Plugin
|
||||
@@ -673,6 +676,7 @@ IssueIdGenerationService,
|
||||
},
|
||||
ProcessService,
|
||||
ProcessResolver,
|
||||
CapitalSubscriptionResolver,
|
||||
{
|
||||
provide: COMMENT_REPOSITORY,
|
||||
useClass: CommentTypeormRepository,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ParticipantPluginModule } from './participant/participant-extension.mod
|
||||
import { Schema as ParticipantSchema } from './participant/types';
|
||||
import { OneCoopPluginModule, OneCoopPlugin, Schema as OneCoopSchema } from './1ccoop/oneccoop-extension.module';
|
||||
import { CapitalPluginModule, CapitalPlugin, Schema as CapitalSchema } from './capital/capital-extension.module';
|
||||
import { ReportsExtensionModule } from './reports/reports-extension.module';
|
||||
|
||||
/**
|
||||
* Конфигурация рабочего стола (workspace), который предоставляет расширение
|
||||
@@ -305,6 +306,30 @@ export const AppRegistry: INamedExtension = {
|
||||
return !!this.desktops && this.desktops.length > 0;
|
||||
},
|
||||
},
|
||||
reports: {
|
||||
is_builtin: false,
|
||||
is_internal: true,
|
||||
is_available: true,
|
||||
desktops: [
|
||||
{
|
||||
name: 'reports',
|
||||
title: 'Отчёты ФНС',
|
||||
icon: 'fa-solid fa-file-invoice',
|
||||
},
|
||||
],
|
||||
title: 'Отчёты ФНС',
|
||||
description: 'Генерация налоговых отчётов для ФНС: бухбаланс, 6-НДФЛ, РСВ, ПСВ, декларация УСН и уведомления.',
|
||||
image: 'https://i.ibb.co/6C5F3kD/Chat-GPT-Image-10-2025-20-42-42.png',
|
||||
class: ReportsExtensionModule,
|
||||
pluginClass: BuiltinPlugin,
|
||||
schema: BuiltinSchema,
|
||||
tags: ['бухгалтерия', 'отчётность', 'ФНС'],
|
||||
readme: getReadmeContent('./reports'),
|
||||
instructions: getInstructionsContent('./reports'),
|
||||
get is_desktop() {
|
||||
return !!this.desktops && this.desktops.length > 0;
|
||||
},
|
||||
},
|
||||
orders: {
|
||||
is_builtin: false,
|
||||
is_internal: true,
|
||||
|
||||
@@ -36,6 +36,71 @@ export class GenerateReportInputDTO {
|
||||
period?: number;
|
||||
}
|
||||
|
||||
@InputType('OrganizationDataInput')
|
||||
export class OrganizationDataInputDTO {
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
inn!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
kpp!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
orgName!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
ogrn!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
okved!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
oktmo!: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
okfs?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
okopf?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
address?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phone?: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
signerLastName!: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
signerFirstName!: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
signerMiddleName?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
signerSnils?: string;
|
||||
}
|
||||
|
||||
@ObjectType('GeneratedReport')
|
||||
export class GeneratedReportDTO {
|
||||
@Field(() => ReportType)
|
||||
|
||||
+51
-20
@@ -1,16 +1,22 @@
|
||||
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { UseGuards, Inject, Logger } from '@nestjs/common';
|
||||
import { GqlJwtAuthGuard } from '~/application/auth/guards/graphql-jwt-auth.guard';
|
||||
import { RolesGuard } from '~/application/auth/guards/roles.guard';
|
||||
import { AuthRoles } from '~/application/auth/decorators/auth.decorator';
|
||||
import { ReportRegistryService } from '../../domain/services/report-registry.service';
|
||||
import { AvailableReportDTO, GenerateReportInputDTO, GeneratedReportDTO } from '../dto/report.dto';
|
||||
import { AvailableReportDTO, GenerateReportInputDTO, GeneratedReportDTO, OrganizationDataInputDTO } from '../dto/report.dto';
|
||||
import { config } from '~/config';
|
||||
import type { ReportInput, LedgerAccountData } from '../../domain/interfaces/report-generator.interface';
|
||||
import { LedgerInteractor } from '~/application/ledger/interactors/ledger.interactor';
|
||||
|
||||
@Resolver()
|
||||
export class ReportResolver {
|
||||
constructor(private readonly reportRegistry: ReportRegistryService) {}
|
||||
private readonly logger = new Logger(ReportResolver.name);
|
||||
|
||||
constructor(
|
||||
private readonly reportRegistry: ReportRegistryService,
|
||||
private readonly ledgerInteractor: LedgerInteractor,
|
||||
) {}
|
||||
|
||||
@Query(() => [AvailableReportDTO], {
|
||||
name: 'getAvailableReports',
|
||||
@@ -29,30 +35,55 @@ export class ReportResolver {
|
||||
@AuthRoles(['chairman'])
|
||||
async generateReport(
|
||||
@Args('data') data: GenerateReportInputDTO,
|
||||
@Args('organization') org: OrganizationDataInputDTO,
|
||||
): Promise<GeneratedReportDTO> {
|
||||
const coopname = config.coopname;
|
||||
|
||||
let ledgerData: LedgerAccountData[] = [];
|
||||
try {
|
||||
const ledgerState = await this.ledgerInteractor.getLedger({ coopname });
|
||||
ledgerData = ledgerState.chartOfAccounts.map(acc => {
|
||||
const amount = this.parseAmount(acc.available);
|
||||
return {
|
||||
accountId: acc.id,
|
||||
name: acc.name,
|
||||
balanceCurrent: amount,
|
||||
balancePrevious: 0,
|
||||
balancePrePrevious: 0,
|
||||
};
|
||||
});
|
||||
} catch (e: any) {
|
||||
this.logger.warn(`Не удалось загрузить данные ledger: ${e.message}`);
|
||||
}
|
||||
|
||||
const input: ReportInput = {
|
||||
reportType: data.reportType,
|
||||
year: data.year,
|
||||
period: data.period,
|
||||
inn: '9728130611',
|
||||
kpp: '772801001',
|
||||
orgName: 'Потребительский Кооператив "ВОСХОД"',
|
||||
ogrn: '1247700283346',
|
||||
okved: '94.99',
|
||||
okfs: '16',
|
||||
okopf: '20200',
|
||||
signerFio: { lastName: 'Иванов', firstName: 'Иван', middleName: 'Иванович' },
|
||||
ledgerData: [],
|
||||
inn: org.inn,
|
||||
kpp: org.kpp,
|
||||
orgName: org.orgName,
|
||||
ogrn: org.ogrn,
|
||||
okved: org.okved,
|
||||
okfs: org.okfs || '16',
|
||||
okopf: org.okopf || '20200',
|
||||
oktmo: org.oktmo,
|
||||
address: org.address,
|
||||
phone: org.phone,
|
||||
signerFio: {
|
||||
lastName: org.signerLastName,
|
||||
firstName: org.signerFirstName,
|
||||
middleName: org.signerMiddleName,
|
||||
},
|
||||
signerSnils: org.signerSnils,
|
||||
ledgerData,
|
||||
};
|
||||
|
||||
// TODO: загрузить реальные данные из ledger
|
||||
// Пока используем тестовые данные
|
||||
input.ledgerData = [
|
||||
{ accountId: 51, name: 'Расчётный счёт', available: 100000, blocked: 0 },
|
||||
{ accountId: 80, name: 'Паевой фонд', available: 50000, blocked: 0 },
|
||||
{ accountId: 86, name: 'Целевое финансирование', available: 30000, blocked: 0 },
|
||||
];
|
||||
|
||||
return this.reportRegistry.generate(input);
|
||||
}
|
||||
|
||||
private parseAmount(amountStr: string): number {
|
||||
const match = amountStr.match(/([\d.]+)/);
|
||||
return match ? parseFloat(match[1]) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-37
@@ -1,78 +1,55 @@
|
||||
import type { ReportType } from '../enums/report-type.enum';
|
||||
|
||||
/**
|
||||
* Входные данные для генерации отчёта
|
||||
*/
|
||||
export interface ReportInput {
|
||||
/** Тип отчёта */
|
||||
reportType: ReportType;
|
||||
/** Год отчётного периода */
|
||||
year: number;
|
||||
/** Квартал (1-4) или месяц (1-12) */
|
||||
period?: number;
|
||||
/** ИНН организации */
|
||||
inn: string;
|
||||
/** КПП организации */
|
||||
kpp: string;
|
||||
/** Наименование организации */
|
||||
orgName: string;
|
||||
/** ОГРН */
|
||||
ogrn: string;
|
||||
/** ОКВЭД */
|
||||
okved: string;
|
||||
/** ОКПО */
|
||||
okpo?: string;
|
||||
/** ОКФС — форма собственности */
|
||||
okfs: string;
|
||||
/** ОКОПФ — организационно-правовая форма */
|
||||
okopf: string;
|
||||
/** ОКТМО — код муниципального образования */
|
||||
oktmo: string;
|
||||
/** Адрес места нахождения */
|
||||
address?: string;
|
||||
/** Номер телефона */
|
||||
phone?: string;
|
||||
/** ФИО подписанта */
|
||||
signerFio: { lastName: string; firstName: string; middleName?: string };
|
||||
/** Данные из ledger (балансы по счетам) */
|
||||
ledgerData?: LedgerAccountData[];
|
||||
/** СНИЛС председателя (для ПСВ) */
|
||||
signerSnils?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Данные по бухгалтерскому счёту из ledger
|
||||
*/
|
||||
export interface LedgerAccountData {
|
||||
/** ID счёта (51, 80, 86, etc.) */
|
||||
accountId: number;
|
||||
/** Наименование счёта */
|
||||
name: string;
|
||||
/** Доступные средства */
|
||||
available: number;
|
||||
/** Заблокированные средства */
|
||||
blocked: number;
|
||||
/** Сальдо на отчётную дату текущего периода */
|
||||
balanceCurrent: number;
|
||||
/** Сальдо на отчётную дату предыдущего периода */
|
||||
balancePrevious: number;
|
||||
/** Сальдо на отчётную дату года, предшествующего предыдущему */
|
||||
balancePrePrevious: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Результат генерации отчёта
|
||||
*/
|
||||
export interface ReportOutput {
|
||||
/** Тип отчёта */
|
||||
reportType: ReportType;
|
||||
/** XML содержимое (windows-1251 encoded) */
|
||||
xml: string;
|
||||
/** Имя файла для ФНС */
|
||||
fileName: string;
|
||||
/** Ошибки валидации (если есть) */
|
||||
errors: string[];
|
||||
/** Успешна ли валидация */
|
||||
isValid: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Интерфейс генератора отчёта.
|
||||
* Каждый тип отчёта реализует этот интерфейс.
|
||||
*/
|
||||
export interface IReportGenerator {
|
||||
/** Тип отчёта */
|
||||
readonly reportType: ReportType;
|
||||
|
||||
/** Генерация XML */
|
||||
generate(input: ReportInput): ReportOutput;
|
||||
|
||||
/** Генерация имени файла по стандарту ФНС */
|
||||
generateFileName(input: ReportInput): string;
|
||||
}
|
||||
|
||||
+154
-119
@@ -1,12 +1,21 @@
|
||||
import { create } from 'xmlbuilder2';
|
||||
import * as iconv from 'iconv-lite';
|
||||
import { ReportType } from '../../domain/enums/report-type.enum';
|
||||
import type { IReportGenerator, ReportInput, ReportOutput } from '../../domain/interfaces/report-generator.interface';
|
||||
import { createXmlDoc, formatDate, generateUuid, addFio } from './xml-utils';
|
||||
|
||||
/**
|
||||
* Генератор бухгалтерского баланса (NO_BUHOTCH)
|
||||
* Формат: XML в кодировке windows-1251 с кириллическими тегами
|
||||
* Стандарт: Приказ ФНС КЧ-17-18/692 от 11.03.2025
|
||||
* Генератор бухгалтерской отчётности (NO_BUHOTCH)
|
||||
* Формат: Приказ ФНС КЧ-17-18/692 от 11.03.2025
|
||||
* XSD: NO_BUHOTCH_1_105_00_05_09_01.xsd
|
||||
*
|
||||
* Кооператив обязан сдавать:
|
||||
* - Баланс (форма 0710001)
|
||||
* - Отчёт о целевом использовании средств (форма 0710006)
|
||||
* - Отчёт о финансовых результатах — только при коммерческой деятельности
|
||||
*
|
||||
* Счета ledger:
|
||||
* 51 — Расчётный счёт (Актив → ОбА → Денежные средства, строка 1250)
|
||||
* 80 — Паевой фонд (Пассив → Капитал, строка 1310)
|
||||
* 86 — Целевое финансирование (Пассив → Капитал, строка 1350)
|
||||
*/
|
||||
export class BuhotchGenerator implements IReportGenerator {
|
||||
readonly reportType = ReportType.BUHOTCH;
|
||||
@@ -17,9 +26,9 @@ export class BuhotchGenerator implements IReportGenerator {
|
||||
|
||||
try {
|
||||
const xml = this.buildXml(input);
|
||||
return { reportType: this.reportType, xml, fileName, errors, isValid: errors.length === 0 };
|
||||
return { reportType: this.reportType, xml, fileName, errors, isValid: true };
|
||||
} catch (e: any) {
|
||||
errors.push(`Ошибка генерации: ${e.message}`);
|
||||
errors.push(`Ошибка генерации бухбаланса: ${e.message}`);
|
||||
return { reportType: this.reportType, xml: '', fileName, errors, isValid: false };
|
||||
}
|
||||
}
|
||||
@@ -27,128 +36,154 @@ export class BuhotchGenerator implements IReportGenerator {
|
||||
generateFileName(input: ReportInput): string {
|
||||
const now = new Date();
|
||||
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||||
return `NO_BUHOTCH_${input.inn}_${input.kpp}_${dateStr}_${randomId()}.xml`;
|
||||
const uuid = generateUuid();
|
||||
return `NO_BUHOTCH_${input.inn}_${input.kpp}_${dateStr}_${uuid}`;
|
||||
}
|
||||
|
||||
private getLedgerBalance(input: ReportInput, accountId: number) {
|
||||
const acc = input.ledgerData?.find(a => a.accountId === accountId);
|
||||
return {
|
||||
current: acc ? Math.round(acc.balanceCurrent / 1000) : 0,
|
||||
previous: acc ? Math.round(acc.balancePrevious / 1000) : 0,
|
||||
prePrevious: acc ? Math.round(acc.balancePrePrevious / 1000) : 0,
|
||||
};
|
||||
}
|
||||
|
||||
private buildXml(input: ReportInput): string {
|
||||
const account51 = input.ledgerData?.find(a => a.accountId === 51);
|
||||
const account80 = input.ledgerData?.find(a => a.accountId === 80);
|
||||
const account86 = input.ledgerData?.find(a => a.accountId === 86);
|
||||
const acc51 = this.getLedgerBalance(input, 51);
|
||||
const acc80 = this.getLedgerBalance(input, 80);
|
||||
const acc86 = this.getLedgerBalance(input, 86);
|
||||
|
||||
const cashAmount = account51 ? Math.round(account51.available / 1000) : 0;
|
||||
const shareCapital = account80 ? Math.round(account80.available / 1000) : 0;
|
||||
const targetFunding = account86 ? Math.round(account86.available / 1000) : 0;
|
||||
const totalOba = { current: acc51.current, previous: acc51.previous, prePrevious: acc51.prePrevious };
|
||||
const totalAssets = { current: totalOba.current, previous: totalOba.previous, prePrevious: totalOba.prePrevious };
|
||||
const totalCapital = {
|
||||
current: acc80.current + acc86.current,
|
||||
previous: acc80.previous + acc86.previous,
|
||||
prePrevious: acc80.prePrevious + acc86.prePrevious,
|
||||
};
|
||||
const totalPassive = totalCapital;
|
||||
|
||||
const totalCurrentAssets = cashAmount;
|
||||
const totalAssets = totalCurrentAssets;
|
||||
const totalCapital = shareCapital + targetFunding;
|
||||
const totalLiabilities = totalCapital;
|
||||
const idFile = this.generateFileName(input);
|
||||
|
||||
const idFile = this.generateFileName(input).replace('.xml', '');
|
||||
|
||||
const doc = create({ version: '1.0', encoding: 'windows-1251' })
|
||||
const doc = createXmlDoc()
|
||||
.ele('Файл')
|
||||
.att('ИдФайл', idFile)
|
||||
.att('ВерсПрог', 'CoopReports 1.0')
|
||||
.att('ВерсФорм', '5.09')
|
||||
.ele('Документ')
|
||||
.att('КНД', '0710099')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('Период', '34')
|
||||
.att('ОтчетГод', String(input.year))
|
||||
.att('НомКорр', '0')
|
||||
.att('ОКЕИ', '384')
|
||||
.att('ПрАудит', '0')
|
||||
.ele('СвНП')
|
||||
.ele('НПЮЛ')
|
||||
.att('НаимОрг', input.orgName)
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.att('АдрМН', '-')
|
||||
.up()
|
||||
.att('ОКВЭД2', input.okved)
|
||||
.att('ОКФС', input.okfs)
|
||||
.att('ОКОПФ', input.okopf)
|
||||
.up()
|
||||
.ele('Подписант')
|
||||
.att('ПрПодп', '1')
|
||||
.ele('ФИО')
|
||||
.att('Фамилия', input.signerFio.lastName)
|
||||
.att('Имя', input.signerFio.firstName)
|
||||
.att('Отчество', input.signerFio.middleName || '')
|
||||
.up()
|
||||
.up()
|
||||
.ele('Баланс')
|
||||
.ele('Актив')
|
||||
.ele('ВнеОбА')
|
||||
.att('Пояснения', '')
|
||||
.att('СумОтч', '0')
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.ele('ОбА')
|
||||
.ele('ДенежнСр')
|
||||
.att('Пояснения', '')
|
||||
.att('СумОтч', String(cashAmount))
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.att('Пояснения', '')
|
||||
.att('СумОтч', String(totalCurrentAssets))
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.att('СумОтч', String(totalAssets))
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.ele('Пассив')
|
||||
.ele('Капитал')
|
||||
.ele('УстКап')
|
||||
.att('Пояснения', '')
|
||||
.att('СумОтч', String(shareCapital))
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.ele('ЦелФинанс')
|
||||
.att('Пояснения', '')
|
||||
.att('СумОтч', String(targetFunding))
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.att('Пояснения', '')
|
||||
.att('СумОтч', String(totalCapital))
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.ele('ДолгОбяз')
|
||||
.att('Пояснения', '')
|
||||
.att('СумОтч', '0')
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.ele('КрОбяз')
|
||||
.att('Пояснения', '')
|
||||
.att('СумОтч', '0')
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.att('СумОтч', String(totalLiabilities))
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up()
|
||||
.up()
|
||||
.up()
|
||||
.up();
|
||||
.att('ВерсФорм', '5.09');
|
||||
|
||||
const dokument = doc
|
||||
.ele('Документ')
|
||||
.att('КНД', '0710099')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('Период', '34')
|
||||
.att('ОтчетГод', String(input.year))
|
||||
.att('НомКорр', '0')
|
||||
.att('ОКЕИ', '384')
|
||||
.att('ПрАудит', '0');
|
||||
|
||||
const svnp = dokument.ele('СвНП')
|
||||
.att('ОКВЭД2', input.okved)
|
||||
.att('ОКФС', input.okfs)
|
||||
.att('ОКОПФ', input.okopf);
|
||||
|
||||
if (input.okpo) {
|
||||
svnp.att('ОКПО', input.okpo);
|
||||
}
|
||||
|
||||
svnp.ele('НПЮЛ')
|
||||
.att('НаимОрг', input.orgName)
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.att('АдрМН', input.address || '-')
|
||||
.up();
|
||||
svnp.up();
|
||||
|
||||
const signer = dokument.ele('Подписант').att('ПрПодп', '1');
|
||||
addFio(signer, input.signerFio);
|
||||
signer.up();
|
||||
|
||||
// === Баланс (форма 0710001) ===
|
||||
const balans = dokument.ele('Баланс').att('ОКУД', '0710001');
|
||||
const aktiv = balans.ele('Актив');
|
||||
|
||||
aktiv.ele('ВнеОбА')
|
||||
.att('СумОтч', '0')
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up();
|
||||
|
||||
aktiv.ele('ОбА')
|
||||
.att('СумОтч', String(totalOba.current))
|
||||
.att('СумПрдщ', String(totalOba.previous))
|
||||
.att('СумПрдшв', String(totalOba.prePrevious))
|
||||
.up();
|
||||
|
||||
aktiv
|
||||
.att('СумОтч', String(totalAssets.current))
|
||||
.att('СумПрдщ', String(totalAssets.previous))
|
||||
.att('СумПрдшв', String(totalAssets.prePrevious));
|
||||
aktiv.up();
|
||||
|
||||
const passiv = balans.ele('Пассив');
|
||||
passiv.ele('ДолгосрОбяз')
|
||||
.att('СумОтч', '0')
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up();
|
||||
|
||||
passiv.ele('КраткосрОбяз')
|
||||
.att('СумОтч', '0')
|
||||
.att('СумПрдщ', '0')
|
||||
.att('СумПрдшв', '0')
|
||||
.up();
|
||||
|
||||
passiv
|
||||
.att('ОКУД', '0710001')
|
||||
.att('СумОтч', String(totalPassive.current))
|
||||
.att('СумПрдщ', String(totalPassive.previous))
|
||||
.att('СумПрдшв', String(totalPassive.prePrevious));
|
||||
passiv.up();
|
||||
balans.up();
|
||||
|
||||
// === Отчёт о целевом использовании средств (форма 0710006) ===
|
||||
const celIsp = dokument.ele('ЦелИсп').att('ОКУД', '0710006');
|
||||
|
||||
celIsp.ele('ОстатНачОтч')
|
||||
.att('СумОтч', String(acc86.previous))
|
||||
.att('СумПред', String(acc86.prePrevious))
|
||||
.up();
|
||||
|
||||
const postup = celIsp.ele('Поступило');
|
||||
postup.ele('ВступВзнос').att('СумОтч', '0').att('СумПред', '0').up();
|
||||
postup.ele('ЧленВзнос').att('СумОтч', '0').att('СумПред', '0').up();
|
||||
postup.ele('ЦелевВзнос').att('СумОтч', '0').att('СумПред', '0').up();
|
||||
postup.ele('ДобрИмВзнос').att('СумОтч', '0').att('СумПред', '0').up();
|
||||
postup.ele('ПрибПредДеят').att('СумОтч', '0').att('СумПред', '0').up();
|
||||
postup.ele('Прочие').att('СумОтч', '0').att('СумПред', '0').up();
|
||||
postup.att('СумОтч', '0').att('СумПред', '0');
|
||||
postup.up();
|
||||
|
||||
const ispolz = celIsp.ele('Использовано');
|
||||
const rashCel = ispolz.ele('РасхЦелМер');
|
||||
rashCel.att('СумОтч', '0').att('СумПред', '0');
|
||||
rashCel.up();
|
||||
const rashAU = ispolz.ele('РасхСодАУ');
|
||||
rashAU.att('СумОтч', '0').att('СумПред', '0');
|
||||
rashAU.up();
|
||||
ispolz.ele('ПриобОСИн').att('СумОтч', '0').att('СумПред', '0').up();
|
||||
ispolz.ele('Прочие').att('СумОтч', '0').att('СумПред', '0').up();
|
||||
ispolz.att('СумОтч', '0').att('СумПред', '0');
|
||||
ispolz.up();
|
||||
|
||||
celIsp.ele('ОстатКонОтч')
|
||||
.att('СумОтч', String(acc86.current))
|
||||
.att('СумПред', String(acc86.previous))
|
||||
.up();
|
||||
celIsp.up();
|
||||
|
||||
dokument.up();
|
||||
doc.up();
|
||||
|
||||
return doc.end({ prettyPrint: true });
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(d: Date): string {
|
||||
return `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${d.getFullYear()}`;
|
||||
}
|
||||
|
||||
function randomId(): string {
|
||||
return Math.random().toString(36).substring(2, 10).toUpperCase();
|
||||
}
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import { ReportType } from '../../domain/enums/report-type.enum';
|
||||
import type { IReportGenerator, ReportInput, ReportOutput } from '../../domain/interfaces/report-generator.interface';
|
||||
import { createXmlDoc, formatDate, generateUuid, addSigner, getTaxOfficeCode } from './xml-utils';
|
||||
|
||||
/**
|
||||
* Генератор ДУСН — Декларация по УСН (нулевая)
|
||||
* XSD: NO_USN_1_030_00_05_09_01.xsd
|
||||
* КНД: 1152017
|
||||
*
|
||||
* Структура: Файл → Документ → [СвНП, Подписант, УСН]
|
||||
* УСН содержит @ОбНал (1=доходы, 2=доходы-расходы)
|
||||
* Для кооператива: ОбНал=1 (доходы)
|
||||
* Раздел 1.1 — СумНалПУ_НП с нулями
|
||||
* Раздел 2.1.1 — РасчНал1 с нулями
|
||||
*/
|
||||
export class DusnGenerator implements IReportGenerator {
|
||||
readonly reportType = ReportType.DUSN;
|
||||
|
||||
generate(input: ReportInput): ReportOutput {
|
||||
const fileName = this.generateFileName(input);
|
||||
const errors: string[] = [];
|
||||
|
||||
try {
|
||||
const xml = this.buildXml(input);
|
||||
return { reportType: this.reportType, xml, fileName, errors, isValid: true };
|
||||
} catch (e: any) {
|
||||
errors.push(`Ошибка генерации ДУСН: ${e.message}`);
|
||||
return { reportType: this.reportType, xml: '', fileName, errors, isValid: false };
|
||||
}
|
||||
}
|
||||
|
||||
generateFileName(input: ReportInput): string {
|
||||
const now = new Date();
|
||||
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||||
return `NO_USN_${input.inn}_${input.kpp}_${dateStr}_${generateUuid()}`;
|
||||
}
|
||||
|
||||
private buildXml(input: ReportInput): string {
|
||||
const idFile = this.generateFileName(input);
|
||||
const kodNO = getTaxOfficeCode(input.kpp);
|
||||
|
||||
const doc = createXmlDoc()
|
||||
.ele('Файл')
|
||||
.att('ИдФайл', idFile)
|
||||
.att('ВерсПрог', 'CoopReports 1.0')
|
||||
.att('ВерсФорм', '5.09');
|
||||
|
||||
const dokument = doc.ele('Документ')
|
||||
.att('КНД', '1152017')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('Период', '34')
|
||||
.att('ОтчетГод', String(input.year))
|
||||
.att('КодНО', kodNO)
|
||||
.att('НомКорр', '0')
|
||||
.att('ПоМесту', '120');
|
||||
|
||||
dokument.ele('СвНП')
|
||||
.ele('НПЮЛ')
|
||||
.att('НаимОрг', input.orgName)
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.up()
|
||||
.up();
|
||||
|
||||
addSigner(dokument, input.signerFio);
|
||||
|
||||
const usn = dokument.ele('УСН').att('ОбНал', '1');
|
||||
|
||||
const section1 = usn.ele('СумНалПУ_НП')
|
||||
.att('ОКТМО', input.oktmo)
|
||||
.att('НалПУУменПер', '0');
|
||||
|
||||
section1.ele('РасчНал1')
|
||||
.att('ПризНП', '2')
|
||||
.ele('Доход')
|
||||
.att('СумЗаНалПер', '0')
|
||||
.up()
|
||||
.ele('Ставка')
|
||||
.att('СтавкаНалПер', '6.0')
|
||||
.up()
|
||||
.ele('Исчисл')
|
||||
.att('СумЗаНалПер', '0')
|
||||
.up()
|
||||
.ele('УменНал')
|
||||
.att('СумЗаНалПер', '0')
|
||||
.up()
|
||||
.up();
|
||||
|
||||
section1.up();
|
||||
usn.up();
|
||||
dokument.up();
|
||||
doc.up();
|
||||
|
||||
return doc.end({ prettyPrint: true });
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { ReportType } from '../../domain/enums/report-type.enum';
|
||||
import type { IReportGenerator, ReportInput, ReportOutput } from '../../domain/interfaces/report-generator.interface';
|
||||
import { createXmlDoc, formatDate, generateUuid, addSigner, getQuarterPeriodCode, getTaxOfficeCode } from './xml-utils';
|
||||
|
||||
/**
|
||||
* Генератор 4-ФСС (ЕФС-1) — нулевой
|
||||
* XSD не предоставлен, формат приближённый к стандарту СФР
|
||||
* КНД: 1111111 (условный)
|
||||
*
|
||||
* С 2023 года 4-ФСС заменён на ЕФС-1 (подраздел 2.1)
|
||||
* Подаётся в Социальный фонд России ежеквартально
|
||||
*/
|
||||
export class Fss4Generator implements IReportGenerator {
|
||||
readonly reportType = ReportType.FSS4;
|
||||
|
||||
generate(input: ReportInput): ReportOutput {
|
||||
const fileName = this.generateFileName(input);
|
||||
const errors: string[] = [];
|
||||
|
||||
try {
|
||||
const xml = this.buildXml(input);
|
||||
return { reportType: this.reportType, xml, fileName, errors, isValid: true };
|
||||
} catch (e: any) {
|
||||
errors.push(`Ошибка генерации 4-ФСС: ${e.message}`);
|
||||
return { reportType: this.reportType, xml: '', fileName, errors, isValid: false };
|
||||
}
|
||||
}
|
||||
|
||||
generateFileName(input: ReportInput): string {
|
||||
const now = new Date();
|
||||
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||||
return `EFS1_${input.inn}_${input.kpp}_${dateStr}_${generateUuid()}`;
|
||||
}
|
||||
|
||||
private buildXml(input: ReportInput): string {
|
||||
const periodCode = getQuarterPeriodCode(input.period);
|
||||
const idFile = this.generateFileName(input);
|
||||
const kodNO = getTaxOfficeCode(input.kpp);
|
||||
|
||||
const doc = createXmlDoc()
|
||||
.ele('Файл')
|
||||
.att('ИдФайл', idFile)
|
||||
.att('ВерсПрог', 'CoopReports 1.0')
|
||||
.att('ВерсФорм', '1.00');
|
||||
|
||||
const dokument = doc.ele('Документ')
|
||||
.att('КНД', '1111111')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('Период', periodCode)
|
||||
.att('ОтчетГод', String(input.year))
|
||||
.att('КодНО', kodNO)
|
||||
.att('НомКорр', '0')
|
||||
.att('ПоМесту', '214');
|
||||
|
||||
dokument.ele('СвНП')
|
||||
.ele('НПЮЛ')
|
||||
.att('НаимОрг', input.orgName)
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.up()
|
||||
.up();
|
||||
|
||||
addSigner(dokument, input.signerFio);
|
||||
|
||||
dokument.ele('ЕФС1')
|
||||
.att('СрЧисл', '1')
|
||||
.att('СумНачислВзнос', '0')
|
||||
.att('СумУплВзнос', '0')
|
||||
.up();
|
||||
|
||||
dokument.up();
|
||||
doc.up();
|
||||
|
||||
return doc.end({ prettyPrint: true });
|
||||
}
|
||||
}
|
||||
+84
-48
@@ -1,9 +1,17 @@
|
||||
import { create } from 'xmlbuilder2';
|
||||
import { ReportType } from '../../domain/enums/report-type.enum';
|
||||
import type { IReportGenerator, ReportInput, ReportOutput } from '../../domain/interfaces/report-generator.interface';
|
||||
import { createXmlDoc, formatDate, generateUuid, addSigner, getQuarterPeriodCode, getTaxOfficeCode } from './xml-utils';
|
||||
|
||||
/**
|
||||
* Генератор 6-НДФЛ — нулевая отчётность
|
||||
* XSD: NO_NDFL6.2_1_231_00_05_05_02.xsd
|
||||
* КНД: 1151100
|
||||
*
|
||||
* Структура:
|
||||
* Файл → Документ → [СвНП, Подписант, НДФЛ6.2]
|
||||
* СвНП содержит @ОКТМО и выбор НПЮЛ/НПФЛ
|
||||
* НДФЛ6.2 содержит ОбязНА[], РасчСумНал[], СправДох[]
|
||||
* Для нулевого отчёта: РасчСумНал со ставкой 13% и нулями
|
||||
*/
|
||||
export class Ndfl6Generator implements IReportGenerator {
|
||||
readonly reportType = ReportType.NDFL6;
|
||||
@@ -13,48 +21,7 @@ export class Ndfl6Generator implements IReportGenerator {
|
||||
const errors: string[] = [];
|
||||
|
||||
try {
|
||||
const periodCode = input.period === 1 ? '21' : input.period === 2 ? '31' : input.period === 3 ? '33' : '34';
|
||||
const idFile = fileName.replace('.xml', '');
|
||||
|
||||
const doc = create({ version: '1.0', encoding: 'windows-1251' })
|
||||
.ele('Файл')
|
||||
.att('ИдФайл', idFile)
|
||||
.att('ВерсПрог', 'CoopReports 1.0')
|
||||
.att('ВерсФорм', '5.05')
|
||||
.ele('Документ')
|
||||
.att('КНД', '1151100')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('Период', periodCode)
|
||||
.att('ОтчетГод', String(input.year))
|
||||
.att('КодНО', input.kpp.substring(0, 4))
|
||||
.att('НомКорр', '0')
|
||||
.att('ПоМесту', '214')
|
||||
.ele('СвНП')
|
||||
.att('ОКВЭД', input.okved)
|
||||
.ele('НПЮЛ')
|
||||
.att('НаимОрг', input.orgName)
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.up()
|
||||
.up()
|
||||
.ele('Подписант')
|
||||
.att('ПрПодп', '1')
|
||||
.ele('ФИО')
|
||||
.att('Фамилия', input.signerFio.lastName)
|
||||
.att('Имя', input.signerFio.firstName)
|
||||
.att('Отчество', input.signerFio.middleName || '')
|
||||
.up()
|
||||
.up()
|
||||
.ele('НДФЛ6')
|
||||
.ele('ОбобщПоказ')
|
||||
.att('КолФЛДоход', '0')
|
||||
.att('КолФЛВыч', '0')
|
||||
.up()
|
||||
.up()
|
||||
.up()
|
||||
.up();
|
||||
|
||||
const xml = doc.end({ prettyPrint: true });
|
||||
const xml = this.buildXml(input);
|
||||
return { reportType: this.reportType, xml, fileName, errors, isValid: true };
|
||||
} catch (e: any) {
|
||||
errors.push(`Ошибка генерации 6-НДФЛ: ${e.message}`);
|
||||
@@ -65,10 +32,79 @@ export class Ndfl6Generator implements IReportGenerator {
|
||||
generateFileName(input: ReportInput): string {
|
||||
const now = new Date();
|
||||
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||||
return `NO_NDFL6.2_${input.inn}_${input.kpp}_${dateStr}.xml`;
|
||||
const uuid = generateUuid();
|
||||
return `NO_NDFL6.2_${input.inn}_${input.kpp}_${dateStr}_${uuid}`;
|
||||
}
|
||||
|
||||
private buildXml(input: ReportInput): string {
|
||||
const periodCode = getQuarterPeriodCode(input.period);
|
||||
const idFile = this.generateFileName(input);
|
||||
const kodNO = getTaxOfficeCode(input.kpp);
|
||||
|
||||
const doc = createXmlDoc()
|
||||
.ele('Файл')
|
||||
.att('ИдФайл', idFile)
|
||||
.att('ВерсПрог', 'CoopReports 1.0')
|
||||
.att('ВерсФорм', '5.05');
|
||||
|
||||
const dokument = doc.ele('Документ')
|
||||
.att('КНД', '1151100')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('Период', periodCode)
|
||||
.att('ОтчетГод', String(input.year))
|
||||
.att('КодНО', kodNO)
|
||||
.att('НомКорр', '0')
|
||||
.att('ПоМесту', '214');
|
||||
|
||||
dokument.ele('СвНП')
|
||||
.att('ОКТМО', input.oktmo)
|
||||
.ele('НПЮЛ')
|
||||
.att('НаимОрг', input.orgName)
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.up()
|
||||
.up();
|
||||
|
||||
addSigner(dokument, input.signerFio);
|
||||
|
||||
const ndfl = dokument.ele('НДФЛ6.2');
|
||||
|
||||
ndfl.ele('РасчСумНал')
|
||||
.att('Ставка', '13')
|
||||
.att('КБК', '18210102010011000110')
|
||||
.att('КолФЛ', '0')
|
||||
.att('КолКвал', '0')
|
||||
.att('СумНачислНач', '0')
|
||||
.att('СумНачислКвал', '0')
|
||||
.att('СумВыч', '0')
|
||||
.att('НалБаза', '0')
|
||||
.att('СумНалИсч', '0')
|
||||
.att('СумНалИсчКвал', '0')
|
||||
.att('СумФикс', '0')
|
||||
.att('СумНалПриб', '0')
|
||||
.att('СумНалИнГос', '0')
|
||||
.att('СумНалУдерж', '0')
|
||||
.att('СумНалУдерж1Мес', '0')
|
||||
.att('СумНалУдерж23_1Мес', '0')
|
||||
.att('СумНалУдерж2Мес', '0')
|
||||
.att('СумНалУдерж23_2Мес', '0')
|
||||
.att('СумНалУдерж3Мес', '0')
|
||||
.att('СумНалУдерж23_3Мес', '0')
|
||||
.att('СумНалНеУдерж', '0')
|
||||
.att('СумНалИзлУдерж', '0')
|
||||
.att('СумНалВозвр', '0')
|
||||
.att('СумНалВозвр1Мес', '0')
|
||||
.att('СумНалВозвр23_1Мес', '0')
|
||||
.att('СумНалВозвр2Мес', '0')
|
||||
.att('СумНалВозвр23_2Мес', '0')
|
||||
.att('СумНалВозвр3Мес', '0')
|
||||
.att('СумНалВозвр23_3Мес', '0')
|
||||
.up();
|
||||
|
||||
ndfl.up();
|
||||
dokument.up();
|
||||
doc.up();
|
||||
|
||||
return doc.end({ prettyPrint: true });
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(d: Date): string {
|
||||
return `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${d.getFullYear()}`;
|
||||
}
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import { ReportType } from '../../domain/enums/report-type.enum';
|
||||
import type { IReportGenerator, ReportInput, ReportOutput } from '../../domain/interfaces/report-generator.interface';
|
||||
import { createXmlDoc, formatDate, generateUuid, addFio, addSigner, getMonthPeriodCode, getTaxOfficeCode } from './xml-utils';
|
||||
|
||||
/**
|
||||
* Генератор ПСВ — Персонифицированные сведения (нулевой)
|
||||
* XSD: NO_PERSSVFL_1_297_00_05_01_02.xsd
|
||||
* КНД: 1151162
|
||||
*
|
||||
* Структура: Файл → Документ → [СвНП, Подписант, ПерсСвФЛ[]]
|
||||
* ПерсСвФЛ обязателен (minOccurs=1), содержит ФИО, @СНИЛС, @СумВыпл
|
||||
* Для нулевого отчёта: одна запись председателя с СумВыпл=0
|
||||
*/
|
||||
export class PsvGenerator implements IReportGenerator {
|
||||
readonly reportType = ReportType.PSV;
|
||||
|
||||
generate(input: ReportInput): ReportOutput {
|
||||
const fileName = this.generateFileName(input);
|
||||
const errors: string[] = [];
|
||||
|
||||
try {
|
||||
const xml = this.buildXml(input);
|
||||
return { reportType: this.reportType, xml, fileName, errors, isValid: true };
|
||||
} catch (e: any) {
|
||||
errors.push(`Ошибка генерации ПСВ: ${e.message}`);
|
||||
return { reportType: this.reportType, xml: '', fileName, errors, isValid: false };
|
||||
}
|
||||
}
|
||||
|
||||
generateFileName(input: ReportInput): string {
|
||||
const now = new Date();
|
||||
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||||
return `NO_PERSSVFL_${input.inn}_${input.kpp}_${dateStr}_${generateUuid()}`;
|
||||
}
|
||||
|
||||
private buildXml(input: ReportInput): string {
|
||||
const periodCode = getMonthPeriodCode(input.period);
|
||||
const idFile = this.generateFileName(input);
|
||||
const kodNO = getTaxOfficeCode(input.kpp);
|
||||
|
||||
const doc = createXmlDoc()
|
||||
.ele('Файл')
|
||||
.att('ИдФайл', idFile)
|
||||
.att('ВерсПрог', 'CoopReports 1.0')
|
||||
.att('ВерсФорм', '5.01');
|
||||
|
||||
const dokument = doc.ele('Документ')
|
||||
.att('КНД', '1151162')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('НомКорр', '0')
|
||||
.att('Период', periodCode)
|
||||
.att('ОтчетГод', String(input.year))
|
||||
.att('КодНО', kodNO)
|
||||
.att('ПоМесту', '214');
|
||||
|
||||
dokument.ele('СвНП')
|
||||
.ele('НПЮЛ')
|
||||
.att('НаимОрг', input.orgName)
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.up()
|
||||
.up();
|
||||
|
||||
addSigner(dokument, input.signerFio);
|
||||
|
||||
const persSv = dokument.ele('ПерсСвФЛ')
|
||||
.att('СНИЛС', input.signerSnils || '000-000-000 00')
|
||||
.att('СумВыпл', '0');
|
||||
|
||||
addFio(persSv, input.signerFio);
|
||||
persSv.up();
|
||||
|
||||
dokument.up();
|
||||
doc.up();
|
||||
|
||||
return doc.end({ prettyPrint: true });
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import { ReportType } from '../../domain/enums/report-type.enum';
|
||||
import type { IReportGenerator, ReportInput, ReportOutput } from '../../domain/interfaces/report-generator.interface';
|
||||
import { createXmlDoc, formatDate, generateUuid, addSigner, getQuarterPeriodCode, getTaxOfficeCode } from './xml-utils';
|
||||
|
||||
/**
|
||||
* Генератор РСВ — Расчёт по страховым взносам (нулевой)
|
||||
* XSD: NO_RASCHSV_1_162_00_05_08_02.xsd
|
||||
* КНД: 1151111
|
||||
*
|
||||
* Структура: Файл → Документ → [СвНП, Подписант, РасчетСВ]
|
||||
* СвНП: @СрЧисл, @Тлф, НПЮЛ(@НаимОрг, @ИННЮЛ, @КПП)
|
||||
* РасчетСВ: ОбязПлатСВ(@ТипПлат, @ОКТМО, ...) для нулевого
|
||||
*/
|
||||
export class RsvGenerator implements IReportGenerator {
|
||||
readonly reportType = ReportType.RSV;
|
||||
|
||||
generate(input: ReportInput): ReportOutput {
|
||||
const fileName = this.generateFileName(input);
|
||||
const errors: string[] = [];
|
||||
|
||||
try {
|
||||
const xml = this.buildXml(input);
|
||||
return { reportType: this.reportType, xml, fileName, errors, isValid: true };
|
||||
} catch (e: any) {
|
||||
errors.push(`Ошибка генерации РСВ: ${e.message}`);
|
||||
return { reportType: this.reportType, xml: '', fileName, errors, isValid: false };
|
||||
}
|
||||
}
|
||||
|
||||
generateFileName(input: ReportInput): string {
|
||||
const now = new Date();
|
||||
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||||
return `NO_RASCHSV_${input.inn}_${input.kpp}_${dateStr}_${generateUuid()}`;
|
||||
}
|
||||
|
||||
private buildXml(input: ReportInput): string {
|
||||
const periodCode = getQuarterPeriodCode(input.period);
|
||||
const idFile = this.generateFileName(input);
|
||||
const kodNO = getTaxOfficeCode(input.kpp);
|
||||
|
||||
const doc = createXmlDoc()
|
||||
.ele('Файл')
|
||||
.att('ИдФайл', idFile)
|
||||
.att('ВерсПрог', 'CoopReports 1.0')
|
||||
.att('ВерсФорм', '5.08');
|
||||
|
||||
const dokument = doc.ele('Документ')
|
||||
.att('КНД', '1151111')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('НомКорр', '0')
|
||||
.att('Период', periodCode)
|
||||
.att('ОтчетГод', String(input.year))
|
||||
.att('КодНО', kodNO)
|
||||
.att('ПоМесту', '214');
|
||||
|
||||
dokument.ele('СвНП')
|
||||
.att('СрЧисл', '1')
|
||||
.ele('НПЮЛ')
|
||||
.att('НаимОрг', input.orgName)
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.up()
|
||||
.up();
|
||||
|
||||
addSigner(dokument, input.signerFio);
|
||||
|
||||
const raschSV = dokument.ele('РасчетСВ');
|
||||
const obyaz = raschSV.ele('ОбязПлатСВ')
|
||||
.att('ТипПлат', '1')
|
||||
.att('ОКТМО', input.oktmo);
|
||||
|
||||
obyaz.ele('УплПерОПС')
|
||||
.att('КБК', '18210202010061010160')
|
||||
.att('СумСВУплПер', '0')
|
||||
.att('СумСВУпл1М', '0')
|
||||
.att('СумСВУпл2М', '0')
|
||||
.att('СумСВУпл3М', '0')
|
||||
.up();
|
||||
|
||||
obyaz.up();
|
||||
raschSV.up();
|
||||
dokument.up();
|
||||
doc.up();
|
||||
|
||||
return doc.end({ prettyPrint: true });
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { ReportType } from '../../domain/enums/report-type.enum';
|
||||
import type { IReportGenerator, ReportInput, ReportOutput } from '../../domain/interfaces/report-generator.interface';
|
||||
import { createXmlDoc, formatDate, generateUuid, addSigner, getQuarterPeriodCode, getTaxOfficeCode } from './xml-utils';
|
||||
|
||||
/**
|
||||
* Генератор Уведомления по УСН (нулевое)
|
||||
* XSD: UT_UVISCHSUMNAL_1_263_00_05_03_01.xsd (общий с UV_VZNOSY)
|
||||
* КНД: 1110355
|
||||
*
|
||||
* Уведомление об исчисленных суммах авансовых платежей по УСН
|
||||
* Подаётся ежеквартально до 25-го числа
|
||||
*/
|
||||
export class UusnGenerator implements IReportGenerator {
|
||||
readonly reportType = ReportType.UUSN;
|
||||
|
||||
generate(input: ReportInput): ReportOutput {
|
||||
const fileName = this.generateFileName(input);
|
||||
const errors: string[] = [];
|
||||
|
||||
try {
|
||||
const xml = this.buildXml(input);
|
||||
return { reportType: this.reportType, xml, fileName, errors, isValid: true };
|
||||
} catch (e: any) {
|
||||
errors.push(`Ошибка генерации уведомления по УСН: ${e.message}`);
|
||||
return { reportType: this.reportType, xml: '', fileName, errors, isValid: false };
|
||||
}
|
||||
}
|
||||
|
||||
generateFileName(input: ReportInput): string {
|
||||
const now = new Date();
|
||||
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||||
return `UT_UVISCHSUMNAL_${input.inn}_${input.kpp}_${dateStr}_${generateUuid()}`;
|
||||
}
|
||||
|
||||
private buildXml(input: ReportInput): string {
|
||||
const idFile = this.generateFileName(input);
|
||||
const kodNO = getTaxOfficeCode(input.kpp);
|
||||
const quarter = input.period || 1;
|
||||
|
||||
const doc = createXmlDoc()
|
||||
.ele('Файл')
|
||||
.att('ИдФайл', idFile)
|
||||
.att('ВерсПрог', 'CoopReports 1.0')
|
||||
.att('ВерсФорм', '5.03');
|
||||
|
||||
const dokument = doc.ele('Документ')
|
||||
.att('КНД', '1110355')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('КодНО', kodNO);
|
||||
|
||||
dokument.ele('СвНП')
|
||||
.ele('НПЮЛ')
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.up()
|
||||
.up();
|
||||
|
||||
addSigner(dokument, input.signerFio);
|
||||
|
||||
dokument.ele('УвИсчСумНалог')
|
||||
.att('КППДекл', input.kpp)
|
||||
.att('ОКТМО', input.oktmo)
|
||||
.att('КБК', '18210501021011000110')
|
||||
.att('СумНалогАванс', '0')
|
||||
.att('Период', '21/01')
|
||||
.att('НомерМесКварт', String(quarter))
|
||||
.att('Год', String(input.year))
|
||||
.up();
|
||||
|
||||
dokument.up();
|
||||
doc.up();
|
||||
|
||||
return doc.end({ prettyPrint: true });
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { ReportType } from '../../domain/enums/report-type.enum';
|
||||
import type { IReportGenerator, ReportInput, ReportOutput } from '../../domain/interfaces/report-generator.interface';
|
||||
import { createXmlDoc, formatDate, generateUuid, addSigner, getTaxOfficeCode } from './xml-utils';
|
||||
|
||||
/**
|
||||
* Генератор Уведомления об исчисленных страховых взносах (нулевое)
|
||||
* XSD: UT_UVISCHSUMNAL_1_263_00_05_03_01.xsd
|
||||
* КНД: 1110355
|
||||
*
|
||||
* Структура: Файл → Документ → [СвНП, Подписант, УвИсчСумНалог[]]
|
||||
* СвНП: НПЮЛ(@ИННЮЛ, @КПП)
|
||||
* УвИсчСумНалог: @ОКТМО, @КБК, @СумНалогАванс, @Период, @НомерМесКварт, @Год
|
||||
*/
|
||||
export class UvVznosyGenerator implements IReportGenerator {
|
||||
readonly reportType = ReportType.UV_VZNOSY;
|
||||
|
||||
generate(input: ReportInput): ReportOutput {
|
||||
const fileName = this.generateFileName(input);
|
||||
const errors: string[] = [];
|
||||
|
||||
try {
|
||||
const xml = this.buildXml(input);
|
||||
return { reportType: this.reportType, xml, fileName, errors, isValid: true };
|
||||
} catch (e: any) {
|
||||
errors.push(`Ошибка генерации уведомления о взносах: ${e.message}`);
|
||||
return { reportType: this.reportType, xml: '', fileName, errors, isValid: false };
|
||||
}
|
||||
}
|
||||
|
||||
generateFileName(input: ReportInput): string {
|
||||
const now = new Date();
|
||||
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||||
return `UT_UVISCHSUMNAL_${input.inn}_${input.kpp}_${dateStr}_${generateUuid()}`;
|
||||
}
|
||||
|
||||
private buildXml(input: ReportInput): string {
|
||||
const idFile = this.generateFileName(input);
|
||||
const kodNO = getTaxOfficeCode(input.kpp);
|
||||
const month = input.period || 1;
|
||||
|
||||
const doc = createXmlDoc()
|
||||
.ele('Файл')
|
||||
.att('ИдФайл', idFile)
|
||||
.att('ВерсПрог', 'CoopReports 1.0')
|
||||
.att('ВерсФорм', '5.03');
|
||||
|
||||
const dokument = doc.ele('Документ')
|
||||
.att('КНД', '1110355')
|
||||
.att('ДатаДок', formatDate(new Date()))
|
||||
.att('КодНО', kodNO);
|
||||
|
||||
dokument.ele('СвНП')
|
||||
.ele('НПЮЛ')
|
||||
.att('ИННЮЛ', input.inn)
|
||||
.att('КПП', input.kpp)
|
||||
.up()
|
||||
.up();
|
||||
|
||||
addSigner(dokument, input.signerFio);
|
||||
|
||||
dokument.ele('УвИсчСумНалог')
|
||||
.att('КППДекл', input.kpp)
|
||||
.att('ОКТМО', input.oktmo)
|
||||
.att('КБК', '18210202000011000160')
|
||||
.att('СумНалогАванс', '0')
|
||||
.att('Период', '21/01')
|
||||
.att('НомерМесКварт', String(month))
|
||||
.att('Год', String(input.year))
|
||||
.up();
|
||||
|
||||
dokument.up();
|
||||
doc.up();
|
||||
|
||||
return doc.end({ prettyPrint: true });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { create } from 'xmlbuilder2';
|
||||
|
||||
export function formatDate(d: Date): string {
|
||||
return `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${d.getFullYear()}`;
|
||||
}
|
||||
|
||||
export function generateUuid(): string {
|
||||
return Math.random().toString(36).substring(2, 10).toUpperCase()
|
||||
+ Math.random().toString(36).substring(2, 6).toUpperCase();
|
||||
}
|
||||
|
||||
export function createXmlDoc(): any {
|
||||
return create({ version: '1.0', encoding: 'windows-1251' });
|
||||
}
|
||||
|
||||
export function addFio(
|
||||
parent: any,
|
||||
fio: { lastName: string; firstName: string; middleName?: string },
|
||||
): any {
|
||||
const el = parent.ele('ФИО')
|
||||
.att('Фамилия', fio.lastName)
|
||||
.att('Имя', fio.firstName);
|
||||
if (fio.middleName) {
|
||||
el.att('Отчество', fio.middleName);
|
||||
}
|
||||
return el.up();
|
||||
}
|
||||
|
||||
export function addSigner(
|
||||
parent: any,
|
||||
fio: { lastName: string; firstName: string; middleName?: string },
|
||||
): any {
|
||||
const signer = parent.ele('Подписант').att('ПрПодп', '1');
|
||||
addFio(signer, fio);
|
||||
return signer.up();
|
||||
}
|
||||
|
||||
export function getQuarterPeriodCode(quarter?: number): string {
|
||||
switch (quarter) {
|
||||
case 1: return '21';
|
||||
case 2: return '31';
|
||||
case 3: return '33';
|
||||
default: return '34';
|
||||
}
|
||||
}
|
||||
|
||||
export function getMonthPeriodCode(month?: number): string {
|
||||
return month ? String(month).padStart(2, '0') : '01';
|
||||
}
|
||||
|
||||
export function getTaxOfficeCode(kpp: string): string {
|
||||
return kpp.substring(0, 4);
|
||||
}
|
||||
+12
-11
@@ -1,13 +1,14 @@
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { ReportRegistryService } from '../../domain/services/report-registry.service';
|
||||
import { ReportType } from '../../domain/enums/report-type.enum';
|
||||
import { BuhotchGenerator } from '../generators/buhotch.generator';
|
||||
import { Ndfl6Generator } from '../generators/ndfl6.generator';
|
||||
import { createZeroReportGenerator } from '../generators/zero-report.generator';
|
||||
import { RsvGenerator } from '../generators/rsv.generator';
|
||||
import { PsvGenerator } from '../generators/psv.generator';
|
||||
import { DusnGenerator } from '../generators/dusn.generator';
|
||||
import { Fss4Generator } from '../generators/fss4.generator';
|
||||
import { UvVznosyGenerator } from '../generators/uv-vznosy.generator';
|
||||
import { UusnGenerator } from '../generators/uusn.generator';
|
||||
|
||||
/**
|
||||
* Сервис инициализации — регистрирует все генераторы при старте модуля
|
||||
*/
|
||||
@Injectable()
|
||||
export class ReportInitService implements OnModuleInit {
|
||||
constructor(private readonly registry: ReportRegistryService) {}
|
||||
@@ -15,11 +16,11 @@ export class ReportInitService implements OnModuleInit {
|
||||
onModuleInit() {
|
||||
this.registry.register(new BuhotchGenerator());
|
||||
this.registry.register(new Ndfl6Generator());
|
||||
this.registry.register(createZeroReportGenerator(ReportType.RSV, '1151111', '5.08'));
|
||||
this.registry.register(createZeroReportGenerator(ReportType.PSV, '1151162', '5.01'));
|
||||
this.registry.register(createZeroReportGenerator(ReportType.DUSN, '1152017', '5.09'));
|
||||
this.registry.register(createZeroReportGenerator(ReportType.FSS4, '1111111', '1.00'));
|
||||
this.registry.register(createZeroReportGenerator(ReportType.UV_VZNOSY, '1110355', '5.03'));
|
||||
this.registry.register(createZeroReportGenerator(ReportType.UUSN, '1110355', '5.03'));
|
||||
this.registry.register(new RsvGenerator());
|
||||
this.registry.register(new PsvGenerator());
|
||||
this.registry.register(new DusnGenerator());
|
||||
this.registry.register(new Fss4Generator());
|
||||
this.registry.register(new UvVznosyGenerator());
|
||||
this.registry.register(new UusnGenerator());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ import { Module } from '@nestjs/common';
|
||||
import { ReportRegistryService } from './domain/services/report-registry.service';
|
||||
import { ReportInitService } from './infrastructure/services/report-init.service';
|
||||
import { ReportResolver } from './application/resolvers/report.resolver';
|
||||
import { LedgerModule } from '~/application/ledger/ledger.module';
|
||||
|
||||
@Module({
|
||||
imports: [LedgerModule],
|
||||
providers: [
|
||||
ReportRegistryService,
|
||||
ReportInitService,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('api_keys')
|
||||
export class ApiKeyEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 12 })
|
||||
coopname!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
name!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 64, unique: true })
|
||||
key_hash!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 8 })
|
||||
key_prefix!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
created_by!: string;
|
||||
|
||||
@Column({ type: 'jsonb', default: '["*"]' })
|
||||
allowed_operations!: string[];
|
||||
|
||||
@Column({ type: 'timestamp', nullable: true })
|
||||
expires_at?: Date;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
is_active!: boolean;
|
||||
|
||||
@Column({ type: 'timestamp', nullable: true })
|
||||
last_used_at?: Date;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||
import { ApiKeyService } from './api-key.service';
|
||||
|
||||
/**
|
||||
* Guard для аутентификации через API ключ.
|
||||
* Проверяет заголовок x-api-key. Если присутствует — валидирует.
|
||||
* Работает как альтернатива JWT — не заменяет его.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ApiKeyGuard implements CanActivate {
|
||||
constructor(private readonly apiKeyService: ApiKeyService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const ctx = GqlExecutionContext.create(context);
|
||||
const request = ctx.getContext().req;
|
||||
|
||||
const apiKey = request.headers['x-api-key'];
|
||||
if (!apiKey) return true;
|
||||
|
||||
const entity = await this.apiKeyService.validateKey(apiKey);
|
||||
if (!entity) {
|
||||
throw new UnauthorizedException('Невалидный или истёкший API ключ');
|
||||
}
|
||||
|
||||
request.apiKeyEntity = entity;
|
||||
if (!request.user) {
|
||||
request.user = {
|
||||
username: entity.created_by,
|
||||
role: 'chairman',
|
||||
isApiKey: true,
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ApiKeyEntity } from './api-key.entity';
|
||||
import { ApiKeyService } from './api-key.service';
|
||||
import { ApiKeyGuard } from './api-key.guard';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([ApiKeyEntity])],
|
||||
providers: [ApiKeyService, ApiKeyGuard],
|
||||
exports: [ApiKeyService, ApiKeyGuard],
|
||||
})
|
||||
export class ApiKeyModule {}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as crypto from 'crypto';
|
||||
import { ApiKeyEntity } from './api-key.entity';
|
||||
|
||||
export interface CreateApiKeyInput {
|
||||
coopname: string;
|
||||
name: string;
|
||||
createdBy: string;
|
||||
allowedOperations?: string[];
|
||||
expiresInDays?: number;
|
||||
}
|
||||
|
||||
export interface ApiKeyCreateResult {
|
||||
id: string;
|
||||
name: string;
|
||||
key: string;
|
||||
key_prefix: string;
|
||||
allowed_operations: string[];
|
||||
expires_at?: Date;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyService {
|
||||
private readonly logger = new Logger(ApiKeyService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(ApiKeyEntity)
|
||||
private readonly repo: Repository<ApiKeyEntity>,
|
||||
) {}
|
||||
|
||||
async createKey(input: CreateApiKeyInput): Promise<ApiKeyCreateResult> {
|
||||
const rawKey = `ck_${crypto.randomBytes(32).toString('hex')}`;
|
||||
const keyHash = crypto.createHash('sha256').update(rawKey).digest('hex');
|
||||
const keyPrefix = rawKey.substring(0, 10);
|
||||
|
||||
const expiresAt = input.expiresInDays
|
||||
? new Date(Date.now() + input.expiresInDays * 24 * 60 * 60 * 1000)
|
||||
: undefined;
|
||||
|
||||
const entity = this.repo.create({
|
||||
coopname: input.coopname,
|
||||
name: input.name,
|
||||
key_hash: keyHash,
|
||||
key_prefix: keyPrefix,
|
||||
created_by: input.createdBy,
|
||||
allowed_operations: input.allowedOperations || ['*'],
|
||||
expires_at: expiresAt,
|
||||
is_active: true,
|
||||
});
|
||||
|
||||
const saved = await this.repo.save(entity);
|
||||
|
||||
return {
|
||||
id: saved.id,
|
||||
name: saved.name,
|
||||
key: rawKey,
|
||||
key_prefix: keyPrefix,
|
||||
allowed_operations: saved.allowed_operations,
|
||||
expires_at: saved.expires_at,
|
||||
created_at: saved.created_at,
|
||||
};
|
||||
}
|
||||
|
||||
async validateKey(rawKey: string): Promise<ApiKeyEntity | null> {
|
||||
const keyHash = crypto.createHash('sha256').update(rawKey).digest('hex');
|
||||
const entity = await this.repo.findOneBy({ key_hash: keyHash, is_active: true });
|
||||
|
||||
if (!entity) return null;
|
||||
if (entity.expires_at && entity.expires_at < new Date()) return null;
|
||||
|
||||
entity.last_used_at = new Date();
|
||||
await this.repo.save(entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
async listKeys(coopname: string): Promise<ApiKeyEntity[]> {
|
||||
return this.repo.find({
|
||||
where: { coopname },
|
||||
order: { created_at: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async revokeKey(id: string, revokedBy: string): Promise<void> {
|
||||
const entity = await this.repo.findOneBy({ id, created_by: revokedBy });
|
||||
if (entity) {
|
||||
entity.is_active = false;
|
||||
await this.repo.save(entity);
|
||||
}
|
||||
}
|
||||
|
||||
isOperationAllowed(entity: ApiKeyEntity, operation: string): boolean {
|
||||
if (entity.allowed_operations.includes('*')) return true;
|
||||
return entity.allowed_operations.includes(operation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Стандартные действия CASL
|
||||
*/
|
||||
export enum Action {
|
||||
Manage = 'manage', // Полный доступ
|
||||
Read = 'read', // Чтение
|
||||
Create = 'create', // Создание
|
||||
Update = 'update', // Обновление
|
||||
Delete = 'delete', // Удаление
|
||||
Execute = 'execute', // Выполнение действий (approve, decline, etc.)
|
||||
Share = 'share', // Поделиться ссылкой
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AbilityBuilder, createMongoAbility, type MongoAbility } from '@casl/ability';
|
||||
import { Action } from './actions';
|
||||
import { Subject } from './subjects';
|
||||
|
||||
export type AppAbility = MongoAbility<[Action, Subject]>;
|
||||
|
||||
export interface UserForAbility {
|
||||
username: string;
|
||||
role: 'chairman' | 'member' | 'user';
|
||||
/** Дополнительные гранулированные права (future) */
|
||||
permissions?: GrantedPermission[];
|
||||
}
|
||||
|
||||
export interface GrantedPermission {
|
||||
action: Action;
|
||||
subject: Subject;
|
||||
conditions?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Фабрика CASL abilities.
|
||||
* Создаёт набор прав на основе роли + гранулированных разрешений.
|
||||
* Обратная совместимость: chairman/member/user сохраняют текущие права.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CaslAbilityFactory {
|
||||
createForUser(user: UserForAbility): AppAbility {
|
||||
const { can, cannot, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
|
||||
|
||||
switch (user.role) {
|
||||
case 'chairman':
|
||||
can(Action.Manage, Subject.All);
|
||||
break;
|
||||
|
||||
case 'member':
|
||||
can(Action.Read, Subject.All);
|
||||
can(Action.Create, Subject.Issue);
|
||||
can(Action.Update, Subject.Issue);
|
||||
can(Action.Create, Subject.Commit);
|
||||
can(Action.Execute, Subject.Decision);
|
||||
can(Action.Read, Subject.Payment);
|
||||
can(Action.Read, Subject.Participant);
|
||||
can(Action.Read, Subject.Document);
|
||||
can(Action.Read, Subject.Meet);
|
||||
can(Action.Read, Subject.Ledger);
|
||||
can(Action.Create, Subject.Process);
|
||||
can(Action.Update, Subject.Process);
|
||||
can(Action.Read, Subject.Report);
|
||||
can(Action.Share, Subject.All);
|
||||
// member не может управлять системой и расширениями
|
||||
cannot(Action.Manage, Subject.System);
|
||||
cannot(Action.Manage, Subject.Extension);
|
||||
break;
|
||||
|
||||
case 'user':
|
||||
// user может читать свои данные
|
||||
can(Action.Read, Subject.Wallet);
|
||||
can(Action.Read, Subject.Profile);
|
||||
can(Action.Read, Subject.UserDocument);
|
||||
can(Action.Read, Subject.UserPayment);
|
||||
can(Action.Read, Subject.Meet);
|
||||
can(Action.Read, Subject.Search);
|
||||
// user может обновлять свой профиль
|
||||
can(Action.Update, Subject.Profile);
|
||||
// user может работать с задачами
|
||||
can(Action.Read, Subject.Issue);
|
||||
can(Action.Update, Subject.Issue);
|
||||
break;
|
||||
}
|
||||
|
||||
// Применяем гранулированные права (из share tokens и т.д.)
|
||||
if (user.permissions) {
|
||||
for (const perm of user.permissions) {
|
||||
can(perm.action, perm.subject, perm.conditions);
|
||||
}
|
||||
}
|
||||
|
||||
return build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||
import { CaslAbilityFactory, type UserForAbility } from './casl-ability.factory';
|
||||
import { Action } from './actions';
|
||||
import { Subject } from './subjects';
|
||||
import config from '~/config/config';
|
||||
|
||||
export const CHECK_ABILITY_KEY = 'check_ability';
|
||||
|
||||
/**
|
||||
* Декоратор для указания требуемых прав
|
||||
*/
|
||||
export function CheckAbility(action: Action, subject: Subject) {
|
||||
return (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => {
|
||||
Reflect.defineMetadata(CHECK_ABILITY_KEY, { action, subject }, descriptor?.value || target);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* CASL Guard — проверяет granular permissions.
|
||||
* Работает ПАРАЛЛЕЛЬНО с RolesGuard для обратной совместимости.
|
||||
* Если @CheckAbility не установлен, пропускает (как и раньше).
|
||||
*/
|
||||
@Injectable()
|
||||
export class CaslGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly reflector: Reflector,
|
||||
private readonly caslAbilityFactory: CaslAbilityFactory,
|
||||
) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const ctx = GqlExecutionContext.create(context);
|
||||
const request = ctx.getContext().req;
|
||||
|
||||
// server-secret bypass
|
||||
if (request.headers['server-secret'] === config.server_secret) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const requirement = this.reflector.get<{ action: Action; subject: Subject }>(
|
||||
CHECK_ABILITY_KEY,
|
||||
context.getHandler(),
|
||||
);
|
||||
|
||||
// Если @CheckAbility не установлен, пропускаем (обратная совместимость)
|
||||
if (!requirement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { user } = request;
|
||||
if (!user) {
|
||||
throw new ForbiddenException('Пользователь не авторизован');
|
||||
}
|
||||
|
||||
const userForAbility: UserForAbility = {
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
permissions: user.grantedPermissions || [],
|
||||
};
|
||||
|
||||
const ability = this.caslAbilityFactory.createForUser(userForAbility);
|
||||
|
||||
if (!ability.can(requirement.action, requirement.subject)) {
|
||||
throw new ForbiddenException(
|
||||
`Недостаточно прав: ${requirement.action} ${requirement.subject}`,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { CaslAbilityFactory } from './casl-ability.factory';
|
||||
import { CaslGuard } from './casl.guard';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [CaslAbilityFactory, CaslGuard],
|
||||
exports: [CaslAbilityFactory, CaslGuard],
|
||||
})
|
||||
export class CaslModule {}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Субъекты (ресурсы) для проверки прав.
|
||||
* Каждая страница/раздел = отдельный subject.
|
||||
*/
|
||||
export enum Subject {
|
||||
// Системные
|
||||
All = 'all',
|
||||
System = 'System',
|
||||
Extension = 'Extension',
|
||||
|
||||
// Кооператив
|
||||
Participant = 'Participant',
|
||||
Agreement = 'Agreement',
|
||||
Document = 'Document',
|
||||
Payment = 'Payment',
|
||||
Meet = 'Meet',
|
||||
Branch = 'Branch',
|
||||
BoardMember = 'BoardMember',
|
||||
Decision = 'Decision',
|
||||
|
||||
// Пользовательские
|
||||
Wallet = 'Wallet',
|
||||
Profile = 'Profile',
|
||||
UserDocument = 'UserDocument',
|
||||
UserPayment = 'UserPayment',
|
||||
|
||||
// Capital
|
||||
Project = 'Project',
|
||||
Component = 'Component',
|
||||
Issue = 'Issue',
|
||||
Process = 'Process',
|
||||
Commit = 'Commit',
|
||||
|
||||
// Отчёты
|
||||
Report = 'Report',
|
||||
|
||||
// Поиск
|
||||
Search = 'Search',
|
||||
|
||||
// Ledger
|
||||
Ledger = 'Ledger',
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Global, Module } from '@nestjs/common';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import config from '~/config/config';
|
||||
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import { docDirectiveTransformer } from './directives/doc.directive';
|
||||
import { GraphQLError, GraphQLFormattedError } from 'graphql';
|
||||
import { fieldAuthDirectiveTransformer } from './directives/fieldAuth.directive';
|
||||
@@ -17,9 +18,32 @@ import logger from '~/config/logger';
|
||||
autoSchemaFile: 'schema.gql',
|
||||
sortSchema: true,
|
||||
debug: config.env !== 'production',
|
||||
// context: ({ req }) => req,
|
||||
playground: { endpoint: '/v1/graphql', settings: { 'request.credentials': 'same-origin' } },
|
||||
path: '/v1/graphql', // здесь можно задать другой путь, когда потребуется,
|
||||
path: '/v1/graphql',
|
||||
subscriptions: {
|
||||
'graphql-ws': {
|
||||
path: '/v1/graphql',
|
||||
onConnect: (context: any) => {
|
||||
const { connectionParams } = context;
|
||||
const token = connectionParams?.token || connectionParams?.authorization;
|
||||
if (!token) {
|
||||
throw new Error('Необходима аутентификация для WebSocket подписок');
|
||||
}
|
||||
try {
|
||||
const decoded = jwt.verify(token, config.jwt.secret);
|
||||
return { user: decoded, token };
|
||||
} catch {
|
||||
throw new Error('Невалидный токен аутентификации');
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
context: ({ req, connection }: any) => {
|
||||
if (connection) {
|
||||
return { req: { headers: {}, user: connection.context?.user } };
|
||||
}
|
||||
return { req };
|
||||
},
|
||||
transformSchema: (schema) => {
|
||||
schema = docDirectiveTransformer(schema, 'auth');
|
||||
schema = fieldAuthDirectiveTransformer(schema, 'auth');
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
|
||||
export const PUB_SUB = Symbol('PUB_SUB');
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: PUB_SUB,
|
||||
useValue: new PubSub(),
|
||||
},
|
||||
],
|
||||
exports: [PUB_SUB],
|
||||
})
|
||||
export class PubSubModule {}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
||||
|
||||
export enum ShareTargetType {
|
||||
GUEST = 'guest',
|
||||
MEMBER = 'member',
|
||||
}
|
||||
|
||||
@Entity('share_tokens')
|
||||
export class ShareTokenEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 12 })
|
||||
coopname!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
created_by!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
page_path!: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
page_name!: string;
|
||||
|
||||
@Column({ type: 'enum', enum: ShareTargetType })
|
||||
target_type!: ShareTargetType;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
target_username?: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
link_name?: string;
|
||||
|
||||
@Column({ type: 'jsonb', default: '[]' })
|
||||
allowed_actions!: string[];
|
||||
|
||||
@Column({ type: 'text' })
|
||||
token!: string;
|
||||
|
||||
@Column({ type: 'timestamp', nullable: true })
|
||||
expires_at?: Date;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
is_active!: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ShareTokenEntity } from './share-token.entity';
|
||||
import { ShareService } from './share.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([ShareTokenEntity])],
|
||||
providers: [ShareService],
|
||||
exports: [ShareService],
|
||||
})
|
||||
export class ShareModule {}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import { ShareTokenEntity, ShareTargetType } from './share-token.entity';
|
||||
import config from '~/config/config';
|
||||
|
||||
export interface CreateShareLinkInput {
|
||||
coopname: string;
|
||||
createdBy: string;
|
||||
pagePath: string;
|
||||
pageName: string;
|
||||
targetType: ShareTargetType;
|
||||
targetUsername?: string;
|
||||
linkName?: string;
|
||||
allowedActions: string[];
|
||||
expiresInDays?: number;
|
||||
}
|
||||
|
||||
export interface ShareTokenPayload {
|
||||
shareId: string;
|
||||
coopname: string;
|
||||
pagePath: string;
|
||||
allowedActions: string[];
|
||||
targetType: ShareTargetType;
|
||||
targetUsername?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ShareService {
|
||||
private readonly logger = new Logger(ShareService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(ShareTokenEntity)
|
||||
private readonly repo: Repository<ShareTokenEntity>,
|
||||
) {}
|
||||
|
||||
async createShareLink(input: CreateShareLinkInput): Promise<ShareTokenEntity> {
|
||||
const expiresAt = input.expiresInDays
|
||||
? new Date(Date.now() + input.expiresInDays * 24 * 60 * 60 * 1000)
|
||||
: undefined;
|
||||
|
||||
const entity = this.repo.create({
|
||||
coopname: input.coopname,
|
||||
created_by: input.createdBy,
|
||||
page_path: input.pagePath,
|
||||
page_name: input.pageName,
|
||||
target_type: input.targetType,
|
||||
target_username: input.targetUsername,
|
||||
link_name: input.linkName,
|
||||
allowed_actions: input.allowedActions,
|
||||
expires_at: expiresAt,
|
||||
is_active: true,
|
||||
token: '',
|
||||
});
|
||||
|
||||
const saved = await this.repo.save(entity);
|
||||
|
||||
const payload: ShareTokenPayload = {
|
||||
shareId: saved.id,
|
||||
coopname: input.coopname,
|
||||
pagePath: input.pagePath,
|
||||
allowedActions: input.allowedActions,
|
||||
targetType: input.targetType,
|
||||
targetUsername: input.targetUsername,
|
||||
};
|
||||
|
||||
saved.token = jwt.sign(payload, config.jwt.secret, {
|
||||
expiresIn: input.expiresInDays ? `${input.expiresInDays}d` : '365d',
|
||||
});
|
||||
|
||||
return this.repo.save(saved);
|
||||
}
|
||||
|
||||
async verifyShareToken(token: string): Promise<ShareTokenPayload | null> {
|
||||
try {
|
||||
const payload = jwt.verify(token, config.jwt.secret) as ShareTokenPayload;
|
||||
|
||||
const entity = await this.repo.findOneBy({ id: payload.shareId, is_active: true });
|
||||
if (!entity) return null;
|
||||
|
||||
if (entity.expires_at && entity.expires_at < new Date()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return payload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getShareLinks(coopname: string, createdBy: string): Promise<ShareTokenEntity[]> {
|
||||
return this.repo.find({
|
||||
where: { coopname, created_by: createdBy, is_active: true },
|
||||
order: { created_at: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async getSharedWithMe(coopname: string, username: string): Promise<ShareTokenEntity[]> {
|
||||
return this.repo.find({
|
||||
where: { coopname, target_username: username, is_active: true },
|
||||
order: { created_at: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async revokeShareLink(id: string, revokedBy: string): Promise<void> {
|
||||
const entity = await this.repo.findOneBy({ id, created_by: revokedBy });
|
||||
if (entity) {
|
||||
entity.is_active = false;
|
||||
await this.repo.save(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
// Тестируем логику API ключей без реальной БД
|
||||
describe('ApiKey Security', () => {
|
||||
describe('key generation', () => {
|
||||
it('generates key with ck_ prefix', () => {
|
||||
const rawKey = `ck_${crypto.randomBytes(32).toString('hex')}`;
|
||||
expect(rawKey.startsWith('ck_')).toBe(true);
|
||||
expect(rawKey.length).toBe(67); // ck_ + 64 hex
|
||||
});
|
||||
|
||||
it('hash is irreversible — different keys produce different hashes', () => {
|
||||
const key1 = `ck_${crypto.randomBytes(32).toString('hex')}`;
|
||||
const key2 = `ck_${crypto.randomBytes(32).toString('hex')}`;
|
||||
const hash1 = crypto.createHash('sha256').update(key1).digest('hex');
|
||||
const hash2 = crypto.createHash('sha256').update(key2).digest('hex');
|
||||
expect(hash1).not.toBe(hash2);
|
||||
});
|
||||
|
||||
it('same key always produces same hash', () => {
|
||||
const key = 'ck_abc123';
|
||||
const hash1 = crypto.createHash('sha256').update(key).digest('hex');
|
||||
const hash2 = crypto.createHash('sha256').update(key).digest('hex');
|
||||
expect(hash1).toBe(hash2);
|
||||
});
|
||||
|
||||
it('prefix is first 10 chars of key', () => {
|
||||
const rawKey = `ck_${crypto.randomBytes(32).toString('hex')}`;
|
||||
const prefix = rawKey.substring(0, 10);
|
||||
expect(prefix.startsWith('ck_')).toBe(true);
|
||||
expect(prefix.length).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expiry validation', () => {
|
||||
it('expired key should be rejected', () => {
|
||||
const expiresAt = new Date(Date.now() - 1000); // 1 second ago
|
||||
expect(expiresAt < new Date()).toBe(true);
|
||||
});
|
||||
|
||||
it('future key should be accepted', () => {
|
||||
const expiresAt = new Date(Date.now() + 86400000); // 1 day from now
|
||||
expect(expiresAt < new Date()).toBe(false);
|
||||
});
|
||||
|
||||
it('null expiry means unlimited', () => {
|
||||
const expiresAt: Date | undefined = undefined;
|
||||
const isExpired = expiresAt ? (expiresAt as Date) < new Date() : false;
|
||||
expect(isExpired).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('operation checking', () => {
|
||||
it('wildcard allows everything', () => {
|
||||
const ops = ['*'];
|
||||
expect(ops.includes('*') || ops.includes('createApiKey')).toBe(true);
|
||||
});
|
||||
|
||||
it('specific operations restrict access', () => {
|
||||
const ops = ['getApiKeys', 'revokeApiKey'];
|
||||
expect(ops.includes('createApiKey')).toBe(false);
|
||||
expect(ops.includes('getApiKeys')).toBe(true);
|
||||
});
|
||||
|
||||
it('empty operations deny everything', () => {
|
||||
const ops: string[] = [];
|
||||
expect(ops.includes('*')).toBe(false);
|
||||
expect(ops.includes('anything')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('security invariants', () => {
|
||||
it('raw key is never stored — only hash', () => {
|
||||
const rawKey = `ck_${crypto.randomBytes(32).toString('hex')}`;
|
||||
const hash = crypto.createHash('sha256').update(rawKey).digest('hex');
|
||||
// hash doesn't contain the raw key
|
||||
expect(hash.includes('ck_')).toBe(false);
|
||||
expect(hash.length).toBe(64); // SHA256 = 64 hex chars
|
||||
});
|
||||
|
||||
it('cannot reconstruct key from hash', () => {
|
||||
const hash = crypto.createHash('sha256').update('ck_test123').digest('hex');
|
||||
// There's no way to get 'ck_test123' back from hash
|
||||
expect(typeof hash).toBe('string');
|
||||
expect(hash).not.toBe('ck_test123');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
import { CaslAbilityFactory, type UserForAbility } from '../../../src/infrastructure/casl/casl-ability.factory';
|
||||
import { Action } from '../../../src/infrastructure/casl/actions';
|
||||
import { Subject } from '../../../src/infrastructure/casl/subjects';
|
||||
|
||||
describe('CaslAbilityFactory', () => {
|
||||
const factory = new CaslAbilityFactory();
|
||||
|
||||
describe('chairman role', () => {
|
||||
const chairman: UserForAbility = { username: 'ant', role: 'chairman' };
|
||||
const ability = factory.createForUser(chairman);
|
||||
|
||||
it('can manage all resources', () => {
|
||||
expect(ability.can(Action.Manage, Subject.All)).toBe(true);
|
||||
});
|
||||
|
||||
it('can read system', () => {
|
||||
expect(ability.can(Action.Read, Subject.System)).toBe(true);
|
||||
});
|
||||
|
||||
it('can manage extensions', () => {
|
||||
expect(ability.can(Action.Manage, Subject.Extension)).toBe(true);
|
||||
});
|
||||
|
||||
it('can generate reports', () => {
|
||||
expect(ability.can(Action.Create, Subject.Report)).toBe(true);
|
||||
});
|
||||
|
||||
it('can share pages', () => {
|
||||
expect(ability.can(Action.Share, Subject.All)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('member role', () => {
|
||||
const member: UserForAbility = { username: 'petr', role: 'member' };
|
||||
const ability = factory.createForUser(member);
|
||||
|
||||
it('can read all resources', () => {
|
||||
expect(ability.can(Action.Read, Subject.Payment)).toBe(true);
|
||||
expect(ability.can(Action.Read, Subject.Participant)).toBe(true);
|
||||
expect(ability.can(Action.Read, Subject.Document)).toBe(true);
|
||||
});
|
||||
|
||||
it('can create/update issues', () => {
|
||||
expect(ability.can(Action.Create, Subject.Issue)).toBe(true);
|
||||
expect(ability.can(Action.Update, Subject.Issue)).toBe(true);
|
||||
});
|
||||
|
||||
it('can execute decisions', () => {
|
||||
expect(ability.can(Action.Execute, Subject.Decision)).toBe(true);
|
||||
});
|
||||
|
||||
it('cannot manage system', () => {
|
||||
expect(ability.can(Action.Manage, Subject.System)).toBe(false);
|
||||
});
|
||||
|
||||
it('cannot manage extensions', () => {
|
||||
expect(ability.can(Action.Manage, Subject.Extension)).toBe(false);
|
||||
});
|
||||
|
||||
it('can share pages', () => {
|
||||
expect(ability.can(Action.Share, Subject.Payment)).toBe(true);
|
||||
});
|
||||
|
||||
it('can read reports', () => {
|
||||
expect(ability.can(Action.Read, Subject.Report)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('user role', () => {
|
||||
const user: UserForAbility = { username: 'ivan', role: 'user' };
|
||||
const ability = factory.createForUser(user);
|
||||
|
||||
it('can read own wallet', () => {
|
||||
expect(ability.can(Action.Read, Subject.Wallet)).toBe(true);
|
||||
});
|
||||
|
||||
it('can read own profile', () => {
|
||||
expect(ability.can(Action.Read, Subject.Profile)).toBe(true);
|
||||
});
|
||||
|
||||
it('can update own profile', () => {
|
||||
expect(ability.can(Action.Update, Subject.Profile)).toBe(true);
|
||||
});
|
||||
|
||||
it('can read/update issues', () => {
|
||||
expect(ability.can(Action.Read, Subject.Issue)).toBe(true);
|
||||
expect(ability.can(Action.Update, Subject.Issue)).toBe(true);
|
||||
});
|
||||
|
||||
it('cannot read payments (cooperative level)', () => {
|
||||
expect(ability.can(Action.Read, Subject.Payment)).toBe(false);
|
||||
});
|
||||
|
||||
it('cannot create decisions', () => {
|
||||
expect(ability.can(Action.Create, Subject.Decision)).toBe(false);
|
||||
});
|
||||
|
||||
it('cannot share pages', () => {
|
||||
expect(ability.can(Action.Share, Subject.Payment)).toBe(false);
|
||||
});
|
||||
|
||||
it('cannot manage system', () => {
|
||||
expect(ability.can(Action.Manage, Subject.System)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('granular permissions', () => {
|
||||
it('user with extra permissions can access granted resources', () => {
|
||||
const user: UserForAbility = {
|
||||
username: 'olga',
|
||||
role: 'user',
|
||||
permissions: [
|
||||
{ action: Action.Read, subject: Subject.Payment },
|
||||
{ action: Action.Read, subject: Subject.Ledger },
|
||||
],
|
||||
};
|
||||
const ability = factory.createForUser(user);
|
||||
|
||||
expect(ability.can(Action.Read, Subject.Payment)).toBe(true);
|
||||
expect(ability.can(Action.Read, Subject.Ledger)).toBe(true);
|
||||
expect(ability.can(Action.Create, Subject.Payment)).toBe(false);
|
||||
});
|
||||
|
||||
it('granted execute permission works', () => {
|
||||
const user: UserForAbility = {
|
||||
username: 'test',
|
||||
role: 'user',
|
||||
permissions: [
|
||||
{ action: Action.Execute, subject: Subject.Payment },
|
||||
],
|
||||
};
|
||||
const ability = factory.createForUser(user);
|
||||
|
||||
expect(ability.can(Action.Execute, Subject.Payment)).toBe(true);
|
||||
expect(ability.can(Action.Delete, Subject.Payment)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,396 @@
|
||||
import { BuhotchGenerator } from '../../../src/extensions/reports/infrastructure/generators/buhotch.generator';
|
||||
import { Ndfl6Generator } from '../../../src/extensions/reports/infrastructure/generators/ndfl6.generator';
|
||||
import { RsvGenerator } from '../../../src/extensions/reports/infrastructure/generators/rsv.generator';
|
||||
import { PsvGenerator } from '../../../src/extensions/reports/infrastructure/generators/psv.generator';
|
||||
import { DusnGenerator } from '../../../src/extensions/reports/infrastructure/generators/dusn.generator';
|
||||
import { Fss4Generator } from '../../../src/extensions/reports/infrastructure/generators/fss4.generator';
|
||||
import { UvVznosyGenerator } from '../../../src/extensions/reports/infrastructure/generators/uv-vznosy.generator';
|
||||
import { UusnGenerator } from '../../../src/extensions/reports/infrastructure/generators/uusn.generator';
|
||||
import { ReportType } from '../../../src/extensions/reports/domain/enums/report-type.enum';
|
||||
import type { ReportInput } from '../../../src/extensions/reports/domain/interfaces/report-generator.interface';
|
||||
|
||||
const baseInput: ReportInput = {
|
||||
reportType: ReportType.BUHOTCH,
|
||||
year: 2025,
|
||||
inn: '9728130611',
|
||||
kpp: '772801001',
|
||||
orgName: 'Потребительский Кооператив "ВОСХОД"',
|
||||
ogrn: '1247700283346',
|
||||
okved: '94.99',
|
||||
okfs: '16',
|
||||
okopf: '20200',
|
||||
oktmo: '45382000',
|
||||
address: 'г. Москва, ул. Тестовая, д.1',
|
||||
signerFio: { lastName: 'Иванов', firstName: 'Иван', middleName: 'Иванович' },
|
||||
signerSnils: '123-456-789 00',
|
||||
ledgerData: [
|
||||
{ accountId: 51, name: 'Расчётный счёт', balanceCurrent: 150000, balancePrevious: 120000, balancePrePrevious: 80000 },
|
||||
{ accountId: 80, name: 'Паевой фонд', balanceCurrent: 50000, balancePrevious: 40000, balancePrePrevious: 30000 },
|
||||
{ accountId: 86, name: 'Целевое финансирование', balanceCurrent: 100000, balancePrevious: 80000, balancePrePrevious: 50000 },
|
||||
],
|
||||
};
|
||||
|
||||
function assertValidXml(xml: string) {
|
||||
expect(xml).toBeTruthy();
|
||||
expect(xml).toContain('<?xml');
|
||||
expect(xml).toContain('windows-1251');
|
||||
expect(xml).toContain('<Файл');
|
||||
expect(xml).toContain('ИдФайл=');
|
||||
expect(xml).toContain('ВерсПрог=');
|
||||
expect(xml).toContain('ВерсФорм=');
|
||||
expect(xml).toContain('<Документ');
|
||||
expect(xml).toContain('</Файл>');
|
||||
}
|
||||
|
||||
function assertHasSigner(xml: string) {
|
||||
expect(xml).toContain('<Подписант');
|
||||
expect(xml).toContain('ПрПодп="1"');
|
||||
expect(xml).toContain('<ФИО');
|
||||
expect(xml).toContain('Фамилия="Иванов"');
|
||||
expect(xml).toContain('Имя="Иван"');
|
||||
}
|
||||
|
||||
describe('Бухгалтерский баланс (BuhotchGenerator)', () => {
|
||||
const gen = new BuhotchGenerator();
|
||||
|
||||
it('reportType = BUHOTCH', () => {
|
||||
expect(gen.reportType).toBe(ReportType.BUHOTCH);
|
||||
});
|
||||
|
||||
it('генерирует валидный XML', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.BUHOTCH });
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.errors).toHaveLength(0);
|
||||
assertValidXml(result.xml);
|
||||
});
|
||||
|
||||
it('содержит корректные атрибуты Документ', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.BUHOTCH });
|
||||
expect(result.xml).toContain('КНД="0710099"');
|
||||
expect(result.xml).toContain('ОтчетГод="2025"');
|
||||
expect(result.xml).toContain('Период="34"');
|
||||
expect(result.xml).toContain('НомКорр="0"');
|
||||
expect(result.xml).toContain('ОКЕИ="384"');
|
||||
});
|
||||
|
||||
it('содержит блок СвНП с НПЮЛ', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.BUHOTCH });
|
||||
expect(result.xml).toContain('<СвНП');
|
||||
expect(result.xml).toContain('ОКВЭД2="94.99"');
|
||||
expect(result.xml).toContain('ОКФС="16"');
|
||||
expect(result.xml).toContain('ОКОПФ="20200"');
|
||||
expect(result.xml).toContain('<НПЮЛ');
|
||||
expect(result.xml).toContain('ИННЮЛ="9728130611"');
|
||||
expect(result.xml).toContain('КПП="772801001"');
|
||||
});
|
||||
|
||||
it('содержит подписанта', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.BUHOTCH });
|
||||
assertHasSigner(result.xml);
|
||||
});
|
||||
|
||||
it('содержит Баланс с Активом и Пассивом', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.BUHOTCH });
|
||||
expect(result.xml).toContain('<Баланс');
|
||||
expect(result.xml).toContain('<Актив');
|
||||
expect(result.xml).toContain('<ВнеОбА');
|
||||
expect(result.xml).toContain('<ОбА');
|
||||
expect(result.xml).toContain('<Пассив');
|
||||
expect(result.xml).toContain('<ДолгосрОбяз');
|
||||
expect(result.xml).toContain('<КраткосрОбяз');
|
||||
});
|
||||
|
||||
it('рассчитывает суммы из ledger (в тыс.руб.)', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.BUHOTCH });
|
||||
expect(result.xml).toContain('СумОтч="150"');
|
||||
expect(result.xml).toContain('СумПрдщ="120"');
|
||||
});
|
||||
|
||||
it('содержит ЦелИсп', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.BUHOTCH });
|
||||
expect(result.xml).toContain('<ЦелИсп');
|
||||
expect(result.xml).toContain('<ОстатНачОтч');
|
||||
expect(result.xml).toContain('<Поступило');
|
||||
expect(result.xml).toContain('<Использовано');
|
||||
expect(result.xml).toContain('<ОстатКонОтч');
|
||||
});
|
||||
|
||||
it('генерирует корректное имя файла', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.BUHOTCH });
|
||||
expect(result.fileName).toMatch(/^NO_BUHOTCH_9728130611_772801001_\d{8}_/);
|
||||
});
|
||||
|
||||
it('работает с нулевыми данными ledger', () => {
|
||||
const input = { ...baseInput, reportType: ReportType.BUHOTCH as const, ledgerData: [] };
|
||||
const result = gen.generate(input);
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.xml).toContain('СумОтч="0"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('6-НДФЛ (Ndfl6Generator)', () => {
|
||||
const gen = new Ndfl6Generator();
|
||||
|
||||
it('reportType = NDFL6', () => {
|
||||
expect(gen.reportType).toBe(ReportType.NDFL6);
|
||||
});
|
||||
|
||||
it('генерирует нулевой отчёт', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.NDFL6, period: 1 });
|
||||
expect(result.isValid).toBe(true);
|
||||
assertValidXml(result.xml);
|
||||
});
|
||||
|
||||
it('атрибуты Документ', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.NDFL6, period: 2 });
|
||||
expect(result.xml).toContain('КНД="1151100"');
|
||||
expect(result.xml).toContain('ВерсФорм="5.05"');
|
||||
expect(result.xml).toContain('Период="31"');
|
||||
expect(result.xml).toContain('КодНО="7728"');
|
||||
expect(result.xml).toContain('ПоМесту="214"');
|
||||
});
|
||||
|
||||
it('ОКТМО в СвНП', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.NDFL6, period: 1 });
|
||||
expect(result.xml).toContain('ОКТМО="45382000"');
|
||||
});
|
||||
|
||||
it('содержит РасчСумНал с нулями', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.NDFL6, period: 1 });
|
||||
expect(result.xml).toContain('<НДФЛ6.2');
|
||||
expect(result.xml).toContain('<РасчСумНал');
|
||||
expect(result.xml).toContain('Ставка="13"');
|
||||
expect(result.xml).toContain('КолФЛ="0"');
|
||||
expect(result.xml).toContain('НалБаза="0"');
|
||||
});
|
||||
|
||||
it('подписант', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.NDFL6, period: 1 });
|
||||
assertHasSigner(result.xml);
|
||||
});
|
||||
|
||||
it('корректные коды периодов', () => {
|
||||
const r1 = gen.generate({ ...baseInput, reportType: ReportType.NDFL6, period: 1 });
|
||||
expect(r1.xml).toContain('Период="21"');
|
||||
const r3 = gen.generate({ ...baseInput, reportType: ReportType.NDFL6, period: 3 });
|
||||
expect(r3.xml).toContain('Период="33"');
|
||||
const r4 = gen.generate({ ...baseInput, reportType: ReportType.NDFL6, period: 4 });
|
||||
expect(r4.xml).toContain('Период="34"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('РСВ (RsvGenerator)', () => {
|
||||
const gen = new RsvGenerator();
|
||||
|
||||
it('reportType = RSV', () => {
|
||||
expect(gen.reportType).toBe(ReportType.RSV);
|
||||
});
|
||||
|
||||
it('генерирует нулевой отчёт', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.RSV, period: 1 });
|
||||
expect(result.isValid).toBe(true);
|
||||
assertValidXml(result.xml);
|
||||
});
|
||||
|
||||
it('атрибуты Документ', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.RSV, period: 1 });
|
||||
expect(result.xml).toContain('КНД="1151111"');
|
||||
expect(result.xml).toContain('ВерсФорм="5.08"');
|
||||
expect(result.xml).toContain('ПоМесту="214"');
|
||||
});
|
||||
|
||||
it('содержит РасчетСВ с ОбязПлатСВ', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.RSV, period: 1 });
|
||||
expect(result.xml).toContain('<РасчетСВ');
|
||||
expect(result.xml).toContain('<ОбязПлатСВ');
|
||||
expect(result.xml).toContain('ТипПлат="1"');
|
||||
expect(result.xml).toContain('ОКТМО="45382000"');
|
||||
});
|
||||
|
||||
it('СвНП с СрЧисл', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.RSV, period: 1 });
|
||||
expect(result.xml).toContain('СрЧисл="1"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ПСВ (PsvGenerator)', () => {
|
||||
const gen = new PsvGenerator();
|
||||
|
||||
it('reportType = PSV', () => {
|
||||
expect(gen.reportType).toBe(ReportType.PSV);
|
||||
});
|
||||
|
||||
it('генерирует нулевой отчёт с ПерсСвФЛ', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.PSV, period: 1 });
|
||||
expect(result.isValid).toBe(true);
|
||||
assertValidXml(result.xml);
|
||||
});
|
||||
|
||||
it('атрибуты Документ', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.PSV, period: 3 });
|
||||
expect(result.xml).toContain('КНД="1151162"');
|
||||
expect(result.xml).toContain('ВерсФорм="5.01"');
|
||||
expect(result.xml).toContain('Период="03"');
|
||||
});
|
||||
|
||||
it('содержит ПерсСвФЛ с СНИЛС', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.PSV, period: 1 });
|
||||
expect(result.xml).toContain('<ПерсСвФЛ');
|
||||
expect(result.xml).toContain('СНИЛС="123-456-789 00"');
|
||||
expect(result.xml).toContain('СумВыпл="0"');
|
||||
});
|
||||
|
||||
it('имя файла NO_PERSSVFL', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.PSV, period: 1 });
|
||||
expect(result.fileName).toMatch(/^NO_PERSSVFL_/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ДУСН (DusnGenerator)', () => {
|
||||
const gen = new DusnGenerator();
|
||||
|
||||
it('reportType = DUSN', () => {
|
||||
expect(gen.reportType).toBe(ReportType.DUSN);
|
||||
});
|
||||
|
||||
it('генерирует нулевую декларацию', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.DUSN });
|
||||
expect(result.isValid).toBe(true);
|
||||
assertValidXml(result.xml);
|
||||
});
|
||||
|
||||
it('атрибуты Документ', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.DUSN });
|
||||
expect(result.xml).toContain('КНД="1152017"');
|
||||
expect(result.xml).toContain('ВерсФорм="5.09"');
|
||||
expect(result.xml).toContain('Период="34"');
|
||||
expect(result.xml).toContain('ПоМесту="120"');
|
||||
});
|
||||
|
||||
it('содержит УСН с ОбНал=1', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.DUSN });
|
||||
expect(result.xml).toContain('<УСН');
|
||||
expect(result.xml).toContain('ОбНал="1"');
|
||||
expect(result.xml).toContain('<СумНалПУ_НП');
|
||||
expect(result.xml).toContain('ОКТМО="45382000"');
|
||||
});
|
||||
|
||||
it('РасчНал1 с нулевыми суммами', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.DUSN });
|
||||
expect(result.xml).toContain('<РасчНал1');
|
||||
expect(result.xml).toContain('ПризНП="2"');
|
||||
expect(result.xml).toContain('СтавкаНалПер="6.0"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('4-ФСС (Fss4Generator)', () => {
|
||||
const gen = new Fss4Generator();
|
||||
|
||||
it('reportType = FSS4', () => {
|
||||
expect(gen.reportType).toBe(ReportType.FSS4);
|
||||
});
|
||||
|
||||
it('генерирует XML', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.FSS4, period: 1 });
|
||||
expect(result.isValid).toBe(true);
|
||||
assertValidXml(result.xml);
|
||||
});
|
||||
|
||||
it('имя файла EFS1', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.FSS4, period: 1 });
|
||||
expect(result.fileName).toMatch(/^EFS1_/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Уведомление о взносах (UvVznosyGenerator)', () => {
|
||||
const gen = new UvVznosyGenerator();
|
||||
|
||||
it('reportType = UV_VZNOSY', () => {
|
||||
expect(gen.reportType).toBe(ReportType.UV_VZNOSY);
|
||||
});
|
||||
|
||||
it('генерирует нулевое уведомление', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.UV_VZNOSY, period: 3 });
|
||||
expect(result.isValid).toBe(true);
|
||||
assertValidXml(result.xml);
|
||||
});
|
||||
|
||||
it('атрибуты Документ', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.UV_VZNOSY, period: 3 });
|
||||
expect(result.xml).toContain('КНД="1110355"');
|
||||
expect(result.xml).toContain('ВерсФорм="5.03"');
|
||||
});
|
||||
|
||||
it('содержит УвИсчСумНалог', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.UV_VZNOSY, period: 5 });
|
||||
expect(result.xml).toContain('<УвИсчСумНалог');
|
||||
expect(result.xml).toContain('ОКТМО="45382000"');
|
||||
expect(result.xml).toContain('СумНалогАванс="0"');
|
||||
expect(result.xml).toContain('Год="2025"');
|
||||
});
|
||||
|
||||
it('СвНП с НПЮЛ (без НаимОрг)', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.UV_VZNOSY, period: 1 });
|
||||
expect(result.xml).toContain('<НПЮЛ');
|
||||
expect(result.xml).toContain('ИННЮЛ="9728130611"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Уведомление по УСН (UusnGenerator)', () => {
|
||||
const gen = new UusnGenerator();
|
||||
|
||||
it('reportType = UUSN', () => {
|
||||
expect(gen.reportType).toBe(ReportType.UUSN);
|
||||
});
|
||||
|
||||
it('генерирует нулевое уведомление', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.UUSN, period: 2 });
|
||||
expect(result.isValid).toBe(true);
|
||||
assertValidXml(result.xml);
|
||||
});
|
||||
|
||||
it('КБК для УСН', () => {
|
||||
const result = gen.generate({ ...baseInput, reportType: ReportType.UUSN, period: 1 });
|
||||
expect(result.xml).toContain('КБК="18210501021011000110"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReportRegistryService', () => {
|
||||
const { ReportRegistryService } = require('../../../src/extensions/reports/domain/services/report-registry.service');
|
||||
const registry = new ReportRegistryService();
|
||||
|
||||
beforeAll(() => {
|
||||
registry.register(new BuhotchGenerator());
|
||||
registry.register(new Ndfl6Generator());
|
||||
registry.register(new RsvGenerator());
|
||||
registry.register(new PsvGenerator());
|
||||
registry.register(new DusnGenerator());
|
||||
registry.register(new Fss4Generator());
|
||||
registry.register(new UvVznosyGenerator());
|
||||
registry.register(new UusnGenerator());
|
||||
});
|
||||
|
||||
it('регистрирует все 8 генераторов', () => {
|
||||
const reports = registry.getAvailableReports();
|
||||
expect(reports).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('генерирует отчёт по типу', () => {
|
||||
const result = registry.generate({ ...baseInput, reportType: ReportType.BUHOTCH });
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.xml).toContain('<Баланс');
|
||||
});
|
||||
|
||||
it('выбрасывает исключение для незарегистрированного типа', () => {
|
||||
expect(() => registry.generate({ ...baseInput, reportType: 'unknown' as any })).toThrow();
|
||||
});
|
||||
|
||||
it('isGenerationAvailable для ежегодного отчёта', () => {
|
||||
expect(registry.isGenerationAvailable(ReportType.BUHOTCH, 2024)).toBe(true);
|
||||
expect(registry.isGenerationAvailable(ReportType.BUHOTCH, 2030)).toBe(false);
|
||||
});
|
||||
|
||||
it('isGenerationAvailable для ежеквартального', () => {
|
||||
expect(registry.isGenerationAvailable(ReportType.NDFL6, 2020, 1)).toBe(true);
|
||||
});
|
||||
});
|
||||
+9
-27
@@ -64,11 +64,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { ContributorGamificationWidget } from 'app/extensions/capital/widgets/ContributorGamificationWidget';
|
||||
import { useContributorStore } from 'app/extensions/capital/entities/Contributor/model';
|
||||
import { useDataPoller } from 'src/shared/lib/composables';
|
||||
import { POLL_INTERVALS } from 'src/shared/lib/consts';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
import { useSessionStore } from 'src/entities/Session/model/store';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { EditAboutInput, EditHoursPerDayInput, EditRatePerHourInput } from 'app/extensions/capital/features/Contributor/EditContributor';
|
||||
@@ -178,42 +177,25 @@ const roleContributions = computed(() => {
|
||||
];
|
||||
});
|
||||
|
||||
/**
|
||||
* Функция для перезагрузки данных профиля
|
||||
* Используется для poll обновлений
|
||||
*/
|
||||
const reloadProfileData = async () => {
|
||||
try {
|
||||
// Для профиля перезагружаем данные участника
|
||||
await contributorStore.loadContributor({ username });
|
||||
} catch (error) {
|
||||
console.warn('Ошибка при перезагрузке данных профиля в poll:', error);
|
||||
console.warn('Ошибка при перезагрузке данных профиля:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Обработчик обновления любого поля профиля
|
||||
const handleFieldUpdated = () => {
|
||||
// Поле профиля обновлено, данные перезагрузятся автоматически через poll
|
||||
reloadProfileData();
|
||||
};
|
||||
|
||||
// Настраиваем poll обновление данных
|
||||
const { start: startProfilePoll, stop: stopProfilePoll } = useDataPoller(
|
||||
reloadProfileData,
|
||||
{ interval: POLL_INTERVALS.SLOW, immediate: false }
|
||||
);
|
||||
|
||||
// Проверяем при монтировании
|
||||
onMounted(async () => {
|
||||
// Загружаем данные текущего участника аналогично CapitalBase
|
||||
await contributorStore.loadSelf({ username });
|
||||
|
||||
// Запускаем poll обновление данных
|
||||
startProfilePoll();
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('capitalDataChanged', null, ['entity', 'action']),
|
||||
onData: () => { reloadProfileData(); },
|
||||
});
|
||||
|
||||
// Останавливаем poll при уходе со страницы
|
||||
onBeforeUnmount(() => {
|
||||
stopProfilePoll();
|
||||
onMounted(async () => {
|
||||
await contributorStore.loadSelf({ username });
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
+9
-28
@@ -239,7 +239,7 @@ div(v-if="shouldShowTemporaryStub").q-pt-md
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount, onUnmounted, watch } from 'vue';
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useGenerateCapitalRegistrationDocuments } from 'app/extensions/capital/features/Contributor/GenerateCapitalRegistrationDocuments/model';
|
||||
import { useCompleteCapitalRegistration } from 'app/extensions/capital/features/Contributor/CompleteCapitalRegistration/model';
|
||||
@@ -248,8 +248,7 @@ import { DocumentHtmlReader } from 'src/shared/ui/DocumentHtmlReader';
|
||||
import { RoleSelector } from 'app/extensions/capital/shared/ui';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useDataPoller } from 'src/shared/lib/composables';
|
||||
import { POLL_INTERVALS } from 'src/shared/lib/consts';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -438,54 +437,36 @@ const updateCurrentStep = () => {
|
||||
// Следим за изменениями статуса регистрации
|
||||
watch(() => contributorStore.isContributorActiveOrPending, updateCurrentStep);
|
||||
|
||||
/**
|
||||
* Функция для перезагрузки данных регистрации
|
||||
* Используется для poll обновлений
|
||||
*/
|
||||
const reloadRegistrationData = async () => {
|
||||
try {
|
||||
// Для страницы регистрации обновляем статус участника
|
||||
await contributorStore.loadContributor({ username: session.username });
|
||||
} catch (error) {
|
||||
console.warn('Ошибка при перезагрузке данных регистрации в poll:', error);
|
||||
console.warn('Ошибка при перезагрузке данных регистрации:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Настраиваем poll обновление данных
|
||||
const { start: startRegistrationPoll, stop: stopRegistrationPoll } = useDataPoller(
|
||||
reloadRegistrationData,
|
||||
{ interval: POLL_INTERVALS.SLOW, immediate: false }
|
||||
);
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('capitalDataChanged', null, ['entity', 'action']),
|
||||
onData: () => { reloadRegistrationData(); },
|
||||
});
|
||||
|
||||
// Инициализация при монтировании
|
||||
onMounted(() => {
|
||||
console.log('🎯 CapitalRegistrationPage mounted');
|
||||
updateCurrentStep();
|
||||
|
||||
// Запускаем poll обновление данных
|
||||
startRegistrationPoll();
|
||||
|
||||
// Генерация пачки документов Capital при монтировании (только если не показывается временный заглушка)
|
||||
if (!shouldShowTemporaryStub.value) {
|
||||
generateCapitalDocuments()
|
||||
.then((documents) => {
|
||||
console.log('✅ Пачка документов сгенерирована:', {
|
||||
console.log('Пачка документов сгенерирована:', {
|
||||
generation_contract: documents?.generation_contract?.hash,
|
||||
storage_agreement: documents?.storage_agreement?.hash,
|
||||
blagorost_agreement: documents?.blagorost_agreement?.hash,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ Ошибка при генерации пачки документов:', error);
|
||||
console.error('Ошибка при генерации пачки документов:', error);
|
||||
FailAlert('Не удалось сгенерировать документы регистрации');
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Останавливаем poll при уходе со страницы
|
||||
onBeforeUnmount(() => {
|
||||
stopRegistrationPoll();
|
||||
});
|
||||
|
||||
// Отслеживание размонтирования
|
||||
|
||||
+20
-43
@@ -54,10 +54,10 @@ div
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount, ref, computed } from 'vue';
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useExpandableState, useDataPoller } from 'src/shared/lib/composables';
|
||||
import { POLL_INTERVALS } from 'src/shared/lib/consts';
|
||||
import { useExpandableState } from 'src/shared/lib/composables';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
import 'src/shared/ui/TitleStyles';
|
||||
import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { ColorCard } from 'src/shared/ui/ColorCard/ui';
|
||||
@@ -134,50 +134,27 @@ const handleVotesChanged = (data: { projectHash: string; voter: string }) => {
|
||||
segmentsToReload.value[data.voter] = Date.now();
|
||||
};
|
||||
|
||||
/**
|
||||
* Функция для перезагрузки данных голосования
|
||||
* Используется для poll обновлений
|
||||
*/
|
||||
const reloadVotingData = async () => {
|
||||
try {
|
||||
// Для голосований используем принудительную перезагрузку через timestamp
|
||||
// Это заставит виджеты перезагрузить данные
|
||||
const timestamp = Date.now();
|
||||
|
||||
// Обновляем все сегменты для перезагрузки
|
||||
Object.keys(segmentsToReload.value).forEach(key => {
|
||||
segmentsToReload.value[key] = timestamp;
|
||||
});
|
||||
|
||||
// Если нет сегментов, добавляем специальный ключ для принудительной перезагрузки
|
||||
if (Object.keys(segmentsToReload.value).length === 0) {
|
||||
segmentsToReload.value['__force_reload__'] = timestamp;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Ошибка при перезагрузке данных голосования в poll:', error);
|
||||
const reloadVotingData = () => {
|
||||
const timestamp = Date.now();
|
||||
Object.keys(segmentsToReload.value).forEach(key => {
|
||||
segmentsToReload.value[key] = timestamp;
|
||||
});
|
||||
if (Object.keys(segmentsToReload.value).length === 0) {
|
||||
segmentsToReload.value['__force_reload__'] = timestamp;
|
||||
}
|
||||
};
|
||||
|
||||
// Настраиваем poll обновление данных
|
||||
const { start: startVotingPoll, stop: stopVotingPoll } = useDataPoller(
|
||||
reloadVotingData,
|
||||
{ interval: POLL_INTERVALS.FAST, immediate: false }
|
||||
);
|
||||
|
||||
// Инициализация
|
||||
onMounted(async () => {
|
||||
// Загружаем проект явно
|
||||
await loadProject();
|
||||
|
||||
// Загружаем сохраненное состояние expanded из LocalStorage
|
||||
loadSegmentsExpandedState();
|
||||
|
||||
// Запускаем poll обновление данных
|
||||
startVotingPoll();
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('capitalDataChanged', null, ['entity', 'action']),
|
||||
onData: (data: any) => {
|
||||
if (data?.capitalDataChanged?.entity === 'voting') {
|
||||
reloadVotingData();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Останавливаем poll при уходе со страницы
|
||||
onBeforeUnmount(() => {
|
||||
stopVotingPoll();
|
||||
onMounted(async () => {
|
||||
await loadProject();
|
||||
loadSegmentsExpandedState();
|
||||
});
|
||||
</script>
|
||||
|
||||
+6
-38
@@ -21,7 +21,7 @@ div
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount, markRaw, computed } from 'vue';
|
||||
import { ref, onMounted, markRaw, computed, onBeforeUnmount } from 'vue';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
import { FailAlert } from 'src/shared/api';
|
||||
@@ -29,8 +29,7 @@ import { ContributorsListWidget } from 'app/extensions/capital/widgets/Contribut
|
||||
import { ImportContributorsButton } from 'app/extensions/capital/widgets/ImportContributorsButton';
|
||||
import { ImportContributorButton } from 'app/extensions/capital/features/Contributor/ImportContributor';
|
||||
import { useContributorStore } from 'app/extensions/capital/entities/Contributor/model';
|
||||
import { useDataPoller } from 'src/shared/lib/composables';
|
||||
import { POLL_INTERVALS } from 'src/shared/lib/consts';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
import { useHeaderActions } from 'src/shared/hooks';
|
||||
|
||||
const contributorStore = useContributorStore();
|
||||
@@ -110,53 +109,22 @@ const onPageChange = async (page: number) => {
|
||||
await loadContributors();
|
||||
};
|
||||
|
||||
/**
|
||||
* Функция для перезагрузки данных участников
|
||||
* Используется для poll обновлений
|
||||
*/
|
||||
const reloadContributors = async () => {
|
||||
try {
|
||||
await contributorStore.loadContributors({
|
||||
filter: {
|
||||
coopname: info.coopname,
|
||||
},
|
||||
pagination: {
|
||||
page: pagination.value.page,
|
||||
limit: pagination.value.rowsPerPage,
|
||||
sortBy: pagination.value.sortBy,
|
||||
descending: pagination.value.descending,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Ошибка при перезагрузке участников в poll:', error);
|
||||
}
|
||||
};
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('capitalDataChanged', null, ['entity', 'action']),
|
||||
onData: () => { loadContributors(); },
|
||||
});
|
||||
|
||||
// Настраиваем poll обновление данных
|
||||
const { start: startContributorsPoll, stop: stopContributorsPoll } = useDataPoller(
|
||||
reloadContributors,
|
||||
{ interval: POLL_INTERVALS.MEDIUM, immediate: false }
|
||||
);
|
||||
|
||||
// Регистрируем кнопки меню в header
|
||||
const { registerAction: registerHeaderAction, clearActions } = useHeaderActions();
|
||||
|
||||
// Инициализация
|
||||
onMounted(async () => {
|
||||
await loadContributors();
|
||||
|
||||
// Запускаем poll обновление данных
|
||||
startContributorsPoll();
|
||||
|
||||
// Регистрируем кнопки меню в header
|
||||
menuButtons.value.forEach(button => {
|
||||
registerHeaderAction(button);
|
||||
});
|
||||
});
|
||||
|
||||
// Останавливаем poll и очищаем действия header при уходе со страницы
|
||||
onBeforeUnmount(() => {
|
||||
stopContributorsPoll();
|
||||
clearActions();
|
||||
});
|
||||
</script>
|
||||
|
||||
+15
-29
@@ -16,9 +16,9 @@ div
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useExpandableState, useDataPoller } from 'src/shared/lib/composables';
|
||||
import { POLL_INTERVALS } from 'src/shared/lib/consts';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useExpandableState } from 'src/shared/lib/composables';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { CommitsListWidget } from 'app/extensions/capital/widgets';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
@@ -75,24 +75,16 @@ const handleCommitsDataLoaded = (commitHashes: string[]) => {
|
||||
};
|
||||
|
||||
const handlePaginationChanged = (paginationData: { page: number; rowsPerPage: number; sortBy: string; descending: boolean }) => {
|
||||
// Сохраняем текущую пагинацию для poll обновлений
|
||||
currentPage.value = paginationData.page;
|
||||
currentRowsPerPage.value = paginationData.rowsPerPage;
|
||||
currentSortBy.value = paginationData.sortBy;
|
||||
currentDescending.value = paginationData.descending;
|
||||
};
|
||||
|
||||
/**
|
||||
* Функция для перезагрузки данных коммитов
|
||||
* Используется для poll обновлений
|
||||
*/
|
||||
const reloadCommitsData = async () => {
|
||||
try {
|
||||
// Используем store для загрузки данных с текущими параметрами пагинации
|
||||
await commitStore.loadCommits({
|
||||
filter: {
|
||||
coopname: info.coopname,
|
||||
},
|
||||
filter: { coopname: info.coopname },
|
||||
options: {
|
||||
page: currentPage.value,
|
||||
limit: currentRowsPerPage.value,
|
||||
@@ -101,28 +93,22 @@ const reloadCommitsData = async () => {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('Ошибка при перезагрузке данных коммитов в poll:', error);
|
||||
console.warn('Ошибка при перезагрузке коммитов:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Настраиваем poll обновление данных
|
||||
const { start: startCommitsPoll, stop: stopCommitsPoll } = useDataPoller(
|
||||
reloadCommitsData,
|
||||
{ interval: POLL_INTERVALS.MEDIUM, immediate: false }
|
||||
);
|
||||
|
||||
// Инициализация состояния при монтировании
|
||||
onMounted(async () => {
|
||||
// Загружаем сохраненное состояние expanded из LocalStorage
|
||||
loadCommitsExpandedState();
|
||||
|
||||
// Запускаем poll обновление данных
|
||||
startCommitsPoll();
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('capitalCommitCreated', null, ['id', 'commit_hash', 'status']),
|
||||
onData: () => { forceReload.value++; reloadCommitsData(); },
|
||||
});
|
||||
|
||||
// Останавливаем poll при уходе со страницы
|
||||
onBeforeUnmount(() => {
|
||||
stopCommitsPoll();
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('capitalCommitUpdated', null, ['id', 'commit_hash', 'status']),
|
||||
onData: () => { forceReload.value++; reloadCommitsData(); },
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
loadCommitsExpandedState();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
+7
-32
@@ -5,45 +5,20 @@ div
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
import { useProjectLoader } from 'app/extensions/capital/entities/Project/model';
|
||||
import ProjectContributorsList from 'app/extensions/capital/widgets/ProjectInfoSelectorWidget/ProjectContributorsList.vue';
|
||||
import { useDataPoller } from 'src/shared/lib/composables';
|
||||
import { POLL_INTERVALS } from 'src/shared/lib/consts';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
|
||||
// Используем composable для загрузки проекта
|
||||
const { project, loadProject } = useProjectLoader();
|
||||
|
||||
/**
|
||||
* Функция для перезагрузки данных проекта
|
||||
* Используется для poll обновлений
|
||||
*/
|
||||
const reloadProjectData = async () => {
|
||||
try {
|
||||
// Перезагружаем данные текущего проекта
|
||||
await loadProject();
|
||||
} catch (error) {
|
||||
console.warn('Ошибка при перезагрузке данных проекта в poll:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Настраиваем poll обновление данных
|
||||
const { start: startProjectPoll, stop: stopProjectPoll } = useDataPoller(
|
||||
reloadProjectData,
|
||||
{ interval: POLL_INTERVALS.MEDIUM, immediate: false }
|
||||
);
|
||||
|
||||
// Инициализация
|
||||
onMounted(async () => {
|
||||
await loadProject();
|
||||
|
||||
// Запускаем poll обновление данных
|
||||
startProjectPoll();
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('capitalDataChanged', null, ['entity', 'action']),
|
||||
onData: () => { loadProject(); },
|
||||
});
|
||||
|
||||
// Останавливаем poll при уходе со страницы
|
||||
onBeforeUnmount(() => {
|
||||
stopProjectPoll();
|
||||
onMounted(async () => {
|
||||
await loadProject();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
+18
-39
@@ -38,10 +38,10 @@ div
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useSystemStore } from 'src/entities/System/model';
|
||||
import { useExpandableState, useDataPoller } from 'src/shared/lib/composables';
|
||||
import { POLL_INTERVALS } from 'src/shared/lib/consts';
|
||||
import { useExpandableState } from 'src/shared/lib/composables';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
import 'src/shared/ui/TitleStyles';
|
||||
import { WindowLoader } from 'src/shared/ui/Loader';
|
||||
import { ListVotingProjectWidget, ProjectVotingSegmentsWidget, SegmentVotesWidget } from 'app/extensions/capital/widgets';
|
||||
@@ -123,48 +123,27 @@ const handleVotesChanged = (data: { projectHash: string; voter: string }) => {
|
||||
segmentsToReload.value[data.voter] = Date.now();
|
||||
};
|
||||
|
||||
/**
|
||||
* Функция для перезагрузки данных голосования
|
||||
* Используется для poll обновлений
|
||||
*/
|
||||
const reloadVotingData = async () => {
|
||||
try {
|
||||
// Для голосований используем принудительную перезагрузку через timestamp
|
||||
// Это заставит виджеты перезагрузить данные
|
||||
const timestamp = Date.now();
|
||||
|
||||
// Обновляем все сегменты для перезагрузки
|
||||
Object.keys(segmentsToReload.value).forEach(key => {
|
||||
segmentsToReload.value[key] = timestamp;
|
||||
});
|
||||
|
||||
// Если нет сегментов, добавляем специальный ключ для принудительной перезагрузки
|
||||
if (Object.keys(segmentsToReload.value).length === 0) {
|
||||
segmentsToReload.value['__force_reload__'] = timestamp;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Ошибка при перезагрузке данных голосования в poll:', error);
|
||||
const reloadVotingData = () => {
|
||||
const timestamp = Date.now();
|
||||
Object.keys(segmentsToReload.value).forEach(key => {
|
||||
segmentsToReload.value[key] = timestamp;
|
||||
});
|
||||
if (Object.keys(segmentsToReload.value).length === 0) {
|
||||
segmentsToReload.value['__force_reload__'] = timestamp;
|
||||
}
|
||||
};
|
||||
|
||||
// Настраиваем poll обновление данных
|
||||
const { start: startVotingPoll, stop: stopVotingPoll } = useDataPoller(
|
||||
reloadVotingData,
|
||||
{ interval: POLL_INTERVALS.FAST, immediate: false }
|
||||
);
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('capitalDataChanged', null, ['entity', 'action']),
|
||||
onData: (data: any) => {
|
||||
if (data?.capitalDataChanged?.entity === 'voting') {
|
||||
reloadVotingData();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Инициализация
|
||||
onMounted(async () => {
|
||||
// Загружаем сохраненное состояние expanded из LocalStorage
|
||||
loadProjectsExpandedState();
|
||||
loadSegmentsExpandedState();
|
||||
|
||||
// Запускаем poll обновление данных
|
||||
startVotingPoll();
|
||||
});
|
||||
|
||||
// Останавливаем poll при уходе со страницы
|
||||
onBeforeUnmount(() => {
|
||||
stopVotingPoll();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ApprovalsPage } from 'app/extensions/chairman/pages/ApprovalsPage';
|
||||
import { SystemSettingsPage } from 'app/extensions/chairman/pages/SystemSettingsPage';
|
||||
import { PaymentProviderPage } from 'app/extensions/chairman/pages/PaymentProviderPage';
|
||||
import { ConnectPage } from 'app/extensions/chairman/pages/ConnectPage';
|
||||
import { ApiKeysPage } from 'app/extensions/chairman/pages/ApiKeysPage';
|
||||
import { AgendaPresetsPage } from 'app/extensions/chairman/pages/AgendaPresetsPage';
|
||||
|
||||
import { agreementsBase } from 'src/shared/lib/consts/workspaces';
|
||||
@@ -234,6 +235,19 @@ export default async function (): Promise<IWorkspaceConfig[]> {
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
path: 'settings/api-keys',
|
||||
name: 'api-keys',
|
||||
component: markRaw(ApiKeysPage),
|
||||
meta: {
|
||||
title: 'API ключи',
|
||||
icon: 'fa-solid fa-key',
|
||||
roles: ['chairman'],
|
||||
agreements: agreementsBase,
|
||||
requiresAuth: true,
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { ApiKeysPage } from './ui'
|
||||
@@ -0,0 +1,162 @@
|
||||
<template lang="pug">
|
||||
div.page-shell
|
||||
q-card.hero-card(flat)
|
||||
.hero-title API Ключи
|
||||
.hero-subtitle Управление ключами доступа к API кооператива
|
||||
|
||||
q-card.q-mt-md(flat)
|
||||
q-card-section
|
||||
.row.items-center
|
||||
.text-h6.col Ключи доступа
|
||||
q-btn(color='primary' icon='add' label='Создать ключ' no-caps @click='showCreate = true')
|
||||
|
||||
q-separator
|
||||
|
||||
q-card-section
|
||||
q-table(
|
||||
:rows='keys'
|
||||
:columns='columns'
|
||||
row-key='id'
|
||||
flat
|
||||
:loading='loading'
|
||||
)
|
||||
template(#body-cell-status='props')
|
||||
q-td(:props='props')
|
||||
q-chip(
|
||||
:color='props.row.is_active ? "positive" : "negative"'
|
||||
text-color='white'
|
||||
dense
|
||||
) {{ props.row.is_active ? 'Активен' : 'Отозван' }}
|
||||
|
||||
template(#body-cell-actions='props')
|
||||
q-td(:props='props')
|
||||
q-btn(
|
||||
v-if='props.row.is_active'
|
||||
flat
|
||||
dense
|
||||
icon='block'
|
||||
color='negative'
|
||||
@click='revoke(props.row.id)'
|
||||
)
|
||||
q-tooltip Отозвать ключ
|
||||
|
||||
.text-center.text-grey-5.q-pa-lg(v-if='keys.length === 0 && !loading')
|
||||
q-icon(name='vpn_key' size='48px')
|
||||
.q-mt-sm Нет API ключей
|
||||
|
||||
//- Диалог создания
|
||||
q-dialog(v-model='showCreate')
|
||||
q-card(style='min-width: 450px')
|
||||
q-card-section
|
||||
.text-h6 Новый API ключ
|
||||
q-card-section
|
||||
q-input(v-model='newName' label='Название ключа' autofocus dense)
|
||||
q-input.q-mt-sm(
|
||||
v-model.number='newExpiresInDays'
|
||||
label='Срок действия (дней, пусто = бессрочно)'
|
||||
type='number'
|
||||
dense
|
||||
)
|
||||
q-card-actions(align='right')
|
||||
q-btn(flat label='Отмена' @click='showCreate = false')
|
||||
q-btn(flat label='Создать' color='primary' @click='create' :loading='creating')
|
||||
|
||||
//- Диалог с созданным ключом
|
||||
q-dialog(v-model='showCreatedKey' persistent)
|
||||
q-card(style='min-width: 500px')
|
||||
q-card-section
|
||||
.text-h6 🔑 Ключ создан
|
||||
.text-body2.text-negative.q-mt-sm Скопируйте ключ сейчас — он больше не будет показан!
|
||||
q-card-section
|
||||
q-input(
|
||||
:modelValue='createdKey'
|
||||
readonly
|
||||
outlined
|
||||
dense
|
||||
type='textarea'
|
||||
rows='2'
|
||||
)
|
||||
template(#append)
|
||||
q-btn(flat dense icon='content_copy' @click='copyKey')
|
||||
q-card-actions(align='right')
|
||||
q-btn(flat label='Закрыть' color='primary' @click='showCreatedKey = false')
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { client } from 'src/shared/api/client'
|
||||
import { copyToClipboard, Notify } from 'quasar'
|
||||
|
||||
const keys = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const showCreate = ref(false)
|
||||
const creating = ref(false)
|
||||
const newName = ref('')
|
||||
const newExpiresInDays = ref<number | undefined>(undefined)
|
||||
const showCreatedKey = ref(false)
|
||||
const createdKey = ref('')
|
||||
|
||||
const columns = [
|
||||
{ name: 'name', label: 'Название', field: 'name', align: 'left' as const },
|
||||
{ name: 'key_prefix', label: 'Префикс', field: 'key_prefix', align: 'left' as const },
|
||||
{ name: 'created_by', label: 'Создал', field: 'created_by', align: 'left' as const },
|
||||
{ name: 'status', label: 'Статус', field: 'is_active', align: 'center' as const },
|
||||
{ name: 'expires_at', label: 'Истекает', field: (r: any) => r.expires_at ? new Date(r.expires_at).toLocaleDateString('ru') : '∞', align: 'center' as const },
|
||||
{ name: 'last_used_at', label: 'Последнее использование', field: (r: any) => r.last_used_at ? new Date(r.last_used_at).toLocaleString('ru') : '—', align: 'center' as const },
|
||||
{ name: 'actions', label: '', field: 'id', align: 'right' as const },
|
||||
]
|
||||
|
||||
onMounted(() => loadKeys())
|
||||
|
||||
async function loadKeys() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { getApiKeys } = await client.Query({
|
||||
getApiKeys: {
|
||||
id: true, name: true, key_prefix: true, created_by: true,
|
||||
allowed_operations: true, is_active: true,
|
||||
expires_at: true, last_used_at: true, created_at: true,
|
||||
},
|
||||
})
|
||||
keys.value = getApiKeys || []
|
||||
} catch {}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function create() {
|
||||
if (!newName.value) return
|
||||
creating.value = true
|
||||
try {
|
||||
const { createApiKey } = await client.Mutation({
|
||||
createApiKey: [{
|
||||
data: {
|
||||
name: newName.value,
|
||||
expiresInDays: newExpiresInDays.value || undefined,
|
||||
},
|
||||
}, { id: true, key: true, name: true }],
|
||||
})
|
||||
createdKey.value = createApiKey.key
|
||||
showCreate.value = false
|
||||
showCreatedKey.value = true
|
||||
newName.value = ''
|
||||
newExpiresInDays.value = undefined
|
||||
await loadKeys()
|
||||
} catch (e: any) {
|
||||
Notify.create({ type: 'negative', message: e?.message || 'Ошибка' })
|
||||
}
|
||||
creating.value = false
|
||||
}
|
||||
|
||||
async function revoke(id: string) {
|
||||
try {
|
||||
await client.Mutation({ revokeApiKey: [{ id }, true] })
|
||||
Notify.create({ type: 'info', message: 'Ключ отозван' })
|
||||
await loadKeys()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function copyKey() {
|
||||
copyToClipboard(createdKey.value)
|
||||
Notify.create({ type: 'positive', message: 'Ключ скопирован' })
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ApiKeysPage } from './ApiKeysPage.vue'
|
||||
@@ -8,6 +8,7 @@ import { MeetDetailsPage } from 'src/pages/Cooperative/MeetDetails';
|
||||
import { UserDocumentsPage } from 'src/pages/User/DocumentsPage';
|
||||
import { UserPaymentsPage } from 'src/pages/User/PaymentsPage';
|
||||
import { SupportTrigger } from 'src/pages/Support';
|
||||
import { SharedWithMePage } from './pages/SharedWithMePage';
|
||||
import { agreementsBase } from 'src/shared/lib/consts/workspaces';
|
||||
import type { IWorkspaceConfig } from 'src/shared/lib/types/workspace';
|
||||
import { markRaw } from 'vue';
|
||||
@@ -138,6 +139,19 @@ export default async function (): Promise<IWorkspaceConfig[]> {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: 'Доступные мне',
|
||||
icon: 'fa-solid fa-share-nodes',
|
||||
roles: ['user', 'member', 'chairman'],
|
||||
agreements: agreementsBase,
|
||||
requiresAuth: true,
|
||||
},
|
||||
path: 'shared-with-me',
|
||||
name: 'shared-with-me',
|
||||
component: markRaw(SharedWithMePage),
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
path: '/:coopname/contacts',
|
||||
name: 'contacts',
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { SharedWithMePage } from './ui'
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
<template lang="pug">
|
||||
div.page-shell
|
||||
q-card.hero-card(flat)
|
||||
.hero-title Доступные мне
|
||||
.hero-subtitle Страницы, к которым вам предоставлен доступ
|
||||
|
||||
q-card.q-mt-md(flat)
|
||||
q-card-section
|
||||
q-table(
|
||||
:rows='sharedLinks'
|
||||
:columns='columns'
|
||||
row-key='id'
|
||||
flat
|
||||
:loading='loading'
|
||||
)
|
||||
template(#body-cell-page='props')
|
||||
q-td(:props='props')
|
||||
.text-weight-medium {{ props.row.page_name }}
|
||||
.text-caption.text-grey {{ props.row.page_path }}
|
||||
|
||||
template(#body-cell-from='props')
|
||||
q-td(:props='props')
|
||||
q-chip(dense color='blue-2' text-color='blue-9')
|
||||
| {{ props.row.link_name || 'Без имени' }}
|
||||
|
||||
template(#body-cell-actions_list='props')
|
||||
q-td(:props='props')
|
||||
q-chip(
|
||||
v-for='action in props.row.allowed_actions'
|
||||
:key='action'
|
||||
dense
|
||||
color='grey-3'
|
||||
text-color='grey-8'
|
||||
).q-mr-xs {{ actionLabel(action) }}
|
||||
|
||||
template(#body-cell-expires='props')
|
||||
q-td(:props='props')
|
||||
span(v-if='props.row.expires_at') {{ formatDate(props.row.expires_at) }}
|
||||
q-chip(v-else dense color='green-2' text-color='green-9') Бессрочно
|
||||
|
||||
template(#body-cell-go='props')
|
||||
q-td(:props='props')
|
||||
q-btn(
|
||||
flat
|
||||
dense
|
||||
icon='fa-solid fa-arrow-right'
|
||||
color='primary'
|
||||
@click='navigateTo(props.row)'
|
||||
)
|
||||
q-tooltip Перейти
|
||||
|
||||
.text-center.text-grey-5.q-pa-lg(v-if='sharedLinks.length === 0 && !loading')
|
||||
q-icon(name='fa-solid fa-share-nodes' size='48px')
|
||||
.q-mt-sm Нет предоставленных страниц
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { client } from 'src/shared/api/client'
|
||||
import { Notify } from 'quasar'
|
||||
|
||||
interface SharedLink {
|
||||
id: string
|
||||
page_path: string
|
||||
page_name: string
|
||||
target_type: string
|
||||
link_name?: string
|
||||
allowed_actions: string[]
|
||||
token: string
|
||||
is_active: boolean
|
||||
expires_at?: string
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const sharedLinks = ref<SharedLink[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
const columns = [
|
||||
{ name: 'page', label: 'Страница', field: 'page_name', align: 'left' as const },
|
||||
{ name: 'from', label: 'Название', field: 'link_name', align: 'left' as const },
|
||||
{ name: 'actions_list', label: 'Разрешения', field: 'allowed_actions', align: 'left' as const },
|
||||
{ name: 'expires', label: 'Истекает', field: 'expires_at', align: 'center' as const },
|
||||
{ name: 'go', label: '', field: 'id', align: 'right' as const },
|
||||
]
|
||||
|
||||
function actionLabel(action: string) {
|
||||
const map: Record<string, string> = {
|
||||
read: 'Чтение',
|
||||
create: 'Создание',
|
||||
update: 'Изменение',
|
||||
delete: 'Удаление',
|
||||
execute: 'Выполнение',
|
||||
manage: 'Управление',
|
||||
}
|
||||
return map[action] || action
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string) {
|
||||
return new Date(dateStr).toLocaleDateString('ru-RU')
|
||||
}
|
||||
|
||||
function navigateTo(link: SharedLink) {
|
||||
const url = link.page_path.includes('?')
|
||||
? `${link.page_path}&share_token=${link.token}`
|
||||
: `${link.page_path}?share_token=${link.token}`
|
||||
router.push(url)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { getSharedWithMe } = await client.Query({
|
||||
getSharedWithMe: {
|
||||
id: true,
|
||||
page_path: true,
|
||||
page_name: true,
|
||||
target_type: true,
|
||||
link_name: true,
|
||||
allowed_actions: true,
|
||||
token: true,
|
||||
is_active: true,
|
||||
expires_at: true,
|
||||
},
|
||||
})
|
||||
sharedLinks.value = (getSharedWithMe || []).filter((l: any) => l.is_active)
|
||||
} catch (e: any) {
|
||||
Notify.create({ type: 'negative', message: e?.message || 'Ошибка загрузки' })
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as SharedWithMePage } from './SharedWithMePage.vue'
|
||||
@@ -0,0 +1 @@
|
||||
export { default as install } from './install'
|
||||
@@ -0,0 +1,40 @@
|
||||
import { markRaw } from 'vue';
|
||||
import { agreementsBase } from 'src/shared/lib/consts/workspaces';
|
||||
import type { IWorkspaceConfig } from 'src/shared/lib/types/workspace';
|
||||
import { ReportsPage } from './pages';
|
||||
|
||||
export default async function (): Promise<IWorkspaceConfig[]> {
|
||||
return [{
|
||||
workspace: 'reports',
|
||||
extension_name: 'reports',
|
||||
title: 'Отчёты ФНС',
|
||||
icon: 'fa-solid fa-file-invoice',
|
||||
defaultRoute: 'reports-main',
|
||||
routes: [
|
||||
{
|
||||
meta: {
|
||||
title: 'Отчёты ФНС',
|
||||
icon: 'fa-solid fa-file-invoice',
|
||||
roles: ['chairman'],
|
||||
},
|
||||
path: '/:coopname/reports',
|
||||
name: 'reports',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'reports-main',
|
||||
component: markRaw(ReportsPage),
|
||||
meta: {
|
||||
title: 'Расписание отчётов',
|
||||
icon: 'fa-solid fa-calendar-days',
|
||||
roles: ['chairman'],
|
||||
agreements: agreementsBase,
|
||||
requiresAuth: true,
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}];
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { ReportsPage } from './ui'
|
||||
@@ -0,0 +1,281 @@
|
||||
<template lang="pug">
|
||||
div.page-shell
|
||||
q-card.hero-card(flat)
|
||||
.hero-title Отчёты ФНС
|
||||
.hero-subtitle Генерация и управление налоговыми отчётами кооператива
|
||||
|
||||
//- Расписание отчётов
|
||||
q-card.q-mt-md(flat)
|
||||
q-card-section
|
||||
.row.items-center
|
||||
.text-h6.col Расписание отчётов
|
||||
|
||||
q-separator
|
||||
|
||||
q-card-section
|
||||
q-table(
|
||||
:rows='reports'
|
||||
:columns='columns'
|
||||
row-key='type'
|
||||
flat
|
||||
:loading='loading'
|
||||
)
|
||||
template(#body-cell-period='props')
|
||||
q-td(:props='props')
|
||||
q-chip(
|
||||
:color='periodColor(props.row.period)'
|
||||
text-color='white'
|
||||
dense
|
||||
) {{ periodLabel(props.row.period) }}
|
||||
|
||||
template(#body-cell-actions='props')
|
||||
q-td(:props='props')
|
||||
q-btn(
|
||||
flat
|
||||
dense
|
||||
icon='fa-solid fa-file-export'
|
||||
color='primary'
|
||||
@click='openGenerate(props.row)'
|
||||
:disable='generating'
|
||||
)
|
||||
q-tooltip Сгенерировать отчёт
|
||||
|
||||
.text-center.text-grey-5.q-pa-lg(v-if='reports.length === 0 && !loading')
|
||||
q-icon(name='fa-solid fa-file-lines' size='48px')
|
||||
.q-mt-sm Загрузка расписания...
|
||||
|
||||
//- Диалог генерации
|
||||
q-dialog(v-model='showGenerate')
|
||||
q-card(style='min-width: 550px')
|
||||
q-card-section
|
||||
.text-h6 Генерация: {{ selectedReport?.name }}
|
||||
|
||||
q-card-section
|
||||
.row.q-gutter-md
|
||||
.col-6
|
||||
q-input(v-model.number='genYear' label='Год' type='number' dense outlined)
|
||||
.col-6
|
||||
q-input(
|
||||
v-if='selectedReport?.period !== "yearly"'
|
||||
v-model.number='genPeriod'
|
||||
:label='selectedReport?.period === "monthly" ? "Месяц (1-12)" : "Квартал (1-4)"'
|
||||
type='number'
|
||||
dense
|
||||
outlined
|
||||
)
|
||||
|
||||
q-separator.q-my-md
|
||||
|
||||
.text-subtitle2.q-mb-sm Данные организации
|
||||
.row.q-gutter-sm
|
||||
.col-6
|
||||
q-input(v-model='orgData.inn' label='ИНН' dense outlined)
|
||||
.col-6
|
||||
q-input(v-model='orgData.kpp' label='КПП' dense outlined)
|
||||
.col-12
|
||||
q-input(v-model='orgData.orgName' label='Наименование' dense outlined)
|
||||
.col-6
|
||||
q-input(v-model='orgData.ogrn' label='ОГРН' dense outlined)
|
||||
.col-6
|
||||
q-input(v-model='orgData.okved' label='ОКВЭД' dense outlined)
|
||||
.col-6
|
||||
q-input(v-model='orgData.oktmo' label='ОКТМО' dense outlined)
|
||||
.col-6
|
||||
q-input(v-model='orgData.okfs' label='ОКФС' dense outlined)
|
||||
|
||||
q-separator.q-my-md
|
||||
|
||||
.text-subtitle2.q-mb-sm Подписант
|
||||
.row.q-gutter-sm
|
||||
.col-4
|
||||
q-input(v-model='orgData.signerLastName' label='Фамилия' dense outlined)
|
||||
.col-4
|
||||
q-input(v-model='orgData.signerFirstName' label='Имя' dense outlined)
|
||||
.col-4
|
||||
q-input(v-model='orgData.signerMiddleName' label='Отчество' dense outlined)
|
||||
|
||||
q-card-actions(align='right')
|
||||
q-btn(flat label='Отмена' @click='showGenerate = false')
|
||||
q-btn(flat label='Сгенерировать' color='primary' @click='generate' :loading='generating')
|
||||
|
||||
//- Диалог результата
|
||||
q-dialog(v-model='showResult')
|
||||
q-card(style='min-width: 600px; max-width: 90vw')
|
||||
q-card-section
|
||||
.row.items-center
|
||||
.text-h6.col Результат генерации
|
||||
q-chip(
|
||||
:color='result?.isValid ? "positive" : "negative"'
|
||||
text-color='white'
|
||||
) {{ result?.isValid ? 'Валиден' : 'Ошибки' }}
|
||||
|
||||
q-card-section(v-if='result?.errors?.length')
|
||||
q-banner.bg-negative.text-white(v-for='err in result.errors' :key='err')
|
||||
| {{ err }}
|
||||
|
||||
q-card-section
|
||||
.text-caption.text-grey Файл: {{ result?.fileName }}
|
||||
q-input.q-mt-sm(
|
||||
:modelValue='result?.xml'
|
||||
readonly
|
||||
outlined
|
||||
type='textarea'
|
||||
rows='12'
|
||||
)
|
||||
|
||||
q-card-actions(align='right')
|
||||
q-btn(flat label='Скачать XML' icon='download' color='primary' @click='downloadXml')
|
||||
q-btn(flat label='Закрыть' @click='showResult = false')
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import { client } from 'src/shared/api/client'
|
||||
import { Notify } from 'quasar'
|
||||
|
||||
interface AvailableReport {
|
||||
type: string
|
||||
name: string
|
||||
period: string
|
||||
deadline: string
|
||||
}
|
||||
|
||||
interface GeneratedReport {
|
||||
reportType: string
|
||||
xml: string
|
||||
fileName: string
|
||||
errors: string[]
|
||||
isValid: boolean
|
||||
}
|
||||
|
||||
const reports = ref<AvailableReport[]>([])
|
||||
const loading = ref(false)
|
||||
const generating = ref(false)
|
||||
const showGenerate = ref(false)
|
||||
const showResult = ref(false)
|
||||
const selectedReport = ref<AvailableReport | null>(null)
|
||||
const result = ref<GeneratedReport | null>(null)
|
||||
const genYear = ref(new Date().getFullYear() - 1)
|
||||
const genPeriod = ref(1)
|
||||
|
||||
const orgData = reactive({
|
||||
inn: '',
|
||||
kpp: '',
|
||||
orgName: '',
|
||||
ogrn: '',
|
||||
okved: '94.99',
|
||||
oktmo: '',
|
||||
okfs: '16',
|
||||
okopf: '20200',
|
||||
signerLastName: '',
|
||||
signerFirstName: '',
|
||||
signerMiddleName: '',
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ name: 'name', label: 'Отчёт', field: 'name', align: 'left' as const, sortable: true },
|
||||
{ name: 'period', label: 'Периодичность', field: 'period', align: 'center' as const },
|
||||
{ name: 'deadline', label: 'Срок сдачи', field: 'deadline', align: 'left' as const },
|
||||
{ name: 'actions', label: '', field: 'type', align: 'right' as const },
|
||||
]
|
||||
|
||||
function periodLabel(p: string) {
|
||||
if (p === 'yearly') return 'Ежегодно'
|
||||
if (p === 'quarterly') return 'Ежеквартально'
|
||||
if (p === 'monthly') return 'Ежемесячно'
|
||||
return p
|
||||
}
|
||||
|
||||
function periodColor(p: string) {
|
||||
if (p === 'yearly') return 'deep-purple'
|
||||
if (p === 'quarterly') return 'blue'
|
||||
if (p === 'monthly') return 'teal'
|
||||
return 'grey'
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadReports()
|
||||
})
|
||||
|
||||
async function loadReports() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { getAvailableReports } = await client.Query({
|
||||
getAvailableReports: {
|
||||
type: true,
|
||||
name: true,
|
||||
period: true,
|
||||
deadline: true,
|
||||
},
|
||||
})
|
||||
reports.value = getAvailableReports || []
|
||||
} catch (e: any) {
|
||||
Notify.create({ type: 'negative', message: 'Ошибка загрузки отчётов: ' + (e?.message || '') })
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function openGenerate(report: AvailableReport) {
|
||||
selectedReport.value = report
|
||||
showGenerate.value = true
|
||||
}
|
||||
|
||||
async function generate() {
|
||||
if (!selectedReport.value) return
|
||||
generating.value = true
|
||||
|
||||
try {
|
||||
const { generateReport } = await client.Mutation({
|
||||
generateReport: [{
|
||||
data: {
|
||||
reportType: selectedReport.value.type as any,
|
||||
year: genYear.value,
|
||||
period: genPeriod.value,
|
||||
},
|
||||
organization: {
|
||||
inn: orgData.inn,
|
||||
kpp: orgData.kpp,
|
||||
orgName: orgData.orgName,
|
||||
ogrn: orgData.ogrn,
|
||||
okved: orgData.okved,
|
||||
oktmo: orgData.oktmo,
|
||||
okfs: orgData.okfs,
|
||||
okopf: orgData.okopf,
|
||||
signerLastName: orgData.signerLastName,
|
||||
signerFirstName: orgData.signerFirstName,
|
||||
signerMiddleName: orgData.signerMiddleName,
|
||||
},
|
||||
}, {
|
||||
reportType: true,
|
||||
xml: true,
|
||||
fileName: true,
|
||||
errors: true,
|
||||
isValid: true,
|
||||
}],
|
||||
})
|
||||
|
||||
result.value = generateReport
|
||||
showGenerate.value = false
|
||||
showResult.value = true
|
||||
|
||||
if (generateReport.isValid) {
|
||||
Notify.create({ type: 'positive', message: 'Отчёт сгенерирован успешно' })
|
||||
}
|
||||
} catch (e: any) {
|
||||
Notify.create({ type: 'negative', message: 'Ошибка генерации: ' + (e?.message || '') })
|
||||
}
|
||||
|
||||
generating.value = false
|
||||
}
|
||||
|
||||
function downloadXml() {
|
||||
if (!result.value) return
|
||||
const blob = new Blob([result.value.xml], { type: 'application/xml;charset=windows-1251' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = result.value.fileName + '.xml'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ReportsPage } from './ReportsPage.vue'
|
||||
@@ -0,0 +1 @@
|
||||
export { ReportsPage } from './ReportsPage'
|
||||
@@ -170,17 +170,9 @@ export const useConnectionAgreementStore = defineStore(namespace, () => {
|
||||
}
|
||||
}
|
||||
|
||||
const startInstanceAutoRefresh = async (intervalMs = 30000) => { // 30 секунд по умолчанию
|
||||
await loadCurrentInstance() // Первая загрузка
|
||||
|
||||
const interval = setInterval(() => {
|
||||
loadCurrentInstance()
|
||||
}, intervalMs)
|
||||
|
||||
// Функция для остановки автообновления
|
||||
const stop = () => clearInterval(interval)
|
||||
|
||||
return stop
|
||||
const startInstanceAutoRefresh = async () => {
|
||||
await loadCurrentInstance()
|
||||
return () => {}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
|
||||
@@ -3,14 +3,11 @@ import { ref, Ref, triggerRef, computed, ComputedRef } from 'vue';
|
||||
import { api } from '../api';
|
||||
import type { ISystemInfo } from '../types';
|
||||
import { Zeus } from '@coopenomics/sdk';
|
||||
import { createClient } from 'graphql-ws';
|
||||
import { env } from 'src/shared/config';
|
||||
|
||||
const namespace = 'systemStore';
|
||||
|
||||
// Константы для экспоненциального backoff
|
||||
const BASE_INTERVAL_MS = 30000; // Базовый интервал 30 секунд (было 10)
|
||||
const MAX_INTERVAL_MS = 300000; // Максимальный интервал 5 минут
|
||||
const BACKOFF_MULTIPLIER = 2; // Множитель для backoff
|
||||
|
||||
interface ISystemStore {
|
||||
info: Ref<ISystemInfo>;
|
||||
backendAvailable: Ref<boolean>;
|
||||
@@ -27,104 +24,100 @@ interface ISystemStore {
|
||||
|
||||
export const useSystemStore = defineStore(namespace, (): ISystemStore => {
|
||||
const info = ref<ISystemInfo>({
|
||||
system_status: 'active', // Начальное значение
|
||||
system_status: 'active',
|
||||
} as ISystemInfo);
|
||||
const backendAvailable = ref<boolean>(true);
|
||||
const maintenanceCounter = ref<number>(0); // Счетчик для принудительного обновления
|
||||
const maintenanceCounter = ref<number>(0);
|
||||
|
||||
let monitoringTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let isLoading = false; // Защита от конкурентных запросов
|
||||
let currentInterval = BASE_INTERVAL_MS; // Текущий интервал (для backoff)
|
||||
let consecutiveErrors = 0; // Счетчик последовательных ошибок
|
||||
let isLoading = false;
|
||||
let wsUnsubscribe: (() => void) | null = null;
|
||||
|
||||
const loadSystemInfo = async () => {
|
||||
// Защита от конкурентных запросов
|
||||
if (isLoading) {
|
||||
console.debug('loadSystemInfo: запрос уже выполняется, пропускаем');
|
||||
return;
|
||||
}
|
||||
if (isLoading) return;
|
||||
|
||||
isLoading = true;
|
||||
try {
|
||||
info.value = await api.loadSystemInfo();
|
||||
backendAvailable.value = true;
|
||||
triggerRef(info); // Принудительно триггерим реактивность
|
||||
|
||||
// При успехе сбрасываем backoff
|
||||
consecutiveErrors = 0;
|
||||
currentInterval = BASE_INTERVAL_MS;
|
||||
triggerRef(info);
|
||||
} catch (error) {
|
||||
console.warn('Failed to load system info, backend might be unavailable:', error);
|
||||
console.warn('Failed to load system info:', error);
|
||||
backendAvailable.value = false;
|
||||
// При недоступности бэкенда устанавливаем статус обслуживания
|
||||
info.value.system_status = Zeus.SystemStatus.maintenance;
|
||||
|
||||
// Увеличиваем интервал при ошибках (экспоненциальный backoff)
|
||||
consecutiveErrors++;
|
||||
currentInterval = Math.min(
|
||||
BASE_INTERVAL_MS * Math.pow(BACKOFF_MULTIPLIER, consecutiveErrors),
|
||||
MAX_INTERVAL_MS
|
||||
);
|
||||
console.debug(`Backoff: следующая попытка через ${currentInterval / 1000} секунд`);
|
||||
|
||||
throw error; // Перебрасываем ошибку для обработки выше
|
||||
throw error;
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const scheduleNextCheck = () => {
|
||||
// КРИТИЧНО: Не запускаем мониторинг на сервере (SSR)
|
||||
// В SSR setInterval/setTimeout создают утечки памяти и накапливают запросы
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
stopSystemMonitoring();
|
||||
|
||||
monitoringTimeout = setTimeout(async () => {
|
||||
try {
|
||||
await loadSystemInfo();
|
||||
} catch (error) {
|
||||
console.warn('Failed to update system info during monitoring:', error);
|
||||
// При недоступности бэкенда устанавливаем статус обслуживания
|
||||
backendAvailable.value = false;
|
||||
info.value.system_status = Zeus.SystemStatus.maintenance;
|
||||
maintenanceCounter.value++; // Увеличиваем счетчик для триггера watch
|
||||
}
|
||||
|
||||
// Планируем следующую проверку с учетом возможного backoff
|
||||
scheduleNextCheck();
|
||||
}, currentInterval);
|
||||
};
|
||||
|
||||
const startSystemMonitoring = () => {
|
||||
// КРИТИЧНО: Не запускаем мониторинг на сервере (SSR)
|
||||
// Это предотвращает утечку таймеров и самоDDoS
|
||||
if (typeof window === 'undefined') {
|
||||
console.debug('startSystemMonitoring: пропускаем на сервере (SSR)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Останавливаем существующий мониторинг, если он есть
|
||||
if (typeof window === 'undefined') return;
|
||||
stopSystemMonitoring();
|
||||
|
||||
// Сбрасываем backoff при явном старте мониторинга
|
||||
consecutiveErrors = 0;
|
||||
currentInterval = BASE_INTERVAL_MS;
|
||||
try {
|
||||
const wsUrl = (env.BACKEND_URL + '/v1/graphql').replace(/^http/, 'ws');
|
||||
|
||||
// Используем setTimeout вместо setInterval для гибкого управления интервалом
|
||||
scheduleNextCheck();
|
||||
const wsClient = createClient({
|
||||
url: wsUrl,
|
||||
connectionParams: () => {
|
||||
try {
|
||||
const globalStore = (window as any).__pinia?.state?.value?.global;
|
||||
const token = globalStore?.tokens?.access?.token;
|
||||
return token ? { token } : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
retryAttempts: Infinity,
|
||||
shouldRetry: () => true,
|
||||
retryWait: async (retries) => {
|
||||
const delay = Math.min(1000 * Math.pow(2, retries), 60000);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
},
|
||||
on: {
|
||||
connected: () => {
|
||||
if (!backendAvailable.value) {
|
||||
backendAvailable.value = true;
|
||||
loadSystemInfo().catch(() => {});
|
||||
}
|
||||
},
|
||||
closed: () => {
|
||||
backendAvailable.value = false;
|
||||
info.value.system_status = Zeus.SystemStatus.maintenance;
|
||||
maintenanceCounter.value++;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
wsUnsubscribe = wsClient.subscribe(
|
||||
{ query: 'subscription { systemStatusChanged { status message } }' },
|
||||
{
|
||||
next(value) {
|
||||
if (value.data) {
|
||||
backendAvailable.value = true;
|
||||
loadSystemInfo().catch(() => {});
|
||||
}
|
||||
},
|
||||
error() {
|
||||
backendAvailable.value = false;
|
||||
info.value.system_status = Zeus.SystemStatus.maintenance;
|
||||
maintenanceCounter.value++;
|
||||
},
|
||||
complete() {},
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn('Failed to start system monitoring subscription:', e);
|
||||
}
|
||||
};
|
||||
|
||||
const stopSystemMonitoring = () => {
|
||||
if (monitoringTimeout) {
|
||||
clearTimeout(monitoringTimeout);
|
||||
monitoringTimeout = null;
|
||||
if (wsUnsubscribe) {
|
||||
wsUnsubscribe();
|
||||
wsUnsubscribe = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Человеко-читаемое название кооператива
|
||||
const cooperativeDisplayName = computed(() => {
|
||||
const vars = info.value?.vars;
|
||||
if (vars?.short_abbr && vars?.name) {
|
||||
@@ -133,16 +126,9 @@ export const useSystemStore = defineStore(namespace, (): ISystemStore => {
|
||||
return info.value.contacts?.full_name || '';
|
||||
});
|
||||
|
||||
// Символ управления (govern)
|
||||
const governSymbol = computed(() => info.value.symbols?.root_govern_symbol || '₽');
|
||||
|
||||
// Точность символа управления
|
||||
const governPrecision = computed(() => info.value.symbols?.root_govern_precision || 2);
|
||||
|
||||
// Системный символ
|
||||
const systemSymbol = computed(() => info.value.symbols?.root_symbol || '₽');
|
||||
|
||||
// Точность системного символа
|
||||
const systemPrecision = computed(() => info.value.symbols?.root_precision || 2);
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { ShareHeaderAction, ShareDialog } from './ui'
|
||||
@@ -0,0 +1,184 @@
|
||||
<template lang="pug">
|
||||
q-dialog(v-model='isOpen' persistent)
|
||||
q-card(style='width: 550px; max-width: 90vw')
|
||||
q-card-section
|
||||
.row.items-center
|
||||
q-icon(name='share' size='24px' color='primary')
|
||||
.text-h6.q-ml-sm Поделиться страницей
|
||||
q-space
|
||||
q-btn(flat round dense icon='close' @click='close')
|
||||
|
||||
q-separator
|
||||
|
||||
q-tabs(v-model='tab' dense class='text-primary')
|
||||
q-tab(name='create' label='Создать ссылку')
|
||||
q-tab(name='active' label='Активные ссылки')
|
||||
|
||||
q-tab-panels(v-model='tab')
|
||||
q-tab-panel(name='create')
|
||||
q-form(@submit.prevent='createLink')
|
||||
q-select(
|
||||
v-model='targetType'
|
||||
:options='targetOptions'
|
||||
label='Тип доступа'
|
||||
emit-value
|
||||
map-options
|
||||
dense
|
||||
)
|
||||
|
||||
q-input(
|
||||
v-if='targetType === "guest"'
|
||||
v-model='linkName'
|
||||
label='Название ссылки'
|
||||
dense
|
||||
class='q-mt-sm'
|
||||
)
|
||||
|
||||
q-input(
|
||||
v-if='targetType === "member"'
|
||||
v-model='targetUsername'
|
||||
label='Имя пайщика'
|
||||
dense
|
||||
class='q-mt-sm'
|
||||
)
|
||||
|
||||
q-select(
|
||||
v-model='selectedActions'
|
||||
:options='actionOptions'
|
||||
label='Разрешённые действия'
|
||||
multiple
|
||||
emit-value
|
||||
map-options
|
||||
dense
|
||||
class='q-mt-sm'
|
||||
)
|
||||
|
||||
q-input(
|
||||
v-model.number='expiresInDays'
|
||||
label='Срок действия (дней, пусто = бессрочно)'
|
||||
type='number'
|
||||
dense
|
||||
class='q-mt-sm'
|
||||
)
|
||||
|
||||
q-btn.q-mt-md(
|
||||
type='submit'
|
||||
color='primary'
|
||||
label='Создать ссылку'
|
||||
:loading='creating'
|
||||
no-caps
|
||||
)
|
||||
|
||||
q-tab-panel(name='active')
|
||||
q-list(v-if='links.length > 0' separator)
|
||||
q-item(v-for='link in links' :key='link.id')
|
||||
q-item-section
|
||||
q-item-label {{ link.link_name || link.target_username || 'Гость' }}
|
||||
q-item-label(caption) {{ link.page_name }} · {{ link.allowed_actions.join(', ') }}
|
||||
q-item-section(side)
|
||||
q-btn(flat dense icon='content_copy' @click='copyToken(link.token)')
|
||||
q-btn(flat dense icon='delete' color='negative' @click='revoke(link.id)')
|
||||
|
||||
.text-center.text-grey-5.q-pa-lg(v-else)
|
||||
| Нет активных ссылок
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { client } from 'src/shared/api/client'
|
||||
import { copyToClipboard, Notify } from 'quasar'
|
||||
|
||||
const props = defineProps<{ modelValue: boolean; pagePath: string; pageName: string }>()
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', v: boolean): void }>()
|
||||
|
||||
const isOpen = ref(props.modelValue)
|
||||
watch(() => props.modelValue, v => { isOpen.value = v })
|
||||
watch(isOpen, v => { emit('update:modelValue', v) })
|
||||
|
||||
const tab = ref('create')
|
||||
const targetType = ref('guest')
|
||||
const linkName = ref('')
|
||||
const targetUsername = ref('')
|
||||
const selectedActions = ref(['read'])
|
||||
const expiresInDays = ref<number | undefined>(undefined)
|
||||
const creating = ref(false)
|
||||
const links = ref<any[]>([])
|
||||
|
||||
const targetOptions = [
|
||||
{ label: 'Гость (по ссылке)', value: 'guest' },
|
||||
{ label: 'Пайщик (по имени)', value: 'member' },
|
||||
]
|
||||
|
||||
const actionOptions = [
|
||||
{ label: 'Просмотр', value: 'read' },
|
||||
{ label: 'Создание', value: 'create' },
|
||||
{ label: 'Редактирование', value: 'update' },
|
||||
{ label: 'Удаление', value: 'delete' },
|
||||
{ label: 'Выполнение действий', value: 'execute' },
|
||||
]
|
||||
|
||||
onMounted(() => loadLinks())
|
||||
|
||||
async function loadLinks() {
|
||||
try {
|
||||
const { getMyShareLinks } = await client.Query({
|
||||
getMyShareLinks: {
|
||||
id: true,
|
||||
page_path: true,
|
||||
page_name: true,
|
||||
target_type: true,
|
||||
target_username: true,
|
||||
link_name: true,
|
||||
allowed_actions: true,
|
||||
token: true,
|
||||
is_active: true,
|
||||
created_at: true,
|
||||
},
|
||||
})
|
||||
links.value = (getMyShareLinks || []).filter((l: any) => l.page_path === props.pagePath)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function createLink() {
|
||||
creating.value = true
|
||||
try {
|
||||
await client.Mutation({
|
||||
createShareLink: [{
|
||||
data: {
|
||||
pagePath: props.pagePath,
|
||||
pageName: props.pageName,
|
||||
targetType: targetType.value as any,
|
||||
targetUsername: targetType.value === 'member' ? targetUsername.value : undefined,
|
||||
linkName: targetType.value === 'guest' ? linkName.value : undefined,
|
||||
allowedActions: selectedActions.value,
|
||||
expiresInDays: expiresInDays.value || undefined,
|
||||
},
|
||||
}, { id: true, token: true }],
|
||||
})
|
||||
Notify.create({ type: 'positive', message: 'Ссылка создана' })
|
||||
await loadLinks()
|
||||
linkName.value = ''
|
||||
targetUsername.value = ''
|
||||
} catch (e: any) {
|
||||
Notify.create({ type: 'negative', message: e?.message || 'Ошибка' })
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function revoke(id: string) {
|
||||
try {
|
||||
await client.Mutation({ revokeShareLink: [{ id }, true] })
|
||||
Notify.create({ type: 'info', message: 'Ссылка отозвана' })
|
||||
await loadLinks()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function copyToken(token: string) {
|
||||
const url = `${window.location.origin}${props.pagePath}?share=${token}`
|
||||
copyToClipboard(url)
|
||||
Notify.create({ type: 'positive', message: 'Ссылка скопирована' })
|
||||
}
|
||||
|
||||
function close() { isOpen.value = false }
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template lang="pug">
|
||||
q-btn(
|
||||
v-if='canShare'
|
||||
flat
|
||||
dense
|
||||
no-caps
|
||||
icon='share'
|
||||
label='Поделиться'
|
||||
@click='showDialog = true'
|
||||
)
|
||||
|
||||
ShareDialog(v-model='showDialog' :pagePath='currentPath' :pageName='currentPageName')
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useSessionStore } from 'src/entities/Session'
|
||||
import ShareDialog from './ShareDialog.vue'
|
||||
|
||||
const session = useSessionStore()
|
||||
const route = useRoute()
|
||||
const showDialog = ref(false)
|
||||
|
||||
const canShare = computed(() => session.isChairman || session.isMember)
|
||||
const currentPath = computed(() => route.fullPath)
|
||||
const currentPageName = computed(() => (route.meta?.title as string) || route.name?.toString() || '')
|
||||
</script>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as ShareHeaderAction } from './ShareHeaderAction.vue'
|
||||
export { default as ShareDialog } from './ShareDialog.vue'
|
||||
@@ -88,20 +88,9 @@ export function useProviderSubscriptions() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Автоматическая загрузка с интервалом
|
||||
*/
|
||||
const startAutoRefresh = (intervalMs = 60000) => { // 1 минута по умолчанию
|
||||
loadSubscriptions(); // Первая загрузка
|
||||
|
||||
const interval = setInterval(() => {
|
||||
loadSubscriptions();
|
||||
}, intervalMs);
|
||||
|
||||
// Функция для остановки автообновления
|
||||
const stop = () => clearInterval(interval);
|
||||
|
||||
return stop;
|
||||
const startAutoRefresh = () => {
|
||||
loadSubscriptions();
|
||||
return () => {};
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -16,7 +16,7 @@ q-card(flat)
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, computed, ref, onMounted } from 'vue';
|
||||
import { computed, ref, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
import { CreateProjectButton } from 'src/features/Decision/CreateProject';
|
||||
@@ -24,6 +24,7 @@ import { useDecisionProcessor } from 'src/processes/process-decisions';
|
||||
import { FailAlert, SuccessAlert } from 'src/shared/api';
|
||||
import { QuestionsTable } from 'src/widgets/Questions';
|
||||
import { useHeaderActions } from 'src/shared/hooks';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
|
||||
const route = useRoute();
|
||||
const session = useSessionStore();
|
||||
@@ -120,13 +121,10 @@ const onVoteAgainst = async (row) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Инициализация
|
||||
loadDecisions(route.params.coopname as string);
|
||||
|
||||
// Периодическое обновление данных
|
||||
const interval = setInterval(
|
||||
() => loadDecisions(route.params.coopname as string, true),
|
||||
10000,
|
||||
);
|
||||
onBeforeUnmount(() => clearInterval(interval));
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('sovietDataChanged', null, ['entity', 'action']),
|
||||
onData: () => { loadDecisions(route.params.coopname as string, true); },
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed, onUnmounted } from 'vue';
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { MeetDetailsInfo } from 'src/widgets/Meets/MeetDetailsInfo';
|
||||
import { MeetDetailsActions } from 'src/widgets/Meets/MeetDetailsActions';
|
||||
@@ -51,6 +51,7 @@ import { useBackButton } from 'src/shared/lib/navigation';
|
||||
import { useVoteOnMeet } from 'src/features/Meet/VoteOnMeet';
|
||||
import { Zeus } from '@coopenomics/sdk';
|
||||
import { FailAlert } from 'src/shared/api';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
|
||||
const route = useRoute();
|
||||
const meetStore = useMeetStore();
|
||||
@@ -72,8 +73,6 @@ const showAgenda = computed(
|
||||
|
||||
const { isVotingNow, setMeet } = useVoteOnMeet();
|
||||
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const loadMeetDetails = async () => {
|
||||
try {
|
||||
await meetStore.loadMeet({
|
||||
@@ -103,16 +102,13 @@ useBackButton({
|
||||
componentId: 'meet-details-' + meetHash.value,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadMeetDetails();
|
||||
intervalId = setInterval(loadMeetDetails, 15000);
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('sovietDataChanged', null, ['entity', 'action']),
|
||||
onData: () => { loadMeetDetails(); },
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
onMounted(() => {
|
||||
loadMeetDetails();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -14,48 +14,28 @@ div
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, onBeforeUnmount, onMounted } from 'vue';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useSessionStore } from 'src/entities/Session';
|
||||
import { Loader } from 'src/shared/ui/Loader';
|
||||
import { useRegistratorStore } from 'src/entities/Registrator';
|
||||
import { useGraphqlSubscription, buildSubscriptionQuery } from 'src/shared/lib/composables';
|
||||
|
||||
const store = useRegistratorStore();
|
||||
|
||||
const currentStep = store.steps.WaitingRegistration;
|
||||
|
||||
const step = computed(() => store.state.step);
|
||||
const interval = ref();
|
||||
|
||||
watch(step, (newValue) => {
|
||||
if (newValue === currentStep) {
|
||||
interval.value = setInterval(() => update(), 10000);
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
const session = useSessionStore();
|
||||
|
||||
const participantAccount = computed(() => session.participantAccount);
|
||||
|
||||
onMounted(() => {
|
||||
if (participantAccount.value && step.value === currentStep) store.next();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (interval.value) {
|
||||
clearInterval(interval.value);
|
||||
if (participantAccount.value && store.state.step === store.steps.WaitingRegistration) {
|
||||
store.next();
|
||||
}
|
||||
});
|
||||
|
||||
const update = async () => {
|
||||
if (store.state.account.username && !participantAccount.value) {
|
||||
// Убираю loadProfile - данные уже загружены
|
||||
// await currentUser.loadProfile(
|
||||
// store.state.account.username,
|
||||
// info.coopname,
|
||||
// );
|
||||
} else {
|
||||
clearInterval(interval.value);
|
||||
}
|
||||
};
|
||||
useGraphqlSubscription({
|
||||
query: buildSubscriptionQuery('sovietDataChanged', null, ['entity', 'action']),
|
||||
onData: () => {
|
||||
if (participantAccount.value) {
|
||||
store.next();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -68,4 +68,9 @@ export async function useInitAppProcess(router: Router) {
|
||||
|
||||
await useInitExtensionsProcess(router);
|
||||
|
||||
// Автоматическая кнопка "Поделиться" на всех страницах
|
||||
if (typeof window !== 'undefined') {
|
||||
const { useShareButtonProcess } = await import('src/processes/share-button-setup');
|
||||
useShareButtonProcess();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import marketAdminInstall from '../../../extensions/market-admin/install';
|
||||
import participantInstall from '../../../extensions/participant/install';
|
||||
import powerupInstall from '../../../extensions/powerup/install';
|
||||
import sovietInstall from '../../../extensions/soviet/install';
|
||||
import reportsInstall from '../../../extensions/reports/install';
|
||||
|
||||
/**
|
||||
* Единый регистр всех доступных расширений
|
||||
@@ -23,6 +24,7 @@ export const extensionsRegistry: Record<string, () => Promise<IWorkspaceConfig[]
|
||||
participant: participantInstall,
|
||||
powerup: powerupInstall,
|
||||
soviet: sovietInstall,
|
||||
reports: reportsInstall,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,10 @@ function hasAccess(to, userRole) {
|
||||
return userRole && to.meta?.roles.includes(userRole);
|
||||
}
|
||||
|
||||
function hasShareToken(to): boolean {
|
||||
return !!(to.query?.share_token && typeof to.query.share_token === 'string' && to.query.share_token.length > 10);
|
||||
}
|
||||
|
||||
// Функция для получения URL для редиректа
|
||||
function getRedirectUrl(router: Router, to: any): string {
|
||||
if (process.env.CLIENT) {
|
||||
@@ -106,8 +110,8 @@ export function setupNavigationGuard(router: Router) {
|
||||
return;
|
||||
}
|
||||
|
||||
// проверка по ролям
|
||||
if (hasAccess(to, userRole)) {
|
||||
// проверка по ролям — при наличии share_token пропускаем проверку ролей
|
||||
if (hasAccess(to, userRole) || hasShareToken(to)) {
|
||||
next();
|
||||
} else {
|
||||
next({ name: 'permissionDenied', query: to.query });
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useSessionStore } from 'src/entities/Session'
|
||||
import { useHeaderActions } from 'src/shared/hooks'
|
||||
import { ShareHeaderAction } from 'src/features/PageShare'
|
||||
|
||||
/**
|
||||
* Автоматически регистрирует кнопку "Поделиться" в header
|
||||
* на страницах где meta.shareable === true.
|
||||
* Доступна только chairman и member.
|
||||
*/
|
||||
export function useShareButtonProcess() {
|
||||
const route = useRoute()
|
||||
const session = useSessionStore()
|
||||
const { registerAction, unregisterAction } = useHeaderActions()
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
const canShare = session.isChairman || session.isMember
|
||||
const isShareable = route.meta?.shareable !== false
|
||||
|
||||
if (canShare && isShareable && session.isAuth) {
|
||||
registerAction({
|
||||
id: 'page-share',
|
||||
component: ShareHeaderAction,
|
||||
order: 100,
|
||||
})
|
||||
} else {
|
||||
unregisterAction('page-share')
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export { useExpandableState } from './useExpandableState';
|
||||
export { useDataPoller } from './useDataPoller';
|
||||
export { useMobileDrawer } from './useMobileDrawer';
|
||||
export { useReferralLink } from './useReferralLink';
|
||||
export { useGraphqlSubscription, buildSubscriptionQuery } from './useGraphqlSubscription';
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { ref, onUnmounted, type Ref } from 'vue';
|
||||
import { createClient, type Client } from 'graphql-ws';
|
||||
import { useGlobalStore } from 'src/shared/store';
|
||||
import { env } from 'src/shared/config';
|
||||
|
||||
let sharedClient: Client | null = null;
|
||||
let activeSubscribers = 0;
|
||||
|
||||
function getWsUrl(): string {
|
||||
const httpUrl = env.BACKEND_URL + '/v1/graphql';
|
||||
return httpUrl.replace(/^http/, 'ws');
|
||||
}
|
||||
|
||||
function getOrCreateClient(): Client {
|
||||
if (sharedClient) return sharedClient;
|
||||
|
||||
const globalStore = useGlobalStore();
|
||||
|
||||
sharedClient = createClient({
|
||||
url: getWsUrl(),
|
||||
connectionParams: () => ({
|
||||
token: globalStore.tokens?.access?.token || '',
|
||||
}),
|
||||
retryAttempts: 5,
|
||||
shouldRetry: () => true,
|
||||
on: {
|
||||
closed: () => {
|
||||
if (activeSubscribers === 0) {
|
||||
sharedClient?.dispose();
|
||||
sharedClient = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return sharedClient;
|
||||
}
|
||||
|
||||
export interface UseSubscriptionOptions<T> {
|
||||
query: string;
|
||||
variables?: Record<string, any>;
|
||||
onData: (data: T) => void;
|
||||
onError?: (error: any) => void;
|
||||
enabled?: Ref<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable для GraphQL подписок через graphql-ws.
|
||||
* Автоматически подключается к WebSocket с JWT токеном.
|
||||
* Автоматически отписывается при уничтожении компонента.
|
||||
*/
|
||||
export function useGraphqlSubscription<T = any>(options: UseSubscriptionOptions<T>) {
|
||||
const isConnected = ref(false);
|
||||
let unsubscribe: (() => void) | null = null;
|
||||
|
||||
function subscribe() {
|
||||
if (unsubscribe) return;
|
||||
if (options.enabled && !options.enabled.value) return;
|
||||
|
||||
const client = getOrCreateClient();
|
||||
activeSubscribers++;
|
||||
isConnected.value = true;
|
||||
|
||||
unsubscribe = client.subscribe(
|
||||
{
|
||||
query: options.query,
|
||||
variables: options.variables,
|
||||
},
|
||||
{
|
||||
next(value) {
|
||||
if (value.data) {
|
||||
options.onData(value.data as T);
|
||||
}
|
||||
},
|
||||
error(error) {
|
||||
console.warn('[Subscription] Error:', error);
|
||||
options.onError?.(error);
|
||||
},
|
||||
complete() {
|
||||
isConnected.value = false;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
unsubscribe = null;
|
||||
activeSubscribers = Math.max(0, activeSubscribers - 1);
|
||||
isConnected.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
subscribe();
|
||||
|
||||
onUnmounted(() => {
|
||||
stop();
|
||||
});
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
stop,
|
||||
restart: () => {
|
||||
stop();
|
||||
subscribe();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Утилита для формирования GraphQL subscription query строки
|
||||
*/
|
||||
export function buildSubscriptionQuery(
|
||||
name: string,
|
||||
args: Record<string, any> | null,
|
||||
fields: string[],
|
||||
): string {
|
||||
const argsStr = args
|
||||
? `(${Object.entries(args).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(', ')})`
|
||||
: '';
|
||||
|
||||
return `subscription { ${name}${argsStr} { ${fields.join(' ')} } }`;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export * as Classes from './classes'
|
||||
export * as Mutations from './mutations'
|
||||
export * as Queries from './queries'
|
||||
export * as Selectors from './selectors'
|
||||
export * as Subscriptions from './subscriptions'
|
||||
|
||||
export * as Types from './types'
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
export const IssueUpdated = {
|
||||
name: 'capitalIssueUpdated' as const,
|
||||
subscription: {
|
||||
capitalIssueUpdated: [
|
||||
{ project_hash: '' },
|
||||
{
|
||||
id: true,
|
||||
issue_hash: true,
|
||||
title: true,
|
||||
status: true,
|
||||
priority: true,
|
||||
estimate: true,
|
||||
created_by: true,
|
||||
submaster: true,
|
||||
creators: true,
|
||||
project_hash: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const IssueCreated = {
|
||||
name: 'capitalIssueCreated' as const,
|
||||
subscription: {
|
||||
capitalIssueCreated: [
|
||||
{ project_hash: '' },
|
||||
{
|
||||
id: true,
|
||||
issue_hash: true,
|
||||
title: true,
|
||||
status: true,
|
||||
priority: true,
|
||||
estimate: true,
|
||||
created_by: true,
|
||||
submaster: true,
|
||||
creators: true,
|
||||
project_hash: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const CommitCreated = {
|
||||
name: 'capitalCommitCreated' as const,
|
||||
subscription: {
|
||||
capitalCommitCreated: [
|
||||
{ project_hash: '' },
|
||||
{
|
||||
id: true,
|
||||
commit_hash: true,
|
||||
hours: true,
|
||||
description: true,
|
||||
status: true,
|
||||
created_by: true,
|
||||
project_hash: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const CommitUpdated = {
|
||||
name: 'capitalCommitUpdated' as const,
|
||||
subscription: {
|
||||
capitalCommitUpdated: [
|
||||
{ project_hash: '' },
|
||||
{
|
||||
id: true,
|
||||
commit_hash: true,
|
||||
hours: true,
|
||||
description: true,
|
||||
status: true,
|
||||
created_by: true,
|
||||
project_hash: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * as Capital from './capital'
|
||||
@@ -256,6 +256,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
close_at:"DateTime",
|
||||
open_at:"DateTime",
|
||||
proposal:"AnnualGeneralMeetingAgendaSignedDocumentInput"
|
||||
},
|
||||
CreateApiKeyInput:{
|
||||
|
||||
},
|
||||
CreateBranchInput:{
|
||||
|
||||
@@ -328,6 +331,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
},
|
||||
CreateProjectPropertyInput:{
|
||||
|
||||
},
|
||||
CreateShareLinkInput:{
|
||||
targetType:"ShareTargetType"
|
||||
},
|
||||
CreateSovietIndividualDataInput:{
|
||||
passport:"PassportInput"
|
||||
@@ -437,6 +443,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
GenerateRegistrationDocumentsInput:{
|
||||
account_type:"AccountType"
|
||||
},
|
||||
GenerateReportInput:{
|
||||
reportType:"ReportType"
|
||||
},
|
||||
GenerationContractGenerateDocumentInput:{
|
||||
|
||||
},
|
||||
@@ -874,6 +883,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
createAnnualGeneralMeet:{
|
||||
data:"CreateAnnualGeneralMeetInput"
|
||||
},
|
||||
createApiKey:{
|
||||
data:"CreateApiKeyInput"
|
||||
},
|
||||
createBranch:{
|
||||
data:"CreateBranchInput"
|
||||
},
|
||||
@@ -892,6 +904,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
createProjectOfFreeDecision:{
|
||||
data:"CreateProjectFreeDecisionInput"
|
||||
},
|
||||
createShareLink:{
|
||||
data:"CreateShareLinkInput"
|
||||
},
|
||||
createWebPushSubscription:{
|
||||
data:"CreateSubscriptionInput"
|
||||
},
|
||||
@@ -983,6 +998,9 @@ export const AllTypesProps: Record<string,any> = {
|
||||
generateRegistrationDocuments:{
|
||||
data:"GenerateRegistrationDocumentsInput"
|
||||
},
|
||||
generateReport:{
|
||||
data:"GenerateReportInput"
|
||||
},
|
||||
generateReturnByAssetAct:{
|
||||
data:"ReturnByAssetActGenerateDocumentInput",
|
||||
options:"GenerateDocumentOptionsInput"
|
||||
@@ -1073,6 +1091,12 @@ export const AllTypesProps: Record<string,any> = {
|
||||
},
|
||||
restartAnnualGeneralMeet:{
|
||||
data:"RestartAnnualGeneralMeetInput"
|
||||
},
|
||||
revokeApiKey:{
|
||||
|
||||
},
|
||||
revokeShareLink:{
|
||||
|
||||
},
|
||||
selectBranch:{
|
||||
data:"SelectBranchInput"
|
||||
@@ -1487,6 +1511,7 @@ export const AllTypesProps: Record<string,any> = {
|
||||
user_agreement:"SignedDigitalDocumentInput",
|
||||
wallet_agreement:"SignedDigitalDocumentInput"
|
||||
},
|
||||
ReportType: "enum" as const,
|
||||
RepresentedByInput:{
|
||||
|
||||
},
|
||||
@@ -1596,6 +1621,7 @@ export const AllTypesProps: Record<string,any> = {
|
||||
SetWifInput:{
|
||||
|
||||
},
|
||||
ShareTargetType: "enum" as const,
|
||||
SignActAsChairmanInput:{
|
||||
act:"SignedDigitalDocumentInput"
|
||||
},
|
||||
@@ -1640,6 +1666,20 @@ export const AllTypesProps: Record<string,any> = {
|
||||
SubmitVoteInput:{
|
||||
votes:"VoteDistributionInput"
|
||||
},
|
||||
Subscription:{
|
||||
capitalCommitCreated:{
|
||||
|
||||
},
|
||||
capitalCommitUpdated:{
|
||||
|
||||
},
|
||||
capitalIssueCreated:{
|
||||
|
||||
},
|
||||
capitalIssueUpdated:{
|
||||
|
||||
}
|
||||
},
|
||||
SupplyOnRequestInput:{
|
||||
document:"AssetContributionActSignedDocumentInput"
|
||||
},
|
||||
@@ -1803,6 +1843,26 @@ export const ReturnTypes: Record<string,any> = {
|
||||
protocol_day_month_year:"String",
|
||||
protocol_number:"String"
|
||||
},
|
||||
ApiKeyCreated:{
|
||||
allowed_operations:"String",
|
||||
created_at:"DateTime",
|
||||
expires_at:"DateTime",
|
||||
id:"String",
|
||||
key:"String",
|
||||
key_prefix:"String",
|
||||
name:"String"
|
||||
},
|
||||
ApiKeyInfo:{
|
||||
allowed_operations:"String",
|
||||
created_at:"DateTime",
|
||||
created_by:"String",
|
||||
expires_at:"DateTime",
|
||||
id:"String",
|
||||
is_active:"Boolean",
|
||||
key_prefix:"String",
|
||||
last_used_at:"DateTime",
|
||||
name:"String"
|
||||
},
|
||||
Approval:{
|
||||
_created_at:"DateTime",
|
||||
_id:"String",
|
||||
@@ -1832,6 +1892,12 @@ export const ReturnTypes: Record<string,any> = {
|
||||
threshold:"Int",
|
||||
waits:"WaitWeight"
|
||||
},
|
||||
AvailableReport:{
|
||||
deadline:"String",
|
||||
name:"String",
|
||||
period:"String",
|
||||
type:"ReportType"
|
||||
},
|
||||
BankAccount:{
|
||||
account_number:"String",
|
||||
bank_name:"String",
|
||||
@@ -2909,6 +2975,13 @@ export const ReturnTypes: Record<string,any> = {
|
||||
order:"Int",
|
||||
title:"String"
|
||||
},
|
||||
GeneratedReport:{
|
||||
errors:"String",
|
||||
fileName:"String",
|
||||
isValid:"Boolean",
|
||||
reportType:"ReportType",
|
||||
xml:"String"
|
||||
},
|
||||
Individual:{
|
||||
birthdate:"String",
|
||||
email:"String",
|
||||
@@ -3148,12 +3221,14 @@ export const ReturnTypes: Record<string,any> = {
|
||||
confirmReceiveOnRequest:"Transaction",
|
||||
confirmSupplyOnRequest:"Transaction",
|
||||
createAnnualGeneralMeet:"MeetAggregate",
|
||||
createApiKey:"ApiKeyCreated",
|
||||
createBranch:"Branch",
|
||||
createChildOrder:"Transaction",
|
||||
createDepositPayment:"GatewayPayment",
|
||||
createInitialPayment:"GatewayPayment",
|
||||
createParentOffer:"Transaction",
|
||||
createProjectOfFreeDecision:"CreatedProjectFreeDecision",
|
||||
createShareLink:"ShareLink",
|
||||
createWebPushSubscription:"CreateSubscriptionResponse",
|
||||
createWithdraw:"CreateWithdrawResponse",
|
||||
deactivateWebPushSubscriptionById:"Boolean",
|
||||
@@ -3180,6 +3255,7 @@ export const ReturnTypes: Record<string,any> = {
|
||||
generatePrivacyAgreement:"GeneratedDocument",
|
||||
generateProjectOfFreeDecision:"GeneratedDocument",
|
||||
generateRegistrationDocuments:"GenerateRegistrationDocumentsOutput",
|
||||
generateReport:"GeneratedReport",
|
||||
generateReturnByAssetAct:"GeneratedDocument",
|
||||
generateReturnByAssetDecision:"GeneratedDocument",
|
||||
generateReturnByAssetStatement:"GeneratedDocument",
|
||||
@@ -3207,6 +3283,8 @@ export const ReturnTypes: Record<string,any> = {
|
||||
registerParticipant:"Account",
|
||||
resetKey:"Boolean",
|
||||
restartAnnualGeneralMeet:"MeetAggregate",
|
||||
revokeApiKey:"Boolean",
|
||||
revokeShareLink:"Boolean",
|
||||
selectBranch:"Boolean",
|
||||
sendAgreement:"Transaction",
|
||||
setPaymentStatus:"GatewayPayment",
|
||||
@@ -3648,6 +3726,8 @@ export const ReturnTypes: Record<string,any> = {
|
||||
getAccounts:"AccountsPaginationResult",
|
||||
getActions:"PaginatedActionsPaginationResult",
|
||||
getAgenda:"AgendaWithDocuments",
|
||||
getApiKeys:"ApiKeyInfo",
|
||||
getAvailableReports:"AvailableReport",
|
||||
getBranches:"Branch",
|
||||
getCapitalIssueLogs:"PaginatedCapitalLogsPaginationResult",
|
||||
getCapitalOnboardingState:"CapitalOnboardingState",
|
||||
@@ -3665,6 +3745,7 @@ export const ReturnTypes: Record<string,any> = {
|
||||
getLedgerHistory:"LedgerHistoryResponse",
|
||||
getMeet:"MeetAggregate",
|
||||
getMeets:"MeetAggregate",
|
||||
getMyShareLinks:"ShareLink",
|
||||
getPaymentMethods:"PaymentMethodPaginationResult",
|
||||
getPayments:"PaginatedGatewayPaymentsPaginationResult",
|
||||
getProgramWallet:"ProgramWallet",
|
||||
@@ -3672,6 +3753,7 @@ export const ReturnTypes: Record<string,any> = {
|
||||
getProviderSubscriptionById:"ProviderSubscription",
|
||||
getProviderSubscriptions:"ProviderSubscription",
|
||||
getRegistrationConfig:"RegistrationConfig",
|
||||
getSharedWithMe:"ShareLink",
|
||||
getSystemInfo:"SystemInfo",
|
||||
getUserWebPushSubscriptions:"WebPushSubscriptionDto",
|
||||
getWebPushSubscriptionStats:"SubscriptionStatsDto",
|
||||
@@ -3764,6 +3846,19 @@ export const ReturnTypes: Record<string,any> = {
|
||||
provider_name:"String",
|
||||
updated_at:"DateTime"
|
||||
},
|
||||
ShareLink:{
|
||||
allowed_actions:"String",
|
||||
created_at:"DateTime",
|
||||
expires_at:"DateTime",
|
||||
id:"String",
|
||||
is_active:"Boolean",
|
||||
link_name:"String",
|
||||
page_name:"String",
|
||||
page_path:"String",
|
||||
target_type:"ShareTargetType",
|
||||
target_username:"String",
|
||||
token:"String"
|
||||
},
|
||||
SignatureInfo:{
|
||||
id:"Float",
|
||||
is_valid:"Boolean",
|
||||
@@ -3799,6 +3894,12 @@ export const ReturnTypes: Record<string,any> = {
|
||||
action:"ExtendedBlockchainAction",
|
||||
documentAggregate:"DocumentAggregate"
|
||||
},
|
||||
Subscription:{
|
||||
capitalCommitCreated:"CapitalCommit",
|
||||
capitalCommitUpdated:"CapitalCommit",
|
||||
capitalIssueCreated:"CapitalIssue",
|
||||
capitalIssueUpdated:"CapitalIssue"
|
||||
},
|
||||
SubscriptionStatsDto:{
|
||||
active:"Int",
|
||||
inactive:"Int",
|
||||
@@ -3921,5 +4022,6 @@ export const ReturnTypes: Record<string,any> = {
|
||||
|
||||
export const Ops = {
|
||||
query: "Query" as const,
|
||||
mutation: "Mutation" as const
|
||||
mutation: "Mutation" as const,
|
||||
subscription: "Subscription" as const
|
||||
}
|
||||
@@ -1463,6 +1463,29 @@ export type ValueTypes = {
|
||||
/** Голос (за/против/воздержался) */
|
||||
vote: string | Variable<any, string>
|
||||
};
|
||||
["ApiKeyCreated"]: AliasType<{
|
||||
allowed_operations?:boolean | `@${string}`,
|
||||
created_at?:boolean | `@${string}`,
|
||||
expires_at?:boolean | `@${string}`,
|
||||
id?:boolean | `@${string}`,
|
||||
/** Полный ключ — показывается ТОЛЬКО при создании */
|
||||
key?:boolean | `@${string}`,
|
||||
key_prefix?:boolean | `@${string}`,
|
||||
name?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ApiKeyInfo"]: AliasType<{
|
||||
allowed_operations?:boolean | `@${string}`,
|
||||
created_at?:boolean | `@${string}`,
|
||||
created_by?:boolean | `@${string}`,
|
||||
expires_at?:boolean | `@${string}`,
|
||||
id?:boolean | `@${string}`,
|
||||
is_active?:boolean | `@${string}`,
|
||||
key_prefix?:boolean | `@${string}`,
|
||||
last_used_at?:boolean | `@${string}`,
|
||||
name?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
/** Одобрение документа председателем совета */
|
||||
["Approval"]: AliasType<{
|
||||
/** Дата создания записи */
|
||||
@@ -1703,6 +1726,13 @@ export type ValueTypes = {
|
||||
/** Вес ожидания */
|
||||
waits?:ValueTypes["WaitWeight"],
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["AvailableReport"]: AliasType<{
|
||||
deadline?:boolean | `@${string}`,
|
||||
name?:boolean | `@${string}`,
|
||||
period?:boolean | `@${string}`,
|
||||
type?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["BankAccount"]: AliasType<{
|
||||
/** Номер банковского счета */
|
||||
@@ -3794,6 +3824,11 @@ export type ValueTypes = {
|
||||
proposal: ValueTypes["AnnualGeneralMeetingAgendaSignedDocumentInput"] | Variable<any, string>,
|
||||
/** Имя аккаунта секретаря */
|
||||
secretary: string | Variable<any, string>
|
||||
};
|
||||
["CreateApiKeyInput"]: {
|
||||
allowedOperations?: Array<string> | undefined | null | Variable<any, string>,
|
||||
expiresInDays?: number | undefined | null | Variable<any, string>,
|
||||
name: string | Variable<any, string>
|
||||
};
|
||||
["CreateBranchInput"]: {
|
||||
/** Документ, на основании которого действует Уполномоченный (решение совета №СС-.. от ..) */
|
||||
@@ -4117,6 +4152,15 @@ export type ValueTypes = {
|
||||
property_hash: string | Variable<any, string>,
|
||||
/** Имя пользователя */
|
||||
username: string | Variable<any, string>
|
||||
};
|
||||
["CreateShareLinkInput"]: {
|
||||
allowedActions: Array<string> | Variable<any, string>,
|
||||
expiresInDays?: number | undefined | null | Variable<any, string>,
|
||||
linkName?: string | undefined | null | Variable<any, string>,
|
||||
pageName: string | Variable<any, string>,
|
||||
pagePath: string | Variable<any, string>,
|
||||
targetType: ValueTypes["ShareTargetType"] | Variable<any, string>,
|
||||
targetUsername?: string | undefined | null | Variable<any, string>
|
||||
};
|
||||
["CreateSovietIndividualDataInput"]: {
|
||||
/** Дата рождения */
|
||||
@@ -4854,6 +4898,11 @@ export type ValueTypes = {
|
||||
username?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["GenerateReportInput"]: {
|
||||
period?: number | undefined | null | Variable<any, string>,
|
||||
reportType: ValueTypes["ReportType"] | Variable<any, string>,
|
||||
year: number | Variable<any, string>
|
||||
};
|
||||
["GeneratedDocument"]: AliasType<{
|
||||
/** Бинарное содержимое документа (base64) */
|
||||
binary?:boolean | `@${string}`,
|
||||
@@ -4887,6 +4936,14 @@ export type ValueTypes = {
|
||||
/** Название документа */
|
||||
title?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["GeneratedReport"]: AliasType<{
|
||||
errors?:boolean | `@${string}`,
|
||||
fileName?:boolean | `@${string}`,
|
||||
isValid?:boolean | `@${string}`,
|
||||
reportType?:boolean | `@${string}`,
|
||||
xml?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["GenerationContractGenerateDocumentInput"]: {
|
||||
/** Номер блока, на котором был создан документ */
|
||||
@@ -5691,12 +5748,14 @@ confirmAgreement?: [{ data: ValueTypes["ConfirmAgreementInput"] | Variable<any,
|
||||
confirmReceiveOnRequest?: [{ data: ValueTypes["ConfirmReceiveOnRequestInput"] | Variable<any, string>},ValueTypes["Transaction"]],
|
||||
confirmSupplyOnRequest?: [{ data: ValueTypes["ConfirmSupplyOnRequestInput"] | Variable<any, string>},ValueTypes["Transaction"]],
|
||||
createAnnualGeneralMeet?: [{ data: ValueTypes["CreateAnnualGeneralMeetInput"] | Variable<any, string>},ValueTypes["MeetAggregate"]],
|
||||
createApiKey?: [{ data: ValueTypes["CreateApiKeyInput"] | Variable<any, string>},ValueTypes["ApiKeyCreated"]],
|
||||
createBranch?: [{ data: ValueTypes["CreateBranchInput"] | Variable<any, string>},ValueTypes["Branch"]],
|
||||
createChildOrder?: [{ data: ValueTypes["CreateChildOrderInput"] | Variable<any, string>},ValueTypes["Transaction"]],
|
||||
createDepositPayment?: [{ data: ValueTypes["CreateDepositPaymentInput"] | Variable<any, string>},ValueTypes["GatewayPayment"]],
|
||||
createInitialPayment?: [{ data: ValueTypes["CreateInitialPaymentInput"] | Variable<any, string>},ValueTypes["GatewayPayment"]],
|
||||
createParentOffer?: [{ data: ValueTypes["CreateParentOfferInput"] | Variable<any, string>},ValueTypes["Transaction"]],
|
||||
createProjectOfFreeDecision?: [{ data: ValueTypes["CreateProjectFreeDecisionInput"] | Variable<any, string>},ValueTypes["CreatedProjectFreeDecision"]],
|
||||
createShareLink?: [{ data: ValueTypes["CreateShareLinkInput"] | Variable<any, string>},ValueTypes["ShareLink"]],
|
||||
createWebPushSubscription?: [{ data: ValueTypes["CreateSubscriptionInput"] | Variable<any, string>},ValueTypes["CreateSubscriptionResponse"]],
|
||||
createWithdraw?: [{ data: ValueTypes["CreateWithdrawInput"] | Variable<any, string>},ValueTypes["CreateWithdrawResponse"]],
|
||||
deactivateWebPushSubscriptionById?: [{ data: ValueTypes["DeactivateSubscriptionInput"] | Variable<any, string>},boolean | `@${string}`],
|
||||
@@ -5723,6 +5782,7 @@ generateParticipantApplicationDecision?: [{ data: ValueTypes["ParticipantApplica
|
||||
generatePrivacyAgreement?: [{ data: ValueTypes["GenerateDocumentInput"] | Variable<any, string>, options?: ValueTypes["GenerateDocumentOptionsInput"] | undefined | null | Variable<any, string>},ValueTypes["GeneratedDocument"]],
|
||||
generateProjectOfFreeDecision?: [{ data: ValueTypes["ProjectFreeDecisionGenerateDocumentInput"] | Variable<any, string>, options?: ValueTypes["GenerateDocumentOptionsInput"] | undefined | null | Variable<any, string>},ValueTypes["GeneratedDocument"]],
|
||||
generateRegistrationDocuments?: [{ data: ValueTypes["GenerateRegistrationDocumentsInput"] | Variable<any, string>},ValueTypes["GenerateRegistrationDocumentsOutput"]],
|
||||
generateReport?: [{ data: ValueTypes["GenerateReportInput"] | Variable<any, string>},ValueTypes["GeneratedReport"]],
|
||||
generateReturnByAssetAct?: [{ data: ValueTypes["ReturnByAssetActGenerateDocumentInput"] | Variable<any, string>, options?: ValueTypes["GenerateDocumentOptionsInput"] | undefined | null | Variable<any, string>},ValueTypes["GeneratedDocument"]],
|
||||
generateReturnByAssetDecision?: [{ data: ValueTypes["ReturnByAssetDecisionGenerateDocumentInput"] | Variable<any, string>, options?: ValueTypes["GenerateDocumentOptionsInput"] | undefined | null | Variable<any, string>},ValueTypes["GeneratedDocument"]],
|
||||
generateReturnByAssetStatement?: [{ data: ValueTypes["ReturnByAssetStatementGenerateDocumentInput"] | Variable<any, string>, options?: ValueTypes["GenerateDocumentOptionsInput"] | undefined | null | Variable<any, string>},ValueTypes["GeneratedDocument"]],
|
||||
@@ -5750,6 +5810,8 @@ registerAccount?: [{ data: ValueTypes["RegisterAccountInput"] | Variable<any, st
|
||||
registerParticipant?: [{ data: ValueTypes["RegisterParticipantInput"] | Variable<any, string>},ValueTypes["Account"]],
|
||||
resetKey?: [{ data: ValueTypes["ResetKeyInput"] | Variable<any, string>},boolean | `@${string}`],
|
||||
restartAnnualGeneralMeet?: [{ data: ValueTypes["RestartAnnualGeneralMeetInput"] | Variable<any, string>},ValueTypes["MeetAggregate"]],
|
||||
revokeApiKey?: [{ id: string | Variable<any, string>},boolean | `@${string}`],
|
||||
revokeShareLink?: [{ id: string | Variable<any, string>},boolean | `@${string}`],
|
||||
selectBranch?: [{ data: ValueTypes["SelectBranchInput"] | Variable<any, string>},boolean | `@${string}`],
|
||||
sendAgreement?: [{ data: ValueTypes["SendAgreementInput"] | Variable<any, string>},ValueTypes["Transaction"]],
|
||||
setPaymentStatus?: [{ data: ValueTypes["SetPaymentStatusInput"] | Variable<any, string>},ValueTypes["GatewayPayment"]],
|
||||
@@ -6792,6 +6854,10 @@ getAccounts?: [{ data?: ValueTypes["GetAccountsInput"] | undefined | null | Vari
|
||||
getActions?: [{ filters?: ValueTypes["ActionFiltersInput"] | undefined | null | Variable<any, string>, pagination?: ValueTypes["PaginationInput"] | undefined | null | Variable<any, string>},ValueTypes["PaginatedActionsPaginationResult"]],
|
||||
/** Получить список вопросов совета кооператива для голосования */
|
||||
getAgenda?:ValueTypes["AgendaWithDocuments"],
|
||||
/** Получить список API ключей кооператива */
|
||||
getApiKeys?:ValueTypes["ApiKeyInfo"],
|
||||
/** Получить список доступных типов отчётов */
|
||||
getAvailableReports?:ValueTypes["AvailableReport"],
|
||||
getBranches?: [{ data: ValueTypes["GetBranchesInput"] | Variable<any, string>},ValueTypes["Branch"]],
|
||||
getCapitalIssueLogs?: [{ data: ValueTypes["GetCapitalIssueLogsInput"] | Variable<any, string>, options?: ValueTypes["PaginationInput"] | undefined | null | Variable<any, string>},ValueTypes["PaginatedCapitalLogsPaginationResult"]],
|
||||
/** Получить состояние онбординга capital */
|
||||
@@ -6813,6 +6879,8 @@ getLedger?: [{ data: ValueTypes["GetLedgerInput"] | Variable<any, string>},Value
|
||||
getLedgerHistory?: [{ data: ValueTypes["GetLedgerHistoryInput"] | Variable<any, string>},ValueTypes["LedgerHistoryResponse"]],
|
||||
getMeet?: [{ data: ValueTypes["GetMeetInput"] | Variable<any, string>},ValueTypes["MeetAggregate"]],
|
||||
getMeets?: [{ data: ValueTypes["GetMeetsInput"] | Variable<any, string>},ValueTypes["MeetAggregate"]],
|
||||
/** Получить созданные мной ссылки доступа */
|
||||
getMyShareLinks?:ValueTypes["ShareLink"],
|
||||
getPaymentMethods?: [{ data?: ValueTypes["GetPaymentMethodsInput"] | undefined | null | Variable<any, string>},ValueTypes["PaymentMethodPaginationResult"]],
|
||||
getPayments?: [{ data?: ValueTypes["PaymentFiltersInput"] | undefined | null | Variable<any, string>, options?: ValueTypes["PaginationInput"] | undefined | null | Variable<any, string>},ValueTypes["PaginatedGatewayPaymentsPaginationResult"]],
|
||||
getProgramWallet?: [{ filter: ValueTypes["ProgramWalletFilterInput"] | Variable<any, string>},ValueTypes["ProgramWallet"]],
|
||||
@@ -6821,6 +6889,8 @@ getProviderSubscriptionById?: [{ id: number | Variable<any, string>},ValueTypes[
|
||||
/** Получить подписки пользователя у провайдера */
|
||||
getProviderSubscriptions?:ValueTypes["ProviderSubscription"],
|
||||
getRegistrationConfig?: [{ account_type: ValueTypes["AccountType"] | Variable<any, string>, coopname: string | Variable<any, string>},ValueTypes["RegistrationConfig"]],
|
||||
/** Получить страницы, к которым мне предоставлен доступ */
|
||||
getSharedWithMe?:ValueTypes["ShareLink"],
|
||||
/** Получить сводную публичную информацию о системе */
|
||||
getSystemInfo?:ValueTypes["SystemInfo"],
|
||||
getUserWebPushSubscriptions?: [{ data: ValueTypes["GetUserSubscriptionsInput"] | Variable<any, string>},ValueTypes["WebPushSubscriptionDto"]],
|
||||
@@ -6989,6 +7059,7 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
|
||||
title?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ReportType"]:ReportType;
|
||||
["RepresentedBy"]: AliasType<{
|
||||
/** На основании чего действует */
|
||||
based_on?:boolean | `@${string}`,
|
||||
@@ -7572,6 +7643,21 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
|
||||
updated_at?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ShareLink"]: AliasType<{
|
||||
allowed_actions?:boolean | `@${string}`,
|
||||
created_at?:boolean | `@${string}`,
|
||||
expires_at?:boolean | `@${string}`,
|
||||
id?:boolean | `@${string}`,
|
||||
is_active?:boolean | `@${string}`,
|
||||
link_name?:boolean | `@${string}`,
|
||||
page_name?:boolean | `@${string}`,
|
||||
page_path?:boolean | `@${string}`,
|
||||
target_type?:boolean | `@${string}`,
|
||||
target_username?:boolean | `@${string}`,
|
||||
token?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ShareTargetType"]:ShareTargetType;
|
||||
["SignActAsChairmanInput"]: {
|
||||
/** Акт о вкладе результатов */
|
||||
act: ValueTypes["SignedDigitalDocumentInput"] | Variable<any, string>,
|
||||
@@ -7734,6 +7820,13 @@ searchPrivateAccounts?: [{ data: ValueTypes["SearchPrivateAccountsInput"] | Vari
|
||||
/** Распределение голосов */
|
||||
votes: Array<ValueTypes["VoteDistributionInput"]> | Variable<any, string>
|
||||
};
|
||||
["Subscription"]: AliasType<{
|
||||
capitalCommitCreated?: [{ project_hash?: string | undefined | null | Variable<any, string>},ValueTypes["CapitalCommit"]],
|
||||
capitalCommitUpdated?: [{ project_hash?: string | undefined | null | Variable<any, string>},ValueTypes["CapitalCommit"]],
|
||||
capitalIssueCreated?: [{ project_hash?: string | undefined | null | Variable<any, string>},ValueTypes["CapitalIssue"]],
|
||||
capitalIssueUpdated?: [{ project_hash?: string | undefined | null | Variable<any, string>},ValueTypes["CapitalIssue"]],
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["SubscriptionStatsDto"]: AliasType<{
|
||||
/** Количество активных подписок */
|
||||
active?:boolean | `@${string}`,
|
||||
@@ -8766,6 +8859,29 @@ export type ResolverInputTypes = {
|
||||
/** Голос (за/против/воздержался) */
|
||||
vote: string
|
||||
};
|
||||
["ApiKeyCreated"]: AliasType<{
|
||||
allowed_operations?:boolean | `@${string}`,
|
||||
created_at?:boolean | `@${string}`,
|
||||
expires_at?:boolean | `@${string}`,
|
||||
id?:boolean | `@${string}`,
|
||||
/** Полный ключ — показывается ТОЛЬКО при создании */
|
||||
key?:boolean | `@${string}`,
|
||||
key_prefix?:boolean | `@${string}`,
|
||||
name?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ApiKeyInfo"]: AliasType<{
|
||||
allowed_operations?:boolean | `@${string}`,
|
||||
created_at?:boolean | `@${string}`,
|
||||
created_by?:boolean | `@${string}`,
|
||||
expires_at?:boolean | `@${string}`,
|
||||
id?:boolean | `@${string}`,
|
||||
is_active?:boolean | `@${string}`,
|
||||
key_prefix?:boolean | `@${string}`,
|
||||
last_used_at?:boolean | `@${string}`,
|
||||
name?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
/** Одобрение документа председателем совета */
|
||||
["Approval"]: AliasType<{
|
||||
/** Дата создания записи */
|
||||
@@ -9006,6 +9122,13 @@ export type ResolverInputTypes = {
|
||||
/** Вес ожидания */
|
||||
waits?:ResolverInputTypes["WaitWeight"],
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["AvailableReport"]: AliasType<{
|
||||
deadline?:boolean | `@${string}`,
|
||||
name?:boolean | `@${string}`,
|
||||
period?:boolean | `@${string}`,
|
||||
type?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["BankAccount"]: AliasType<{
|
||||
/** Номер банковского счета */
|
||||
@@ -11097,6 +11220,11 @@ export type ResolverInputTypes = {
|
||||
proposal: ResolverInputTypes["AnnualGeneralMeetingAgendaSignedDocumentInput"],
|
||||
/** Имя аккаунта секретаря */
|
||||
secretary: string
|
||||
};
|
||||
["CreateApiKeyInput"]: {
|
||||
allowedOperations?: Array<string> | undefined | null,
|
||||
expiresInDays?: number | undefined | null,
|
||||
name: string
|
||||
};
|
||||
["CreateBranchInput"]: {
|
||||
/** Документ, на основании которого действует Уполномоченный (решение совета №СС-.. от ..) */
|
||||
@@ -11420,6 +11548,15 @@ export type ResolverInputTypes = {
|
||||
property_hash: string,
|
||||
/** Имя пользователя */
|
||||
username: string
|
||||
};
|
||||
["CreateShareLinkInput"]: {
|
||||
allowedActions: Array<string>,
|
||||
expiresInDays?: number | undefined | null,
|
||||
linkName?: string | undefined | null,
|
||||
pageName: string,
|
||||
pagePath: string,
|
||||
targetType: ResolverInputTypes["ShareTargetType"],
|
||||
targetUsername?: string | undefined | null
|
||||
};
|
||||
["CreateSovietIndividualDataInput"]: {
|
||||
/** Дата рождения */
|
||||
@@ -12157,6 +12294,11 @@ export type ResolverInputTypes = {
|
||||
username?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["GenerateReportInput"]: {
|
||||
period?: number | undefined | null,
|
||||
reportType: ResolverInputTypes["ReportType"],
|
||||
year: number
|
||||
};
|
||||
["GeneratedDocument"]: AliasType<{
|
||||
/** Бинарное содержимое документа (base64) */
|
||||
binary?:boolean | `@${string}`,
|
||||
@@ -12190,6 +12332,14 @@ export type ResolverInputTypes = {
|
||||
/** Название документа */
|
||||
title?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["GeneratedReport"]: AliasType<{
|
||||
errors?:boolean | `@${string}`,
|
||||
fileName?:boolean | `@${string}`,
|
||||
isValid?:boolean | `@${string}`,
|
||||
reportType?:boolean | `@${string}`,
|
||||
xml?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["GenerationContractGenerateDocumentInput"]: {
|
||||
/** Номер блока, на котором был создан документ */
|
||||
@@ -12994,12 +13144,14 @@ confirmAgreement?: [{ data: ResolverInputTypes["ConfirmAgreementInput"]},Resolve
|
||||
confirmReceiveOnRequest?: [{ data: ResolverInputTypes["ConfirmReceiveOnRequestInput"]},ResolverInputTypes["Transaction"]],
|
||||
confirmSupplyOnRequest?: [{ data: ResolverInputTypes["ConfirmSupplyOnRequestInput"]},ResolverInputTypes["Transaction"]],
|
||||
createAnnualGeneralMeet?: [{ data: ResolverInputTypes["CreateAnnualGeneralMeetInput"]},ResolverInputTypes["MeetAggregate"]],
|
||||
createApiKey?: [{ data: ResolverInputTypes["CreateApiKeyInput"]},ResolverInputTypes["ApiKeyCreated"]],
|
||||
createBranch?: [{ data: ResolverInputTypes["CreateBranchInput"]},ResolverInputTypes["Branch"]],
|
||||
createChildOrder?: [{ data: ResolverInputTypes["CreateChildOrderInput"]},ResolverInputTypes["Transaction"]],
|
||||
createDepositPayment?: [{ data: ResolverInputTypes["CreateDepositPaymentInput"]},ResolverInputTypes["GatewayPayment"]],
|
||||
createInitialPayment?: [{ data: ResolverInputTypes["CreateInitialPaymentInput"]},ResolverInputTypes["GatewayPayment"]],
|
||||
createParentOffer?: [{ data: ResolverInputTypes["CreateParentOfferInput"]},ResolverInputTypes["Transaction"]],
|
||||
createProjectOfFreeDecision?: [{ data: ResolverInputTypes["CreateProjectFreeDecisionInput"]},ResolverInputTypes["CreatedProjectFreeDecision"]],
|
||||
createShareLink?: [{ data: ResolverInputTypes["CreateShareLinkInput"]},ResolverInputTypes["ShareLink"]],
|
||||
createWebPushSubscription?: [{ data: ResolverInputTypes["CreateSubscriptionInput"]},ResolverInputTypes["CreateSubscriptionResponse"]],
|
||||
createWithdraw?: [{ data: ResolverInputTypes["CreateWithdrawInput"]},ResolverInputTypes["CreateWithdrawResponse"]],
|
||||
deactivateWebPushSubscriptionById?: [{ data: ResolverInputTypes["DeactivateSubscriptionInput"]},boolean | `@${string}`],
|
||||
@@ -13026,6 +13178,7 @@ generateParticipantApplicationDecision?: [{ data: ResolverInputTypes["Participan
|
||||
generatePrivacyAgreement?: [{ data: ResolverInputTypes["GenerateDocumentInput"], options?: ResolverInputTypes["GenerateDocumentOptionsInput"] | undefined | null},ResolverInputTypes["GeneratedDocument"]],
|
||||
generateProjectOfFreeDecision?: [{ data: ResolverInputTypes["ProjectFreeDecisionGenerateDocumentInput"], options?: ResolverInputTypes["GenerateDocumentOptionsInput"] | undefined | null},ResolverInputTypes["GeneratedDocument"]],
|
||||
generateRegistrationDocuments?: [{ data: ResolverInputTypes["GenerateRegistrationDocumentsInput"]},ResolverInputTypes["GenerateRegistrationDocumentsOutput"]],
|
||||
generateReport?: [{ data: ResolverInputTypes["GenerateReportInput"]},ResolverInputTypes["GeneratedReport"]],
|
||||
generateReturnByAssetAct?: [{ data: ResolverInputTypes["ReturnByAssetActGenerateDocumentInput"], options?: ResolverInputTypes["GenerateDocumentOptionsInput"] | undefined | null},ResolverInputTypes["GeneratedDocument"]],
|
||||
generateReturnByAssetDecision?: [{ data: ResolverInputTypes["ReturnByAssetDecisionGenerateDocumentInput"], options?: ResolverInputTypes["GenerateDocumentOptionsInput"] | undefined | null},ResolverInputTypes["GeneratedDocument"]],
|
||||
generateReturnByAssetStatement?: [{ data: ResolverInputTypes["ReturnByAssetStatementGenerateDocumentInput"], options?: ResolverInputTypes["GenerateDocumentOptionsInput"] | undefined | null},ResolverInputTypes["GeneratedDocument"]],
|
||||
@@ -13053,6 +13206,8 @@ registerAccount?: [{ data: ResolverInputTypes["RegisterAccountInput"]},ResolverI
|
||||
registerParticipant?: [{ data: ResolverInputTypes["RegisterParticipantInput"]},ResolverInputTypes["Account"]],
|
||||
resetKey?: [{ data: ResolverInputTypes["ResetKeyInput"]},boolean | `@${string}`],
|
||||
restartAnnualGeneralMeet?: [{ data: ResolverInputTypes["RestartAnnualGeneralMeetInput"]},ResolverInputTypes["MeetAggregate"]],
|
||||
revokeApiKey?: [{ id: string},boolean | `@${string}`],
|
||||
revokeShareLink?: [{ id: string},boolean | `@${string}`],
|
||||
selectBranch?: [{ data: ResolverInputTypes["SelectBranchInput"]},boolean | `@${string}`],
|
||||
sendAgreement?: [{ data: ResolverInputTypes["SendAgreementInput"]},ResolverInputTypes["Transaction"]],
|
||||
setPaymentStatus?: [{ data: ResolverInputTypes["SetPaymentStatusInput"]},ResolverInputTypes["GatewayPayment"]],
|
||||
@@ -14097,6 +14252,10 @@ getAccounts?: [{ data?: ResolverInputTypes["GetAccountsInput"] | undefined | nul
|
||||
getActions?: [{ filters?: ResolverInputTypes["ActionFiltersInput"] | undefined | null, pagination?: ResolverInputTypes["PaginationInput"] | undefined | null},ResolverInputTypes["PaginatedActionsPaginationResult"]],
|
||||
/** Получить список вопросов совета кооператива для голосования */
|
||||
getAgenda?:ResolverInputTypes["AgendaWithDocuments"],
|
||||
/** Получить список API ключей кооператива */
|
||||
getApiKeys?:ResolverInputTypes["ApiKeyInfo"],
|
||||
/** Получить список доступных типов отчётов */
|
||||
getAvailableReports?:ResolverInputTypes["AvailableReport"],
|
||||
getBranches?: [{ data: ResolverInputTypes["GetBranchesInput"]},ResolverInputTypes["Branch"]],
|
||||
getCapitalIssueLogs?: [{ data: ResolverInputTypes["GetCapitalIssueLogsInput"], options?: ResolverInputTypes["PaginationInput"] | undefined | null},ResolverInputTypes["PaginatedCapitalLogsPaginationResult"]],
|
||||
/** Получить состояние онбординга capital */
|
||||
@@ -14118,6 +14277,8 @@ getLedger?: [{ data: ResolverInputTypes["GetLedgerInput"]},ResolverInputTypes["L
|
||||
getLedgerHistory?: [{ data: ResolverInputTypes["GetLedgerHistoryInput"]},ResolverInputTypes["LedgerHistoryResponse"]],
|
||||
getMeet?: [{ data: ResolverInputTypes["GetMeetInput"]},ResolverInputTypes["MeetAggregate"]],
|
||||
getMeets?: [{ data: ResolverInputTypes["GetMeetsInput"]},ResolverInputTypes["MeetAggregate"]],
|
||||
/** Получить созданные мной ссылки доступа */
|
||||
getMyShareLinks?:ResolverInputTypes["ShareLink"],
|
||||
getPaymentMethods?: [{ data?: ResolverInputTypes["GetPaymentMethodsInput"] | undefined | null},ResolverInputTypes["PaymentMethodPaginationResult"]],
|
||||
getPayments?: [{ data?: ResolverInputTypes["PaymentFiltersInput"] | undefined | null, options?: ResolverInputTypes["PaginationInput"] | undefined | null},ResolverInputTypes["PaginatedGatewayPaymentsPaginationResult"]],
|
||||
getProgramWallet?: [{ filter: ResolverInputTypes["ProgramWalletFilterInput"]},ResolverInputTypes["ProgramWallet"]],
|
||||
@@ -14126,6 +14287,8 @@ getProviderSubscriptionById?: [{ id: number},ResolverInputTypes["ProviderSubscri
|
||||
/** Получить подписки пользователя у провайдера */
|
||||
getProviderSubscriptions?:ResolverInputTypes["ProviderSubscription"],
|
||||
getRegistrationConfig?: [{ account_type: ResolverInputTypes["AccountType"], coopname: string},ResolverInputTypes["RegistrationConfig"]],
|
||||
/** Получить страницы, к которым мне предоставлен доступ */
|
||||
getSharedWithMe?:ResolverInputTypes["ShareLink"],
|
||||
/** Получить сводную публичную информацию о системе */
|
||||
getSystemInfo?:ResolverInputTypes["SystemInfo"],
|
||||
getUserWebPushSubscriptions?: [{ data: ResolverInputTypes["GetUserSubscriptionsInput"]},ResolverInputTypes["WebPushSubscriptionDto"]],
|
||||
@@ -14294,6 +14457,7 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
|
||||
title?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ReportType"]:ReportType;
|
||||
["RepresentedBy"]: AliasType<{
|
||||
/** На основании чего действует */
|
||||
based_on?:boolean | `@${string}`,
|
||||
@@ -14877,6 +15041,21 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
|
||||
updated_at?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ShareLink"]: AliasType<{
|
||||
allowed_actions?:boolean | `@${string}`,
|
||||
created_at?:boolean | `@${string}`,
|
||||
expires_at?:boolean | `@${string}`,
|
||||
id?:boolean | `@${string}`,
|
||||
is_active?:boolean | `@${string}`,
|
||||
link_name?:boolean | `@${string}`,
|
||||
page_name?:boolean | `@${string}`,
|
||||
page_path?:boolean | `@${string}`,
|
||||
target_type?:boolean | `@${string}`,
|
||||
target_username?:boolean | `@${string}`,
|
||||
token?:boolean | `@${string}`,
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["ShareTargetType"]:ShareTargetType;
|
||||
["SignActAsChairmanInput"]: {
|
||||
/** Акт о вкладе результатов */
|
||||
act: ResolverInputTypes["SignedDigitalDocumentInput"],
|
||||
@@ -15039,6 +15218,13 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
|
||||
/** Распределение голосов */
|
||||
votes: Array<ResolverInputTypes["VoteDistributionInput"]>
|
||||
};
|
||||
["Subscription"]: AliasType<{
|
||||
capitalCommitCreated?: [{ project_hash?: string | undefined | null},ResolverInputTypes["CapitalCommit"]],
|
||||
capitalCommitUpdated?: [{ project_hash?: string | undefined | null},ResolverInputTypes["CapitalCommit"]],
|
||||
capitalIssueCreated?: [{ project_hash?: string | undefined | null},ResolverInputTypes["CapitalIssue"]],
|
||||
capitalIssueUpdated?: [{ project_hash?: string | undefined | null},ResolverInputTypes["CapitalIssue"]],
|
||||
__typename?: boolean | `@${string}`
|
||||
}>;
|
||||
["SubscriptionStatsDto"]: AliasType<{
|
||||
/** Количество активных подписок */
|
||||
active?:boolean | `@${string}`,
|
||||
@@ -15523,6 +15709,7 @@ searchPrivateAccounts?: [{ data: ResolverInputTypes["SearchPrivateAccountsInput"
|
||||
["schema"]: AliasType<{
|
||||
query?:ResolverInputTypes["Query"],
|
||||
mutation?:ResolverInputTypes["Mutation"],
|
||||
subscription?:ResolverInputTypes["Subscription"],
|
||||
__typename?: boolean | `@${string}`
|
||||
}>
|
||||
}
|
||||
@@ -16063,6 +16250,27 @@ export type ModelTypes = {
|
||||
number: string,
|
||||
/** Голос (за/против/воздержался) */
|
||||
vote: string
|
||||
};
|
||||
["ApiKeyCreated"]: {
|
||||
allowed_operations: Array<string>,
|
||||
created_at: ModelTypes["DateTime"],
|
||||
expires_at?: ModelTypes["DateTime"] | undefined | null,
|
||||
id: string,
|
||||
/** Полный ключ — показывается ТОЛЬКО при создании */
|
||||
key: string,
|
||||
key_prefix: string,
|
||||
name: string
|
||||
};
|
||||
["ApiKeyInfo"]: {
|
||||
allowed_operations: Array<string>,
|
||||
created_at: ModelTypes["DateTime"],
|
||||
created_by: string,
|
||||
expires_at?: ModelTypes["DateTime"] | undefined | null,
|
||||
id: string,
|
||||
is_active: boolean,
|
||||
key_prefix: string,
|
||||
last_used_at?: ModelTypes["DateTime"] | undefined | null,
|
||||
name: string
|
||||
};
|
||||
/** Одобрение документа председателем совета */
|
||||
["Approval"]: {
|
||||
@@ -16300,6 +16508,12 @@ export type ModelTypes = {
|
||||
threshold: number,
|
||||
/** Вес ожидания */
|
||||
waits: Array<ModelTypes["WaitWeight"]>
|
||||
};
|
||||
["AvailableReport"]: {
|
||||
deadline: string,
|
||||
name: string,
|
||||
period: string,
|
||||
type: ModelTypes["ReportType"]
|
||||
};
|
||||
["BankAccount"]: {
|
||||
/** Номер банковского счета */
|
||||
@@ -18338,6 +18552,11 @@ export type ModelTypes = {
|
||||
proposal: ModelTypes["AnnualGeneralMeetingAgendaSignedDocumentInput"],
|
||||
/** Имя аккаунта секретаря */
|
||||
secretary: string
|
||||
};
|
||||
["CreateApiKeyInput"]: {
|
||||
allowedOperations?: Array<string> | undefined | null,
|
||||
expiresInDays?: number | undefined | null,
|
||||
name: string
|
||||
};
|
||||
["CreateBranchInput"]: {
|
||||
/** Документ, на основании которого действует Уполномоченный (решение совета №СС-.. от ..) */
|
||||
@@ -18661,6 +18880,15 @@ export type ModelTypes = {
|
||||
property_hash: string,
|
||||
/** Имя пользователя */
|
||||
username: string
|
||||
};
|
||||
["CreateShareLinkInput"]: {
|
||||
allowedActions: Array<string>,
|
||||
expiresInDays?: number | undefined | null,
|
||||
linkName?: string | undefined | null,
|
||||
pageName: string,
|
||||
pagePath: string,
|
||||
targetType: ModelTypes["ShareTargetType"],
|
||||
targetUsername?: string | undefined | null
|
||||
};
|
||||
["CreateSovietIndividualDataInput"]: {
|
||||
/** Дата рождения */
|
||||
@@ -19369,6 +19597,11 @@ export type ModelTypes = {
|
||||
documents: Array<ModelTypes["GeneratedRegistrationDocument"]>,
|
||||
/** Имя пользователя */
|
||||
username: string
|
||||
};
|
||||
["GenerateReportInput"]: {
|
||||
period?: number | undefined | null,
|
||||
reportType: ModelTypes["ReportType"],
|
||||
year: number
|
||||
};
|
||||
["GeneratedDocument"]: {
|
||||
/** Бинарное содержимое документа (base64) */
|
||||
@@ -19401,6 +19634,13 @@ export type ModelTypes = {
|
||||
order: number,
|
||||
/** Название документа */
|
||||
title: string
|
||||
};
|
||||
["GeneratedReport"]: {
|
||||
errors: Array<string>,
|
||||
fileName: string,
|
||||
isValid: boolean,
|
||||
reportType: ModelTypes["ReportType"],
|
||||
xml: string
|
||||
};
|
||||
["GenerationContractGenerateDocumentInput"]: {
|
||||
/** Номер блока, на котором был создан документ */
|
||||
@@ -20272,6 +20512,8 @@ export type ModelTypes = {
|
||||
confirmSupplyOnRequest: ModelTypes["Transaction"],
|
||||
/** Сгенерировать документ предложения повестки очередного общего собрания пайщиков */
|
||||
createAnnualGeneralMeet: ModelTypes["MeetAggregate"],
|
||||
/** Создать API ключ кооператива. Полный ключ показывается только при создании. */
|
||||
createApiKey: ModelTypes["ApiKeyCreated"],
|
||||
/** Создать кооперативный участок */
|
||||
createBranch: ModelTypes["Branch"],
|
||||
/** Создать заявку на поставку имущества по предложению Поставщика */
|
||||
@@ -20284,6 +20526,8 @@ export type ModelTypes = {
|
||||
createParentOffer: ModelTypes["Transaction"],
|
||||
/** Создать повестку дня и проект решения, и сохранить в хранилище для дальнейшей генерации документа и его публикации */
|
||||
createProjectOfFreeDecision: ModelTypes["CreatedProjectFreeDecision"],
|
||||
/** Создать ссылку доступа к странице */
|
||||
createShareLink: ModelTypes["ShareLink"],
|
||||
/** Создать веб-пуш подписку для пользователя */
|
||||
createWebPushSubscription: ModelTypes["CreateSubscriptionResponse"],
|
||||
/** Создать заявку на вывод средств */
|
||||
@@ -20336,6 +20580,8 @@ export type ModelTypes = {
|
||||
generateProjectOfFreeDecision: ModelTypes["GeneratedDocument"],
|
||||
/** Генерирует пакет документов для регистрации пайщика. Возвращает список документов с метаданными для отображения на фронтенде. */
|
||||
generateRegistrationDocuments: ModelTypes["GenerateRegistrationDocumentsOutput"],
|
||||
/** Генерация отчёта для ФНС/ФСС */
|
||||
generateReport: ModelTypes["GeneratedReport"],
|
||||
/** Сгенерировать документ акта возврата имущества. */
|
||||
generateReturnByAssetAct: ModelTypes["GeneratedDocument"],
|
||||
/** Сгенерировать документ решения о возврате имущества. */
|
||||
@@ -20390,6 +20636,10 @@ export type ModelTypes = {
|
||||
resetKey: boolean,
|
||||
/** Перезапуск общего собрания пайщиков */
|
||||
restartAnnualGeneralMeet: ModelTypes["MeetAggregate"],
|
||||
/** Отозвать API ключ */
|
||||
revokeApiKey: boolean,
|
||||
/** Отозвать ссылку доступа */
|
||||
revokeShareLink: boolean,
|
||||
/** Выбрать кооперативный участок */
|
||||
selectBranch: boolean,
|
||||
/** Отправить соглашение */
|
||||
@@ -21431,6 +21681,10 @@ export type ModelTypes = {
|
||||
getActions: ModelTypes["PaginatedActionsPaginationResult"],
|
||||
/** Получить список вопросов совета кооператива для голосования */
|
||||
getAgenda: Array<ModelTypes["AgendaWithDocuments"]>,
|
||||
/** Получить список API ключей кооператива */
|
||||
getApiKeys: Array<ModelTypes["ApiKeyInfo"]>,
|
||||
/** Получить список доступных типов отчётов */
|
||||
getAvailableReports: Array<ModelTypes["AvailableReport"]>,
|
||||
/** Получить список кооперативных участков */
|
||||
getBranches: Array<ModelTypes["Branch"]>,
|
||||
/** Получить логи событий по задаче */
|
||||
@@ -21464,6 +21718,8 @@ export type ModelTypes = {
|
||||
getMeet: ModelTypes["MeetAggregate"],
|
||||
/** Получить список всех собраний кооператива */
|
||||
getMeets: Array<ModelTypes["MeetAggregate"]>,
|
||||
/** Получить созданные мной ссылки доступа */
|
||||
getMyShareLinks: Array<ModelTypes["ShareLink"]>,
|
||||
/** Получить список методов оплаты */
|
||||
getPaymentMethods: ModelTypes["PaymentMethodPaginationResult"],
|
||||
/** Получить список платежей с возможностью фильтрации по типу, статусу и направлению. */
|
||||
@@ -21478,6 +21734,8 @@ export type ModelTypes = {
|
||||
getProviderSubscriptions: Array<ModelTypes["ProviderSubscription"]>,
|
||||
/** Получить конфигурацию программ регистрации для кооператива */
|
||||
getRegistrationConfig: ModelTypes["RegistrationConfig"],
|
||||
/** Получить страницы, к которым мне предоставлен доступ */
|
||||
getSharedWithMe: Array<ModelTypes["ShareLink"]>,
|
||||
/** Получить сводную публичную информацию о системе */
|
||||
getSystemInfo: ModelTypes["SystemInfo"],
|
||||
/** Получить веб-пуш подписки пользователя */
|
||||
@@ -21644,6 +21902,7 @@ export type ModelTypes = {
|
||||
/** Название программы для отображения */
|
||||
title: string
|
||||
};
|
||||
["ReportType"]:ReportType;
|
||||
["RepresentedBy"]: {
|
||||
/** На основании чего действует */
|
||||
based_on: string,
|
||||
@@ -22218,6 +22477,20 @@ export type ModelTypes = {
|
||||
/** Дата последнего обновления */
|
||||
updated_at: ModelTypes["DateTime"]
|
||||
};
|
||||
["ShareLink"]: {
|
||||
allowed_actions: Array<string>,
|
||||
created_at: ModelTypes["DateTime"],
|
||||
expires_at?: ModelTypes["DateTime"] | undefined | null,
|
||||
id: string,
|
||||
is_active: boolean,
|
||||
link_name?: string | undefined | null,
|
||||
page_name: string,
|
||||
page_path: string,
|
||||
target_type: ModelTypes["ShareTargetType"],
|
||||
target_username?: string | undefined | null,
|
||||
token: string
|
||||
};
|
||||
["ShareTargetType"]:ShareTargetType;
|
||||
["SignActAsChairmanInput"]: {
|
||||
/** Акт о вкладе результатов */
|
||||
act: ModelTypes["SignedDigitalDocumentInput"],
|
||||
@@ -22373,6 +22646,16 @@ export type ModelTypes = {
|
||||
project_hash: string,
|
||||
/** Распределение голосов */
|
||||
votes: Array<ModelTypes["VoteDistributionInput"]>
|
||||
};
|
||||
["Subscription"]: {
|
||||
/** Подписка на создание коммитов */
|
||||
capitalCommitCreated: ModelTypes["CapitalCommit"],
|
||||
/** Подписка на обновления коммитов */
|
||||
capitalCommitUpdated: ModelTypes["CapitalCommit"],
|
||||
/** Подписка на создание задач */
|
||||
capitalIssueCreated: ModelTypes["CapitalIssue"],
|
||||
/** Подписка на обновления задач */
|
||||
capitalIssueUpdated: ModelTypes["CapitalIssue"]
|
||||
};
|
||||
["SubscriptionStatsDto"]: {
|
||||
/** Количество активных подписок */
|
||||
@@ -22836,7 +23119,8 @@ export type ModelTypes = {
|
||||
};
|
||||
["schema"]: {
|
||||
query?: ModelTypes["Query"] | undefined | null,
|
||||
mutation?: ModelTypes["Mutation"] | undefined | null
|
||||
mutation?: ModelTypes["Mutation"] | undefined | null,
|
||||
subscription?: ModelTypes["Subscription"] | undefined | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23392,6 +23676,29 @@ export type GraphQLTypes = {
|
||||
number: string,
|
||||
/** Голос (за/против/воздержался) */
|
||||
vote: string
|
||||
};
|
||||
["ApiKeyCreated"]: {
|
||||
__typename: "ApiKeyCreated",
|
||||
allowed_operations: Array<string>,
|
||||
created_at: GraphQLTypes["DateTime"],
|
||||
expires_at?: GraphQLTypes["DateTime"] | undefined | null,
|
||||
id: string,
|
||||
/** Полный ключ — показывается ТОЛЬКО при создании */
|
||||
key: string,
|
||||
key_prefix: string,
|
||||
name: string
|
||||
};
|
||||
["ApiKeyInfo"]: {
|
||||
__typename: "ApiKeyInfo",
|
||||
allowed_operations: Array<string>,
|
||||
created_at: GraphQLTypes["DateTime"],
|
||||
created_by: string,
|
||||
expires_at?: GraphQLTypes["DateTime"] | undefined | null,
|
||||
id: string,
|
||||
is_active: boolean,
|
||||
key_prefix: string,
|
||||
last_used_at?: GraphQLTypes["DateTime"] | undefined | null,
|
||||
name: string
|
||||
};
|
||||
/** Одобрение документа председателем совета */
|
||||
["Approval"]: {
|
||||
@@ -23633,6 +23940,13 @@ export type GraphQLTypes = {
|
||||
threshold: number,
|
||||
/** Вес ожидания */
|
||||
waits: Array<GraphQLTypes["WaitWeight"]>
|
||||
};
|
||||
["AvailableReport"]: {
|
||||
__typename: "AvailableReport",
|
||||
deadline: string,
|
||||
name: string,
|
||||
period: string,
|
||||
type: GraphQLTypes["ReportType"]
|
||||
};
|
||||
["BankAccount"]: {
|
||||
__typename: "BankAccount",
|
||||
@@ -25724,6 +26038,11 @@ export type GraphQLTypes = {
|
||||
proposal: GraphQLTypes["AnnualGeneralMeetingAgendaSignedDocumentInput"],
|
||||
/** Имя аккаунта секретаря */
|
||||
secretary: string
|
||||
};
|
||||
["CreateApiKeyInput"]: {
|
||||
allowedOperations?: Array<string> | undefined | null,
|
||||
expiresInDays?: number | undefined | null,
|
||||
name: string
|
||||
};
|
||||
["CreateBranchInput"]: {
|
||||
/** Документ, на основании которого действует Уполномоченный (решение совета №СС-.. от ..) */
|
||||
@@ -26047,6 +26366,15 @@ export type GraphQLTypes = {
|
||||
property_hash: string,
|
||||
/** Имя пользователя */
|
||||
username: string
|
||||
};
|
||||
["CreateShareLinkInput"]: {
|
||||
allowedActions: Array<string>,
|
||||
expiresInDays?: number | undefined | null,
|
||||
linkName?: string | undefined | null,
|
||||
pageName: string,
|
||||
pagePath: string,
|
||||
targetType: GraphQLTypes["ShareTargetType"],
|
||||
targetUsername?: string | undefined | null
|
||||
};
|
||||
["CreateSovietIndividualDataInput"]: {
|
||||
/** Дата рождения */
|
||||
@@ -26783,6 +27111,11 @@ export type GraphQLTypes = {
|
||||
documents: Array<GraphQLTypes["GeneratedRegistrationDocument"]>,
|
||||
/** Имя пользователя */
|
||||
username: string
|
||||
};
|
||||
["GenerateReportInput"]: {
|
||||
period?: number | undefined | null,
|
||||
reportType: GraphQLTypes["ReportType"],
|
||||
year: number
|
||||
};
|
||||
["GeneratedDocument"]: {
|
||||
__typename: "GeneratedDocument",
|
||||
@@ -26817,6 +27150,14 @@ export type GraphQLTypes = {
|
||||
order: number,
|
||||
/** Название документа */
|
||||
title: string
|
||||
};
|
||||
["GeneratedReport"]: {
|
||||
__typename: "GeneratedReport",
|
||||
errors: Array<string>,
|
||||
fileName: string,
|
||||
isValid: boolean,
|
||||
reportType: GraphQLTypes["ReportType"],
|
||||
xml: string
|
||||
};
|
||||
["GenerationContractGenerateDocumentInput"]: {
|
||||
/** Номер блока, на котором был создан документ */
|
||||
@@ -27710,6 +28051,8 @@ export type GraphQLTypes = {
|
||||
confirmSupplyOnRequest: GraphQLTypes["Transaction"],
|
||||
/** Сгенерировать документ предложения повестки очередного общего собрания пайщиков */
|
||||
createAnnualGeneralMeet: GraphQLTypes["MeetAggregate"],
|
||||
/** Создать API ключ кооператива. Полный ключ показывается только при создании. */
|
||||
createApiKey: GraphQLTypes["ApiKeyCreated"],
|
||||
/** Создать кооперативный участок */
|
||||
createBranch: GraphQLTypes["Branch"],
|
||||
/** Создать заявку на поставку имущества по предложению Поставщика */
|
||||
@@ -27722,6 +28065,8 @@ export type GraphQLTypes = {
|
||||
createParentOffer: GraphQLTypes["Transaction"],
|
||||
/** Создать повестку дня и проект решения, и сохранить в хранилище для дальнейшей генерации документа и его публикации */
|
||||
createProjectOfFreeDecision: GraphQLTypes["CreatedProjectFreeDecision"],
|
||||
/** Создать ссылку доступа к странице */
|
||||
createShareLink: GraphQLTypes["ShareLink"],
|
||||
/** Создать веб-пуш подписку для пользователя */
|
||||
createWebPushSubscription: GraphQLTypes["CreateSubscriptionResponse"],
|
||||
/** Создать заявку на вывод средств */
|
||||
@@ -27774,6 +28119,8 @@ export type GraphQLTypes = {
|
||||
generateProjectOfFreeDecision: GraphQLTypes["GeneratedDocument"],
|
||||
/** Генерирует пакет документов для регистрации пайщика. Возвращает список документов с метаданными для отображения на фронтенде. */
|
||||
generateRegistrationDocuments: GraphQLTypes["GenerateRegistrationDocumentsOutput"],
|
||||
/** Генерация отчёта для ФНС/ФСС */
|
||||
generateReport: GraphQLTypes["GeneratedReport"],
|
||||
/** Сгенерировать документ акта возврата имущества. */
|
||||
generateReturnByAssetAct: GraphQLTypes["GeneratedDocument"],
|
||||
/** Сгенерировать документ решения о возврате имущества. */
|
||||
@@ -27828,6 +28175,10 @@ export type GraphQLTypes = {
|
||||
resetKey: boolean,
|
||||
/** Перезапуск общего собрания пайщиков */
|
||||
restartAnnualGeneralMeet: GraphQLTypes["MeetAggregate"],
|
||||
/** Отозвать API ключ */
|
||||
revokeApiKey: boolean,
|
||||
/** Отозвать ссылку доступа */
|
||||
revokeShareLink: boolean,
|
||||
/** Выбрать кооперативный участок */
|
||||
selectBranch: boolean,
|
||||
/** Отправить соглашение */
|
||||
@@ -28937,6 +29288,10 @@ export type GraphQLTypes = {
|
||||
getActions: GraphQLTypes["PaginatedActionsPaginationResult"],
|
||||
/** Получить список вопросов совета кооператива для голосования */
|
||||
getAgenda: Array<GraphQLTypes["AgendaWithDocuments"]>,
|
||||
/** Получить список API ключей кооператива */
|
||||
getApiKeys: Array<GraphQLTypes["ApiKeyInfo"]>,
|
||||
/** Получить список доступных типов отчётов */
|
||||
getAvailableReports: Array<GraphQLTypes["AvailableReport"]>,
|
||||
/** Получить список кооперативных участков */
|
||||
getBranches: Array<GraphQLTypes["Branch"]>,
|
||||
/** Получить логи событий по задаче */
|
||||
@@ -28970,6 +29325,8 @@ export type GraphQLTypes = {
|
||||
getMeet: GraphQLTypes["MeetAggregate"],
|
||||
/** Получить список всех собраний кооператива */
|
||||
getMeets: Array<GraphQLTypes["MeetAggregate"]>,
|
||||
/** Получить созданные мной ссылки доступа */
|
||||
getMyShareLinks: Array<GraphQLTypes["ShareLink"]>,
|
||||
/** Получить список методов оплаты */
|
||||
getPaymentMethods: GraphQLTypes["PaymentMethodPaginationResult"],
|
||||
/** Получить список платежей с возможностью фильтрации по типу, статусу и направлению. */
|
||||
@@ -28984,6 +29341,8 @@ export type GraphQLTypes = {
|
||||
getProviderSubscriptions: Array<GraphQLTypes["ProviderSubscription"]>,
|
||||
/** Получить конфигурацию программ регистрации для кооператива */
|
||||
getRegistrationConfig: GraphQLTypes["RegistrationConfig"],
|
||||
/** Получить страницы, к которым мне предоставлен доступ */
|
||||
getSharedWithMe: Array<GraphQLTypes["ShareLink"]>,
|
||||
/** Получить сводную публичную информацию о системе */
|
||||
getSystemInfo: GraphQLTypes["SystemInfo"],
|
||||
/** Получить веб-пуш подписки пользователя */
|
||||
@@ -29155,6 +29514,7 @@ export type GraphQLTypes = {
|
||||
/** Название программы для отображения */
|
||||
title: string
|
||||
};
|
||||
["ReportType"]: ReportType;
|
||||
["RepresentedBy"]: {
|
||||
__typename: "RepresentedBy",
|
||||
/** На основании чего действует */
|
||||
@@ -29738,6 +30098,21 @@ export type GraphQLTypes = {
|
||||
/** Дата последнего обновления */
|
||||
updated_at: GraphQLTypes["DateTime"]
|
||||
};
|
||||
["ShareLink"]: {
|
||||
__typename: "ShareLink",
|
||||
allowed_actions: Array<string>,
|
||||
created_at: GraphQLTypes["DateTime"],
|
||||
expires_at?: GraphQLTypes["DateTime"] | undefined | null,
|
||||
id: string,
|
||||
is_active: boolean,
|
||||
link_name?: string | undefined | null,
|
||||
page_name: string,
|
||||
page_path: string,
|
||||
target_type: GraphQLTypes["ShareTargetType"],
|
||||
target_username?: string | undefined | null,
|
||||
token: string
|
||||
};
|
||||
["ShareTargetType"]: ShareTargetType;
|
||||
["SignActAsChairmanInput"]: {
|
||||
/** Акт о вкладе результатов */
|
||||
act: GraphQLTypes["SignedDigitalDocumentInput"],
|
||||
@@ -29899,6 +30274,17 @@ export type GraphQLTypes = {
|
||||
project_hash: string,
|
||||
/** Распределение голосов */
|
||||
votes: Array<GraphQLTypes["VoteDistributionInput"]>
|
||||
};
|
||||
["Subscription"]: {
|
||||
__typename: "Subscription",
|
||||
/** Подписка на создание коммитов */
|
||||
capitalCommitCreated: GraphQLTypes["CapitalCommit"],
|
||||
/** Подписка на обновления коммитов */
|
||||
capitalCommitUpdated: GraphQLTypes["CapitalCommit"],
|
||||
/** Подписка на создание задач */
|
||||
capitalIssueCreated: GraphQLTypes["CapitalIssue"],
|
||||
/** Подписка на обновления задач */
|
||||
capitalIssueUpdated: GraphQLTypes["CapitalIssue"]
|
||||
};
|
||||
["SubscriptionStatsDto"]: {
|
||||
__typename: "SubscriptionStatsDto",
|
||||
@@ -30653,6 +31039,16 @@ export enum ProjectStatus {
|
||||
UNDEFINED = "UNDEFINED",
|
||||
VOTING = "VOTING"
|
||||
}
|
||||
export enum ReportType {
|
||||
BUHOTCH = "BUHOTCH",
|
||||
DUSN = "DUSN",
|
||||
FSS4 = "FSS4",
|
||||
NDFL6 = "NDFL6",
|
||||
PSV = "PSV",
|
||||
RSV = "RSV",
|
||||
UUSN = "UUSN",
|
||||
UV_VZNOSY = "UV_VZNOSY"
|
||||
}
|
||||
/** Статус результата в системе CAPITAL */
|
||||
export enum ResultStatus {
|
||||
ACT1 = "ACT1",
|
||||
@@ -30677,6 +31073,10 @@ export enum SegmentStatus {
|
||||
STATEMENT = "STATEMENT",
|
||||
UNDEFINED = "UNDEFINED"
|
||||
}
|
||||
export enum ShareTargetType {
|
||||
GUEST = "GUEST",
|
||||
MEMBER = "MEMBER"
|
||||
}
|
||||
/** Статус истории в системе CAPITAL */
|
||||
export enum StoryStatus {
|
||||
CANCELLED = "CANCELLED",
|
||||
@@ -30791,6 +31191,7 @@ type ZEUS_VARIABLES = {
|
||||
["ConvertToAxonStatementSignedMetaDocumentInput"]: ValueTypes["ConvertToAxonStatementSignedMetaDocumentInput"];
|
||||
["Country"]: ValueTypes["Country"];
|
||||
["CreateAnnualGeneralMeetInput"]: ValueTypes["CreateAnnualGeneralMeetInput"];
|
||||
["CreateApiKeyInput"]: ValueTypes["CreateApiKeyInput"];
|
||||
["CreateBranchInput"]: ValueTypes["CreateBranchInput"];
|
||||
["CreateChildOrderInput"]: ValueTypes["CreateChildOrderInput"];
|
||||
["CreateCommitInput"]: ValueTypes["CreateCommitInput"];
|
||||
@@ -30812,6 +31213,7 @@ type ZEUS_VARIABLES = {
|
||||
["CreateProjectInput"]: ValueTypes["CreateProjectInput"];
|
||||
["CreateProjectInvestInput"]: ValueTypes["CreateProjectInvestInput"];
|
||||
["CreateProjectPropertyInput"]: ValueTypes["CreateProjectPropertyInput"];
|
||||
["CreateShareLinkInput"]: ValueTypes["CreateShareLinkInput"];
|
||||
["CreateSovietIndividualDataInput"]: ValueTypes["CreateSovietIndividualDataInput"];
|
||||
["CreateStoryInput"]: ValueTypes["CreateStoryInput"];
|
||||
["CreateSubscriptionInput"]: ValueTypes["CreateSubscriptionInput"];
|
||||
@@ -30851,6 +31253,7 @@ type ZEUS_VARIABLES = {
|
||||
["GenerateDocumentInput"]: ValueTypes["GenerateDocumentInput"];
|
||||
["GenerateDocumentOptionsInput"]: ValueTypes["GenerateDocumentOptionsInput"];
|
||||
["GenerateRegistrationDocumentsInput"]: ValueTypes["GenerateRegistrationDocumentsInput"];
|
||||
["GenerateReportInput"]: ValueTypes["GenerateReportInput"];
|
||||
["GenerationContractGenerateDocumentInput"]: ValueTypes["GenerationContractGenerateDocumentInput"];
|
||||
["GenerationContractSignedDocumentInput"]: ValueTypes["GenerationContractSignedDocumentInput"];
|
||||
["GenerationContractSignedMetaDocumentInput"]: ValueTypes["GenerationContractSignedMetaDocumentInput"];
|
||||
@@ -30946,6 +31349,7 @@ type ZEUS_VARIABLES = {
|
||||
["RegisterAccountInput"]: ValueTypes["RegisterAccountInput"];
|
||||
["RegisterContributorInput"]: ValueTypes["RegisterContributorInput"];
|
||||
["RegisterParticipantInput"]: ValueTypes["RegisterParticipantInput"];
|
||||
["ReportType"]: ValueTypes["ReportType"];
|
||||
["RepresentedByInput"]: ValueTypes["RepresentedByInput"];
|
||||
["ResetKeyInput"]: ValueTypes["ResetKeyInput"];
|
||||
["RestartAnnualGeneralMeetInput"]: ValueTypes["RestartAnnualGeneralMeetInput"];
|
||||
@@ -30980,6 +31384,7 @@ type ZEUS_VARIABLES = {
|
||||
["SetPlanInput"]: ValueTypes["SetPlanInput"];
|
||||
["SetVarsInput"]: ValueTypes["SetVarsInput"];
|
||||
["SetWifInput"]: ValueTypes["SetWifInput"];
|
||||
["ShareTargetType"]: ValueTypes["ShareTargetType"];
|
||||
["SignActAsChairmanInput"]: ValueTypes["SignActAsChairmanInput"];
|
||||
["SignActAsContributorInput"]: ValueTypes["SignActAsContributorInput"];
|
||||
["SignByPresiderOnAnnualGeneralMeetInput"]: ValueTypes["SignByPresiderOnAnnualGeneralMeetInput"];
|
||||
|
||||
Generated
+50
@@ -167,6 +167,9 @@ importers:
|
||||
'@a2seven/yoo-checkout':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.4
|
||||
'@casl/ability':
|
||||
specifier: ^6.8.0
|
||||
version: 6.8.0
|
||||
'@coopenomics/factory':
|
||||
specifier: workspace:*
|
||||
version: link:../factory
|
||||
@@ -338,6 +341,9 @@ importers:
|
||||
graphql-scalars:
|
||||
specifier: ^1.24.0
|
||||
version: 1.24.0(graphql@16.9.0)
|
||||
graphql-subscriptions:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0(graphql@16.9.0)
|
||||
graphql-tools:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2(@types/react@18.3.20)(graphql-ws@5.16.0(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(subscriptions-transport-ws@0.11.0(graphql@16.9.0))
|
||||
@@ -2329,6 +2335,9 @@ packages:
|
||||
'@bufbuild/protobuf@1.10.1':
|
||||
resolution: {integrity: sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==}
|
||||
|
||||
'@casl/ability@6.8.0':
|
||||
resolution: {integrity: sha512-Ipt4mzI4gSgnomFdaPjaLgY2MWuXqAEZLrU6qqWBB7khGiBBuuEp6ytYDnq09bRXqcjaeeHiaCvCGFbBA2SpvA==}
|
||||
|
||||
'@cfaester/enzyme-adapter-react-18@0.8.0':
|
||||
resolution: {integrity: sha512-3Z3ThTUouHwz8oIyhTYQljEMNRFtlVyc3VOOHCbxs47U6cnXs8K9ygi/c1tv49s7MBlTXeIcuN+Ttd9aPtILFQ==}
|
||||
peerDependencies:
|
||||
@@ -6465,6 +6474,18 @@ packages:
|
||||
resolution: {integrity: sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@ucast/core@1.10.2':
|
||||
resolution: {integrity: sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g==}
|
||||
|
||||
'@ucast/js@3.1.0':
|
||||
resolution: {integrity: sha512-eJ7yQeYtMK85UZjxoxBEbTWx6UMxEXKbjVyp+NlzrT5oMKV5Gpo/9bjTl3r7msaXTVC8iD9NJacqJ8yp7joX+Q==}
|
||||
|
||||
'@ucast/mongo2js@1.4.1':
|
||||
resolution: {integrity: sha512-9aeg5cmqwRQnKCXHN6I17wk83Rcm487bHelaG8T4vfpWneAI469wSI3Srnbu+PuZ5znWRbnwtVq9RgPL+bN6CA==}
|
||||
|
||||
'@ucast/mongo@2.4.3':
|
||||
resolution: {integrity: sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==}
|
||||
|
||||
'@ungap/structured-clone@1.2.0':
|
||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||
|
||||
@@ -10441,6 +10462,11 @@ packages:
|
||||
peerDependencies:
|
||||
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
|
||||
graphql-subscriptions@3.0.0:
|
||||
resolution: {integrity: sha512-kZCdevgmzDjGAOqH7GlDmQXYAkuHoKpMlJrqF40HMPhUhM5ZWSFSxCwD/nSi6AkaijmMfsFhoJRGJ27UseCvRA==}
|
||||
peerDependencies:
|
||||
graphql: ^15.7.2 || ^16.0.0
|
||||
|
||||
graphql-tag@2.12.6:
|
||||
resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -18697,6 +18723,10 @@ snapshots:
|
||||
|
||||
'@bufbuild/protobuf@1.10.1': {}
|
||||
|
||||
'@casl/ability@6.8.0':
|
||||
dependencies:
|
||||
'@ucast/mongo2js': 1.4.1
|
||||
|
||||
'@cfaester/enzyme-adapter-react-18@0.8.0(enzyme@3.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
enzyme: 3.11.0
|
||||
@@ -23632,6 +23662,22 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.12.2
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@ucast/core@1.10.2': {}
|
||||
|
||||
'@ucast/js@3.1.0':
|
||||
dependencies:
|
||||
'@ucast/core': 1.10.2
|
||||
|
||||
'@ucast/mongo2js@1.4.1':
|
||||
dependencies:
|
||||
'@ucast/core': 1.10.2
|
||||
'@ucast/js': 3.1.0
|
||||
'@ucast/mongo': 2.4.3
|
||||
|
||||
'@ucast/mongo@2.4.3':
|
||||
dependencies:
|
||||
'@ucast/core': 1.10.2
|
||||
|
||||
'@ungap/structured-clone@1.2.0': {}
|
||||
|
||||
'@vitejs/plugin-vue@2.3.4(vite@2.9.16(sass@1.80.5))(vue@3.5.12(typescript@4.9.5))':
|
||||
@@ -29154,6 +29200,10 @@ snapshots:
|
||||
graphql: 16.9.0
|
||||
tslib: 2.8.0
|
||||
|
||||
graphql-subscriptions@3.0.0(graphql@16.9.0):
|
||||
dependencies:
|
||||
graphql: 16.9.0
|
||||
|
||||
graphql-tag@2.12.6(graphql@16.9.0):
|
||||
dependencies:
|
||||
graphql: 16.9.0
|
||||
|
||||
Reference in New Issue
Block a user