-
Tasas e Impuestos (%)
+
Tasas e impuestos (%)
-
-
- {{ errors.vat_rate }}
-
-
-
-
- Tasa de IVA (opcional)
+
-
- Retención ISR (opcional)
+
-
- Retención IVA (opcional)
+
+
+
+
+
+
@@ -109,315 +160,204 @@
-
-
Dirección Fiscal
+
Certificados SAT
-
-
Password del certificado
+
- {{ errors.country }}
+ {{ errors.certificate_password }}
-
- Archivo .cer
+
- {{ errors.postal_code }}
+ {{ errors.certificate_cer_file }}
-
-
- {{ errors.state }}
-
-
-
-
-
- {{ errors.municipality }}
-
-
-
-
-
- {{ errors.city }}
-
-
-
-
-
- {{ errors.street }}
-
-
-
-
-
- {{ errors.num_ext }}
-
-
-
-
- Archivo .key
+
+ {{ errors.certificate_key_file }}
-
- * Campos obligatorios
-
+
* Campos obligatorios
-
-
+
+
@@ -426,4 +366,4 @@ const handleClose = () => {
.space-y-6 > * + * {
margin-top: 1.5rem;
}
-
\ No newline at end of file
+
diff --git a/src/modules/catalog/components/companies/companies.mapper.ts b/src/modules/catalog/components/companies/companies.mapper.ts
new file mode 100644
index 0000000..5ec4cc5
--- /dev/null
+++ b/src/modules/catalog/components/companies/companies.mapper.ts
@@ -0,0 +1,114 @@
+import type { CreateTenantPayload, TenantFormData, UpdateTenantPayload } from './companies.types';
+
+const appendIfNotEmpty = (formData: FormData, key: string, value: string | number | null | undefined) => {
+ if (value === null || value === undefined) {
+ return;
+ }
+
+ const normalized = String(value).trim();
+ if (normalized.length > 0) {
+ formData.append(key, normalized);
+ }
+};
+
+const appendNumericIfDefined = (formData: FormData, key: string, value: number | null | undefined) => {
+ if (value === null || value === undefined || Number.isNaN(value)) {
+ return;
+ }
+
+ formData.append(key, String(value));
+};
+
+const appendFileIfPresent = (formData: FormData, key: string, value: File | null | undefined) => {
+ if (value instanceof File) {
+ formData.append(key, value);
+ }
+};
+
+export const buildCreateTenantPayload = (data: TenantFormData): CreateTenantPayload => ({
+ company_name: data.company_name.trim(),
+ rfc: data.rfc.trim().toUpperCase(),
+ email: data.email.trim(),
+ primary_domain: data.primary_domain.trim(),
+ curp: data.curp.trim().toUpperCase(),
+ phone: data.phone.trim(),
+ legal_representative: data.legal_representative.trim(),
+ tax_regime: data.tax_regime.trim(),
+ is_active: data.is_active,
+ vat_rate: data.vat_rate,
+ isr_withholding: data.isr_withholding,
+ vat_withholding: data.vat_withholding,
+ additional_tax: data.additional_tax,
+ certificate_password: data.certificate_password,
+ certificate_cer_file: data.certificate_cer_file,
+ certificate_key_file: data.certificate_key_file,
+});
+
+export const buildUpdateTenantPayload = (data: TenantFormData): UpdateTenantPayload => ({
+ company_name: data.company_name.trim(),
+ rfc: data.rfc.trim().toUpperCase(),
+ email: data.email.trim(),
+ primary_domain: data.primary_domain.trim() || undefined,
+ curp: data.curp.trim().toUpperCase(),
+ phone: data.phone.trim(),
+ legal_representative: data.legal_representative.trim(),
+ tax_regime: data.tax_regime.trim(),
+ is_active: data.is_active,
+ vat_rate: data.vat_rate,
+ isr_withholding: data.isr_withholding,
+ vat_withholding: data.vat_withholding,
+ additional_tax: data.additional_tax,
+ certificate_password: data.certificate_password,
+ certificate_cer_file: data.certificate_cer_file,
+ certificate_key_file: data.certificate_key_file,
+});
+
+export const toTenantFormData = (data?: Partial
): TenantFormData => ({
+ id: data?.id,
+ company_name: data?.company_name ?? '',
+ rfc: data?.rfc ?? '',
+ email: data?.email ?? '',
+ primary_domain: data?.primary_domain ?? '',
+ curp: data?.curp ?? '',
+ phone: data?.phone ?? '',
+ legal_representative: data?.legal_representative ?? '',
+ tax_regime: data?.tax_regime ?? '',
+ is_active: data?.is_active ?? true,
+ vat_rate: data?.vat_rate ?? 0.0,
+ isr_withholding: data?.isr_withholding ?? 0.0,
+ vat_withholding: data?.vat_withholding ?? 0.0,
+ additional_tax: data?.additional_tax ?? 0.0,
+ certificate_password: data?.certificate_password ?? '',
+ certificate_cer_file: null,
+ certificate_key_file: null,
+});
+
+export const tenantPayloadToFormData = (
+ data: CreateTenantPayload | UpdateTenantPayload,
+ options?: { forcePut?: boolean }
+): FormData => {
+ const formData = new FormData();
+
+ if (options?.forcePut) {
+ formData.append('_method', 'PUT');
+ }
+
+ appendIfNotEmpty(formData, 'company_name', data.company_name);
+ appendIfNotEmpty(formData, 'rfc', data.rfc);
+ appendIfNotEmpty(formData, 'email', data.email);
+ appendIfNotEmpty(formData, 'primary_domain', data.primary_domain);
+ appendIfNotEmpty(formData, 'curp', data.curp);
+ appendIfNotEmpty(formData, 'phone', data.phone);
+ appendIfNotEmpty(formData, 'legal_representative', data.legal_representative);
+ appendIfNotEmpty(formData, 'tax_regime', data.tax_regime);
+ appendNumericIfDefined(formData, 'vat_rate', data.vat_rate);
+ appendNumericIfDefined(formData, 'isr_withholding', data.isr_withholding);
+ appendNumericIfDefined(formData, 'vat_withholding', data.vat_withholding);
+ appendNumericIfDefined(formData, 'additional_tax', data.additional_tax);
+ appendIfNotEmpty(formData, 'certificate_password', data.certificate_password);
+ formData.append('is_active', data.is_active ? '1' : '0');
+ appendFileIfPresent(formData, 'certificate_cer_file', data.certificate_cer_file);
+ appendFileIfPresent(formData, 'certificate_key_file', data.certificate_key_file);
+
+ return formData;
+};
diff --git a/src/modules/catalog/components/companies/companies.service.ts b/src/modules/catalog/components/companies/companies.service.ts
index 5b81e6d..ab99294 100644
--- a/src/modules/catalog/components/companies/companies.service.ts
+++ b/src/modules/catalog/components/companies/companies.service.ts
@@ -1,123 +1,79 @@
import api from '@/services/api';
-import type {
- Company,
- CompanyCreateResponse,
- CompanyFormData,
- CompanyQueryParams,
- PaginatedResponse
-} from './companies.types';
-
-
-export class CompanyService {
+import {
+ buildCreateTenantPayload,
+ buildUpdateTenantPayload,
+ tenantPayloadToFormData,
+} from './companies.mapper';
+import type { CompaniesPagination, TenantFormData } from './companies.types';
+/**
+ * Servicio para obtener todas las empresas (tenants) con paginación.
+ */
+export class CompaniesService {
/**
- * Obtener todas las empresas con paginación y filtros opcionales
+ * Obtiene todas las empresas del sistema.
+ * @returns Promesa con la respuesta paginada de empresas
*/
- async getAll(params?: CompanyQueryParams): Promise | Company[]> {
+ public async getAll(page = 1): Promise {
try {
- const response = await api.get('api/catalogs/companies', { params });
-
- // Si la respuesta tiene paginación, devolver el objeto completo
- if (response.data.current_page !== undefined) {
- return response.data as PaginatedResponse;
- }
-
- // Si no tiene paginación, devolver solo el array de data
- return response.data.data as Company[];
- } catch (error: any) {
+ const response = await api.get('/api/admin/admin/tenants', {
+ params: { page },
+ });
+ return response.data;
+ } catch (error) {
console.error('Error al obtener empresas:', error);
throw error;
}
}
/**
- * Obtener una empresa por ID
+ * Crea una empresa (tenant) usando multipart/form-data.
*/
- async getById(id: number): Promise {
+ public async create(data: TenantFormData): Promise {
try {
- const response = await api.get(`api/catalogs/companies/${id}`);
- return response.data;
- } catch (error: any) {
- console.error(`Error al obtener empresa con ID ${id}:`, error);
- throw error;
- }
- }
-
- /**
- * Crear una nueva empresa
- */
- async create(data: CompanyFormData): Promise {
- try {
- const response = await api.post('api/catalogs/companies', data);
- return response.data;
- } catch (error: any) {
- console.error('Error al crear empresa:', error);
- // Mejorar el mensaje de error si viene del backend
- if (error.response?.data) {
- throw {
- ...error,
- message: error.response.data.message || error.response.data.error || 'Error al crear la empresa'
- };
- }
- throw error;
- }
- }
-
- /**
- * Actualizar una empresa existente
- */
- async update(id: number, data: Partial): Promise {
- try {
- const response = await api.put(`api/catalogs/companies/${id}`, data);
- return response.data;
- } catch (error: any) {
- console.error(`Error al actualizar empresa con ID ${id}:`, error);
- // Mejorar el mensaje de error si viene del backend
- if (error.response?.data) {
- throw {
- ...error,
- message: error.response.data.message || error.response.data.error || 'Error al actualizar la empresa'
- };
- }
- throw error;
- }
- }
-
- /**
- * Eliminar una empresa
- */
- async delete(id: number): Promise {
- try {
- await api.delete(`api/catalogs/companies/${id}`);
- } catch (error: any) {
- console.error(`Error al eliminar empresa con ID ${id}:`, error);
- // Mejorar el mensaje de error si viene del backend
- if (error.response?.data) {
- throw {
- ...error,
- message: error.response.data.message || error.response.data.error || 'Error al eliminar la empresa'
- };
- }
- throw error;
- }
- }
-
- /**
- * Buscar empresas por múltiples criterios
- */
- async search(params: CompanyQueryParams): Promise {
- try {
- const response = await api.get('api/catalogs/companies', {
- params: { ...params, paginate: false }
+ const payload = buildCreateTenantPayload(data);
+ const formData = tenantPayloadToFormData(payload);
+ await api.post('/api/admin/admin/tenants', formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
});
- return response.data.data;
- } catch (error: any) {
- console.error('Error al buscar empresas:', error);
+ } catch (error) {
+ console.error('Error al crear empresa:', error);
throw error;
}
}
-};
+ /**
+ * Actualiza una empresa (tenant) usando POST + _method=PUT para multipart.
+ */
+ public async update(id: number, data: TenantFormData): Promise {
+ try {
+ const payload = buildUpdateTenantPayload(data);
+ const formData = tenantPayloadToFormData(payload, { forcePut: true });
+ await api.post(`/api/admin/admin/tenants/${id}`, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+ } catch (error) {
+ console.error('Error al actualizar empresa:', error);
+ throw error;
+ }
+ }
-// Exportar una instancia única del servicio
-export const companyService = new CompanyService();
+ /**
+ * Elimina una empresa por su ID.
+ * @param id ID de la empresa
+ */
+ public async delete(id: number): Promise {
+ try {
+ await api.delete(`/api/admin/admin/tenants/${id}`);
+ } catch (error) {
+ console.error('Error al eliminar empresa:', error);
+ throw error;
+ }
+ }
+}
+
+export const companiesService = new CompaniesService();
diff --git a/src/modules/catalog/components/companies/companies.types.ts b/src/modules/catalog/components/companies/companies.types.ts
index 3351a50..392a172 100644
--- a/src/modules/catalog/components/companies/companies.types.ts
+++ b/src/modules/catalog/components/companies/companies.types.ts
@@ -1,72 +1,71 @@
-export interface CompanyAddress {
- country: string;
- postal_code: string;
- state: string;
- municipality: string;
- city: string;
- street: string;
- num_ext: string;
- num_int?: string;
+export interface Company {
+ id: number;
+ rfc: string;
+ curp?: string | null;
+ vat_rate?: number | null;
+ isr_withholding?: number | null;
+ vat_withholding?: number | null;
+ additional_tax?: number | null;
+ company_name: string;
+ email?: string;
+ primary_domain?: string;
+ phone?: string | null;
+ legal_representative?: string | null;
+ tax_regime?: string | null;
+ created_at: string;
+ updated_at: string;
+ deleted_at?: string | null;
+ data?: Record | null;
+ is_active: boolean;
+ trial_ends_at?: string | null;
+ subscribed_until?: string | null;
+ domains_count: number;
+ domains: CompanyDomain[];
}
-export interface Company {
+export interface CompanyDomain {
+ id?: number;
+ domain: string;
+}
+
+export interface TenantFormData {
id?: number;
- rfc: string;
- curp?: string;
company_name: string;
+ rfc: string;
+ email: string;
+ primary_domain: string;
+ curp: string;
+ phone: string;
+ legal_representative: string;
+ tax_regime: string;
+ is_active: boolean;
vat_rate: number;
isr_withholding: number;
vat_withholding: number;
additional_tax: number;
- address: CompanyAddress;
- created_at?: string;
- updated_at?: string;
+ certificate_password: string;
+ certificate_cer_file: File | null;
+ certificate_key_file: File | null;
}
-export interface CompanyCreateResponse{
- data: Company;
-}
+export type CreateTenantPayload = Omit;
-export interface CompanyFormData extends Omit {}
+export type UpdateTenantPayload = Omit & {
+ primary_domain?: string;
+};
-export interface CompanyStats {
- total: number;
- active: number;
- pending: number;
- monthlyIncrease: number;
- activePercentage: number;
-}
-
-export interface PaginationLink {
- url: string | null;
- label: string;
- active: boolean;
-}
-
-export interface PaginatedResponse {
+export interface CompaniesPagination {
current_page: number;
- data: T[];
+ data: Company[];
first_page_url: string;
from: number;
last_page: number;
last_page_url: string;
- links: PaginationLink[];
- next_page_url: string | null;
+ links: Array<{ url: string | null; label: string; active: boolean }>;
+ next_page_url?: string | null;
path: string;
per_page: number;
- prev_page_url: string | null;
+ prev_page_url?: string | null;
to: number;
total: number;
}
-
-export interface CompanyListResponse {
- data: Company[];
-}
-
-export interface CompanyQueryParams {
- paginate?: boolean;
- rfc?: string;
- curp?: string;
- company_name?: string;
- page?: number;
-}
diff --git a/src/modules/catalog/components/units/Units.vue b/src/modules/catalog/components/units/Units.vue
index e6e4568..c18fa5d 100644
--- a/src/modules/catalog/components/units/Units.vue
+++ b/src/modules/catalog/components/units/Units.vue
@@ -1,5 +1,5 @@
@@ -159,6 +304,7 @@ onMounted(async () => {
Unidades de Medida
-