mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-09-10 06:05:41 +00:00
feat: add conversation status categories and update related logic.
- Introduced a new category field for conversation statuses to classify them as 'open', 'waiting', or 'resolved'. Which allows max assignment limits in team to work with custom statuses as well.
This commit is contained in:
+2
-2
@@ -33,7 +33,7 @@ func handleCreateStatus(r *fastglue.Request) error {
|
||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
||||
}
|
||||
|
||||
createdStatus, err := app.status.Create(status.Name)
|
||||
createdStatus, err := app.status.Create(status.Name, status.Category)
|
||||
if err != nil {
|
||||
return sendErrorEnvelope(r, err)
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func handleUpdateStatus(r *fastglue.Request) error {
|
||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil, envelope.InputError)
|
||||
}
|
||||
|
||||
updatedStatus, err := app.status.Update(id, status.Name)
|
||||
updatedStatus, err := app.status.Update(id, status.Name, status.Category)
|
||||
if err != nil {
|
||||
return sendErrorEnvelope(r, err)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ var migList = []migFunc{
|
||||
{"v0.10.0", migrations.V0_10_0},
|
||||
{"v1.0.1", migrations.V1_0_1},
|
||||
{"v2.0.0", migrations.V2_0_0},
|
||||
{"v2.2.0", migrations.V2_2_0},
|
||||
}
|
||||
|
||||
// upgrade upgrades the database to the current version by running SQL migration files
|
||||
|
||||
@@ -6,11 +6,31 @@
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="Spam" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<!-- Form submit button slot -->
|
||||
|
||||
<FormField v-slot="{ componentField }" name="category">
|
||||
<FormItem class="mt-4">
|
||||
<FormLabel>{{ $t('globals.terms.category') }}</FormLabel>
|
||||
<FormControl>
|
||||
<Select v-bind="componentField">
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue :placeholder="$t('admin.conversationStatus.category.placeholder')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value="open">{{ $t('globals.terms.open') }}</SelectItem>
|
||||
<SelectItem value="waiting">{{ $t('globals.terms.waiting') }}</SelectItem>
|
||||
<SelectItem value="resolved">{{ $t('globals.terms.resolved') }}</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<slot name="footer"></slot>
|
||||
</form>
|
||||
</template>
|
||||
@@ -18,11 +38,18 @@
|
||||
<script setup>
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@shared-ui/components/ui/form'
|
||||
import { Input } from '@shared-ui/components/ui/input'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@shared-ui/components/ui/select'
|
||||
</script>
|
||||
|
||||
@@ -3,6 +3,13 @@ import dropdown from './dataTableDropdown.vue'
|
||||
import { format } from 'date-fns'
|
||||
import { CONVERSATION_DEFAULT_STATUSES_LIST } from '@/constants/conversation.js'
|
||||
|
||||
const DEFAULT_STATUS_KEY = {
|
||||
Open: 'globals.terms.open',
|
||||
Snoozed: 'globals.terms.snoozed',
|
||||
Resolved: 'globals.terms.resolved',
|
||||
Closed: 'globals.terms.closed'
|
||||
}
|
||||
|
||||
export const createColumns = (t, { onEdit } = {}) => [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
@@ -10,17 +17,28 @@ export const createColumns = (t, { onEdit } = {}) => [
|
||||
return h('div', { class: 'text-center' }, t('globals.terms.name'))
|
||||
},
|
||||
cell: function ({ row }) {
|
||||
const isDefault = CONVERSATION_DEFAULT_STATUSES_LIST.includes(row.getValue('name'))
|
||||
const name = row.getValue('name')
|
||||
const isDefault = CONVERSATION_DEFAULT_STATUSES_LIST.includes(name)
|
||||
const label = isDefault ? t(DEFAULT_STATUS_KEY[name]) : name
|
||||
return h('div', { class: 'text-center' },
|
||||
onEdit && !isDefault
|
||||
? h('span', {
|
||||
class: 'text-primary hover:underline cursor-pointer',
|
||||
onClick: () => onEdit(row.original)
|
||||
}, row.getValue('name'))
|
||||
: row.getValue('name')
|
||||
}, label)
|
||||
: label
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'category',
|
||||
header: function () {
|
||||
return h('div', { class: 'text-center' }, t('globals.terms.category'))
|
||||
},
|
||||
cell: function ({ row }) {
|
||||
return h('div', { class: 'text-center' }, t(`globals.terms.${row.getValue('category')}`))
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'created_at',
|
||||
header: function () {
|
||||
|
||||
@@ -15,5 +15,8 @@ export const createFormSchema = (t) => z.object({
|
||||
min: 1,
|
||||
max: 25,
|
||||
})
|
||||
})
|
||||
}),
|
||||
category: z.enum(['open', 'waiting', 'resolved'], {
|
||||
required_error: t('globals.messages.required'),
|
||||
})
|
||||
})
|
||||
|
||||
+7
-1
@@ -90,6 +90,7 @@
|
||||
"admin.businessHours.setBusinessHours": "Indstil åbningstider",
|
||||
"admin.contextLink.help.description": "Kontekstlinks vises i samtalens sidebjælke og lader agenter åbne eksterne værktøjer som CRM-systemer, faktureringssystemer eller interne dashboards, hvor kontaktens oplysninger automatisk sendes med.",
|
||||
"admin.contextLink.help.detail": "Brug {'{{token}}'} til en fuldt krypteret payload, som den eksterne app dekrypterer med en delt hemmelighed. Eller brug individuelle variabler som {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'} til almindelige URL-links til betroede interne værktøjer.",
|
||||
"admin.conversationStatus.category.placeholder": "Vælg kategori",
|
||||
"admin.conversationStatus.name.description": "Opsæt statusnavn. Klik på Gem når færdig.",
|
||||
"admin.conversationTags.edit.description": "Skift tagnavn. Klik på Gem når færdig.",
|
||||
"admin.conversationTags.name.valid": "Tagnavn skal udgøre mindst 3 tegn",
|
||||
@@ -365,7 +366,7 @@
|
||||
"admin.team.help.description": "Konfigurer teamindstillinger inkl. arbejdstider og SLA-politikker.",
|
||||
"admin.team.help.detail": "Administrer grænser for automatisk agenttildeling og mere.",
|
||||
"admin.team.maxAutoAssigned": "Maks. automatisk tildelte samtaler",
|
||||
"admin.team.maxAutoAssigned.description": "Maks. antal samtaler, der kan tildeles automatisk til en agent. Samtaler med status \"Løst\" eller \"Lukket\" tæller ikke med. Sæt til 0 for ubegrænset.",
|
||||
"admin.team.maxAutoAssigned.description": "Maks. antal samtaler, der kan tildeles automatisk til en agent. Kun samtaler med status i kategorien \"Åben\" tæller med i denne grænse. Sæt til 0 for ubegrænset.",
|
||||
"admin.team.noPermissionBusinessHours": "Du har ikke tilladelse til at se åbningstider.",
|
||||
"admin.team.slaPolicy.description": "SLA-politik, der automatisk anvendes på samtaler, når de tildeles dette team.",
|
||||
"admin.team.slaPolicy.placeholder": "Vælg politik",
|
||||
@@ -657,9 +658,11 @@
|
||||
"globals.terms.brandName": "Brandnavn",
|
||||
"globals.terms.businessHour": "Åbningstid | Åbningstider",
|
||||
"globals.terms.callbackURL": "Tilbagekalds-URL",
|
||||
"globals.terms.category": "Kategori",
|
||||
"globals.terms.channel": "Kanal",
|
||||
"globals.terms.clientID": "Klient-ID",
|
||||
"globals.terms.clientSecret": "Klienthemmelighed",
|
||||
"globals.terms.closed": "Lukket",
|
||||
"globals.terms.closedAt": "Lukket kl.",
|
||||
"globals.terms.collapse": "Sammenfold",
|
||||
"globals.terms.contact": "Kontakt | Kontakter",
|
||||
@@ -769,6 +772,7 @@
|
||||
"globals.terms.report": "Rapport | Rapporter",
|
||||
"globals.terms.required": "Obligatorisk",
|
||||
"globals.terms.resolve": "Løs",
|
||||
"globals.terms.resolved": "Løst",
|
||||
"globals.terms.resolvedAt": "Løst kl.",
|
||||
"globals.terms.role": "Rolle | Roller",
|
||||
"globals.terms.rootURL": "Rod URL",
|
||||
@@ -786,6 +790,7 @@
|
||||
"globals.terms.smtpHost": "SMTP-vært | SMTP-værter",
|
||||
"globals.terms.smtpPort": "SMTP-port | SMTP-porte",
|
||||
"globals.terms.snooze": "Slumre",
|
||||
"globals.terms.snoozed": "Slumret",
|
||||
"globals.terms.solid": "Ensfarvet",
|
||||
"globals.terms.someone": "Nogen",
|
||||
"globals.terms.sso": "SSO | SSO'er",
|
||||
@@ -819,6 +824,7 @@
|
||||
"globals.terms.view": "Visning | Visninger",
|
||||
"globals.terms.visibility": "Synlighed | Synligheder",
|
||||
"globals.terms.visitor": "Besøgende | Besøgende",
|
||||
"globals.terms.waiting": "Afventer",
|
||||
"globals.terms.warning": "Advarsel | Advarsler",
|
||||
"globals.terms.webhook": "WebHook | Webhooks",
|
||||
"globals.terms.week": "Uge | Uger",
|
||||
|
||||
+7
-1
@@ -90,6 +90,7 @@
|
||||
"admin.businessHours.setBusinessHours": "Öffnungszeiten auswählen",
|
||||
"admin.contextLink.help.description": "Kontextlinks erscheinen in der Seitenleiste der Konversation und ermöglichen es Agenten, externe Tools wie CRMs, Abrechnungssysteme oder interne Dashboards zu öffnen, wobei die Kontaktdaten automatisch übergeben werden.",
|
||||
"admin.contextLink.help.detail": "Verwenden Sie {'{{token}}'} für eine vollständig verschlüsselte Nutzlast, die die externe App mit einem gemeinsamen Schlüssel entschlüsselt. Oder verwenden Sie einzelne Variablen wie {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'} für einfache URL-Links zu vertrauenswürdigen internen Tools.",
|
||||
"admin.conversationStatus.category.placeholder": "Kategorie auswählen",
|
||||
"admin.conversationStatus.name.description": "Status-Name festlegen. Klicken auf Speichern, wenn du fertig bist.",
|
||||
"admin.conversationTags.edit.description": "Ändere den Tag-Namen. Klicke auf Speichern, wenn du fertig bist.",
|
||||
"admin.conversationTags.name.valid": "Tag-Name muss mindestens 3 Zeichen lang sein",
|
||||
@@ -365,7 +366,7 @@
|
||||
"admin.team.help.description": "Konfigurieren Sie Teameinstellungen einschließlich Arbeitszeiten und SLA-Richtlinien.",
|
||||
"admin.team.help.detail": "Verwalten Sie Limits für die automatische Agentenzuweisung und mehr.",
|
||||
"admin.team.maxAutoAssigned": "Maximale automatisch zugewiesene Konversationen",
|
||||
"admin.team.maxAutoAssigned.description": "Maximale Anzahl von Konversationen, die einem Agenten automatisch zugewiesen werden können. Konversationen mit dem Status \"Gelöst\" oder \"Geschlossen\" zählen nicht zu diesem Limit. Auf 0 setzen für unbegrenzt.",
|
||||
"admin.team.maxAutoAssigned.description": "Maximale Anzahl von Konversationen, die einem Agenten automatisch zugewiesen werden können. Nur Konversationen mit Status in der Kategorie \"Offen\" zählen zu diesem Limit. Auf 0 setzen für unbegrenzt.",
|
||||
"admin.team.noPermissionBusinessHours": "Sie haben keine Berechtigung, Geschäftszeiten anzuzeigen.",
|
||||
"admin.team.slaPolicy.description": "SLA-Richtlinie, die automatisch auf Konversationen angewendet wird, wenn Konversationen diesem Team zugewiesen werden.",
|
||||
"admin.team.slaPolicy.placeholder": "Richtlinie auswählen",
|
||||
@@ -657,9 +658,11 @@
|
||||
"globals.terms.brandName": "Markenname",
|
||||
"globals.terms.businessHour": "Öffnungszeiten | Öffnungszeiten",
|
||||
"globals.terms.callbackURL": "Callback-URL",
|
||||
"globals.terms.category": "Kategorie",
|
||||
"globals.terms.channel": "Kanal",
|
||||
"globals.terms.clientID": "Client-ID",
|
||||
"globals.terms.clientSecret": "Client-Geheimnis",
|
||||
"globals.terms.closed": "Geschlossen",
|
||||
"globals.terms.closedAt": "Geschlossen am",
|
||||
"globals.terms.collapse": "Einklappen",
|
||||
"globals.terms.contact": "Kontakt | Kontakte",
|
||||
@@ -769,6 +772,7 @@
|
||||
"globals.terms.report": "Bericht | Berichte",
|
||||
"globals.terms.required": "Erforderlich",
|
||||
"globals.terms.resolve": "Erledigt",
|
||||
"globals.terms.resolved": "Gelöst",
|
||||
"globals.terms.resolvedAt": "Gelöst am",
|
||||
"globals.terms.role": "Rolle | Rollen",
|
||||
"globals.terms.rootURL": "Basis-URL",
|
||||
@@ -786,6 +790,7 @@
|
||||
"globals.terms.smtpHost": "SMTP-Host | SMTP-Hosts",
|
||||
"globals.terms.smtpPort": "SMTP-Port | SMTP-Ports",
|
||||
"globals.terms.snooze": "Später erinnern",
|
||||
"globals.terms.snoozed": "Zurückgestellt",
|
||||
"globals.terms.solid": "Einfarbig",
|
||||
"globals.terms.someone": "Jemand",
|
||||
"globals.terms.sso": "SSO | SSO",
|
||||
@@ -819,6 +824,7 @@
|
||||
"globals.terms.view": "Ansicht | Ansichten",
|
||||
"globals.terms.visibility": "Sichtbarkeit | Sichtbarkeit",
|
||||
"globals.terms.visitor": "Besucher | Besucher",
|
||||
"globals.terms.waiting": "Wartend",
|
||||
"globals.terms.warning": "Warnung | Warnungen",
|
||||
"globals.terms.webhook": "Webhook | Webhook",
|
||||
"globals.terms.week": "Woche | Wochen",
|
||||
|
||||
+7
-1
@@ -90,6 +90,7 @@
|
||||
"admin.businessHours.setBusinessHours": "Set business hours",
|
||||
"admin.contextLink.help.description": "Context links appear in the conversation sidebar, letting agents open external tools like CRMs, billing systems, or internal dashboards with the contact's details passed automatically.",
|
||||
"admin.contextLink.help.detail": "Use {'{{token}}'} for a fully encrypted payload that the external app decrypts with a shared secret. Or use individual variables like {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'} for plain URL links to trusted internal tools.",
|
||||
"admin.conversationStatus.category.placeholder": "Select category",
|
||||
"admin.conversationStatus.name.description": "Set status name. Click save when you're done.",
|
||||
"admin.conversationTags.edit.description": "Change the tag name. Click save when you're done.",
|
||||
"admin.conversationTags.name.valid": "Tag name should at least 3 characters",
|
||||
@@ -365,7 +366,7 @@
|
||||
"admin.team.help.description": "Configure team settings including working hours and SLA policies.",
|
||||
"admin.team.help.detail": "Manage agent auto-assignment limits and more.",
|
||||
"admin.team.maxAutoAssigned": "Maximum auto-assigned conversations",
|
||||
"admin.team.maxAutoAssigned.description": "Maximum number of conversations that can be auto-assigned to an agent, conversations in \"Resolved\" or \"Closed\" states do not count toward this limit. Set to 0 for unlimited.",
|
||||
"admin.team.maxAutoAssigned.description": "Maximum number of conversations that can be auto-assigned to an agent. Only conversations in statuses with the \"Open\" category count toward this limit. Set to 0 for unlimited.",
|
||||
"admin.team.noPermissionBusinessHours": "You do not have permission to view business hours.",
|
||||
"admin.team.slaPolicy.description": "SLA policy to be auto applied to conversations, when conversations are assigned to this team.",
|
||||
"admin.team.slaPolicy.placeholder": "Select policy",
|
||||
@@ -657,9 +658,11 @@
|
||||
"globals.terms.brandName": "Brand name",
|
||||
"globals.terms.businessHour": "Business hour | Business hours",
|
||||
"globals.terms.callbackURL": "Callback URL",
|
||||
"globals.terms.category": "Category",
|
||||
"globals.terms.channel": "Channel",
|
||||
"globals.terms.clientID": "Client ID",
|
||||
"globals.terms.clientSecret": "Client secret",
|
||||
"globals.terms.closed": "Closed",
|
||||
"globals.terms.closedAt": "Closed at",
|
||||
"globals.terms.collapse": "Collapse",
|
||||
"globals.terms.contact": "Contact | Contacts",
|
||||
@@ -769,6 +772,7 @@
|
||||
"globals.terms.report": "Report | Reports",
|
||||
"globals.terms.required": "Required",
|
||||
"globals.terms.resolve": "Resolve",
|
||||
"globals.terms.resolved": "Resolved",
|
||||
"globals.terms.resolvedAt": "Resolved at",
|
||||
"globals.terms.role": "Role | Roles",
|
||||
"globals.terms.rootURL": "Root URL",
|
||||
@@ -786,6 +790,7 @@
|
||||
"globals.terms.smtpHost": "SMTP Host | SMTP Hosts",
|
||||
"globals.terms.smtpPort": "SMTP Port | SMTP Ports",
|
||||
"globals.terms.snooze": "Snooze",
|
||||
"globals.terms.snoozed": "Snoozed",
|
||||
"globals.terms.solid": "Solid",
|
||||
"globals.terms.someone": "Someone",
|
||||
"globals.terms.sso": "SSO | SSOs",
|
||||
@@ -819,6 +824,7 @@
|
||||
"globals.terms.view": "View | Views",
|
||||
"globals.terms.visibility": "Visibility | Visibilities",
|
||||
"globals.terms.visitor": "Visitor | Visitors",
|
||||
"globals.terms.waiting": "Waiting",
|
||||
"globals.terms.warning": "Warning | Warnings",
|
||||
"globals.terms.webhook": "Webhook | Webhooks",
|
||||
"globals.terms.week": "Week | Weeks",
|
||||
|
||||
+7
-1
@@ -90,6 +90,7 @@
|
||||
"admin.businessHours.setBusinessHours": "Establecer horario de atención",
|
||||
"admin.contextLink.help.description": "Los enlaces de contexto aparecen en la barra lateral de la conversación, permitiendo a los agentes abrir herramientas externas como CRMs, sistemas de facturación o paneles internos con los datos del contacto pasados automáticamente.",
|
||||
"admin.contextLink.help.detail": "Usa {'{{token}}'} para un payload completamente cifrado que la aplicación externa descifra con un secreto compartido. O usa variables individuales como {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'} para enlaces URL en texto plano hacia herramientas internas de confianza.",
|
||||
"admin.conversationStatus.category.placeholder": "Selecciona una categoría",
|
||||
"admin.conversationStatus.name.description": "Establece el nombre del estado. Haz clic en guardar cuando termines.",
|
||||
"admin.conversationTags.edit.description": "Cambia el nombre de la etiqueta. Haz clic en guardar cuando termines.",
|
||||
"admin.conversationTags.name.valid": "El nombre de la etiqueta debe tener al menos 3 caracteres",
|
||||
@@ -365,7 +366,7 @@
|
||||
"admin.team.help.description": "Configura los ajustes del equipo incluyendo horario de trabajo y políticas SLA.",
|
||||
"admin.team.help.detail": "Administra los límites de asignación automática de agentes y más.",
|
||||
"admin.team.maxAutoAssigned": "Conversaciones máximas asignadas automáticamente",
|
||||
"admin.team.maxAutoAssigned.description": "Número máximo de conversaciones que se pueden asignar automáticamente a un agente. Las conversaciones en estado \"Resuelto\" o \"Cerrado\" no cuentan para este límite. Establece 0 para ilimitado.",
|
||||
"admin.team.maxAutoAssigned.description": "Número máximo de conversaciones que se pueden asignar automáticamente a un agente. Solo las conversaciones en estados con la categoría \"Abierto\" cuentan para este límite. Establece 0 para ilimitado.",
|
||||
"admin.team.noPermissionBusinessHours": "No tienes permiso para ver el horario de atención.",
|
||||
"admin.team.slaPolicy.description": "Política SLA a aplicar automáticamente a las conversaciones cuando se asignen a este equipo.",
|
||||
"admin.team.slaPolicy.placeholder": "Seleccionar política",
|
||||
@@ -657,9 +658,11 @@
|
||||
"globals.terms.brandName": "Nombre de marca",
|
||||
"globals.terms.businessHour": "Horario de atención | Horario de trabajo",
|
||||
"globals.terms.callbackURL": "URL de retorno (callback)",
|
||||
"globals.terms.category": "Categoría",
|
||||
"globals.terms.channel": "Canal",
|
||||
"globals.terms.clientID": "ID del cliente",
|
||||
"globals.terms.clientSecret": "Secreto del cliente",
|
||||
"globals.terms.closed": "Cerrada",
|
||||
"globals.terms.closedAt": "Cerrado el",
|
||||
"globals.terms.collapse": "Contraer",
|
||||
"globals.terms.contact": "Contacto | Contactos",
|
||||
@@ -769,6 +772,7 @@
|
||||
"globals.terms.report": "Informe | Informes",
|
||||
"globals.terms.required": "Requerido",
|
||||
"globals.terms.resolve": "Resolver",
|
||||
"globals.terms.resolved": "Resuelto",
|
||||
"globals.terms.resolvedAt": "Resuelto el",
|
||||
"globals.terms.role": "Rol | Roles",
|
||||
"globals.terms.rootURL": "URL raíz",
|
||||
@@ -786,6 +790,7 @@
|
||||
"globals.terms.smtpHost": "Host SMTP | Hosts SMTP",
|
||||
"globals.terms.smtpPort": "Puerto SMTP | Puertos SMTP",
|
||||
"globals.terms.snooze": "Posponer",
|
||||
"globals.terms.snoozed": "Pospuesta",
|
||||
"globals.terms.solid": "Sólido",
|
||||
"globals.terms.someone": "Alguien",
|
||||
"globals.terms.sso": "SSO | SSO",
|
||||
@@ -819,6 +824,7 @@
|
||||
"globals.terms.view": "Vista | Vistas",
|
||||
"globals.terms.visibility": "Visibilidad | Visibilidades",
|
||||
"globals.terms.visitor": "Visitante | Visitantes",
|
||||
"globals.terms.waiting": "En espera",
|
||||
"globals.terms.warning": "Advertencia | Advertencias",
|
||||
"globals.terms.webhook": "Web Hook | Hooks web",
|
||||
"globals.terms.week": "Semana | Semanas",
|
||||
|
||||
+7
-1
@@ -90,6 +90,7 @@
|
||||
"admin.businessHours.setBusinessHours": "تنظیم ساعات کاری",
|
||||
"admin.contextLink.help.description": "لینکهای زمینهای در نوار کناری مکالمه نمایش داده میشوند و به نمایندگان امکان میدهند ابزارهای خارجی مانند CRM، سیستمهای صورتحساب یا داشبوردهای داخلی را با ارسال خودکار اطلاعات مخاطب باز کنند.",
|
||||
"admin.contextLink.help.detail": "از {'{{token}}'} برای ارسال بار رمزگذاریشده استفاده کنید که اپلیکیشن خارجی آن را با یک رمز مشترک رمزگشایی میکند. یا از متغیرهای جداگانه مانند {'{{email}}'}، {'{{phone}}'}، {'{{external_user_id}}'} برای لینکهای URL ساده به ابزارهای داخلی مورد اعتماد استفاده کنید.",
|
||||
"admin.conversationStatus.category.placeholder": "انتخاب دستهبندی",
|
||||
"admin.conversationStatus.name.description": "نام وضعیت را تنظیم کنید. پس از اتمام، ذخیره را کلیک کنید.",
|
||||
"admin.conversationTags.edit.description": "نام برچسب را تغییر دهید. پس از اتمام، ذخیره را کلیک کنید.",
|
||||
"admin.conversationTags.name.valid": "نام برچسب باید حداقل 3 کاراکتر باشد",
|
||||
@@ -365,7 +366,7 @@
|
||||
"admin.team.help.description": "تنظیمات تیم از جمله ساعات کاری و سیاستهای SLA را پیکربندی کنید.",
|
||||
"admin.team.help.detail": "محدودیتهای تخصیص خودکار کارشناسان و موارد دیگر را مدیریت کنید.",
|
||||
"admin.team.maxAutoAssigned": "حداکثر مکالمات تخصیص خودکار",
|
||||
"admin.team.maxAutoAssigned.description": "حداکثر تعداد مکالماتی که میتواند به صورت خودکار به یک کارشناس تخصیص یابد. مکالمات در وضعیت «حلشده» یا «بسته» در این محدودیت محاسبه نمیشوند. برای نامحدود 0 تنظیم کنید.",
|
||||
"admin.team.maxAutoAssigned.description": "حداکثر تعداد مکالماتی که میتواند به صورت خودکار به یک کارشناس تخصیص یابد. فقط مکالماتی که وضعیت آنها در دستهبندی «باز» قرار دارد در این محدودیت محاسبه میشوند. برای نامحدود 0 تنظیم کنید.",
|
||||
"admin.team.noPermissionBusinessHours": "شما مجوز مشاهده ساعات کاری را ندارید.",
|
||||
"admin.team.slaPolicy.description": "سیاست SLA که به صورت خودکار هنگام تخصیص مکالمات به این تیم اعمال میشود.",
|
||||
"admin.team.slaPolicy.placeholder": "انتخاب سیاست",
|
||||
@@ -657,9 +658,11 @@
|
||||
"globals.terms.brandName": "نام برند",
|
||||
"globals.terms.businessHour": "ساعت کاری | ساعات کاری",
|
||||
"globals.terms.callbackURL": "URL بازگشت",
|
||||
"globals.terms.category": "دستهبندی",
|
||||
"globals.terms.channel": "کانال",
|
||||
"globals.terms.clientID": "شناسه کلاینت",
|
||||
"globals.terms.clientSecret": "رمز کلاینت",
|
||||
"globals.terms.closed": "بستهشده",
|
||||
"globals.terms.closedAt": "بسته شده در",
|
||||
"globals.terms.collapse": "جمع کردن",
|
||||
"globals.terms.contact": "مخاطب | مخاطبان",
|
||||
@@ -769,6 +772,7 @@
|
||||
"globals.terms.report": "گزارش | گزارشها",
|
||||
"globals.terms.required": "الزامی",
|
||||
"globals.terms.resolve": "حل کردن",
|
||||
"globals.terms.resolved": "حلشده",
|
||||
"globals.terms.resolvedAt": "حل شده در",
|
||||
"globals.terms.role": "نقش | نقشها",
|
||||
"globals.terms.rootURL": "URL ریشه",
|
||||
@@ -786,6 +790,7 @@
|
||||
"globals.terms.smtpHost": "میزبان SMTP | میزبانهای SMTP",
|
||||
"globals.terms.smtpPort": "پورت SMTP | پورتهای SMTP",
|
||||
"globals.terms.snooze": "تعویق",
|
||||
"globals.terms.snoozed": "به تعویق افتاده",
|
||||
"globals.terms.solid": "تکرنگ",
|
||||
"globals.terms.someone": "شخصی",
|
||||
"globals.terms.sso": "SSO | SSO",
|
||||
@@ -819,6 +824,7 @@
|
||||
"globals.terms.view": "نمایش | نمایشها",
|
||||
"globals.terms.visibility": "قابلیت مشاهده | قابلیت مشاهده",
|
||||
"globals.terms.visitor": "بازدیدکننده | بازدیدکنندگان",
|
||||
"globals.terms.waiting": "در انتظار",
|
||||
"globals.terms.warning": "هشدار | هشدارها",
|
||||
"globals.terms.webhook": "وبهوک | وبهوک ها",
|
||||
"globals.terms.week": "هفته | هفتهها",
|
||||
|
||||
+7
-1
@@ -90,6 +90,7 @@
|
||||
"admin.businessHours.setBusinessHours": "Fixer les heures d'ouverture",
|
||||
"admin.contextLink.help.description": "Les liens contextuels apparaissent dans la barre latérale de la conversation, permettant aux agents d'ouvrir des outils externes comme des CRM, des systèmes de facturation ou des tableaux de bord internes avec les coordonnées du contact transmises automatiquement.",
|
||||
"admin.contextLink.help.detail": "Utilisez {'{{token}}'} pour un payload entièrement chiffré que l'application externe déchiffre avec un secret partagé. Ou utilisez des variables individuelles comme {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'} pour des liens URL en clair vers des outils internes de confiance.",
|
||||
"admin.conversationStatus.category.placeholder": "Sélectionner une catégorie",
|
||||
"admin.conversationStatus.name.description": "Définissez le nom de l'état. Cliquez sur enregistrer lorsque vous avez terminé.",
|
||||
"admin.conversationTags.edit.description": "Modifiez le nom de la balise. Cliquez sur enregistrer lorsque vous avez terminé.",
|
||||
"admin.conversationTags.name.valid": "Le nom de l'étiquette doit comporter au moins 3 caractères",
|
||||
@@ -365,7 +366,7 @@
|
||||
"admin.team.help.description": "Configurez les paramètres de l'équipe, y compris les heures de travail et les politiques SLA.",
|
||||
"admin.team.help.detail": "Gérez les limites d'auto-assignation des agents et plus encore.",
|
||||
"admin.team.maxAutoAssigned": "Conversations auto-assignées maximum",
|
||||
"admin.team.maxAutoAssigned.description": "Nombre maximum de conversations pouvant être auto-assignées à un agent. Les conversations en statut « Résolu » ou « Fermé » ne comptent pas. Définir à 0 pour illimité.",
|
||||
"admin.team.maxAutoAssigned.description": "Nombre maximum de conversations pouvant être auto-assignées à un agent. Seules les conversations dont le statut appartient à la catégorie « Ouvert » comptent dans cette limite. Définir à 0 pour illimité.",
|
||||
"admin.team.noPermissionBusinessHours": "Vous n'avez pas la permission de voir les horaires d'ouverture.",
|
||||
"admin.team.slaPolicy.description": "Politique SLA appliquée automatiquement aux conversations lorsqu'elles sont assignées à cette équipe.",
|
||||
"admin.team.slaPolicy.placeholder": "Sélectionner une politique",
|
||||
@@ -657,9 +658,11 @@
|
||||
"globals.terms.brandName": "Nom de marque",
|
||||
"globals.terms.businessHour": "Horaire d’ouverture | Horaires d'ouverture",
|
||||
"globals.terms.callbackURL": "URL de callback",
|
||||
"globals.terms.category": "Catégorie",
|
||||
"globals.terms.channel": "Canal",
|
||||
"globals.terms.clientID": "ID du client",
|
||||
"globals.terms.clientSecret": "Clé secrète du client",
|
||||
"globals.terms.closed": "Fermée",
|
||||
"globals.terms.closedAt": "Fermé le",
|
||||
"globals.terms.collapse": "Réduire",
|
||||
"globals.terms.contact": "Contact | Contacts",
|
||||
@@ -769,6 +772,7 @@
|
||||
"globals.terms.report": "Rapport | Rapports",
|
||||
"globals.terms.required": "Requis",
|
||||
"globals.terms.resolve": "Résoudre",
|
||||
"globals.terms.resolved": "Résolu",
|
||||
"globals.terms.resolvedAt": "Résolu le",
|
||||
"globals.terms.role": "Rôle | Rôles",
|
||||
"globals.terms.rootURL": "URL racine",
|
||||
@@ -786,6 +790,7 @@
|
||||
"globals.terms.smtpHost": "Hôte SMTP | Hôtes SMTP",
|
||||
"globals.terms.smtpPort": "Port SMTP | Ports SMTP",
|
||||
"globals.terms.snooze": "Reporter",
|
||||
"globals.terms.snoozed": "En pause",
|
||||
"globals.terms.solid": "Uni",
|
||||
"globals.terms.someone": "Quelqu'un",
|
||||
"globals.terms.sso": "SSO | SSO",
|
||||
@@ -819,6 +824,7 @@
|
||||
"globals.terms.view": "Vue | Vues",
|
||||
"globals.terms.visibility": "Visibilité | Visibilités",
|
||||
"globals.terms.visitor": "Visiteur | Visiteurs",
|
||||
"globals.terms.waiting": "En attente",
|
||||
"globals.terms.warning": "Avertissement | Avertissements",
|
||||
"globals.terms.webhook": "Webhook | Webhooks",
|
||||
"globals.terms.week": "Semaine | Semaines",
|
||||
|
||||
+7
-1
@@ -90,6 +90,7 @@
|
||||
"admin.businessHours.setBusinessHours": "Imposta l'orario di lavoro",
|
||||
"admin.contextLink.help.description": "I link contestuali appaiono nella barra laterale della conversazione, consentendo agli agenti di aprire strumenti esterni come CRM, sistemi di fatturazione o dashboard interne con i dettagli del contatto passati automaticamente.",
|
||||
"admin.contextLink.help.detail": "Usa {'{{token}}'} per un payload completamente crittografato che l'app esterna decrittografa con un segreto condiviso. Oppure usa variabili individuali come {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'} per link URL in chiaro verso strumenti interni affidabili.",
|
||||
"admin.conversationStatus.category.placeholder": "Seleziona categoria",
|
||||
"admin.conversationStatus.name.description": "Imposta il nome di stato. Fai clic su Salva quando hai finito.",
|
||||
"admin.conversationTags.edit.description": "Cambia il nome del tag. Fai clic su Salva quando hai finito.",
|
||||
"admin.conversationTags.name.valid": "Il nome del sito deve essere di almeno 3 carattere",
|
||||
@@ -365,7 +366,7 @@
|
||||
"admin.team.help.description": "Configura le impostazioni del team inclusi orari lavorativi e policy SLA.",
|
||||
"admin.team.help.detail": "Gestisci i limiti di assegnazione automatica degli agenti e altro.",
|
||||
"admin.team.maxAutoAssigned": "Conversazioni massime auto-assegnate",
|
||||
"admin.team.maxAutoAssigned.description": "Numero massimo di conversazioni che possono essere auto-assegnate a un agente. Le conversazioni in stato \"Risolto\" o \"Chiuso\" non contano per questo limite. Imposta 0 per illimitato.",
|
||||
"admin.team.maxAutoAssigned.description": "Numero massimo di conversazioni che possono essere auto-assegnate a un agente. Solo le conversazioni in stati con categoria \"Aperto\" contano per questo limite. Imposta 0 per illimitato.",
|
||||
"admin.team.noPermissionBusinessHours": "Non hai i permessi per visualizzare gli orari di attività.",
|
||||
"admin.team.slaPolicy.description": "Policy SLA da applicare automaticamente alle conversazioni quando vengono assegnate a questo team.",
|
||||
"admin.team.slaPolicy.placeholder": "Seleziona policy",
|
||||
@@ -657,9 +658,11 @@
|
||||
"globals.terms.brandName": "Nome del marchio",
|
||||
"globals.terms.businessHour": "Orario attività | Orari attività",
|
||||
"globals.terms.callbackURL": "URL di callback",
|
||||
"globals.terms.category": "Categoria",
|
||||
"globals.terms.channel": "Canale",
|
||||
"globals.terms.clientID": "ID client",
|
||||
"globals.terms.clientSecret": "Client Secret",
|
||||
"globals.terms.closed": "Chiusa",
|
||||
"globals.terms.closedAt": "Chiuso il ",
|
||||
"globals.terms.collapse": "Riduci",
|
||||
"globals.terms.contact": "Contatto | Contatti",
|
||||
@@ -769,6 +772,7 @@
|
||||
"globals.terms.report": "Relazione | Relazioni",
|
||||
"globals.terms.required": "Richiesto",
|
||||
"globals.terms.resolve": "Risolvi",
|
||||
"globals.terms.resolved": "Risolto",
|
||||
"globals.terms.resolvedAt": "Risolto il",
|
||||
"globals.terms.role": "Ruolo | Ruoli",
|
||||
"globals.terms.rootURL": "URL base",
|
||||
@@ -786,6 +790,7 @@
|
||||
"globals.terms.smtpHost": "Host SMTP | Hosts SMTP",
|
||||
"globals.terms.smtpPort": "Porta SMTP | Porta SMTP",
|
||||
"globals.terms.snooze": "Posticipa",
|
||||
"globals.terms.snoozed": "Posticipata",
|
||||
"globals.terms.solid": "Tinta unita",
|
||||
"globals.terms.someone": "Qualcuno",
|
||||
"globals.terms.sso": "SSO | SSO",
|
||||
@@ -819,6 +824,7 @@
|
||||
"globals.terms.view": "Vista | Viste",
|
||||
"globals.terms.visibility": "Visibilità | Visibilità",
|
||||
"globals.terms.visitor": "Visitatore | Visitatori",
|
||||
"globals.terms.waiting": "In attesa",
|
||||
"globals.terms.warning": "Attenzione | Avvisi",
|
||||
"globals.terms.webhook": "Webhook | Webhook",
|
||||
"globals.terms.week": "Settimana | Settimane",
|
||||
|
||||
+7
-1
@@ -90,6 +90,7 @@
|
||||
"admin.businessHours.setBusinessHours": "営業時間を設定",
|
||||
"admin.contextLink.help.description": "コンテキストリンクは会話サイドバーに表示され、エージェントがCRM、請求システム、社内ダッシュボードなどの外部ツールを、コンタクトの情報を自動的に渡して開くことができます。",
|
||||
"admin.contextLink.help.detail": "{'{{token}}'} を使用すると、外部アプリが共有シークレットで復号する完全に暗号化されたペイロードになります。または {'{{email}}'}、{'{{phone}}'}、{'{{external_user_id}}'} などの個別変数を使用して、信頼できる社内ツールへのプレーンURLリンクを作成できます。",
|
||||
"admin.conversationStatus.category.placeholder": "カテゴリーを選択",
|
||||
"admin.conversationStatus.name.description": "ステータス名を設定してください。設定が終わったら「保存」をクリックしてください。",
|
||||
"admin.conversationTags.edit.description": "タグ名を変更してください。変更が終わったら「保存」をクリックしてください。",
|
||||
"admin.conversationTags.name.valid": "タグ名は少なくとも 3 文字必要です",
|
||||
@@ -365,7 +366,7 @@
|
||||
"admin.team.help.description": "営業時間やSLAポリシーを含むチーム設定を構成します。",
|
||||
"admin.team.help.detail": "エージェントの自動割り当て上限などを管理します。",
|
||||
"admin.team.maxAutoAssigned": "自動割り当て会話の最大数",
|
||||
"admin.team.maxAutoAssigned.description": "エージェントに自動割り当てできる会話の最大数。「解決済み」または「クローズ」状態の会話はこの上限にカウントされません。0に設定すると無制限です。",
|
||||
"admin.team.maxAutoAssigned.description": "エージェントに自動割り当てできる会話の最大数。「オープン」カテゴリーのステータスの会話のみがこの上限にカウントされます。0に設定すると無制限です。",
|
||||
"admin.team.noPermissionBusinessHours": "営業時間を表示する権限がありません。",
|
||||
"admin.team.slaPolicy.description": "会話がこのチームに割り当てられた際に自動適用されるSLAポリシー。",
|
||||
"admin.team.slaPolicy.placeholder": "ポリシーを選択",
|
||||
@@ -657,9 +658,11 @@
|
||||
"globals.terms.brandName": "ブランド名",
|
||||
"globals.terms.businessHour": "営業時間",
|
||||
"globals.terms.callbackURL": "コールバック URL",
|
||||
"globals.terms.category": "カテゴリー",
|
||||
"globals.terms.channel": "チャンネル",
|
||||
"globals.terms.clientID": "クライアントID",
|
||||
"globals.terms.clientSecret": "クライアントシークレット",
|
||||
"globals.terms.closed": "クローズ済み",
|
||||
"globals.terms.closedAt": "クローズ日時",
|
||||
"globals.terms.collapse": "折りたたむ",
|
||||
"globals.terms.contact": "連絡先",
|
||||
@@ -769,6 +772,7 @@
|
||||
"globals.terms.report": "レポート",
|
||||
"globals.terms.required": "必須",
|
||||
"globals.terms.resolve": "解決",
|
||||
"globals.terms.resolved": "解決済み",
|
||||
"globals.terms.resolvedAt": "解決日時",
|
||||
"globals.terms.role": "役割",
|
||||
"globals.terms.rootURL": "ルート URL",
|
||||
@@ -786,6 +790,7 @@
|
||||
"globals.terms.smtpHost": "SMTP ホスト",
|
||||
"globals.terms.smtpPort": "SMTP ポート",
|
||||
"globals.terms.snooze": "スヌーズ",
|
||||
"globals.terms.snoozed": "スヌーズ中",
|
||||
"globals.terms.solid": "単色",
|
||||
"globals.terms.someone": "誰か",
|
||||
"globals.terms.sso": "SSO",
|
||||
@@ -819,6 +824,7 @@
|
||||
"globals.terms.view": "表示",
|
||||
"globals.terms.visibility": "公開範囲",
|
||||
"globals.terms.visitor": "訪問者",
|
||||
"globals.terms.waiting": "待機中",
|
||||
"globals.terms.warning": "警告",
|
||||
"globals.terms.webhook": "Webhook",
|
||||
"globals.terms.week": "週",
|
||||
|
||||
+7
-1
@@ -90,6 +90,7 @@
|
||||
"admin.businessHours.setBusinessHours": "व्यवसाय तास सेट करा",
|
||||
"admin.contextLink.help.description": "संदर्भ दुवे संभाषण साइडबारमध्ये दिसतात, ज्यामुळे एजंट संपर्काची माहिती आपोआप पाठवून CRM, बिलिंग प्रणाली किंवा अंतर्गत डॅशबोर्ड यांसारखी बाह्य साधने उघडू शकतात.",
|
||||
"admin.contextLink.help.detail": "संपूर्ण एन्क्रिप्टेड पेलोडसाठी {'{{token}}'} वापरा जो बाह्य अॅप सामायिक गुपिताने डिक्रिप्ट करतो. किंवा विश्वसनीय अंतर्गत साधनांसाठी सादा URL दुव्यांमध्ये {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'} सारखे वैयक्तिक व्हेरिएबल्स वापरा.",
|
||||
"admin.conversationStatus.category.placeholder": "श्रेणी निवडा",
|
||||
"admin.conversationStatus.name.description": "स्थिती नाव सेट करा. पूर्ण झाल्यावर जतन करा क्लिक करा.",
|
||||
"admin.conversationTags.edit.description": "टॅग नाव बदला. पूर्ण झाल्यावर जतन करा क्लिक करा.",
|
||||
"admin.conversationTags.name.valid": "टॅग नाव किमान 3 अक्षरे असावे",
|
||||
@@ -365,7 +366,7 @@
|
||||
"admin.team.help.description": "कामाचे तास आणि SLA धोरणांसह संघ सेटिंग्ज कॉन्फिगर करा.",
|
||||
"admin.team.help.detail": "एजंट स्वयं-नियुक्ती मर्यादा आणि बरेच काही व्यवस्थापित करा.",
|
||||
"admin.team.maxAutoAssigned": "कमाल स्वयं-नियुक्त संभाषणे",
|
||||
"admin.team.maxAutoAssigned.description": "एजंटला स्वयं-नियुक्त करता येणाऱ्या संभाषणांची कमाल संख्या, \"सोडवलेली\" किंवा \"बंद\" स्थितीतील संभाषणे या मर्यादेत गणली जात नाहीत. अमर्यादित करण्यासाठी 0 सेट करा.",
|
||||
"admin.team.maxAutoAssigned.description": "एजंटला स्वयं-नियुक्त करता येणाऱ्या संभाषणांची कमाल संख्या. फक्त \"उघडे\" श्रेणीतील स्थितीत असलेली संभाषणे या मर्यादेत गणली जातात. अमर्यादित करण्यासाठी 0 सेट करा.",
|
||||
"admin.team.noPermissionBusinessHours": "व्यवसाय तास पाहण्याची तुम्हाला परवानगी नाही.",
|
||||
"admin.team.slaPolicy.description": "संभाषणे या संघाला नियुक्त केली जातात तेव्हा स्वयं लागू होणारे SLA धोरण.",
|
||||
"admin.team.slaPolicy.placeholder": "धोरण निवडा",
|
||||
@@ -657,9 +658,11 @@
|
||||
"globals.terms.brandName": "ब्रँड नाव",
|
||||
"globals.terms.businessHour": "व्यावसायिक तास | व्यावसायिक तास",
|
||||
"globals.terms.callbackURL": "कॉलबॅक URL",
|
||||
"globals.terms.category": "श्रेणी",
|
||||
"globals.terms.channel": "चॅनेल",
|
||||
"globals.terms.clientID": "क्लायंट ID",
|
||||
"globals.terms.clientSecret": "क्लायंट गुप्त",
|
||||
"globals.terms.closed": "बंद",
|
||||
"globals.terms.closedAt": "बंद केले",
|
||||
"globals.terms.collapse": "कोलॅप्स करा",
|
||||
"globals.terms.contact": "संपर्क | संपर्क",
|
||||
@@ -769,6 +772,7 @@
|
||||
"globals.terms.report": "रिपोर्ट | रिपोर्ट",
|
||||
"globals.terms.required": "आवश्यक",
|
||||
"globals.terms.resolve": "सोडवा",
|
||||
"globals.terms.resolved": "सोडवलेले",
|
||||
"globals.terms.resolvedAt": "सोडवले",
|
||||
"globals.terms.role": "भूमिका | भूमिका",
|
||||
"globals.terms.rootURL": "रूट URL",
|
||||
@@ -786,6 +790,7 @@
|
||||
"globals.terms.smtpHost": "SMTP होस्ट | SMTP होस्ट्स",
|
||||
"globals.terms.smtpPort": "SMTP पोर्ट | SMTP पोर्ट्स",
|
||||
"globals.terms.snooze": "स्नूझ",
|
||||
"globals.terms.snoozed": "स्नूझ केलेले",
|
||||
"globals.terms.solid": "सॉलिड",
|
||||
"globals.terms.someone": "कोणीतरी",
|
||||
"globals.terms.sso": "एसएसओ | एसएसओ",
|
||||
@@ -819,6 +824,7 @@
|
||||
"globals.terms.view": "दृश्य | दृश्ये",
|
||||
"globals.terms.visibility": "दृश्यता | दृश्यता",
|
||||
"globals.terms.visitor": "व्हिजिटर | व्हिजिटर",
|
||||
"globals.terms.waiting": "प्रतीक्षा",
|
||||
"globals.terms.warning": "वॉर्निंग | वॉर्निंग",
|
||||
"globals.terms.webhook": "वेबहुक | वेबहुक्स",
|
||||
"globals.terms.week": "आठवडा | आठवडे",
|
||||
|
||||
@@ -497,6 +497,7 @@ type Status struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Category string `db:"category" json:"category"`
|
||||
}
|
||||
|
||||
type Priority struct {
|
||||
|
||||
@@ -331,16 +331,19 @@ WHERE uuid = $1;
|
||||
|
||||
|
||||
-- name: update-conversation-status
|
||||
WITH new_status AS (
|
||||
SELECT id, category FROM conversation_statuses WHERE name = $2
|
||||
)
|
||||
UPDATE conversations
|
||||
SET status_id = (SELECT id FROM conversation_statuses WHERE name = $2),
|
||||
resolved_at = COALESCE(resolved_at, CASE WHEN $2 IN ('Resolved', 'Closed') THEN NOW() END),
|
||||
closed_at = COALESCE(closed_at, CASE WHEN $2 = 'Closed' THEN NOW() END),
|
||||
SET status_id = (SELECT id FROM new_status),
|
||||
resolved_at = COALESCE(resolved_at, CASE WHEN (SELECT category FROM new_status) = 'resolved' THEN NOW() END),
|
||||
closed_at = COALESCE(closed_at, CASE WHEN $2 = 'Closed' THEN NOW() END),
|
||||
snoozed_until = CASE WHEN $2 = 'Snoozed' THEN $3::timestamptz ELSE snoozed_until END,
|
||||
updated_at = NOW()
|
||||
updated_at = NOW()
|
||||
WHERE uuid = $1;
|
||||
|
||||
-- name: get-user-active-conversations-count
|
||||
SELECT COUNT(*) FROM conversations WHERE status_id IN (SELECT id FROM conversation_statuses WHERE name NOT IN ('Resolved', 'Closed')) and assigned_user_id = $1;
|
||||
SELECT COUNT(*) FROM conversations WHERE status_id IN (SELECT id FROM conversation_statuses WHERE category = 'open') AND assigned_user_id = $1;
|
||||
|
||||
-- name: update-conversation-priority
|
||||
UPDATE conversations
|
||||
@@ -449,7 +452,7 @@ WHERE m.uuid = $1;
|
||||
UPDATE conversations
|
||||
SET assigned_user_id = NULL,
|
||||
updated_at = NOW()
|
||||
WHERE assigned_user_id = $1 AND status_id in (SELECT id FROM conversation_statuses WHERE name NOT IN ('Resolved', 'Closed'));
|
||||
WHERE assigned_user_id = $1 AND status_id IN (SELECT id FROM conversation_statuses WHERE category != 'resolved');
|
||||
|
||||
-- name: update-conversation-custom-attributes
|
||||
UPDATE conversations
|
||||
|
||||
@@ -9,8 +9,21 @@ var DefaultStatuses = []string{
|
||||
"Closed",
|
||||
}
|
||||
|
||||
const (
|
||||
CategoryOpen = "open"
|
||||
CategoryWaiting = "waiting"
|
||||
CategoryResolved = "resolved"
|
||||
)
|
||||
|
||||
var ValidCategories = []string{
|
||||
CategoryOpen,
|
||||
CategoryWaiting,
|
||||
CategoryResolved,
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Category string `db:"category" json:"category"`
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
-- name: get-status
|
||||
select id,
|
||||
select id,
|
||||
created_at,
|
||||
name
|
||||
name,
|
||||
category
|
||||
from conversation_statuses
|
||||
where id = $1;
|
||||
|
||||
-- name: get-all-statuses
|
||||
select id,
|
||||
select id,
|
||||
created_at,
|
||||
name
|
||||
name,
|
||||
category
|
||||
from conversation_statuses;
|
||||
|
||||
-- name: insert-status
|
||||
INSERT into conversation_statuses(name) values ($1) RETURNING *;
|
||||
INSERT into conversation_statuses(name, category) values ($1, $2) RETURNING *;
|
||||
|
||||
-- name: delete-status
|
||||
DELETE from conversation_statuses where id = $1;
|
||||
|
||||
-- name: update-status
|
||||
UPDATE conversation_statuses set name = $2 where id = $1 RETURNING *;
|
||||
UPDATE conversation_statuses set name = $2, category = $3 where id = $1 RETURNING *;
|
||||
|
||||
@@ -70,12 +70,15 @@ func (m *Manager) GetAll() ([]models.Status, error) {
|
||||
}
|
||||
|
||||
// Create creates a new status.
|
||||
func (m *Manager) Create(name string) (models.Status, error) {
|
||||
func (m *Manager) Create(name, category string) (models.Status, error) {
|
||||
var status models.Status
|
||||
if err := m.validateStatusName(name); err != nil {
|
||||
return status, err
|
||||
}
|
||||
if err := m.q.InsertStatus.Get(&status, name); err != nil {
|
||||
if err := m.validateCategory(category); err != nil {
|
||||
return status, err
|
||||
}
|
||||
if err := m.q.InsertStatus.Get(&status, name, category); err != nil {
|
||||
m.lo.Error("error inserting status", "error", err)
|
||||
return status, envelope.NewError(envelope.GeneralError, m.i18n.T("globals.messages.somethingWentWrong"), nil)
|
||||
}
|
||||
@@ -105,22 +108,25 @@ func (m *Manager) Delete(id int) error {
|
||||
}
|
||||
|
||||
// Update updates a status by id.
|
||||
func (m *Manager) Update(id int, name string) (models.Status, error) {
|
||||
func (m *Manager) Update(id int, name, category string) (models.Status, error) {
|
||||
var updatedStatus models.Status
|
||||
if err := m.validateStatusName(name); err != nil {
|
||||
return updatedStatus, err
|
||||
}
|
||||
// Disallow updating of default statuses.
|
||||
if err := m.validateCategory(category); err != nil {
|
||||
return updatedStatus, err
|
||||
}
|
||||
status, err := m.Get(id)
|
||||
if err != nil {
|
||||
return updatedStatus, envelope.NewError(envelope.GeneralError, m.i18n.T("globals.messages.somethingWentWrong"), nil)
|
||||
}
|
||||
|
||||
// Default statuses are locked. Silently no-op and return the existing row.
|
||||
if slices.Contains(models.DefaultStatuses, status.Name) {
|
||||
return updatedStatus, envelope.NewError(envelope.InputError, m.i18n.T("conversationStatus.cannotUpdateDefault"), nil)
|
||||
return status, nil
|
||||
}
|
||||
|
||||
if err := m.q.UpdateStatus.Get(&updatedStatus, id, name); err != nil {
|
||||
if err := m.q.UpdateStatus.Get(&updatedStatus, id, name, category); err != nil {
|
||||
m.lo.Error("error updating status", "error", err)
|
||||
return updatedStatus, envelope.NewError(envelope.GeneralError, m.i18n.T("globals.messages.somethingWentWrong"), nil)
|
||||
}
|
||||
@@ -147,3 +153,11 @@ func (m *Manager) validateStatusName(name string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateCategory checks that the category is one of the allowed values.
|
||||
func (m *Manager) validateCategory(category string) error {
|
||||
if !slices.Contains(models.ValidCategories, category) {
|
||||
return envelope.NewError(envelope.InputError, m.i18n.Ts("validation.invalidFields", "name", "`category`"), nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/knadh/stuffbin"
|
||||
)
|
||||
|
||||
func V2_2_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error {
|
||||
_, err := db.Exec(`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'conversation_status_category') THEN
|
||||
CREATE TYPE conversation_status_category AS ENUM ('open', 'waiting', 'resolved');
|
||||
END IF;
|
||||
END$$;
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`
|
||||
ALTER TABLE conversation_statuses
|
||||
ADD COLUMN IF NOT EXISTS category conversation_status_category NOT NULL DEFAULT 'open';
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`UPDATE conversation_statuses SET category = 'waiting' WHERE name = 'Snoozed'`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`UPDATE conversation_statuses SET category = 'resolved' WHERE name IN ('Resolved', 'Closed')`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -70,7 +70,7 @@ FROM
|
||||
conversations c
|
||||
INNER JOIN conversation_statuses s ON c.status_id = s.id
|
||||
WHERE
|
||||
s.name not in ('Resolved', 'Closed');
|
||||
s.category != 'resolved';
|
||||
|
||||
-- name: get-overview-sla-counts
|
||||
WITH first_and_resolution AS (
|
||||
|
||||
@@ -90,6 +90,7 @@ type AppliedSLA struct {
|
||||
ConversationSubject string `db:"conversation_subject"`
|
||||
ConversationAssignedUserID null.Int `db:"conversation_assigned_user_id"`
|
||||
ConversationStatus string `db:"conversation_status"`
|
||||
ConversationStatusCategory string `db:"conversation_status_category"`
|
||||
}
|
||||
|
||||
type SLAEvent struct {
|
||||
|
||||
@@ -74,8 +74,8 @@ WHERE id = $1;
|
||||
-- name: update-conversation-sla-deadline
|
||||
UPDATE conversations c
|
||||
SET next_sla_deadline_at = CASE
|
||||
-- If resolved or closed, clear the deadline
|
||||
WHEN c.status_id IN (SELECT id FROM conversation_statuses WHERE name IN ('Resolved', 'Closed')) THEN NULL
|
||||
-- If the conversation is in a resolved-category status, clear the deadline
|
||||
WHEN c.status_id IN (SELECT id FROM conversation_statuses WHERE category = 'resolved') THEN NULL
|
||||
|
||||
-- If an external timestamp ($2) is provided (e.g. next_response), use the earliest of $2.
|
||||
WHEN $2::TIMESTAMPTZ IS NOT NULL THEN LEAST(
|
||||
@@ -149,7 +149,8 @@ SELECT a.id,
|
||||
c.reference_number as conversation_reference_number,
|
||||
c.subject as conversation_subject,
|
||||
c.assigned_user_id as conversation_assigned_user_id,
|
||||
s.name as conversation_status
|
||||
s.name as conversation_status,
|
||||
s.category as conversation_status_category
|
||||
FROM applied_slas a INNER JOIN conversations c on a.conversation_id = c.id
|
||||
LEFT JOIN conversation_statuses s ON c.status_id = s.id
|
||||
WHERE a.id = $1;
|
||||
|
||||
+4
-4
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
businesshours "github.com/abhinavxd/libredesk/internal/business_hours"
|
||||
bmodels "github.com/abhinavxd/libredesk/internal/business_hours/models"
|
||||
cmodels "github.com/abhinavxd/libredesk/internal/conversation/models"
|
||||
cstatusmodels "github.com/abhinavxd/libredesk/internal/conversation/status/models"
|
||||
"github.com/abhinavxd/libredesk/internal/dbutil"
|
||||
"github.com/abhinavxd/libredesk/internal/envelope"
|
||||
notifier "github.com/abhinavxd/libredesk/internal/notification"
|
||||
@@ -544,9 +544,9 @@ func (m *Manager) SendNotification(scheduledNotification models.ScheduledSLANoti
|
||||
return fmt.Errorf("fetching applied SLA for notification: %w", err)
|
||||
}
|
||||
|
||||
// If conversation is `Resolved` / `Closed`, mark the notification as processed and return.
|
||||
if appliedSLA.ConversationStatus == cmodels.StatusResolved || appliedSLA.ConversationStatus == cmodels.StatusClosed {
|
||||
m.lo.Info("marking sla notification as processed as the conversation is resolved/closed", "status", appliedSLA.ConversationStatus, "scheduled_notification_id", scheduledNotification.ID)
|
||||
// Any status in the resolved category is terminal for SLA tracking.
|
||||
if appliedSLA.ConversationStatusCategory == cstatusmodels.CategoryResolved {
|
||||
m.lo.Info("marking sla notification as processed as the conversation is in a resolved-category status", "status", appliedSLA.ConversationStatus, "scheduled_notification_id", scheduledNotification.ID)
|
||||
if _, err := m.q.UpdateSLANotificationProcessed.Exec(scheduledNotification.ID); err != nil {
|
||||
m.lo.Error("error marking notification as processed", "error", err)
|
||||
}
|
||||
|
||||
+8
-6
@@ -23,6 +23,7 @@ DROP TYPE IF EXISTS "sla_notification_type" CASCADE; CREATE TYPE "sla_notificati
|
||||
DROP TYPE IF EXISTS "activity_log_type" CASCADE; CREATE TYPE "activity_log_type" AS ENUM ('agent_login', 'agent_logout', 'agent_away', 'agent_away_reassigned', 'agent_online', 'agent_password_set', 'agent_role_permissions_changed');
|
||||
DROP TYPE IF EXISTS "macro_visible_when" CASCADE; CREATE TYPE "macro_visible_when" AS ENUM ('replying', 'starting_conversation', 'adding_private_note');
|
||||
DROP TYPE IF EXISTS "user_notification_type" CASCADE; CREATE TYPE "user_notification_type" AS ENUM ('mention', 'assignment', 'sla_warning', 'sla_breach');
|
||||
DROP TYPE IF EXISTS "conversation_status_category" CASCADE; CREATE TYPE "conversation_status_category" AS ENUM ('open', 'waiting', 'resolved');
|
||||
DROP TYPE IF EXISTS "webhook_event" CASCADE; CREATE TYPE webhook_event AS ENUM (
|
||||
'conversation.created',
|
||||
'conversation.status_changed',
|
||||
@@ -189,7 +190,8 @@ CREATE TABLE conversation_statuses (
|
||||
id SERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
"name" TEXT NOT NULL UNIQUE
|
||||
"name" TEXT NOT NULL UNIQUE,
|
||||
category conversation_status_category NOT NULL DEFAULT 'open'
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS conversation_priorities CASCADE;
|
||||
@@ -741,11 +743,11 @@ INSERT INTO conversation_priorities (name) VALUES
|
||||
('High');
|
||||
|
||||
-- Default conversation statuses
|
||||
INSERT INTO conversation_statuses (name) VALUES
|
||||
('Open'),
|
||||
('Snoozed'),
|
||||
('Resolved'),
|
||||
('Closed');
|
||||
INSERT INTO conversation_statuses (name, category) VALUES
|
||||
('Open', 'open'),
|
||||
('Snoozed', 'waiting'),
|
||||
('Resolved', 'resolved'),
|
||||
('Closed', 'resolved');
|
||||
|
||||
-- Default roles
|
||||
INSERT INTO
|
||||
|
||||
Reference in New Issue
Block a user