Crear Asunto funciona
This commit is contained in:
@@ -589,7 +589,6 @@ async def get_ticket_issues(
|
|||||||
async def create_ticket_issue(
|
async def create_ticket_issue(
|
||||||
ticket_id: str,
|
ticket_id: str,
|
||||||
issue: IssueCreate,
|
issue: IssueCreate,
|
||||||
file: Optional[UploadFile] = File(None),
|
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
current_tenant: Tenant = Depends(get_current_tenant),
|
current_tenant: Tenant = Depends(get_current_tenant),
|
||||||
@@ -633,21 +632,11 @@ async def create_ticket_issue(
|
|||||||
detail=f"Usuarios no encontrados: {missing}",
|
detail=f"Usuarios no encontrados: {missing}",
|
||||||
)
|
)
|
||||||
|
|
||||||
attachment_path = None
|
|
||||||
attachment_filename = None
|
|
||||||
attachment_mime_type = None
|
|
||||||
if file and file.filename:
|
|
||||||
file_metadata = await file_handler.save_upload(file, current_tenant.id, ticket_uuid)
|
|
||||||
attachment_path = file_metadata["file_path"]
|
|
||||||
attachment_filename = file_metadata["original_filename"]
|
|
||||||
attachment_mime_type = file_metadata["mime_type"]
|
|
||||||
|
|
||||||
new_issue = TicketIssue(
|
new_issue = TicketIssue(
|
||||||
id=uuid.uuid4(), ticket_id=ticket_uuid,
|
id=uuid.uuid4(), ticket_id=ticket_uuid,
|
||||||
tenant_id=current_user.tenant_id, created_by=current_user.id,
|
tenant_id=ticket_obj.tenant_id, created_by=current_user.id,
|
||||||
content=issue.content, priority=TicketPriority[issue.priority],
|
content=issue.content, priority=TicketPriority[issue.priority],
|
||||||
attachment_path=attachment_path, attachment_filename=attachment_filename,
|
attachment_path=None, attachment_filename=None, attachment_mime_type=None,
|
||||||
attachment_mime_type=attachment_mime_type,
|
|
||||||
created_at=datetime.utcnow(), updated_at=datetime.utcnow(),
|
created_at=datetime.utcnow(), updated_at=datetime.utcnow(),
|
||||||
)
|
)
|
||||||
new_issue.tagged_users = tagged_users
|
new_issue.tagged_users = tagged_users
|
||||||
@@ -678,3 +667,46 @@ async def create_ticket_issue(
|
|||||||
attachment_mime_type=new_issue.attachment_mime_type,
|
attachment_mime_type=new_issue.attachment_mime_type,
|
||||||
created_at=new_issue.created_at, updated_at=new_issue.updated_at,
|
created_at=new_issue.created_at, updated_at=new_issue.updated_at,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{ticket_id}/issues/{issue_id}/attachment", status_code=status.HTTP_200_OK)
|
||||||
|
async def upload_issue_attachment(
|
||||||
|
ticket_id: str,
|
||||||
|
issue_id: str,
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
current_tenant: Tenant = Depends(get_current_tenant),
|
||||||
|
):
|
||||||
|
"""Subir adjunto a un asunto existente."""
|
||||||
|
ticket_uuid = validate_uuid_param(ticket_id, "ticket ID")
|
||||||
|
issue_uuid = validate_uuid_param(issue_id, "issue ID")
|
||||||
|
|
||||||
|
result = await db.execute(
|
||||||
|
select(TicketIssue).where(
|
||||||
|
TicketIssue.id == issue_uuid,
|
||||||
|
TicketIssue.ticket_id == ticket_uuid,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
db_issue = result.scalars().first()
|
||||||
|
if not db_issue:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Asunto no encontrado")
|
||||||
|
|
||||||
|
if db_issue.created_by != current_user.id and not current_user.role.is_global:
|
||||||
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Sin permisos")
|
||||||
|
|
||||||
|
upload_tenant_id = db_issue.tenant_id
|
||||||
|
file_metadata = await file_handler.save_upload(file, upload_tenant_id, ticket_uuid)
|
||||||
|
|
||||||
|
db_issue.attachment_path = file_metadata["file_path"]
|
||||||
|
db_issue.attachment_filename = file_metadata["original_filename"]
|
||||||
|
db_issue.attachment_mime_type = file_metadata["mime_type"]
|
||||||
|
db_issue.updated_at = datetime.utcnow()
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": "Adjunto subido correctamente",
|
||||||
|
"attachment_filename": db_issue.attachment_filename,
|
||||||
|
"attachment_mime_type": db_issue.attachment_mime_type,
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { api } from '$lib/utils/api';
|
|
||||||
import { toast } from '$lib/stores/toast';
|
import { toast } from '$lib/stores/toast';
|
||||||
|
import { api } from '$lib/utils/api';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let ticketId: string;
|
export let ticketId: string;
|
||||||
export let users: any[] = [];
|
export let users: any[] = [];
|
||||||
@@ -43,20 +43,18 @@
|
|||||||
|
|
||||||
isSubmitting = true;
|
isSubmitting = true;
|
||||||
try {
|
try {
|
||||||
// Si hay archivo usamos FormData, si no JSON normal
|
// 1. Crear el issue con JSON
|
||||||
|
const newIssue = await api.post(`/tickets/${ticketId}/issues`, {
|
||||||
|
content: content.trim(),
|
||||||
|
priority,
|
||||||
|
tagged_user_ids: taggedUserIds
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Si hay archivo, subirlo en request separado
|
||||||
if (file) {
|
if (file) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('content', content.trim());
|
|
||||||
formData.append('priority', priority);
|
|
||||||
taggedUserIds.forEach(id => formData.append('tagged_user_ids', id));
|
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
await api.postForm(`/tickets/${ticketId}/issues`, formData);
|
await api.postForm(`/tickets/${ticketId}/issues/${newIssue.id}/attachment`, formData);
|
||||||
} else {
|
|
||||||
await api.post(`/tickets/${ticketId}/issues`, {
|
|
||||||
content: content.trim(),
|
|
||||||
priority,
|
|
||||||
tagged_user_ids: taggedUserIds
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success('Asunto creado correctamente');
|
toast.success('Asunto creado correctamente');
|
||||||
@@ -83,7 +81,7 @@
|
|||||||
on:click={handleClose}
|
on:click={handleClose}
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
on:keydown={(e) => e.key === 'Escape' && handleClose()}
|
on:keydown={e => e.key === 'Escape' && handleClose()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
@@ -91,21 +89,21 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200">
|
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200">
|
||||||
<h3 class="text-lg font-semibold text-gray-900">Crear Asunto</h3>
|
<h3 class="text-lg font-semibold text-gray-900">Crear Asunto</h3>
|
||||||
<button
|
<button type="button" on:click={handleClose} class="text-gray-400 hover:text-gray-500">
|
||||||
type="button"
|
|
||||||
on:click={handleClose}
|
|
||||||
class="text-gray-400 hover:text-gray-500"
|
|
||||||
>
|
|
||||||
<span class="sr-only">Cerrar</span>
|
<span class="sr-only">Cerrar</span>
|
||||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div class="px-6 py-5 space-y-5">
|
<div class="px-6 py-5 space-y-5">
|
||||||
|
|
||||||
<!-- Contenido -->
|
<!-- Contenido -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
@@ -141,10 +139,10 @@
|
|||||||
<!-- Usuarios a etiquetar -->
|
<!-- Usuarios a etiquetar -->
|
||||||
{#if users.length > 0}
|
{#if users.length > 0}
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
<label class="block text-sm font-medium text-gray-700 mb-2"> Etiquetar usuarios </label>
|
||||||
Etiquetar usuarios
|
<div
|
||||||
</label>
|
class="max-h-40 overflow-y-auto border border-gray-200 rounded-md divide-y divide-gray-100"
|
||||||
<div class="max-h-40 overflow-y-auto border border-gray-200 rounded-md divide-y divide-gray-100">
|
>
|
||||||
{#each users as user}
|
{#each users as user}
|
||||||
<label class="flex items-center gap-3 px-3 py-2 hover:bg-gray-50 cursor-pointer">
|
<label class="flex items-center gap-3 px-3 py-2 hover:bg-gray-50 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
@@ -157,7 +155,8 @@
|
|||||||
/>
|
/>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-sm font-medium text-gray-900">
|
<span class="text-sm font-medium text-gray-900">
|
||||||
{user.first_name} {user.last_name}
|
{user.first_name}
|
||||||
|
{user.last_name}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs text-gray-500">{user.email}</span>
|
<span class="text-xs text-gray-500">{user.email}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,7 +165,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if taggedUserIds.length > 0}
|
{#if taggedUserIds.length > 0}
|
||||||
<p class="text-xs text-blue-600 mt-1">
|
<p class="text-xs text-blue-600 mt-1">
|
||||||
{taggedUserIds.length} usuario{taggedUserIds.length > 1 ? 's' : ''} seleccionado{taggedUserIds.length > 1 ? 's' : ''}
|
{taggedUserIds.length} usuario{taggedUserIds.length > 1 ? 's' : ''} seleccionado{taggedUserIds.length >
|
||||||
|
1
|
||||||
|
? 's'
|
||||||
|
: ''}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -174,9 +176,7 @@
|
|||||||
|
|
||||||
<!-- Adjunto -->
|
<!-- Adjunto -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
<label class="block text-sm font-medium text-gray-700 mb-1"> Adjunto (opcional) </label>
|
||||||
Adjunto (opcional)
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
bind:this={fileInput}
|
bind:this={fileInput}
|
||||||
type="file"
|
type="file"
|
||||||
|
|||||||
@@ -73,9 +73,11 @@
|
|||||||
|
|
||||||
isSubmittingComment = true;
|
isSubmittingComment = true;
|
||||||
try {
|
try {
|
||||||
const comment = await api.post(`/tickets/${ticketId}/comments`, {
|
const comment = await api.post(`/tickets/${ticketId}/issues`, {
|
||||||
content: newComment.trim(),
|
content: content.trim(),
|
||||||
is_internal: false
|
priority,
|
||||||
|
tagged_user_ids: taggedUserIds
|
||||||
|
|
||||||
});
|
});
|
||||||
comments = [...comments, comment];
|
comments = [...comments, comment];
|
||||||
newComment = '';
|
newComment = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user