useTicketEdit.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { isEqualWith } from 'lodash-es'
  3. import { computed, ref, watch } from 'vue'
  4. import { populateEditorNewLines } from '#shared/components/Form/fields/FieldEditor/utils.ts'
  5. import type {
  6. FormValues,
  7. FormRef,
  8. FormSubmitData,
  9. } from '#shared/components/Form/types.ts'
  10. import { getNodeByName } from '#shared/components/Form/utils.ts'
  11. import { useObjectAttributeFormData } from '#shared/entities/object-attributes/composables/useObjectAttributeFormData.ts'
  12. import { useObjectAttributes } from '#shared/entities/object-attributes/composables/useObjectAttributes.ts'
  13. import { useTicketUpdateMutation } from '#shared/entities/ticket/graphql/mutations/update.api.ts'
  14. import type { TicketById } from '#shared/entities/ticket/types.ts'
  15. import type { TicketArticleFormValues } from '#shared/entities/ticket-article/action/plugins/types.ts'
  16. import type {
  17. TicketUpdateInput,
  18. TicketUpdateMetaInput,
  19. } from '#shared/graphql/types.ts'
  20. import { EnumObjectManagerObjects } from '#shared/graphql/types.ts'
  21. import { MutationHandler } from '#shared/server/apollo/handler/index.ts'
  22. import type { PartialRequired } from '#shared/types/utils.ts'
  23. import { convertFilesToAttachmentInput } from '#shared/utils/files.ts'
  24. import type { ComputedRef, ShallowRef } from 'vue'
  25. type TicketArticleReceivedFormValues = PartialRequired<
  26. TicketArticleFormValues,
  27. // form always has these values
  28. 'articleType' | 'body' | 'internal'
  29. >
  30. export const useTicketEdit = (
  31. ticket: ComputedRef<TicketById | undefined>,
  32. form: ShallowRef<FormRef | undefined>,
  33. ) => {
  34. const initialTicketValue = ref<FormValues>()
  35. const mutationUpdate = new MutationHandler(useTicketUpdateMutation({}))
  36. watch(
  37. ticket,
  38. (newTicket, oldTicket) => {
  39. if (!newTicket) {
  40. return
  41. }
  42. const ticketId = initialTicketValue.value?.id || newTicket.id
  43. const { internalId: ownerInternalId } = newTicket.owner
  44. initialTicketValue.value = {
  45. id: newTicket.id,
  46. owner_id: ownerInternalId === 1 ? null : ownerInternalId,
  47. }
  48. // We need only to reset the form, when really something was changed (updatedAt is not relevant for the form).
  49. if (
  50. !oldTicket ||
  51. isEqualWith(newTicket, oldTicket, (value1, value2, key) => {
  52. if (key === 'updatedAt') return true
  53. })
  54. ) {
  55. return
  56. }
  57. form.value?.resetForm(
  58. { values: initialTicketValue.value, object: newTicket },
  59. {
  60. // don't reset to new values, if user changes something
  61. // if ticket is different, it's probably navigation to another ticket,
  62. // so we can safely reset the form
  63. // TODO: navigation to another ticket is currently always a re-render of the form, because of the component key(=newTicket.id) or?
  64. resetDirty: ticketId !== newTicket.id,
  65. },
  66. )
  67. },
  68. { immediate: true },
  69. )
  70. const isTicketFormGroupValid = computed(() => {
  71. const ticketGroup = form.value?.formNode?.at('ticket')
  72. return !!ticketGroup?.context?.state.valid
  73. })
  74. const { attributesLookup: ticketObjectAttributesLookup } =
  75. useObjectAttributes(EnumObjectManagerObjects.Ticket)
  76. const processArticle = (
  77. formId: string,
  78. article: TicketArticleReceivedFormValues | undefined,
  79. ) => {
  80. if (!article) return null
  81. const contentType =
  82. getNodeByName(formId, 'body')?.context?.contentType || 'text/html'
  83. if (contentType === 'text/html') {
  84. article.body = populateEditorNewLines(article.body)
  85. }
  86. return {
  87. type: article.articleType,
  88. body: article.body,
  89. internal: article.internal,
  90. cc: article.cc,
  91. to: article.to,
  92. subject: article.subject,
  93. subtype: article.subtype,
  94. inReplyTo: article.inReplyTo,
  95. contentType,
  96. attachments: convertFilesToAttachmentInput(formId, article.attachments),
  97. security: article.security,
  98. timeUnit: article.timeUnit,
  99. accountedTimeTypeId: article.accountedTimeTypeId,
  100. }
  101. }
  102. const editTicket = async (
  103. formData: FormSubmitData,
  104. meta?: TicketUpdateMetaInput,
  105. ) => {
  106. if (!ticket.value || !form.value) return undefined
  107. if (!formData.owner_id) {
  108. formData.owner_id = 1
  109. }
  110. const { internalObjectAttributeValues, additionalObjectAttributeValues } =
  111. useObjectAttributeFormData(ticketObjectAttributesLookup.value, formData)
  112. const formArticle = formData.article as
  113. | TicketArticleReceivedFormValues
  114. | undefined
  115. const article = processArticle(form.value.formId, formArticle)
  116. const ticketMeta = meta || {}
  117. return mutationUpdate.send({
  118. ticketId: ticket.value.id,
  119. input: {
  120. ...internalObjectAttributeValues,
  121. objectAttributeValues: additionalObjectAttributeValues,
  122. article,
  123. } as TicketUpdateInput,
  124. meta: ticketMeta,
  125. })
  126. }
  127. return {
  128. initialTicketValue,
  129. isTicketFormGroupValid,
  130. editTicket,
  131. }
  132. }