@coopenomics/desktop 2025.5.14 → 2025.6.13
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.
- package/.env-example +4 -0
- package/extensions/participant/install.ts +37 -25
- package/extensions/soviet/install.ts +23 -20
- package/package.json +7 -5
- package/quasar.config.cjs +1 -1
- package/src/app/App.vue +1 -0
- package/src/app/providers/routes/index.ts +12 -0
- package/src/app/styles/app.scss +1 -1
- package/src/app/styles/style.css +11 -0
- package/src/css/quasar.variables.scss +1 -1
- package/src/entities/Desktop/model/store.ts +68 -0
- package/src/entities/Desktop/model/types.ts +7 -0
- package/src/entities/Document/model/types.ts +1 -1
- package/src/entities/Meet/api/index.ts +2 -28
- package/src/entities/Meet/model/store.ts +7 -22
- package/src/entities/Wallet/api/index.ts +3 -3
- package/src/env.d.ts +1 -0
- package/src/features/Meet/CloseMeetWithDecision/model/index.ts +119 -17
- package/src/features/Meet/CreateMeet/model/index.ts +51 -9
- package/src/features/Meet/CreateMeet/ui/CreateMeet.vue +37 -6
- package/src/features/Meet/CreateMeet/ui/CreateMeetForm.vue +87 -65
- package/src/features/Meet/GenerateSovietDecision/model/index.ts +14 -4
- package/src/features/Meet/RestartMeet/model/index.ts +121 -3
- package/src/features/Meet/RestartMeet/ui/RestartMeet.vue +4 -6
- package/src/features/Meet/RestartMeet/ui/RestartMeetForm.vue +64 -28
- package/src/features/Meet/SignNotification/index.ts +2 -0
- package/src/features/Meet/SignNotification/model/index.ts +137 -0
- package/src/features/Meet/SignNotification/ui/SignNotificationButton.vue +61 -0
- package/src/features/Meet/SignNotification/ui/index.ts +1 -0
- package/src/features/Meet/VoteOnMeet/model/composable.ts +180 -0
- package/src/features/Meet/VoteOnMeet/model/index.ts +2 -17
- package/src/features/Meet/VoteOnMeet/model/types.ts +4 -0
- package/src/features/Meet/index.ts +6 -0
- package/src/features/User/LoginRedirect/ui/LoginRedirectPage.vue +15 -0
- package/src/features/User/LoginRedirect/ui/index.ts +1 -0
- package/src/features/User/LoginUser/ui/LoginForm/LoginForm.vue +38 -1
- package/src/features/User/LoginWithRedirect/ui/LoginRedirectForm/LoginRedirectForm.vue +0 -0
- package/src/pages/Cooperative/ListOfMeets/ui/ListOfMeetsPage.vue +22 -46
- package/src/pages/Cooperative/MeetDetails/ui/MeetDetailsPage.vue +84 -28
- package/src/pages/PermissionDenied/PermissionDenied.vue +1 -1
- package/src/processes/init-app/index.ts +12 -5
- package/src/processes/navigation-guard-setup/index.ts +37 -5
- package/src/processes/process-decisions/index.ts +7 -6
- package/src/shared/config/Environment.ts +35 -27
- package/src/shared/lib/composables/index.ts +1 -0
- package/src/shared/lib/composables/useMeetStatus.ts +96 -0
- package/src/shared/lib/consts/index.ts +1 -0
- package/src/shared/lib/consts/meet-statuses.ts +114 -0
- package/src/shared/lib/document/model/entity.ts +4 -2
- package/src/shared/lib/types/certificate/index.ts +6 -0
- package/src/shared/lib/types/document/index.ts +1 -1
- package/src/shared/lib/types/workspace.ts +24 -0
- package/src/shared/lib/utils/dates/index.ts +5 -0
- package/src/shared/lib/utils/dates/moment.ts +43 -2
- package/src/shared/lib/utils/dates/timezone.ts +75 -0
- package/src/shared/lib/utils/getNameFromCertificate.ts +108 -0
- package/src/shared/lib/utils/index.ts +1 -0
- package/src/shared/lib/utils/parseLinks.ts +10 -0
- package/src/shared/ui/AgendaNumberAvatar/AgendaNumberAvatar.vue +12 -0
- package/src/shared/ui/AgendaNumberAvatar/index.ts +1 -0
- package/src/shared/ui/BaseDocument/BaseDocument.vue +37 -8
- package/src/shared/ui/ExpandableDocument/ExpandableDocument.vue +49 -0
- package/src/shared/ui/ExpandableDocument/index.ts +1 -0
- package/src/shared/ui/MeetInfoCard/index.ts +1 -0
- package/src/shared/ui/MeetInfoCard/ui/MeetInfoCard.vue +62 -0
- package/src/shared/ui/MeetStatusBanner/index.ts +1 -0
- package/src/shared/ui/MeetStatusBanner/ui/MeetStatusBanner.vue +94 -0
- package/src/shared/ui/index.ts +1 -0
- package/src/widgets/Cooperative/Documents/ListOfDocuments/ui/DocumentsTable.vue +3 -3
- package/src/widgets/Cooperative/Orders/ListOfOrders/ui/ListOfOrdersWidget.vue +2 -2
- package/src/widgets/Cooperative/Payments/ListOfPayments/ui/ListOfPaymentsWidget.vue +2 -2
- package/src/widgets/Desktop/WorkspaceMenu/WorkspaceMenu.vue +74 -82
- package/src/widgets/Header/CommonHeader/CooperativeSettingsHeader.vue +1 -1
- package/src/widgets/Header/CommonHeader/ExtstoreHeader.vue +1 -1
- package/src/widgets/Header/CommonHeader/MainHeader.vue +6 -0
- package/src/widgets/Header/CommonHeader/UserSettingsHeader.vue +1 -1
- package/src/widgets/Meets/MeetCardsList/index.ts +1 -0
- package/src/widgets/Meets/MeetCardsList/ui/MeetCardsList.vue +55 -0
- package/src/widgets/Meets/MeetDetailsActions/MeetDetailsActions.vue +33 -15
- package/src/widgets/Meets/MeetDetailsAgenda/MeetDetailsAgenda.vue +34 -15
- package/src/widgets/Meets/MeetDetailsInfo/MeetDetailsInfo.vue +27 -0
- package/src/widgets/Meets/MeetDetailsInfo/index.ts +1 -0
- package/src/widgets/Meets/MeetDetailsResults/MeetDetailsResults.vue +129 -0
- package/src/widgets/Meets/MeetDetailsResults/index.ts +1 -0
- package/src/widgets/Meets/MeetDetailsVoting/MeetDetailsVoting.vue +221 -62
- package/src/widgets/Meets/MeetQuorumIndicator/MeetQuorumIndicator.vue +0 -0
- package/src/widgets/Meets/MeetQuorumIndicator/index.ts +1 -0
- package/src/widgets/Meets/MeetQuorumIndicator/ui/MeetQuorumIndicator.vue +35 -0
- package/src/widgets/Meets/MeetsTable/ui/MeetsTable.vue +56 -65
- package/src/widgets/NotificationCenter/NotificationCenter.vue +90 -0
- package/src/widgets/NotificationCenter/index.ts +1 -0
- package/src/widgets/Participants/ui/ParticipantsTable.vue +1 -1
- package/src/widgets/Questions/ui/QuestionsTable/QuestionsTable.vue +22 -10
- package/src/widgets/Questions/ui/VotingButtons/VotingButtons.vue +59 -14
- package/src/widgets/Registrator/AlreadyRegistered/AlreadyRegistered.vue +1 -1
- package/src/widgets/User/PaymentMethods/ui/PaymentMethods.vue +0 -1
- package/src-ssr/middlewares/injectEnv.ts +18 -12
- package/src/features/Meet/GenerateAgenda/index.ts +0 -1
- package/src/features/Meet/GenerateAgenda/model/index.ts +0 -18
- package/src/features/Meet/GenerateBallot/index.ts +0 -1
- package/src/features/Meet/GenerateBallot/model/index.ts +0 -18
- package/src/features/Meet/GenerateNotification/index.ts +0 -1
- package/src/features/Meet/GenerateNotification/model/index.ts +0 -18
- package/src/features/Meet/MeetDetailsManagement/index.ts +0 -1
- package/src/features/Meet/MeetDetailsManagement/model/index.ts +0 -121
- package/src/pages/Cooperative/ListOfMeets/model/index.ts +0 -1
- package/src/pages/Cooperative/ListOfMeets/model/model.ts +0 -117
- package/src/widgets/Meets/MeetDetailsActions/model.ts +0 -46
- package/src/widgets/Meets/MeetDetailsHeader/MeetDetailsHeader.vue +0 -40
- package/src/widgets/Meets/MeetDetailsHeader/index.ts +0 -1
- package/src/widgets/Meets/MeetDetailsVoting/model.ts +0 -117
- package/src/widgets/Meets/MeetInfoCard/ui/MeetInfoCard.vue +0 -38
- package/src/widgets/Meets/MeetInfoCard/ui/index.ts +0 -1
- /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
|
-
:
|
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="
|
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="
|
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
|
-
|
31
|
-
div.q-mb-
|
32
|
-
|
33
|
-
div.
|
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="!
|
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
|
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
|
-
|
68
|
-
|
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
|
-
|
75
|
-
|
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
|
-
|
81
|
-
|
107
|
+
const meet = meetStore.currentMeet
|
108
|
+
if (!meet) return
|
109
|
+
|
82
110
|
// Проверяем наличие вопросов повестки
|
83
|
-
if (!
|
111
|
+
if (!meet.processing?.questions || meet.processing.questions.length === 0) {
|
84
112
|
return
|
85
113
|
}
|
86
114
|
|
87
|
-
|
88
|
-
|
89
|
-
|
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,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'
|