@coopenomics/desktop 2025.5.14 → 2025.6.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/.env-example +4 -0
  2. package/extensions/participant/install.ts +37 -25
  3. package/extensions/soviet/install.ts +23 -20
  4. package/package.json +7 -5
  5. package/src/app/App.vue +1 -0
  6. package/src/app/providers/routes/index.ts +12 -0
  7. package/src/app/styles/app.scss +1 -1
  8. package/src/app/styles/style.css +11 -0
  9. package/src/css/quasar.variables.scss +1 -1
  10. package/src/entities/Desktop/model/store.ts +68 -0
  11. package/src/entities/Desktop/model/types.ts +7 -0
  12. package/src/entities/Document/model/types.ts +1 -1
  13. package/src/entities/Meet/api/index.ts +2 -28
  14. package/src/entities/Meet/model/store.ts +7 -22
  15. package/src/entities/Wallet/api/index.ts +3 -3
  16. package/src/env.d.ts +1 -0
  17. package/src/features/Meet/CloseMeetWithDecision/model/index.ts +119 -17
  18. package/src/features/Meet/CreateMeet/model/index.ts +51 -9
  19. package/src/features/Meet/CreateMeet/ui/CreateMeet.vue +37 -6
  20. package/src/features/Meet/CreateMeet/ui/CreateMeetForm.vue +87 -65
  21. package/src/features/Meet/GenerateSovietDecision/model/index.ts +14 -4
  22. package/src/features/Meet/RestartMeet/model/index.ts +121 -3
  23. package/src/features/Meet/RestartMeet/ui/RestartMeet.vue +4 -6
  24. package/src/features/Meet/RestartMeet/ui/RestartMeetForm.vue +64 -28
  25. package/src/features/Meet/SignNotification/index.ts +2 -0
  26. package/src/features/Meet/SignNotification/model/index.ts +137 -0
  27. package/src/features/Meet/SignNotification/ui/SignNotificationButton.vue +61 -0
  28. package/src/features/Meet/SignNotification/ui/index.ts +1 -0
  29. package/src/features/Meet/VoteOnMeet/model/composable.ts +180 -0
  30. package/src/features/Meet/VoteOnMeet/model/index.ts +2 -17
  31. package/src/features/Meet/VoteOnMeet/model/types.ts +4 -0
  32. package/src/features/Meet/index.ts +6 -0
  33. package/src/features/User/LoginRedirect/ui/LoginRedirectPage.vue +15 -0
  34. package/src/features/User/LoginRedirect/ui/index.ts +1 -0
  35. package/src/features/User/LoginUser/ui/LoginForm/LoginForm.vue +38 -1
  36. package/src/features/User/LoginWithRedirect/ui/LoginRedirectForm/LoginRedirectForm.vue +0 -0
  37. package/src/pages/Cooperative/ListOfMeets/ui/ListOfMeetsPage.vue +22 -46
  38. package/src/pages/Cooperative/MeetDetails/ui/MeetDetailsPage.vue +84 -28
  39. package/src/pages/PermissionDenied/PermissionDenied.vue +1 -1
  40. package/src/processes/init-app/index.ts +12 -5
  41. package/src/processes/navigation-guard-setup/index.ts +37 -5
  42. package/src/processes/process-decisions/index.ts +7 -6
  43. package/src/shared/config/Environment.ts +10 -2
  44. package/src/shared/lib/composables/index.ts +1 -0
  45. package/src/shared/lib/composables/useMeetStatus.ts +96 -0
  46. package/src/shared/lib/consts/index.ts +1 -0
  47. package/src/shared/lib/consts/meet-statuses.ts +114 -0
  48. package/src/shared/lib/document/model/entity.ts +4 -2
  49. package/src/shared/lib/types/certificate/index.ts +6 -0
  50. package/src/shared/lib/types/document/index.ts +1 -1
  51. package/src/shared/lib/types/workspace.ts +24 -0
  52. package/src/shared/lib/utils/dates/index.ts +5 -0
  53. package/src/shared/lib/utils/dates/moment.ts +43 -2
  54. package/src/shared/lib/utils/dates/timezone.ts +75 -0
  55. package/src/shared/lib/utils/getNameFromCertificate.ts +108 -0
  56. package/src/shared/lib/utils/index.ts +1 -0
  57. package/src/shared/lib/utils/parseLinks.ts +10 -0
  58. package/src/shared/ui/AgendaNumberAvatar/AgendaNumberAvatar.vue +12 -0
  59. package/src/shared/ui/AgendaNumberAvatar/index.ts +1 -0
  60. package/src/shared/ui/BaseDocument/BaseDocument.vue +37 -8
  61. package/src/shared/ui/ExpandableDocument/ExpandableDocument.vue +49 -0
  62. package/src/shared/ui/ExpandableDocument/index.ts +1 -0
  63. package/src/shared/ui/MeetInfoCard/index.ts +1 -0
  64. package/src/shared/ui/MeetInfoCard/ui/MeetInfoCard.vue +62 -0
  65. package/src/shared/ui/MeetStatusBanner/index.ts +1 -0
  66. package/src/shared/ui/MeetStatusBanner/ui/MeetStatusBanner.vue +94 -0
  67. package/src/shared/ui/index.ts +1 -0
  68. package/src/widgets/Cooperative/Documents/ListOfDocuments/ui/DocumentsTable.vue +3 -3
  69. package/src/widgets/Cooperative/Orders/ListOfOrders/ui/ListOfOrdersWidget.vue +2 -2
  70. package/src/widgets/Cooperative/Payments/ListOfPayments/ui/ListOfPaymentsWidget.vue +2 -2
  71. package/src/widgets/Desktop/WorkspaceMenu/WorkspaceMenu.vue +74 -82
  72. package/src/widgets/Header/CommonHeader/CooperativeSettingsHeader.vue +1 -1
  73. package/src/widgets/Header/CommonHeader/ExtstoreHeader.vue +1 -1
  74. package/src/widgets/Header/CommonHeader/MainHeader.vue +6 -0
  75. package/src/widgets/Header/CommonHeader/UserSettingsHeader.vue +1 -1
  76. package/src/widgets/Meets/MeetCardsList/index.ts +1 -0
  77. package/src/widgets/Meets/MeetCardsList/ui/MeetCardsList.vue +55 -0
  78. package/src/widgets/Meets/MeetDetailsActions/MeetDetailsActions.vue +33 -15
  79. package/src/widgets/Meets/MeetDetailsAgenda/MeetDetailsAgenda.vue +34 -15
  80. package/src/widgets/Meets/MeetDetailsInfo/MeetDetailsInfo.vue +27 -0
  81. package/src/widgets/Meets/MeetDetailsInfo/index.ts +1 -0
  82. package/src/widgets/Meets/MeetDetailsResults/MeetDetailsResults.vue +129 -0
  83. package/src/widgets/Meets/MeetDetailsResults/index.ts +1 -0
  84. package/src/widgets/Meets/MeetDetailsVoting/MeetDetailsVoting.vue +221 -62
  85. package/src/widgets/Meets/MeetQuorumIndicator/MeetQuorumIndicator.vue +0 -0
  86. package/src/widgets/Meets/MeetQuorumIndicator/index.ts +1 -0
  87. package/src/widgets/Meets/MeetQuorumIndicator/ui/MeetQuorumIndicator.vue +35 -0
  88. package/src/widgets/Meets/MeetsTable/ui/MeetsTable.vue +56 -65
  89. package/src/widgets/NotificationCenter/NotificationCenter.vue +90 -0
  90. package/src/widgets/NotificationCenter/index.ts +1 -0
  91. package/src/widgets/Participants/ui/ParticipantsTable.vue +1 -1
  92. package/src/widgets/Questions/ui/QuestionsTable/QuestionsTable.vue +22 -10
  93. package/src/widgets/Questions/ui/VotingButtons/VotingButtons.vue +59 -14
  94. package/src/widgets/Registrator/AlreadyRegistered/AlreadyRegistered.vue +1 -1
  95. package/src/widgets/User/PaymentMethods/ui/PaymentMethods.vue +0 -1
  96. package/src-ssr/middlewares/injectEnv.ts +5 -1
  97. package/src/features/Meet/GenerateAgenda/index.ts +0 -1
  98. package/src/features/Meet/GenerateAgenda/model/index.ts +0 -18
  99. package/src/features/Meet/GenerateBallot/index.ts +0 -1
  100. package/src/features/Meet/GenerateBallot/model/index.ts +0 -18
  101. package/src/features/Meet/GenerateNotification/index.ts +0 -1
  102. package/src/features/Meet/GenerateNotification/model/index.ts +0 -18
  103. package/src/features/Meet/MeetDetailsManagement/index.ts +0 -1
  104. package/src/features/Meet/MeetDetailsManagement/model/index.ts +0 -121
  105. package/src/pages/Cooperative/ListOfMeets/model/index.ts +0 -1
  106. package/src/pages/Cooperative/ListOfMeets/model/model.ts +0 -117
  107. package/src/widgets/Meets/MeetDetailsActions/model.ts +0 -46
  108. package/src/widgets/Meets/MeetDetailsHeader/MeetDetailsHeader.vue +0 -40
  109. package/src/widgets/Meets/MeetDetailsHeader/index.ts +0 -1
  110. package/src/widgets/Meets/MeetDetailsVoting/model.ts +0 -117
  111. package/src/widgets/Meets/MeetInfoCard/ui/MeetInfoCard.vue +0 -38
  112. package/src/widgets/Meets/MeetInfoCard/ui/index.ts +0 -1
  113. /package/src/{widgets/Meets/MeetInfoCard → features/User/LoginRedirect}/index.ts +0 -0
