diff --git a/src/components/layout/Sidebar.vue b/src/components/layout/Sidebar.vue index 081a7c3..5a01820 100644 --- a/src/components/layout/Sidebar.vue +++ b/src/components/layout/Sidebar.vue @@ -62,6 +62,12 @@ const menuItems = ref([ 'companies.update', 'companies.destroy', ], + }, + { + label: 'Conceptos de Documentos', + icon: 'pi pi-book', + to: '/catalog/document-concepts', + permission: 'document_concepts.index' } ] }, diff --git a/src/main.ts b/src/main.ts index 1b0756f..79fc326 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,7 @@ import PrimeVue from "primevue/config"; import ConfirmationService from 'primevue/confirmationservice'; import ToastService from 'primevue/toastservice'; import StyleClass from "primevue/styleclass"; +import Tooltip from "primevue/tooltip"; import { createApp } from "vue"; import { createPinia } from "pinia"; import App from "./App.vue"; @@ -48,6 +49,7 @@ app.use(PrimeVue, { }); app.directive("styleclass", StyleClass); +app.directive("tooltip", Tooltip); const bootstrap = async () => { const { initAuth } = useAuth(); diff --git a/src/modules/catalog/components/document-concepts/DocumentConceptsForm.vue b/src/modules/catalog/components/document-concepts/DocumentConceptsForm.vue new file mode 100644 index 0000000..0baf5ea --- /dev/null +++ b/src/modules/catalog/components/document-concepts/DocumentConceptsForm.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/modules/catalog/components/document-concepts/DocumentConceptsTable.vue b/src/modules/catalog/components/document-concepts/DocumentConceptsTable.vue new file mode 100644 index 0000000..80c569f --- /dev/null +++ b/src/modules/catalog/components/document-concepts/DocumentConceptsTable.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/modules/catalog/components/document-concepts/DocumentConceptsView.vue b/src/modules/catalog/components/document-concepts/DocumentConceptsView.vue new file mode 100644 index 0000000..dd80b98 --- /dev/null +++ b/src/modules/catalog/components/document-concepts/DocumentConceptsView.vue @@ -0,0 +1,290 @@ + + + + diff --git a/src/modules/catalog/components/document-concepts/document-concept-convertibles.interface.ts b/src/modules/catalog/components/document-concepts/document-concept-convertibles.interface.ts new file mode 100644 index 0000000..402e3e0 --- /dev/null +++ b/src/modules/catalog/components/document-concepts/document-concept-convertibles.interface.ts @@ -0,0 +1,52 @@ +// document-concept-convertibles.interface.ts + +/** + * Convertible Document Concept (response item for GET/POST convertibles) + */ +export interface ConvertibleDocumentConcept { + id: number; + tenant_id: number; + document_model_id: number; + name: string; + folder_path: string; + serie: string; + initial_number: number; + created_at: string; // ISO 8601 + updated_at: string; // ISO 8601 + deleted_at: string | null; + pivot: ConvertiblePivot; +} + +export interface ConvertiblePivot { + concept_id: number; + target_concept_id: number; + tenant_id: number; +} + +/** + * GET/POST response for convertibles + */ +export interface ConvertibleDocumentConceptsResponse { + data: ConvertibleDocumentConcept[]; +} + +/** + * POST body for assigning convertibles + */ +export interface AssignConvertiblesBody { + target_ids: number[]; +} + +/** + * Documentation + */ +/** + * GET /api/document-concepts/{concept_id}/convertibles + * Busca todos los conceptos de documento convertibles para el concepto dado + * Response: { data: ConvertibleDocumentConcept[] } + * + * POST /api/document-concepts/{concept_id}/convertibles + * Asigna los destinos convertibles para el concepto dado + * Body: { target_ids: number[] } + * Response: { data: ConvertibleDocumentConcept[] } + */ diff --git a/src/modules/catalog/components/document-concepts/document-concept-convertibles.services.ts b/src/modules/catalog/components/document-concepts/document-concept-convertibles.services.ts new file mode 100644 index 0000000..487a688 --- /dev/null +++ b/src/modules/catalog/components/document-concepts/document-concept-convertibles.services.ts @@ -0,0 +1,38 @@ +import api from "@/services/api"; +import type { + ConvertibleDocumentConceptsResponse, + AssignConvertiblesBody +} from "./document-concept-convertibles.interface"; + +export class DocumentConceptConvertiblesService { + /** + * Obtiene los conceptos de documento convertibles para el concepto dado + * @param conceptId (ID del concepto base) + * @returns ConvertibleDocumentConceptsResponse + */ + public async getConvertibles(conceptId: number): Promise { + try { + const response = await api.get(`/api/document-concepts/${conceptId}/convertibles`); + return response.data; + } catch (error) { + console.error("Error fetching convertibles:", error); + throw error; + } + } + + /** + * Asigna / sobreescribe los destinos convertibles + * @param conceptId (ID del concepto base) + * @param body { target_ids: number[] } + * @returns ConvertibleDocumentConceptsResponse + */ + public async assignConvertibles(conceptId: number, body: AssignConvertiblesBody): Promise { + try { + const response = await api.post(`/api/document-concepts/${conceptId}/convertibles`, body); + return response.data; + } catch (error) { + console.error("Error assigning convertibles:", error); + throw error; + } + } +} diff --git a/src/modules/catalog/components/document-concepts/document-concepts.interface.ts b/src/modules/catalog/components/document-concepts/document-concepts.interface.ts new file mode 100644 index 0000000..18bf293 --- /dev/null +++ b/src/modules/catalog/components/document-concepts/document-concepts.interface.ts @@ -0,0 +1,64 @@ +// document-concepts.interface.ts + +/** + * Entity: DocumentConcept + * Representa un concepto documental gestionado en el sistema. + */ +export interface DocumentConcept { + id: number; + tenant_id: number; + document_model_id: number; + name: string; + folder_path: string; // Generado automáticamente por el backend + serie?: string | null; // Opcional + initial_number: number; + created_at: string; // ISO string + updated_at: string; // ISO string + deleted_at: string | null; // Puede ser null (no borrado) +} + +/** + * DTO para creación de un concepto + */ +export interface CreateDocumentConceptDTO { + document_model_id: number; + name: string; + serie?: string | null; + initial_number: number; +} + +/** + * DTO para actualización parcial de un concepto + * Todos los campos son opcionales para permitir updates parciales. + */ +export interface UpdateDocumentConceptDTO { + document_model_id?: number; + name?: string; + serie?: string | null; + initial_number?: number; +} + +/** + * Respuesta de la API para listado de conceptos + */ +export interface ResponseDocumentConceptDTO { + data: DocumentConcept[]; + message?: string; + success?: boolean; +} + +/** + * Respuesta de la API para una sola entidad de concepto (por id o creación) + */ +export interface SingleDocumentConceptResponseDTO { + id: number; + tenant_id: number; + document_model_id: number; + name: string; + folder_path: string; + serie?: string | null; + initial_number: number; + created_at: string; + updated_at: string; + deleted_at: string | null; +} diff --git a/src/modules/catalog/components/document-concepts/document-concepts.services.ts b/src/modules/catalog/components/document-concepts/document-concepts.services.ts new file mode 100644 index 0000000..44157fe --- /dev/null +++ b/src/modules/catalog/components/document-concepts/document-concepts.services.ts @@ -0,0 +1,79 @@ +// document-concepts.services.ts +import api from '@/services/api'; +import type { + CreateDocumentConceptDTO, + ResponseDocumentConceptDTO, + SingleDocumentConceptResponseDTO +} from './document-concepts.interface'; + +/** + * Servicio para gestión de conceptos documentales. + */ +export class DocumentConceptsService { + /** + * Obtiene la lista de conceptos documentales, con filtros opcionales. + * @param filters name (string, opcional), document_model_id (number, opcional) + */ + public async getDocumentConcepts(filters?: { name?: string; document_model_id?: number }): Promise { + try { + const response = await api.get('/api/document-concepts', { params: filters }); + return response.data; + } catch (error) { + console.error('Error fetching document concepts:', error); + throw error; + } + } + + /** + * Crea un nuevo concepto documental. + * @param data Payload con los valores requeridos. + */ + public async createDocumentConcept(data: CreateDocumentConceptDTO): Promise { + try { + const response = await api.post('/api/document-concepts', data); + return response.data; + } catch (error) { + console.error('Error creating document concept:', error); + throw error; + } + } + + /** + * Consulta un concepto documental por su id. + * @param id Identificador único del concepto + */ + public async getDocumentConceptById(id: number): Promise { + try { + const response = await api.get(`/api/document-concepts/${id}`); + return response.data; + } catch (error) { + console.error('Error fetching document concept by id:', error); + throw error; + } + } + + /** + * Actualiza parcialmente un concepto documental (PATCH /document-concepts/:id) + */ + public async updateDocumentConcept(id: number, data: Partial): Promise { + try { + const response = await api.patch(`/api/document-concepts/${id}`, data); + return response.data; + } catch (error) { + console.error('Error updating document concept:', error); + throw error; + } + } + + /** + * Elimina un concepto documental por su id (DELETE /document-concepts/:id) + */ + public async deleteDocumentConcept(id: number): Promise { + try { + await api.delete(`/api/document-concepts/${id}`); + } catch (error) { + console.error('Error deleting document concept:', error); + throw error; + } + } +} diff --git a/src/modules/catalog/components/document-concepts/document-models.interface.ts b/src/modules/catalog/components/document-concepts/document-models.interface.ts new file mode 100644 index 0000000..b61b114 --- /dev/null +++ b/src/modules/catalog/components/document-concepts/document-models.interface.ts @@ -0,0 +1,48 @@ +// document-models.interface.ts + +export interface DocumentModel { + id: number; + name: string; + module: number; + type: number; + num_folio: number; + created_at: string; // ISO date + updated_at: string; // ISO date + deleted_at: string | null; +} + +export interface CreateDocumentModelDTO { + name: string; + module: number; + type: number; + num_folio?: number; +} + +export interface UpdateDocumentModelDTO { + name?: string; + module?: number; + type?: number; + num_folio?: number; +} + +export interface PaginatedDocumentModelsResponse { + current_page: number; + data: DocumentModel[]; + first_page_url: string; + from: number; + last_page: number; + last_page_url: string; + next_page_url: string | null; + path: string; + per_page: number; + prev_page_url: string | null; + to: number; + total: number; + links: PaginationLink[]; +} + +export interface PaginationLink { + url: string | null; + label: string; + active: boolean; +} diff --git a/src/modules/catalog/components/document-concepts/document-models.services.ts b/src/modules/catalog/components/document-concepts/document-models.services.ts new file mode 100644 index 0000000..b7cf4a5 --- /dev/null +++ b/src/modules/catalog/components/document-concepts/document-models.services.ts @@ -0,0 +1,41 @@ +import api from "@/services/api"; +import type { + DocumentModel, + PaginatedDocumentModelsResponse +} from "./document-models.interface"; + +export class DocumentModelsService { + /** + * Obtiene modelos de documento, con paginación o sin ella. + * @param paginate default: true. Si es false, devuelve DocumentModel[] directamente; + * @param per_paginate default: 15. Si se envía, cambia el page size. + */ + public async getDocumentModels(opts?: { + paginate?: boolean; + per_paginate?: number; + search?: string; + [key: string]: any; + }): Promise { + try { + // Construye params siempre + const params: Record = { + paginate: opts?.paginate, + per_paginate: opts?.per_paginate, + ...opts + }; + // Evita undefined + if (params.paginate === undefined) delete params.paginate; + if (params.per_paginate === undefined) delete params.per_paginate; + const response = await api.get("/api/catalogs/document-models", { params }); + if (opts?.paginate === false) { + return response.data.data; // Solo el array de modelos + } else { + return response.data as PaginatedDocumentModelsResponse; + } + } catch (error) { + console.error("Error fetching document models:", error); + throw error; + } + } +} + diff --git a/src/router/index.ts b/src/router/index.ts index caddcd6..056daf7 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -201,6 +201,16 @@ const routes: RouteRecordRaw[] = [ requiresAuth: true } }, + { + path: 'document-concepts', + name: 'DocumentConcepts', + component: () => import('@/modules/catalog/components/document-concepts/DocumentConceptsView.vue'), + meta: { + title: 'Conceptos de Documentos', + requiresAuth: true, + permission: 'document_concepts.index' + } + }, companiesRouter ] },