Skip to content

Commit f548075

Browse files
cloture de masse
1 parent 28e3dda commit f548075

File tree

3 files changed

+86
-26
lines changed

3 files changed

+86
-26
lines changed

app/src/pages/tickets.vue

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ q-page
55
tk-searchfilters(:fields="fieldsList")
66
.q-pa-md
77
q-table(
8-
:rows="tickets.data" :rows-per-page-options="[5, 10, 15]" :loading="pending" :columns="columns" row-key="sequence" :visible-columns="visibleColumns"
8+
:rows="tickets.data" :rows-per-page-options="[5, 10, 15]" :loading="pending" :columns="columns" row-key="_id" :visible-columns="visibleColumns"
99
v-model:pagination="pagination" title="Tickets" @request="onRequest"
1010
rows-per-page-label="Lignes par page" no-data-label="Aucune donnée" loading-label="Chargement..." no-results-label="Aucun résultat"
1111
:pagination-label="(firstRowIndex, endRowIndex, totalRowsNumber) => `${firstRowIndex}-${endRowIndex} sur ${totalRowsNumber} lignes`"
@@ -20,8 +20,10 @@ q-page
2020
q-tooltip.text-body2(transition-show="scale" transition-hide="scale") Fusionner les tickets sélectionnés
2121
q-btn(flat icon="mdi-eye" color="primary" rounded @click="goToTicket(selected[0])" size="md" :disable="selected.length === 0 || selected.length !== 1")
2222
q-tooltip.text-body2(transition-show="scale" transition-hide="scale") Afficher les tickets sélectionnés
23-
q-btn(flat icon="mdi-delete" color="primary" rounded @click="deleteTickets" size="md" :disable="selected.length === 0")
24-
q-tooltip.text-body2(transition-show="scale" transition-hide="scale") Supprimer les tickets sélectionnés
23+
q-btn(flat icon="mdi-delete" color="primary" rounded @click="closeTicketsDialog = true" size="md" :disable="selected.length === 0")
24+
q-tooltip.text-body2(transition-show="scale" transition-hide="scale") Cloturer les tickets sélectionnés
25+
q-btn(flat icon="mdi-close" color="primary" rounded @click="selected = []" size="md")
26+
q-tooltip.text-body2(transition-show="scale" transition-hide="scale") Nettoyer la selection
2527
.col-12.col-sm.flex.justify-end
2628
q-btn-group(rounded flat)
2729
q-btn(flat icon="mdi-table-headers-eye" color="primary")
@@ -65,13 +67,23 @@ q-page
6567
span.q-ml-sm {{ props.row.envelope.assigned.length === 0 ? "Pas d'assigné" : props.row.envelope.assigned[0].name }}
6668
span(v-if="props.row.envelope.assigned.length > 1") , {{ props.row.envelope.assigned.length -1 }} autre{{ props.row.envelope.assigned.length === 2 ? '' : 's' }}...
6769
q-tooltip.text-body2(transition-show="scale" transition-hide="scale") ...{{ [...props.row.envelope.assigned].slice(1).map(s => s.name).join(', ') }}
70+
71+
q-dialog(v-model="closeTicketsDialog")
72+
q-card.q-pa-sm
73+
q-card-section.row.items-center
74+
.col-12.text-h6.text-center
75+
| Voulez vous vraiment cloturer les tickets sélectionnés ?
76+
q-card-actions
77+
q-btn(color="red" label="Annuler" flat @click="closeTicketsDialog = false")
78+
q-btn(color="green" label="Confirmer" flat @click="closeTickets")
6879
</template>
6980

7081
<script lang="ts" setup>
7182
import { ref, provide } from "vue";
7283
import { useHttpApi } from "~/composables/useHttpApi";
7384
import { computed, useDayjs, onMounted } from "#imports";
7485
import { useRoute, useRouter } from "nuxt/app";
86+
import { useQuasar } from "quasar";
7587
import type { QTableProps } from "quasar";
7688
import type { components } from '#build/types/service-api'
7789
import { ticketType, lifeSteps } from "#imports";
@@ -81,22 +93,45 @@ type State = components["schemas"]['StatesDto']
8193
const daysjs = useDayjs()
8294
const route = useRoute()
8395
const router = useRouter()
96+
const $q = useQuasar()
8497
98+
const closeTicketsDialog = ref<boolean>(false)
8599
const { data: tickets, pending, refresh, error } = await useHttpApi('/tickets/ticket', {
86100
method: 'get',
87101
query: computed(() => {
88102
return {
89103
...route.query,
90104
}
91105
})
92-
}).catch((err) => console.error(err))
106+
})
107+
108+
if (error.value) {
109+
$q.notify({
110+
message: 'Impossible de récupérer les tickets',
111+
type: 'negative'
112+
})
113+
}
93114
const { data: categories, pending: categoriesPending, refresh: categoriesRefresh, error: categoriesError } = await useHttpApi('/core/categories', {
94115
method: 'get'
95116
})
117+
118+
if (categoriesError.value) {
119+
$q.notify({
120+
message: 'Impossible de récupérer les catégories',
121+
type: 'negative'
122+
})
123+
}
96124
const { data: states, pending: statesPending, refresh: statesRefresh, error: statesError } = await useHttpApi('/tickets/state', {
97125
method: 'get'
98126
})
99127
128+
if (statesError.value) {
129+
$q.notify({
130+
message: 'Impossible de récupérer les états',
131+
type: 'negative'
132+
})
133+
}
134+
100135
onMounted(async () => {
101136
pagination.value!.rowsNumber = tickets.value?.total
102137
const query = { ...route.query }
@@ -105,7 +140,7 @@ onMounted(async () => {
105140
pagination.value!.rowsPerPage = parseInt(limit as string)
106141
pagination.value!.page = parseInt(skip as string) / parseInt(limit as string) + 1
107142
108-
let sortKey = 'updatedAt'
143+
let sortKey = 'metadata.lastUpdatedAt'
109144
let sortDirection = 'desc'
110145
for (const key in query) {
111146
if (key.startsWith('sort')) {
@@ -298,8 +333,31 @@ const mergeTickets = () => {
298333
console.log('mergeTickets')
299334
}
300335
301-
const deleteTickets = () => {
302-
console.log('deleteTickets')
336+
const openCloseTicketsModale = () => {
337+
closeTicketsDialog.value = true
338+
}
339+
340+
const closeTickets = async () => {
341+
const { data, error } = await useHttpApi('/tickets/ticket/close-many', {
342+
method: 'post',
343+
body: {
344+
ids: selected.value.map(s => s._id)
345+
}
346+
})
347+
if (error.value) {
348+
closeTicketsDialog.value = false
349+
$q.notify({
350+
message: 'Impossible de cloturer les tickets',
351+
type: 'negative'
352+
})
353+
} else {
354+
refresh()
355+
closeTicketsDialog.value = false
356+
$q.notify({
357+
message: 'Tickets cloturés',
358+
type: 'positive'
359+
})
360+
}
303361
}
304362
305363
provide('fieldsList', fieldsList)

service/src/tickets/ticket/ticket.controller.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Body, Controller, Delete, Get, HttpStatus, Param, Patch, Post, Res } from '@nestjs/common'
1+
import { Body, Controller, Delete, Get, HttpStatus, Logger, Param, Patch, Post, Res } from '@nestjs/common'
22
import { TicketService } from './ticket.service'
33
import { AbstractController } from '~/_common/abstracts/abstract.controller'
44
import { TicketCreateDto, TicketDto, TicketUpdateDto } from './_dto/ticket.dto'
@@ -42,6 +42,16 @@ export class TicketController extends AbstractController {
4242
})
4343
}
4444

45+
@Post('close-many')
46+
@ApiParam({ name: '_ids', type: Array<string> })
47+
public async closeMany(@Res() res: Response, @Body() body: { ids: Types.ObjectId[] }) {
48+
const data = await this._service.closeMany(body.ids)
49+
return res.status(HttpStatus.OK).json({
50+
statusCode: HttpStatus.OK,
51+
data,
52+
})
53+
}
54+
4555
@Get()
4656
@ApiPaginatedDecorator(PickProjectionHelper(TicketDto, TicketController.projection))
4757
public async search(@Res() res: Response, @SearchFilterSchema() searchFilterSchema: FilterSchema, @SearchFilterOptions() searchFilterOptions: FilterOptions): Promise<Response> {

service/src/tickets/ticket/ticket.service.ts

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
1-
import { forwardRef, Inject, Injectable, Scope } from '@nestjs/common'
1+
import { forwardRef, Inject, Injectable, Logger, Scope } from '@nestjs/common'
22
import { InjectModel } from '@nestjs/mongoose'
33
import { Ticket } from './_schemas/ticket.schema'
4-
import {
5-
Document,
6-
HydratedDocument,
7-
Model,
8-
ModifyResult,
9-
Query,
10-
QueryOptions,
11-
SaveOptions,
12-
Types,
13-
UpdateQuery,
14-
} from 'mongoose'
4+
import { Document, Model, ModifyResult, Query, QueryOptions, SaveOptions, Types, UpdateQuery } from 'mongoose'
155
import { AbstractServiceSchema } from '~/_common/abstracts/abstract.service.schema'
166
import { ModuleRef, REQUEST } from '@nestjs/core'
177
import { Request } from 'express'
@@ -23,7 +13,8 @@ import { I18nService } from 'nestjs-i18n'
2313
import { isEqual, reduce } from 'radash'
2414
import { SettingsService } from '~/core/settings/settings.service'
2515
import { Filestorage } from '~/core/filestorage/_schemas/filestorage.schema'
26-
16+
import { State } from '../state/_schemas/state.schema'
17+
import { TicketLifestep } from './_enum/ticket-lifestep.enum'
2718
@Injectable({ scope: Scope.REQUEST })
2819
export class TicketService extends AbstractServiceSchema {
2920
public constructor(
@@ -47,6 +38,11 @@ export class TicketService extends AbstractServiceSchema {
4738
return created
4839
}
4940

41+
public async closeMany<T extends AbstractSchema | Document>(ids: Types.ObjectId[]): Promise<UpdateQuery<Query<T, T, any, T>>> {
42+
Logger.log(`closeMany: ${ids}`)
43+
return await this._model.updateMany({ _id: { $in: ids } }, { $set: { lifestep: TicketLifestep.CLOSED } })
44+
}
45+
5046
public async update<T extends AbstractSchema | Document>(
5147
_id: Types.ObjectId | any,
5248
update: UpdateQuery<T>,
@@ -56,11 +52,7 @@ export class TicketService extends AbstractServiceSchema {
5652
const beforeData = await this.findById<T>(_id)
5753
await this._model.db.transaction(async (session) => {
5854
updated = await super.update<T>(_id, update, { ...options, session })
59-
const diff = await reduce(
60-
Object.keys(update),
61-
async (acc, key) => isEqual(update[key], beforeData[key]) ? acc : acc.concat(key),
62-
[],
63-
)
55+
const diff = await reduce(Object.keys(update), async (acc, key) => (isEqual(update[key], beforeData[key]) ? acc : acc.concat(key)), [])
6456
if (diff.length) {
6557
const message = this.i18n.t(`ticket.service.update.newFragment`, {
6658
args: {

0 commit comments

Comments
 (0)