@@ -1,9 +1,17 @@
1
1
  import { client } from 'src/shared/api/client';
2
2
  import { Mutations } from '@coopenomics/sdk';
3
- import { generateAgenda } from 'src/features/Meet/GenerateAgenda/model'
4
3
  import { DigitalDocument } from 'src/shared/lib/document'
5
4
  import { useSessionStore } from 'src/entities/Session';
6
5
  import type { Cooperative } from 'cooptypes';
6
+ import { computed, ref, type Ref } from 'vue'
7
+ import { useMeetStore } from 'src/entities/Meet'
8
+ import { FailAlert, SuccessAlert } from 'src/shared/api'
9
+ import moment from 'moment-with-locales-es6'
10
+ import { useSystemStore } from 'src/entities/System/model';
11
+ import { IGenerateAgendaInput, IGenerateAgendaResult } from 'src/features/Meet/CreateMeet/model'
12
+ import { type Router } from 'vue-router';
13
+
14
+ moment.locale('ru')
7
15
 
8
16
  export type IRestartMeetInput = Mutations.Meet.RestartAnnualGeneralMeet.IInput['data'];
9
17
  export type IRestartMeetResult = Mutations.Meet.RestartAnnualGeneralMeet.IOutput[typeof Mutations.Meet.RestartAnnualGeneralMeet.name];
@@ -21,6 +29,25 @@ export interface IRestartMeetWithProposalInput {
21
29
  }[]
22
30
  }
23
31
 
32
+ /**
33
+ * Генерирует документ повестки собрания
34
+ * @private Внутренняя функция, не экспортируется
35
+ */
36
+ async function generateAgenda(data: IGenerateAgendaInput, options?: any): Promise<IGenerateAgendaResult> {
37
+ const { [Mutations.Meet.GenerateAnnualGeneralMeetAgendaDocument.name]: generatedDocument } = await client.Mutation(
38
+ Mutations.Meet.GenerateAnnualGeneralMeetAgendaDocument.mutation,
39
+ {
40
+ variables: {
41
+ data,
42
+ options
43
+ }
44
+ }
45
+ );
46
+
47
+ return generatedDocument;
48
+ }
49
+
50
+ // Базовые функции для работы с API
24
51
  export async function restartMeet(data: IRestartMeetInput): Promise<IRestartMeetResult> {
25
52
  const { [Mutations.Meet.RestartAnnualGeneralMeet.name]: result } = await client.Mutation(
26
53
  Mutations.Meet.RestartAnnualGeneralMeet.mutation,
@@ -36,11 +63,31 @@ export async function restartMeet(data: IRestartMeetInput): Promise<IRestartMeet
36
63
 
37
64
  export async function restartMeetWithProposal(data: IRestartMeetWithProposalInput): Promise<IRestartMeetResult> {
38
65
  const { username } = useSessionStore()
39
- // Генерируем документ повестки
66
+
67
+ // Преобразуем формат даты для документа
68
+ const openAtFormatted = moment(data.new_open_at).format('DD.MM.YYYY HH:mm')
69
+ const closeAtFormatted = moment(data.new_close_at).format('DD.MM.YYYY HH:mm')
70
+
71
+ // Формируем вопросы повестки в требуемом формате
72
+ const questions = data.agenda_points.map((point, index) => ({
73
+ number: String(index + 1),
74
+ title: point.title,
75
+ decision: point.decision,
76
+ context: point.context || ''
77
+ }))
78
+
79
+ // Генерируем документ повестки с правильными параметрами согласно DTO
40
80
  const generatedDocument = await generateAgenda({
41
81
  coopname: data.coopname,
42
- username: data.username
82
+ username: data.username,
83
+ meet: {
84
+ type: 'regular', // По умолчанию очередное собрание
85
+ open_at_datetime: openAtFormatted,
86
+ close_at_datetime: closeAtFormatted
87
+ },
88
+ questions: questions
43
89
  })
90
+
44
91
  // Подписываем документ
45
92
  const rawDocument = new DigitalDocument(generatedDocument)
46
93
  const signedDocument = await rawDocument.sign<Cooperative.Registry.AnnualGeneralMeetingAgenda.Meta>(username)
@@ -53,5 +100,76 @@ export async function restartMeetWithProposal(data: IRestartMeetWithProposalInpu
53
100
  new_close_at: data.new_close_at,
54
101
  newproposal: signedDocument
55
102
  })
103
+
104
+ console.log('on result', result)
105
+
56
106
  return result
57
107
  }
108
+
109
+
110
+ // Композабл для использования в компонентах
111
+ export const useRestartMeet = (
112
+ router: Router,
113
+ isProcessing?: Ref<boolean>
114
+ ) => {
115
+ const localIsProcessing = ref(false)
116
+ const { info } = useSystemStore()
117
+
118
+ const processingRef = isProcessing || localIsProcessing
119
+
120
+ const meetStore = useMeetStore()
121
+ const sessionStore = useSessionStore()
122
+
123
+ const canRestartMeet = computed(() => {
124
+ const meet = meetStore.currentMeet
125
+ if (!meet?.processing?.meet) return false
126
+
127
+ const now = moment()
128
+ const closeAt = moment(meet.processing.meet.close_at)
129
+
130
+ const isAfterCloseDate = now.isAfter(closeAt)
131
+ const isQuorumNotPassed = meet.processing.meet.quorum_passed !== true
132
+
133
+ return isAfterCloseDate && isQuorumNotPassed && meet.processing.meet.status === 'authorized'
134
+ })
135
+
136
+ const handleRestartMeet = async (data: {
137
+ new_open_at: string;
138
+ new_close_at: string;
139
+ agenda_points: {
140
+ title: string;
141
+ context: string;
142
+ decision: string;
143
+ }[];
144
+ }) => {
145
+ if (!meetStore.currentMeet) return false
146
+ processingRef.value = true
147
+ try {
148
+ const result = await restartMeetWithProposal({
149
+ coopname: info.coopname,
150
+ hash: meetStore.currentMeet.hash,
151
+ username: sessionStore.username,
152
+ new_open_at: data.new_open_at,
153
+ new_close_at: data.new_close_at,
154
+ agenda_points: data.agenda_points
155
+ })
156
+
157
+ await meetStore.loadMeet({ coopname: info.coopname, hash: result.processing?.hash as string})
158
+ console.log('router on push', router)
159
+ router.push({params: {hash: result.processing?.hash as string}})
160
+
161
+ SuccessAlert('Собрание успешно перезапущено')
162
+ return true
163
+ } catch (error: any) {
164
+ FailAlert(error)
165
+ return false
166
+ } finally {
167
+ processingRef.value = false
168
+ }
169
+ }
170
+
171
+ return {
172
+ canRestartMeet,
173
+ handleRestartMeet
174
+ }
175
+ }
@@ -6,13 +6,12 @@ div
6
6
  icon="fa-solid fa-rotate"
7
7
  label="Перезапустить собрание"
8
8
  @click="showRestartDialog = true"
9
- :loading="isProcessing"
9
+ :loading="loading || isProcessing"
10
10
  )
11
11
 
12
12
  RestartMeetForm(
13
13
  v-model="showRestartDialog"
14
- :meet="meet"
15
- :loading="isProcessing"
14
+ :loading="loading || isProcessing"
16
15
  @restart="handleRestart"
17
16
  )
18
17
  </template>
@@ -20,11 +19,10 @@ div
20
19
  <script setup lang="ts">
21
20
  import { ref } from 'vue'
22
21
  import { RestartMeetForm } from '.';
23
- import type { IMeet } from 'src/entities/Meet'
24
22
 
25
23
  defineProps<{
26
- meet: IMeet
27
24
  showButton?: boolean
25
+ loading?: boolean
28
26
  }>()
29
27
 
30
28
  const emit = defineEmits<{
@@ -43,4 +41,4 @@ const handleRestart = async (data: any) => {
43
41
  isProcessing.value = false
44
42
  }
45
43
  }
46
- </script>
44
+ </script>
@@ -10,7 +10,7 @@ q-dialog(:model-value="modelValue" @update:model-value="$emit('update:modelValue
10
10
  div.text-subtitle1.q-mb-sm Выберите новые даты для собрания
11
11
  q-input(
12
12
  v-model="formData.new_open_at"
13
- label="Новая дата и время открытия, UTC"
13
+ :label="env.NODE_ENV === 'development' ? `Новая дата и время открытия (мин. через 1 минуту, ${timezoneLabel})` : `Новая дата и время открытия (мин. через 15 дней, ${timezoneLabel})`"
14
14
  type="datetime-local"
15
15
  :rules="[val => !!val || 'Обязательное поле']"
16
16
  dense
@@ -18,7 +18,7 @@ q-dialog(:model-value="modelValue" @update:model-value="$emit('update:modelValue
18
18
  )
19
19
  q-input(
20
20
  v-model="formData.new_close_at"
21
- label="Новая дата и время закрытия, UTC"
21
+ :label="`Новая дата и время закрытия (${timezoneLabel})`"
22
22
  type="datetime-local"
23
23
  :rules="[val => !!val || 'Обязательное поле']"
24
24
  dense
@@ -27,33 +27,43 @@ q-dialog(:model-value="modelValue" @update:model-value="$emit('update:modelValue
27
27
 
28
28
  div.text-subtitle1.q-mb-sm При перезапуске собрания будут использованы существующие пункты повестки:
29
29
 
30
- div.q-pa-sm.q-my-sm.bg-grey-2.rounded-borders(v-if="meet?.processing?.questions?.length")
31
- div.q-mb-md(v-for="(question, index) in meet.processing.questions" :key="index")
32
- div.text-weight-bold {{ question.title }}
33
- div.text-caption {{ question.context }}
34
-
30
+ q-card(bordered flat v-if="meetStore.currentMeet?.processing?.questions?.length").q-pa-xs.q-my-sm.rounded-borders.bg-grey-1
31
+ div.q-mb-xs.flex.items-start(v-for="(question, index) in meetStore.currentMeet.processing.questions" :key="index").q-mb-lg.q-pa-xs
32
+ AgendaNumberAvatar(:number="index + 1" size="22px" class="q-mr-xs")
33
+ div.col
34
+ div.text-body2.text-weight-medium.q-mb-2 {{ question.title }}
35
+ div.text-caption.q-mb-1.q-mt-md
36
+ span.text-weight-bold.text-black Проект решения:
37
+ span.text-black.q-ml-xs {{question.decision }}
38
+ div.text-caption.q-mt-md
39
+ span.text-weight-bold.text-black Приложения:
40
+ span.text-black(v-if="question.context" v-html="parseLinks(question.context)").q-ml-xs
41
+ span.text-black(v-else) —
42
+
35
43
  div.q-pa-sm.q-my-sm.bg-red-1.text-red-8.rounded-borders(v-else)
36
44
  div.text-center Вопросы повестки не найдены
37
45
 
38
46
  q-card-actions(align="right")
39
47
  q-btn(flat label="Отмена" v-close-popup @click="$emit('update:modelValue', false)" :disable="loading")
40
48
  q-btn(
41
- color="primary"
42
- label="Перезапустить"
43
- type="submit"
44
- @click="handleSubmit"
49
+ color="primary"
50
+ label="Перезапустить"
51
+ type="submit"
52
+ @click="handleSubmit"
45
53
  :loading="loading"
46
- :disable="!meet?.processing?.questions?.length"
54
+ :disable="!meetStore.currentMeet?.processing?.questions?.length"
47
55
  )
48
56
  </template>
49
57
 
50
58
  <script setup lang="ts">
51
59
  import { reactive, watch } from 'vue'
52
- import type { IMeet } from 'src/entities/Meet'
53
-
60
+ import { useMeetStore } from 'src/entities/Meet'
61
+ import { getCurrentLocalDateForForm, convertLocalDateToUTC, getTimezoneLabel, getFutureDateForForm } from 'src/shared/lib/utils/dates/timezone'
62
+ import { env } from 'src/shared/config/Environment'
63
+ import { AgendaNumberAvatar } from 'src/shared/ui/AgendaNumberAvatar'
64
+ import { parseLinks } from 'src/shared/lib/utils'
54
65
  const props = defineProps<{
55
66
  modelValue: boolean,
56
- meet: IMeet | null,
57
67
  loading?: boolean
58
68
  }>()
59
69
 
@@ -62,31 +72,57 @@ const emit = defineEmits<{
62
72
  (e: 'restart', data: any): void
63
73
  }>()
64
74
 
75
+ const meetStore = useMeetStore()
76
+
77
+ // Название часового пояса для отображения в лейблах
78
+ const timezoneLabel = getTimezoneLabel()
79
+
65
80
  // Форма для перезапуска собрания
66
- const formData = reactive({
67
- new_open_at: '',
68
- new_close_at: ''
69
- })
81
+ const formData = reactive(
82
+ env.NODE_ENV === 'development'
83
+ ? {
84
+ new_open_at: getCurrentLocalDateForForm(0.17),
85
+ new_close_at: getCurrentLocalDateForForm(1),
86
+ }
87
+ : {
88
+ new_open_at: getFutureDateForForm(15, 6, 0),
89
+ new_close_at: getFutureDateForForm(18, 12, 0),
90
+ }
91
+ )
70
92
 
71
93
  // Сброс формы при открытии диалога
72
94
  watch(() => props.modelValue, (newVal) => {
73
95
  if (newVal) {
74
- formData.new_open_at = ''
75
- formData.new_close_at = ''
96
+ if (env.NODE_ENV === 'development') {
97
+ formData.new_open_at = getCurrentLocalDateForForm(0.17)
98
+ formData.new_close_at = getCurrentLocalDateForForm(1)
99
+ } else {
100
+ formData.new_open_at = getFutureDateForForm(15, 6, 0)
101
+ formData.new_close_at = getFutureDateForForm(18, 12, 0)
102
+ }
76
103
  }
77
104
  })
78
105
 
79
106
  const handleSubmit = () => {
80
- if (!props.meet) return
81
-
107
+ const meet = meetStore.currentMeet
108
+ if (!meet) return
109
+
82
110
  // Проверяем наличие вопросов повестки
83
- if (!props.meet.processing?.questions || props.meet.processing.questions.length === 0) {
111
+ if (!meet.processing?.questions || meet.processing.questions.length === 0) {
84
112
  return
85
113
  }
86
114
 
87
- emit('restart', {
88
- ...formData,
89
- hash: props.meet.hash
90
- })
115
+ // Конвертируем локальные даты в UTC для отправки в блокчейн
116
+ const dataToSend = {
117
+ new_open_at: convertLocalDateToUTC(formData.new_open_at),
118
+ new_close_at: convertLocalDateToUTC(formData.new_close_at),
119
+ agenda_points: meet.processing.questions.map(q => ({
120
+ title: q.title,
121
+ context: q.context,
122
+ decision: q.decision
123
+ }))
124
+ }
125
+
126
+ emit('restart', dataToSend)
91
127
  }
92
128
  </script>
@@ -0,0 +1,2 @@
1
+ export * from './ui'
2
+ export * from './model'
@@ -0,0 +1,137 @@
1
+ import { useMeetStore } from 'src/entities/Meet'
2
+ import { SuccessAlert, FailAlert } from 'src/shared/api'
3
+ import { ref, computed } from 'vue'
4
+ import { client } from 'src/shared/api/client'
5
+ import { Mutations } from '@coopenomics/sdk'
6
+ import { useSignDocument } from 'src/shared/lib/document/model/entity'
7
+ import { useSessionStore } from 'src/entities/Session'
8
+
9
+ export type IGenerateNotificationInput = Mutations.Meet.GenerateAnnualGeneralMeetNotificationDocument.IInput['data']
10
+ export type IGenerateNotificationResult = Mutations.Meet.GenerateAnnualGeneralMeetNotificationDocument.IOutput[typeof Mutations.Meet.GenerateAnnualGeneralMeetNotificationDocument.name]
11
+
12
+ export type INotifyOnAnnualGeneralMeetInput = Mutations.Meet.NotifyOnAnnualGeneralMeet.IInput['data']
13
+ export type INotifyOnAnnualGeneralMeetResult = Mutations.Meet.NotifyOnAnnualGeneralMeet.IOutput[typeof Mutations.Meet.NotifyOnAnnualGeneralMeet.name]
14
+
15
+ /**
16
+ * Генерирует документ уведомления о собрании
17
+ * @private Внутренняя функция, не экспортируется
18
+ */
19
+ async function generateNotification(data: IGenerateNotificationInput, options?: any): Promise<IGenerateNotificationResult> {
20
+ if (!data.meet_hash) {
21
+ throw new Error('Параметр meet_hash обязателен для генерации документа уведомления')
22
+ }
23
+ console.log('data on send', data)
24
+ const { [Mutations.Meet.GenerateAnnualGeneralMeetNotificationDocument.name]: generatedDocument } = await client.Mutation(
25
+ Mutations.Meet.GenerateAnnualGeneralMeetNotificationDocument.mutation,
26
+ {
27
+ variables: {
28
+ data,
29
+ options
30
+ }
31
+ }
32
+ )
33
+
34
+ return generatedDocument
35
+ }
36
+
37
+ /**
38
+ * Отправляет уведомление о собрании в блокчейн
39
+ * @private Внутренняя функция, не экспортируется
40
+ */
41
+ async function notifyOnAnnualGeneralMeet(data: INotifyOnAnnualGeneralMeetInput): Promise<INotifyOnAnnualGeneralMeetResult> {
42
+ const { [Mutations.Meet.NotifyOnAnnualGeneralMeet.name]: result } = await client.Mutation(
43
+ Mutations.Meet.NotifyOnAnnualGeneralMeet.mutation,
44
+ {
45
+ variables: {
46
+ data
47
+ }
48
+ }
49
+ )
50
+
51
+ return result
52
+ }
53
+
54
+ /**
55
+ * Проверяет, отправлял ли пользователь уже уведомление о собрании
56
+ * @param meet Данные о собрании
57
+ * @param username Имя пользователя
58
+ */
59
+ export function isNotificationSent(meet: any, username: string): boolean {
60
+ if (!meet || !meet.processing || !meet.processing.meet || !meet.processing.meet.notified_users) {
61
+ return false
62
+ }
63
+
64
+ // Проверяем, есть ли имя пользователя в массиве notified_users
65
+ return meet.processing.meet.notified_users.includes(username)
66
+ }
67
+
68
+ /**
69
+ * Композабл функция для генерации и подписания уведомления о собрании
70
+ */
71
+ export function useSignNotification() {
72
+ const meetStore = useMeetStore()
73
+ const session = useSessionStore()
74
+ const loading = ref(false)
75
+
76
+ // Получаем текущее собрание из store
77
+ const currentMeet = computed(() => meetStore.currentMeet)
78
+
79
+ // Проверяем, отправлял ли текущий пользователь уведомление
80
+ const hasUserSentNotification = computed(() => {
81
+ return isNotificationSent(currentMeet.value, session.username)
82
+ })
83
+
84
+ /**
85
+ * Генерирует и подписывает документ уведомления о собрании
86
+ */
87
+ const signNotification = async (params: {
88
+ coopname: string,
89
+ meet_hash: string,
90
+ username: string
91
+ }) => {
92
+ try {
93
+ loading.value = true
94
+
95
+ // Генерируем документ уведомления
96
+ const generatedDocument = await generateNotification({
97
+ coopname: params.coopname,
98
+ meet_hash: params.meet_hash,
99
+ username: params.username
100
+ })
101
+
102
+ // Подписываем документ
103
+ const { signDocument } = useSignDocument()
104
+ const signedDocument = await signDocument(generatedDocument, params.username)
105
+ console.log('signedDocument', signedDocument)
106
+
107
+ // Отправляем уведомление в блокчейн
108
+ await notifyOnAnnualGeneralMeet({
109
+ coopname: params.coopname,
110
+ meet_hash: params.meet_hash,
111
+ username: params.username,
112
+ notification: signedDocument
113
+ })
114
+ console.log('after notify')
115
+
116
+ // Перезагружаем информацию о собрании
117
+ await meetStore.loadMeet({
118
+ coopname: params.coopname,
119
+ hash: params.meet_hash
120
+ })
121
+
122
+ SuccessAlert('Уведомление успешно подписано и отправлено')
123
+ return true
124
+ } catch (error: any) {
125
+ FailAlert(error || 'Не удалось подписать уведомление')
126
+ return false
127
+ } finally {
128
+ loading.value = false
129
+ }
130
+ }
131
+
132
+ return {
133
+ signNotification,
134
+ loading,
135
+ hasUserSentNotification
136
+ }
137
+ }
@@ -0,0 +1,61 @@
1
+ <template lang="pug">
2
+ q-btn(
3
+ v-if="!hasUserSentNotification && isAvailableForNotification"
4
+ :loading="loading"
5
+ color="primary"
6
+ icon="note_add"
7
+ :label="label"
8
+ @click="handleSignNotification"
9
+ ).q-mb-md.q-mt-md
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { computed } from 'vue'
14
+ import { useSignNotification } from '../model'
15
+ import { useSessionStore } from 'src/entities/Session'
16
+ import { useMeetStore } from 'src/entities/Meet'
17
+ import { Zeus } from '@coopenomics/sdk';
18
+
19
+ const props = defineProps<{
20
+ coopname: string
21
+ meetHash: string
22
+ label?: string
23
+ }>()
24
+
25
+ const session = useSessionStore()
26
+ const meetStore = useMeetStore()
27
+ const { signNotification, loading, hasUserSentNotification } = useSignNotification()
28
+
29
+ const username = computed(() => session.username)
30
+ const label = computed(() => props.label || 'Подписать уведомление')
31
+
32
+ const isAvailableForNotification = computed(() => {
33
+ const meet = meetStore.currentMeet
34
+ if (!meet || !meet.processing || !meet.processing.extendedStatus) {
35
+ return false
36
+ }
37
+
38
+ return meet.processing.extendedStatus === Zeus.ExtendedMeetStatus.WAITING_FOR_OPENING
39
+ })
40
+
41
+ // Загружаем данные о собрании, если они еще не загружены
42
+ const loadMeetData = async () => {
43
+ if (!meetStore.currentMeet || meetStore.currentMeet.hash !== props.meetHash) {
44
+ await meetStore.loadMeet({
45
+ coopname: props.coopname,
46
+ hash: props.meetHash
47
+ })
48
+ }
49
+ }
50
+
51
+ // Загружаем данные при создании компонента
52
+ loadMeetData()
53
+
54
+ const handleSignNotification = async () => {
55
+ await signNotification({
56
+ coopname: props.coopname,
57
+ meet_hash: props.meetHash,
58
+ username: username.value
59
+ })
60
+ }
61
+ </script>
@@ -0,0 +1 @@
1
+ export { default as SignNotificationButton } from './SignNotificationButton.vue'