Se agrego un modal provicional para el fomrulario y creacion de los registros en la base de datos. Aun faltan 3 catalogos para la integracion con la base de datos
This commit is contained in:
@@ -2,60 +2,71 @@ import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface ClassificationConcept {
|
||||
id: number;
|
||||
classification: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
classification: string;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ClassificationConceptCreate {
|
||||
classification: string;
|
||||
description?: string;
|
||||
classification: string;
|
||||
}
|
||||
|
||||
export interface ClassificationConceptUpdate extends Partial<ClassificationConceptCreate> {}
|
||||
|
||||
export interface ClassificationConceptListResponse {
|
||||
items: ClassificationConcept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: ClassificationConcept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getClassificationConcepts(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ClassificationConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/classification_concepts?${params.toString()}`);
|
||||
return response.data;
|
||||
|
||||
const response = await api.get(`/v1/a76/classification-concepts/?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getClassificationConcept(id: number): Promise<ClassificationConcept> {
|
||||
const response = await api.get(`/a76/classification_concepts/${id}`);
|
||||
return response.data;
|
||||
export async function getClassificationConcept(id: number, companyId: number): Promise<ClassificationConcept> {
|
||||
const response = await api.get(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createClassificationConcept(data: ClassificationConceptCreate): Promise<ClassificationConcept> {
|
||||
const response = await api.post('/a76/classification_concepts', data);
|
||||
return response.data;
|
||||
export async function createClassificationConcept(
|
||||
data: ClassificationConceptCreate,
|
||||
companyId: number
|
||||
): Promise<ClassificationConcept> {
|
||||
// 👇 AQUÍ ESTABA EL ERROR GRAVE
|
||||
const response = await api.post(`/v1/a76/classification-concepts/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateClassificationConcept(id: number, data: ClassificationConceptUpdate): Promise<ClassificationConcept> {
|
||||
const response = await api.patch(`/a76/classification_concepts/${id}`, data);
|
||||
return response.data;
|
||||
|
||||
export async function updateClassificationConcept(
|
||||
id: number,
|
||||
data: ClassificationConceptUpdate,
|
||||
companyId: number // <-- Faltaba esto
|
||||
): Promise<ClassificationConcept> {
|
||||
const response = await api.put(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteClassificationConcept(id: number): Promise<void> {
|
||||
await api.delete(`/a76/classification_concepts/${id}`);
|
||||
}
|
||||
|
||||
export async function deleteClassificationConcept(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -1,91 +1,91 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Company {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
name: string | null;
|
||||
rfc: string | null;
|
||||
main_activity: string | null;
|
||||
program: string | null;
|
||||
program_number: string | null;
|
||||
prosec: number | null;
|
||||
prosec_authorization: string | null;
|
||||
manufacturer_id: string | null;
|
||||
broker_company: string | null;
|
||||
responsible: string | null;
|
||||
responsible_name: string | null;
|
||||
responsible_last_name: string | null;
|
||||
responsible_mother_last_name: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
export interface Company {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
name: string | null;
|
||||
rfc: string | null;
|
||||
main_activity: string | null;
|
||||
program: string | null;
|
||||
program_number: string | null;
|
||||
prosec: number | null;
|
||||
prosec_authorization: string | null;
|
||||
manufacturer_id: string | null;
|
||||
broker_company: string | null;
|
||||
responsible: string | null;
|
||||
responsible_name: string | null;
|
||||
responsible_last_name: string | null;
|
||||
responsible_mother_last_name: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyCreate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
main_activity?: string | null;
|
||||
program?: string | null;
|
||||
program_number?: string | null;
|
||||
prosec?: number | null;
|
||||
prosec_authorization?: string | null;
|
||||
manufacturer_id?: string | null;
|
||||
broker_company?: string | null;
|
||||
responsible?: string | null;
|
||||
responsible_name?: string | null;
|
||||
responsible_last_name?: string | null;
|
||||
responsible_mother_last_name?: string | null;
|
||||
}
|
||||
export interface CompanyCreate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
main_activity?: string | null;
|
||||
program?: string | null;
|
||||
program_number?: string | null;
|
||||
prosec?: number | null;
|
||||
prosec_authorization?: string | null;
|
||||
manufacturer_id?: string | null;
|
||||
broker_company?: string | null;
|
||||
responsible?: string | null;
|
||||
responsible_name?: string | null;
|
||||
responsible_last_name?: string | null;
|
||||
responsible_mother_last_name?: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyUpdate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
main_activity?: string | null;
|
||||
program?: string | null;
|
||||
program_number?: string | null;
|
||||
prosec?: number | null;
|
||||
prosec_authorization?: string | null;
|
||||
manufacturer_id?: string | null;
|
||||
broker_company?: string | null;
|
||||
responsible?: string | null;
|
||||
responsible_name?: string | null;
|
||||
responsible_last_name?: string | null;
|
||||
responsible_mother_last_name?: string | null;
|
||||
}
|
||||
export interface CompanyUpdate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
main_activity?: string | null;
|
||||
program?: string | null;
|
||||
program_number?: string | null;
|
||||
prosec?: number | null;
|
||||
prosec_authorization?: string | null;
|
||||
manufacturer_id?: string | null;
|
||||
broker_company?: string | null;
|
||||
responsible?: string | null;
|
||||
responsible_name?: string | null;
|
||||
responsible_last_name?: string | null;
|
||||
responsible_mother_last_name?: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyListResponse {
|
||||
items: Company[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
export interface CompanyListResponse {
|
||||
items: Company[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getCompanies(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<CompanyListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/company?${queryParams.toString()}`);
|
||||
}
|
||||
export async function getCompanies(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<CompanyListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/company?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
|
||||
return await api.get(`/a76/company/${id}`);
|
||||
}
|
||||
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
|
||||
return await api.get(`/a76/company/${id}`);
|
||||
}
|
||||
|
||||
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
|
||||
return await api.post(`/a76/company`, data);
|
||||
}
|
||||
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
|
||||
return await api.post(`/v1/a76/company`, data);
|
||||
}
|
||||
|
||||
export async function updateCompany(id: number, data: CompanyUpdate): Promise<ApiResponse<Company>> {
|
||||
return await api.put(`/a76/company/${id}`, data);
|
||||
}
|
||||
export async function updateCompany(id: number, data: CompanyUpdate): Promise<ApiResponse<Company>> {
|
||||
return await api.put(`/a76/company/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/company/${id}`);
|
||||
}
|
||||
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/company/${id}`);
|
||||
}
|
||||
|
||||
@@ -2,78 +2,88 @@ import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Concept {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
priority_ame?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
priority_ame?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ConceptCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
priority_ame?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
priority_ame?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
}
|
||||
|
||||
export interface ConceptUpdate extends Partial<ConceptCreate> {}
|
||||
|
||||
export interface ConceptListResponse {
|
||||
items: Concept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: Concept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getConcepts(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {},
|
||||
): Promise<ApiResponse<ConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/concepts?${params.toString()}`);
|
||||
return response.data;
|
||||
const response = await api.get(`/v1/a76/concepts/?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getConcept(id: number): Promise<Concept> {
|
||||
const response = await api.get(`/a76/concepts/${id}`);
|
||||
return response.data;
|
||||
|
||||
export async function getConcept(id: number, companyId: number): Promise<Concept> {
|
||||
|
||||
const response = await api.get(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createConcept(data: ConceptCreate): Promise<Concept> {
|
||||
const response = await api.post('/a76/concepts', data);
|
||||
return response.data;
|
||||
|
||||
export async function createConcept(data: ConceptCreate, companyId: number): Promise<Concept> {
|
||||
|
||||
const response = await api.post(`/v1/a76/concepts/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateConcept(id: number, data: ConceptUpdate): Promise<Concept> {
|
||||
const response = await api.patch(`/a76/concepts/${id}`, data);
|
||||
return response.data;
|
||||
|
||||
export async function updateConcept(id: number, data: ConceptUpdate, companyId: number): Promise<Concept> {
|
||||
|
||||
const response = await api.put(`/v1/a76/concepts/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteConcept(id: number): Promise<void> {
|
||||
await api.delete(`/a76/concepts/${id}`);
|
||||
}
|
||||
|
||||
export async function deleteConcept(id: number, companyId: number): Promise<void> {
|
||||
|
||||
await api.delete(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -2,76 +2,68 @@ import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface CustomsBrokerConcept {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
broker_key: string;
|
||||
concept: string;
|
||||
amount?: number;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export interface CustomsBrokerConceptCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
broker_key: string;
|
||||
concept?: string;
|
||||
amount?: number;
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export interface CustomsBrokerConceptUpdate extends Partial<CustomsBrokerConceptCreate> {}
|
||||
|
||||
export interface CustomsBrokerConceptListResponse {
|
||||
items: CustomsBrokerConcept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: CustomsBrokerConcept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
|
||||
export async function getCustomsBrokerConcepts(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number, // <-- Nuevo
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<CustomsBrokerConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/customs_broker_concepts?${params.toString()}`);
|
||||
return response.data;
|
||||
|
||||
const response = await api.get(`/v1/a76/customs-broker-concepts/?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getCustomsBrokerConcept(id: number): Promise<CustomsBrokerConcept> {
|
||||
const response = await api.get(`/a76/customs_broker_concepts/${id}`);
|
||||
return response.data;
|
||||
export async function createCustomsBrokerConcept(
|
||||
data: CustomsBrokerConceptCreate,
|
||||
companyId: number
|
||||
): Promise<CustomsBrokerConcept> {
|
||||
|
||||
const response = await api.post(`/v1/a76/customs-broker-concepts/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate): Promise<CustomsBrokerConcept> {
|
||||
const response = await api.post('/a76/customs_broker_concepts', data);
|
||||
return response.data;
|
||||
export async function updateCustomsBrokerConcept(
|
||||
id: number,
|
||||
data: CustomsBrokerConceptUpdate,
|
||||
companyId: number
|
||||
): Promise<CustomsBrokerConcept> {
|
||||
|
||||
const response = await api.put(`/v1/a76/customs-broker-concepts/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateCustomsBrokerConcept(id: number, data: CustomsBrokerConceptUpdate): Promise<CustomsBrokerConcept> {
|
||||
const response = await api.patch(`/a76/customs_broker_concepts/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteCustomsBrokerConcept(id: number): Promise<void> {
|
||||
await api.delete(`/a76/customs_broker_concepts/${id}`);
|
||||
}
|
||||
export async function deleteCustomsBrokerConcept(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/customs-broker-concepts/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -1,61 +1,134 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
// ==========================================
|
||||
// ERROR CLASSIFICATION
|
||||
// ==========================================
|
||||
|
||||
export interface ErrorClassification {
|
||||
id: number;
|
||||
code: string;
|
||||
level?: string;
|
||||
errors?: ErrorCatalog[];
|
||||
tenant_id?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ErrorClassificationCreate {
|
||||
code: string;
|
||||
level?: string;
|
||||
}
|
||||
|
||||
export interface ErrorClassificationUpdate {
|
||||
level?: string;
|
||||
}
|
||||
|
||||
export interface ErrorClassificationListResponse {
|
||||
items: ErrorClassification[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
|
||||
export interface ErrorCatalog {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
classification_id?: number;
|
||||
classification?: ErrorClassification;
|
||||
tenant_id?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ErrorCatalogCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
code: string;
|
||||
description?: string;
|
||||
classification_id?: number;
|
||||
}
|
||||
|
||||
export interface ErrorCatalogUpdate extends Partial<ErrorCatalogCreate> {}
|
||||
export interface ErrorCatalogUpdate {
|
||||
description?: string;
|
||||
classification_id?: number;
|
||||
}
|
||||
|
||||
export interface ErrorCatalogListResponse {
|
||||
items: ErrorCatalog[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: ErrorCatalog[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getErrorCatalogs(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ErrorCatalogListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
export async function getErrorClassifications(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ErrorClassificationListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/error_catalogs?${params.toString()}`);
|
||||
return response.data;
|
||||
const response = await api.get(`/a76/error_classifications?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getErrorClassification(id: number): Promise<ErrorClassification> {
|
||||
const response = await api.get(`/a76/error_classifications/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createErrorClassification(data: ErrorClassificationCreate): Promise<ErrorClassification> {
|
||||
const response = await api.post('/a76/error_classifications', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateErrorClassification(id: number, data: ErrorClassificationUpdate): Promise<ErrorClassification> {
|
||||
const response = await api.patch(`/a76/error_classifications/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteErrorClassification(id: number): Promise<void> {
|
||||
await api.delete(`/a76/error_classifications/${id}`);
|
||||
}
|
||||
|
||||
// --- Catalogs ---
|
||||
|
||||
export async function getErrorCatalogs(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ErrorCatalogListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/error_catalogs?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getErrorCatalog(id: number): Promise<ErrorCatalog> {
|
||||
const response = await api.get(`/a76/error_catalogs/${id}`);
|
||||
return response.data;
|
||||
const response = await api.get(`/a76/error_catalogs/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createErrorCatalog(data: ErrorCatalogCreate): Promise<ErrorCatalog> {
|
||||
const response = await api.post('/a76/error_catalogs', data);
|
||||
return response.data;
|
||||
const response = await api.post('/a76/error_catalogs', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateErrorCatalog(id: number, data: ErrorCatalogUpdate): Promise<ErrorCatalog> {
|
||||
const response = await api.patch(`/a76/error_catalogs/${id}`, data);
|
||||
return response.data;
|
||||
const response = await api.patch(`/a76/error_catalogs/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteErrorCatalog(id: number): Promise<void> {
|
||||
await api.delete(`/a76/error_catalogs/${id}`);
|
||||
}
|
||||
await api.delete(`/a76/error_catalogs/${id}`);
|
||||
}
|
||||
@@ -7,8 +7,8 @@ export interface Identifier {
|
||||
description: string | null;
|
||||
level: string | null;
|
||||
complement: string | null;
|
||||
company_id: number;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
tenant_id: number;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
@@ -18,7 +18,6 @@ export interface IdentifierCreate {
|
||||
description?: string | null;
|
||||
level?: string | null;
|
||||
complement?: string | null;
|
||||
company_id: number;
|
||||
}
|
||||
|
||||
export interface IdentifierUpdate {
|
||||
@@ -39,24 +38,37 @@ export interface IdentifierListResponse {
|
||||
export async function getIdentifiers(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<IdentifierListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/identifiers?${queryParams.toString()}`);
|
||||
|
||||
return await api.get(`/v1/a76/identifiers/?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createIdentifier(data: IdentifierCreate): Promise<ApiResponse<Identifier>> {
|
||||
return await api.post('/a76/identifiers', data);
|
||||
export async function createIdentifier(
|
||||
data: IdentifierCreate,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<Identifier>> {
|
||||
return await api.post(`/v1/a76/identifiers/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function updateIdentifier(id: number, data: IdentifierUpdate): Promise<ApiResponse<Identifier>> {
|
||||
return await api.put(`/a76/identifiers/${id}`, data);
|
||||
export async function updateIdentifier(
|
||||
id: number,
|
||||
data: IdentifierUpdate,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<Identifier>> {
|
||||
return await api.put(`/v1/a76/identifiers/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteIdentifier(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/identifiers/${id}`);
|
||||
}
|
||||
export async function deleteIdentifier(
|
||||
id: number,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/identifiers/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -2,62 +2,77 @@ import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface INPC {
|
||||
id: number;
|
||||
year: string;
|
||||
month: string;
|
||||
value?: number;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
year: string;
|
||||
month: string;
|
||||
value?: number;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface INPCCreate {
|
||||
year: string;
|
||||
month: string;
|
||||
value?: number;
|
||||
year: string;
|
||||
month: string;
|
||||
value?: number;
|
||||
|
||||
}
|
||||
|
||||
export interface INPCUpdate extends Partial<INPCCreate> {}
|
||||
|
||||
export interface INPCListResponse {
|
||||
items: INPC[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: INPC[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
|
||||
export async function getINPCs(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<INPCListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/inpc?${params.toString()}`);
|
||||
return response.data;
|
||||
const response = await api.get(`/v1/a76/inpc/?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getINPC(id: number): Promise<INPC> {
|
||||
const response = await api.get(`/a76/inpc/${id}`);
|
||||
return response.data;
|
||||
|
||||
export async function getINPC(id: number, companyId: number): Promise<INPC> {
|
||||
const response = await api.get(`/v1/a76/inpc/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createINPC(data: INPCCreate): Promise<INPC> {
|
||||
const response = await api.post('/a76/inpc', data);
|
||||
return response.data;
|
||||
|
||||
export async function createINPC(
|
||||
data: INPCCreate,
|
||||
companyId: number
|
||||
): Promise<INPC> {
|
||||
const response = await api.post(`/v1/a76/inpc/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateINPC(id: number, data: INPCUpdate): Promise<INPC> {
|
||||
const response = await api.patch(`/a76/inpc/${id}`, data);
|
||||
return response.data;
|
||||
|
||||
export async function updateINPC(
|
||||
id: number,
|
||||
data: INPCUpdate,
|
||||
companyId: number
|
||||
): Promise<INPC> {
|
||||
|
||||
const response = await api.put(`/v1/a76/inpc/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteINPC(id: number): Promise<void> {
|
||||
await api.delete(`/a76/inpc/${id}`);
|
||||
}
|
||||
export async function deleteINPC(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/inpc/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -2,60 +2,71 @@ import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Legend {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
code: number;
|
||||
description?: string;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface LegendCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
code: number;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface LegendUpdate extends Partial<LegendCreate> {}
|
||||
|
||||
export interface LegendListResponse {
|
||||
items: Legend[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: Legend[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getLegends(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<LegendListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/legends?${params.toString()}`);
|
||||
return response.data;
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await api.get(`/v1/a76/legends/?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getLegend(id: number): Promise<Legend> {
|
||||
const response = await api.get(`/a76/legends/${id}`);
|
||||
return response.data;
|
||||
export async function getLegend(id: number, companyId: number): Promise<Legend> {
|
||||
const response = await api.get(`/v1/a76/legends/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createLegend(data: LegendCreate): Promise<Legend> {
|
||||
const response = await api.post('/a76/legends', data);
|
||||
return response.data;
|
||||
|
||||
export async function createLegend(
|
||||
data: LegendCreate,
|
||||
companyId: number
|
||||
): Promise<Legend> {
|
||||
|
||||
const response = await api.post(`/v1/a76/legends/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateLegend(id: number, data: LegendUpdate): Promise<Legend> {
|
||||
const response = await api.patch(`/a76/legends/${id}`, data);
|
||||
return response.data;
|
||||
export async function updateLegend(
|
||||
id: number,
|
||||
data: LegendUpdate,
|
||||
companyId: number
|
||||
): Promise<Legend> {
|
||||
|
||||
const response = await api.put(`/v1/a76/legends/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteLegend(id: number): Promise<void> {
|
||||
await api.delete(`/a76/legends/${id}`);
|
||||
}
|
||||
export async function deleteLegend(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/legends/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -1,52 +1,69 @@
|
||||
/**
|
||||
* API Client para Locations - Ubicaciones relacionadas con puertos
|
||||
* Basado en los campos location_code y location_description del módulo de puertos
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Location {
|
||||
location_code: string;
|
||||
location_description: string | null;
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
|
||||
|
||||
tenant_id?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nota: Las ubicaciones están integradas en el módulo de puertos.
|
||||
* Este archivo proporciona tipos para trabajar con ubicaciones,
|
||||
* pero las operaciones se realizan a través del módulo de puertos.
|
||||
*
|
||||
* Ver: /a76/ports para operaciones relacionadas con ubicaciones
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtiene ubicaciones únicas de los puertos
|
||||
* Esta función extrae las ubicaciones únicas de la lista de puertos
|
||||
*/
|
||||
export async function getLocationsFromPorts(): Promise<ApiResponse<Location[]>> {
|
||||
const portsResponse = await api.get('/a76/ports?page_size=1000');
|
||||
|
||||
if (portsResponse.error || !portsResponse.data) {
|
||||
return {
|
||||
error: portsResponse.error || 'Error al obtener puertos',
|
||||
status: portsResponse.status
|
||||
};
|
||||
}
|
||||
export interface LocationCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// Extraer ubicaciones únicas
|
||||
const locationMap = new Map<string, Location>();
|
||||
const ports = portsResponse.data.items || [];
|
||||
|
||||
ports.forEach((port: any) => {
|
||||
if (port.location_code && !locationMap.has(port.location_code)) {
|
||||
locationMap.set(port.location_code, {
|
||||
location_code: port.location_code,
|
||||
location_description: port.location_description
|
||||
});
|
||||
}
|
||||
|
||||
export interface LocationUpdate {
|
||||
code?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface LocationListResponse {
|
||||
items: Location[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function getLocations(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<LocationListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
return {
|
||||
data: Array.from(locationMap.values()),
|
||||
status: 200
|
||||
};
|
||||
const response = await api.get(`/a24/locations?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getLocation(id: number): Promise<Location> {
|
||||
const response = await api.get(`/a24/locations/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createLocation(data: LocationCreate): Promise<Location> {
|
||||
const response = await api.post('/a24/locations', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateLocation(id: number, data: LocationUpdate): Promise<Location> {
|
||||
const response = await api.patch(`/a24/locations/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteLocation(id: number): Promise<void> {
|
||||
await api.delete(`/a24/locations/${id}`);
|
||||
}
|
||||
@@ -2,60 +2,78 @@ import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface MultiCurrencyType {
|
||||
id: number;
|
||||
key: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
|
||||
currency_type_code: string;
|
||||
country_key: string | null;
|
||||
conversion_factor: number | null;
|
||||
publication_date: number;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface MultiCurrencyTypeCreate {
|
||||
key: string;
|
||||
description?: string;
|
||||
currency_type_code: string;
|
||||
country_key?: string | null;
|
||||
conversion_factor?: number | null;
|
||||
publication_date: number;
|
||||
}
|
||||
|
||||
export interface MultiCurrencyTypeUpdate extends Partial<MultiCurrencyTypeCreate> {}
|
||||
|
||||
export interface MultiCurrencyTypeListResponse {
|
||||
items: MultiCurrencyType[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: MultiCurrencyType[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getMultiCurrencyTypes(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<MultiCurrencyTypeListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/multi_currency_types?${params.toString()}`);
|
||||
return response.data;
|
||||
// Agregamos /v1 y el path correcto
|
||||
const response = await api.get(`/v1/a76/multi_currency_types/?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getMultiCurrencyType(id: number): Promise<MultiCurrencyType> {
|
||||
const response = await api.get(`/a76/multi_currency_types/${id}`);
|
||||
return response.data;
|
||||
|
||||
export async function getMultiCurrencyType(id: number, companyId: number): Promise<MultiCurrencyType> {
|
||||
const response = await api.get(`/v1/a76/multi_currency_types/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createMultiCurrencyType(data: MultiCurrencyTypeCreate): Promise<MultiCurrencyType> {
|
||||
const response = await api.post('/a76/multi_currency_types', data);
|
||||
return response.data;
|
||||
export async function createMultiCurrencyType(
|
||||
data: MultiCurrencyTypeCreate,
|
||||
companyId: number
|
||||
): Promise<MultiCurrencyType> {
|
||||
const response = await api.post(`/v1/a76/multi_currency_types/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateMultiCurrencyType(id: number, data: MultiCurrencyTypeUpdate): Promise<MultiCurrencyType> {
|
||||
const response = await api.patch(`/a76/multi_currency_types/${id}`, data);
|
||||
return response.data;
|
||||
|
||||
export async function updateMultiCurrencyType(
|
||||
id: number,
|
||||
data: MultiCurrencyTypeUpdate,
|
||||
companyId: number
|
||||
): Promise<MultiCurrencyType> {
|
||||
const response = await api.put(`/v1/a76/multi_currency_types/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteMultiCurrencyType(id: number): Promise<void> {
|
||||
await api.delete(`/a76/multi_currency_types/${id}`);
|
||||
}
|
||||
|
||||
export async function deleteMultiCurrencyType(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/multi_currency_types/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -4,17 +4,17 @@ import type { ApiResponse } from '$lib/api';
|
||||
export interface Package {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
key: string;
|
||||
description_es: string | null;
|
||||
description_en: string | null;
|
||||
weight_unit: number | null;
|
||||
plurals: string | null;
|
||||
plural_in: string | null;
|
||||
code_ace: string | null;
|
||||
code_aamex: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
company_id: number;
|
||||
key: string;
|
||||
description_es: string | null;
|
||||
description_en: string | null;
|
||||
weight_unit: number | null;
|
||||
plurals: string | null;
|
||||
plural_in: string | null;
|
||||
code_ace: string | null;
|
||||
code_aamex: string | null;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface PackageCreate {
|
||||
@@ -26,19 +26,9 @@ export interface PackageCreate {
|
||||
plural_in?: string | null;
|
||||
code_ace?: string | null;
|
||||
code_aamex?: string | null;
|
||||
company_id: number;
|
||||
}
|
||||
|
||||
export interface PackageUpdate {
|
||||
key?: string;
|
||||
description_es?: string | null;
|
||||
description_en?: string | null;
|
||||
weight_unit?: number | null;
|
||||
plurals?: string | null;
|
||||
plural_in?: string | null;
|
||||
code_ace?: string | null;
|
||||
code_aamex?: string | null;
|
||||
}
|
||||
export interface PackageUpdate extends Partial<PackageCreate> {}
|
||||
|
||||
export interface PackageListResponse {
|
||||
items: Package[];
|
||||
@@ -48,31 +38,48 @@ export interface PackageListResponse {
|
||||
pages: number;
|
||||
}
|
||||
|
||||
|
||||
export async function getPackages(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number, // Obligatorio por el Mixin
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<PackageListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/packages?${queryParams.toString()}`);
|
||||
|
||||
const response = await api.get(`/v1/a76/packages/?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getPackage(id: number): Promise<ApiResponse<Package>> {
|
||||
return await api.get(`/a76/packages/${id}`);
|
||||
|
||||
export async function getPackage(id: number, companyId: number): Promise<Package> {
|
||||
const response = await api.get(`/v1/a76/packages/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createPackage(data: PackageCreate): Promise<ApiResponse<Package>> {
|
||||
return await api.post(`/a76/packages`, data);
|
||||
export async function createPackage(
|
||||
data: PackageCreate,
|
||||
companyId: number
|
||||
): Promise<Package> {
|
||||
const response = await api.post(`/v1/a76/packages/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updatePackage(id: number, data: PackageUpdate): Promise<ApiResponse<Package>> {
|
||||
return await api.put(`/a76/packages/${id}`, data);
|
||||
|
||||
export async function updatePackage(
|
||||
id: number,
|
||||
data: PackageUpdate,
|
||||
companyId: number
|
||||
): Promise<Package> {
|
||||
const response = await api.put(`/v1/a76/packages/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deletePackage(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/packages/${id}`);
|
||||
}
|
||||
export async function deletePackage(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/packages/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -2,62 +2,79 @@ import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Signature {
|
||||
id: number;
|
||||
name: string;
|
||||
position?: string;
|
||||
certificate?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
|
||||
code: string;
|
||||
signature: string | null;
|
||||
photo_path: string | null;
|
||||
|
||||
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface SignatureCreate {
|
||||
name: string;
|
||||
position?: string;
|
||||
certificate?: string;
|
||||
code: string;
|
||||
signature?: string | null;
|
||||
photo_path?: string | null;
|
||||
|
||||
}
|
||||
|
||||
export interface SignatureUpdate extends Partial<SignatureCreate> {}
|
||||
|
||||
export interface SignatureListResponse {
|
||||
items: Signature[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: Signature[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
|
||||
export async function getSignatures(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<SignatureListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/signatures?${params.toString()}`);
|
||||
return response.data;
|
||||
|
||||
const response = await api.get(`/v1/a76/signatures/?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getSignature(id: number): Promise<Signature> {
|
||||
const response = await api.get(`/a76/signatures/${id}`);
|
||||
return response.data;
|
||||
|
||||
export async function getSignature(id: number, companyId: number): Promise<Signature> {
|
||||
const response = await api.get(`/v1/a76/signatures/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createSignature(data: SignatureCreate): Promise<Signature> {
|
||||
const response = await api.post('/a76/signatures', data);
|
||||
return response.data;
|
||||
|
||||
export async function createSignature(
|
||||
data: SignatureCreate,
|
||||
companyId: number
|
||||
): Promise<Signature> {
|
||||
const response = await api.post(`/v1/a76/signatures/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateSignature(id: number, data: SignatureUpdate): Promise<Signature> {
|
||||
const response = await api.patch(`/a76/signatures/${id}`, data);
|
||||
return response.data;
|
||||
export async function updateSignature(
|
||||
id: number,
|
||||
data: SignatureUpdate,
|
||||
companyId: number
|
||||
): Promise<Signature> {
|
||||
const response = await api.put(`/v1/a76/signatures/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteSignature(id: number): Promise<void> {
|
||||
await api.delete(`/a76/signatures/${id}`);
|
||||
}
|
||||
export async function deleteSignature(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/signatures/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -2,62 +2,76 @@ import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface UnitConversion {
|
||||
id: number;
|
||||
from_unit_id: number;
|
||||
to_unit_id: number;
|
||||
conversion_factor: number;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
from_unit_code: string;
|
||||
to_unit_code: string;
|
||||
conversion_factor: number;
|
||||
tenant_id: number; // number
|
||||
company_id: number; // number
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface UnitConversionCreate {
|
||||
from_unit_id: number;
|
||||
to_unit_id: number;
|
||||
conversion_factor: number;
|
||||
from_unit_code: string; // Ej: "KGM"
|
||||
to_unit_code: string; // Ej: "LBR"
|
||||
conversion_factor: number;
|
||||
// company_id va en la URL
|
||||
}
|
||||
|
||||
export interface UnitConversionUpdate extends Partial<UnitConversionCreate> {}
|
||||
|
||||
export interface UnitConversionListResponse {
|
||||
items: UnitConversion[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: UnitConversion[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
|
||||
export async function getUnitConversions(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number, // 👈 Obligatorio
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitConversionListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/unit_conversions?${params.toString()}`);
|
||||
return response.data;
|
||||
// Agregamos /v1 y prefijo.
|
||||
// NOTA: Revisa si en tu router definiste "unit_conversions" o "unit-conversions"
|
||||
const response = await api.get(`/v1/a76/unit-conversions/?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getUnitConversion(id: number): Promise<UnitConversion> {
|
||||
const response = await api.get(`/a76/unit_conversions/${id}`);
|
||||
return response.data;
|
||||
export async function getUnitConversion(id: number, companyId: number): Promise<UnitConversion> {
|
||||
const response = await api.get(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createUnitConversion(data: UnitConversionCreate): Promise<UnitConversion> {
|
||||
const response = await api.post('/a76/unit_conversions', data);
|
||||
return response.data;
|
||||
export async function createUnitConversion(
|
||||
data: UnitConversionCreate,
|
||||
companyId: number
|
||||
): Promise<UnitConversion> {
|
||||
const response = await api.post(`/v1/a76/unit-conversions/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateUnitConversion(id: number, data: UnitConversionUpdate): Promise<UnitConversion> {
|
||||
const response = await api.patch(`/a76/unit_conversions/${id}`, data);
|
||||
return response.data;
|
||||
|
||||
export async function updateUnitConversion(
|
||||
id: number,
|
||||
data: UnitConversionUpdate,
|
||||
companyId: number
|
||||
): Promise<UnitConversion> {
|
||||
const response = await api.put(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteUnitConversion(id: number): Promise<void> {
|
||||
await api.delete(`/a76/unit_conversions/${id}`);
|
||||
}
|
||||
export async function deleteUnitConversion(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/unit_conversions/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -124,7 +124,7 @@ export interface InvoiceListResponse {
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
export interface CreateInvoiceData {
|
||||
export interface InvoiceData {
|
||||
operation_type?: OperationType | null;
|
||||
invoice_type?: string | null;
|
||||
invoice_number?: string | null;
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { createCompany, updateCompany, type Company } from "$lib/api/dashboard/a76/general_catalogs/company";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Company | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Empresa" : "Nueva Empresa");
|
||||
|
||||
let formData = $state({
|
||||
name: item?.name || '',
|
||||
rfc: item?.rfc || '',
|
||||
main_activity: item?.main_activity || '',
|
||||
program: item?.program || '',
|
||||
program_number: item?.program_number || ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (item) {
|
||||
formData = {
|
||||
name: item.name || '',
|
||||
rfc: item.rfc || '',
|
||||
main_activity: item.main_activity || '',
|
||||
program: item.program || '',
|
||||
program_number: item.program_number || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
name: '',
|
||||
rfc: '',
|
||||
main_activity: '',
|
||||
program: '',
|
||||
program_number: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
if (!formData.name.trim()) throw new Error('El nombre es requerido');
|
||||
|
||||
const dataToSend = {
|
||||
name: formData.name.trim(),
|
||||
rfc: formData.rfc.trim() || null,
|
||||
main_activity: formData.main_activity.trim() || null,
|
||||
program: formData.program.trim() || null,
|
||||
program_number: formData.program_number.trim() || null
|
||||
};
|
||||
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateCompany(item.id, dataToSend);
|
||||
} else {
|
||||
response = await createCompany(dataToSend);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-md">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="text-destructive text-sm">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="name">Nombre <span class="text-destructive">*</span></Label>
|
||||
<Input id="name" bind:value={formData.name} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="rfc">RFC</Label>
|
||||
<Input id="rfc" bind:value={formData.rfc} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="program">Programa</Label>
|
||||
<Input id="program" bind:value={formData.program} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="program_number">No. Programa</Label>
|
||||
<Input id="program_number" bind:value={formData.program_number} />
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false}>Cancelar</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -1,164 +1,187 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
import type {
|
||||
ExchangeRate,
|
||||
ExchangeRateCreate,
|
||||
ExchangeRateUpdate
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
|
||||
import { createExchangeRate, updateExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import {
|
||||
createExchangeRate,
|
||||
updateExchangeRate,
|
||||
type ExchangeRate
|
||||
} from "$lib/api/dashboard/a76/general_catalogs/exchange-rate";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
item?: ExchangeRate | null;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSuccess?: (item: ExchangeRate) => void;
|
||||
}
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: ExchangeRate | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let { open = $bindable(false), item = null, onOpenChange, onSuccess }: Props = $props();
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Tipo de Cambio" : "Nuevo Tipo de Cambio");
|
||||
|
||||
let formData = $state<ExchangeRateCreate | ExchangeRateUpdate>({
|
||||
date: '',
|
||||
value: null,
|
||||
local_currency: null,
|
||||
foreign_currency: null
|
||||
});
|
||||
// Estado del formulario
|
||||
let formData = $state({
|
||||
date: '', // Se usará con input type="date" (YYYY-MM-DD)
|
||||
value: null as number | null,
|
||||
local_currency: '',
|
||||
foreign_currency: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
let isEdit = $derived(!!item);
|
||||
// Cargar datos al abrir
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (item && item.date) {
|
||||
// Truco: Si la fecha viene con hora (ej: 2023-01-01T12:00:00),
|
||||
// solo tomamos la parte de la fecha para el input.
|
||||
const formattedDate = item.date.includes('T') ? item.date.split('T')[0] : item.date;
|
||||
|
||||
$effect(() => {
|
||||
if (item) {
|
||||
const date = new Date(item.date);
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
|
||||
formData = {
|
||||
date: dateStr,
|
||||
value: item.value,
|
||||
local_currency: item.local_currency,
|
||||
foreign_currency: item.foreign_currency
|
||||
};
|
||||
} else {
|
||||
const today = new Date();
|
||||
const dateStr = today.toISOString().split('T')[0];
|
||||
formData = {
|
||||
date: dateStr,
|
||||
value: null,
|
||||
local_currency: null,
|
||||
foreign_currency: null
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
});
|
||||
formData = {
|
||||
date: formattedDate,
|
||||
value: item.value,
|
||||
local_currency: item.local_currency || '',
|
||||
foreign_currency: item.foreign_currency || ''
|
||||
};
|
||||
} else {
|
||||
// Reset para nuevo registro. Ponemos la fecha de hoy por default.
|
||||
formData = {
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
value: null,
|
||||
local_currency: 'MXN', // Default común
|
||||
foreign_currency: 'USD' // Default común
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
loading = true;
|
||||
error = null;
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
error = 'No hay una empresa seleccionada';
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
try {
|
||||
let result: ExchangeRate;
|
||||
if (isEdit && item) {
|
||||
result = await updateExchangeRate(item.id, formData as ExchangeRateUpdate, companyId);
|
||||
} else {
|
||||
result = await createExchangeRate(formData as ExchangeRateCreate, companyId);
|
||||
}
|
||||
// Validaciones básicas
|
||||
if (!formData.date) throw new Error('La fecha es requerida');
|
||||
if (formData.value === null || formData.value === undefined) throw new Error('El valor es requerido');
|
||||
|
||||
// Preparar datos
|
||||
// Pydantic suele aceptar YYYY-MM-DD para campos datetime sin problema.
|
||||
const dataToSend = {
|
||||
date: formData.date,
|
||||
value: Number(formData.value),
|
||||
// Estandarizamos a mayúsculas las monedas
|
||||
local_currency: formData.local_currency?.trim().toUpperCase() || null,
|
||||
foreign_currency: formData.foreign_currency?.trim().toUpperCase() || null
|
||||
};
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess(result);
|
||||
}
|
||||
onOpenChange(false);
|
||||
} catch (err: any) {
|
||||
error = err.message || `Error al ${isEdit ? 'actualizar' : 'crear'} el tipo de cambio`;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
let response;
|
||||
|
||||
// 👇 companyId va como tercer argumento, ¡bien ahí!
|
||||
if (isEdit && item) {
|
||||
response = await updateExchangeRate(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
response = await createExchangeRate(dataToSend, companyId);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
|
||||
} catch (e) {
|
||||
// Si el API wrapper no lanza error, revisa cómo manejar la respuesta de error aquí
|
||||
error = e instanceof Error ? e.message : 'Error al guardar el tipo de cambio';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root {open} onOpenChange={onOpenChange}>
|
||||
<Dialog.Content class="sm:max-w-[500px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{isEdit ? 'Editar' : 'Crear'} Tipo de Cambio</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[500px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="date">Fecha *</Label>
|
||||
<Input
|
||||
id="date"
|
||||
type="date"
|
||||
bind:value={formData.date}
|
||||
required
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="value">Tipo de Cambio *</Label>
|
||||
<Input
|
||||
id="value"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
bind:value={formData.value}
|
||||
placeholder="0.000000"
|
||||
required
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="date" class="text-right">Fecha <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="date"
|
||||
type="date"
|
||||
bind:value={formData.date}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="local_currency">Moneda Local</Label>
|
||||
<Input
|
||||
id="local_currency"
|
||||
type="text"
|
||||
maxlength="7"
|
||||
bind:value={formData.local_currency}
|
||||
placeholder="MXN"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="value" class="text-right">Valor <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="value"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
bind:value={formData.value}
|
||||
placeholder="Ej: 18.5000"
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Hasta 6 decimales.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="foreign_currency">Moneda Extranjera</Label>
|
||||
<Input
|
||||
id="foreign_currency"
|
||||
type="text"
|
||||
maxlength="7"
|
||||
bind:value={formData.foreign_currency}
|
||||
placeholder="USD"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="local_currency" class="text-right">Moneda Local</Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="local_currency"
|
||||
bind:value={formData.local_currency}
|
||||
placeholder="Ej: MXN"
|
||||
maxlength={7}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<p class="text-sm text-destructive">{error}</p>
|
||||
{/if}
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="foreign_currency" class="text-right">Moneda Ext.</Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="foreign_currency"
|
||||
bind:value={formData.foreign_currency}
|
||||
placeholder="Ej: USD"
|
||||
maxlength={7}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onclick={() => onOpenChange(false)} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
{isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
// 👇 1. Importamos la API correcta
|
||||
import {
|
||||
updateClassificationConcept,
|
||||
createClassificationConcept,
|
||||
type ClassificationConcept
|
||||
} from "$lib/api/dashboard/a76/general_catalogs/classification-concepts";
|
||||
// 👇 2. Importamos el Store para el ID de la empresa
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: ClassificationConcept | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Clasificación" : "Nueva Clasificación");
|
||||
|
||||
// 👇 3. Estado limpio: Solo lo que existe en la BD
|
||||
let formData = $state({
|
||||
classification: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Cargar datos al abrir
|
||||
$effect(() => {
|
||||
if (item) {
|
||||
formData = {
|
||||
classification: item.classification || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
classification: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
// 👇 4. Validar Company ID
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
// Validar campos
|
||||
if (!formData.classification.trim()) throw new Error('El nombre de la clasificación es requerido');
|
||||
|
||||
const dataToSend = {
|
||||
classification: formData.classification.trim()
|
||||
};
|
||||
|
||||
// 👇 5. Llamar a la API pasando el companyId
|
||||
if (isEdit && item) {
|
||||
await updateClassificationConcept(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
await createClassificationConcept(dataToSend, companyId);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-md">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="p-3 text-sm text-destructive bg-destructive/10 rounded-md">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="classification">Clasificación <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="classification"
|
||||
bind:value={formData.classification}
|
||||
placeholder="Ej: GENERAL"
|
||||
maxlength={30}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false}>Cancelar</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,296 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
import { Switch } from "$lib/components/ui/switch";
|
||||
import { createCompany, updateCompany, type Company } from "$lib/api/dashboard/a76/general_catalogs/company";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Company | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Empresa" : "Nueva Empresa");
|
||||
|
||||
// Inicialización completa con todos los campos del modelo
|
||||
let formData = $state({
|
||||
// General
|
||||
name: '',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
main_activity: '',
|
||||
|
||||
// Programas
|
||||
program: '',
|
||||
program_number: '',
|
||||
prosec: 0,
|
||||
prosec_authorization: '',
|
||||
|
||||
// Responsable
|
||||
responsible_name: '',
|
||||
responsible_last_name: '',
|
||||
responsible_mother_last_name: '',
|
||||
responsible_rfc: '',
|
||||
position: '',
|
||||
|
||||
// Configuración / Operativo
|
||||
manufacturer_id: '',
|
||||
has_express_line: false,
|
||||
is_service_company: false,
|
||||
order_format_type: '',
|
||||
|
||||
// Certificaciones
|
||||
ctpat_svi: '',
|
||||
trusted_exporter_number: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Cargar datos al abrir para editar
|
||||
$effect(() => {
|
||||
if (item) {
|
||||
formData = {
|
||||
name: item.name || '',
|
||||
rfc: item.rfc || '',
|
||||
curp: item.curp || '',
|
||||
main_activity: item.main_activity || '',
|
||||
|
||||
program: item.program || '',
|
||||
program_number: item.program_number || '',
|
||||
prosec: item.prosec || 0,
|
||||
prosec_authorization: item.prosec_authorization || '',
|
||||
|
||||
responsible_name: item.responsible_name || '',
|
||||
responsible_last_name: item.responsible_last_name || '',
|
||||
responsible_mother_last_name: item.responsible_mother_last_name || '',
|
||||
responsible_rfc: item.responsible_rfc || '',
|
||||
position: item.position || '',
|
||||
|
||||
manufacturer_id: item.manufacturer_id || '',
|
||||
has_express_line: item.has_express_line || false,
|
||||
is_service_company: item.is_service_company || false,
|
||||
order_format_type: item.order_format_type || '',
|
||||
|
||||
ctpat_svi: item.ctpat_svi || '',
|
||||
trusted_exporter_number: item.trusted_exporter_number || ''
|
||||
};
|
||||
} else {
|
||||
// Reset manual si es nuevo
|
||||
formData = {
|
||||
name: '', rfc: '', curp: '', main_activity: '',
|
||||
program: '', program_number: '', prosec: 0, prosec_authorization: '',
|
||||
responsible_name: '', responsible_last_name: '', responsible_mother_last_name: '', responsible_rfc: '', position: '',
|
||||
manufacturer_id: '', has_express_line: false, is_service_company: false, order_format_type: '',
|
||||
ctpat_svi: '', trusted_exporter_number: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
if (!formData.name.trim()) throw new Error('El nombre es requerido');
|
||||
if (!formData.rfc.trim()) throw new Error('El RFC es requerido');
|
||||
|
||||
// Construir payload limpiando strings vacíos a null
|
||||
const dataToSend = {
|
||||
name: formData.name.trim(),
|
||||
rfc: formData.rfc.trim(),
|
||||
curp: formData.curp.trim() || null,
|
||||
main_activity: formData.main_activity.trim() || null,
|
||||
|
||||
program: formData.program.trim() || null,
|
||||
program_number: formData.program_number.trim() || null,
|
||||
prosec: formData.prosec || null,
|
||||
prosec_authorization: formData.prosec_authorization.trim() || null,
|
||||
|
||||
// Concatenar nombre completo del responsable si se desea guardar en 'responsible' también
|
||||
responsible: `${formData.responsible_name} ${formData.responsible_last_name}`.trim() || null,
|
||||
responsible_name: formData.responsible_name.trim() || null,
|
||||
responsible_last_name: formData.responsible_last_name.trim() || null,
|
||||
responsible_mother_last_name: formData.responsible_mother_last_name.trim() || null,
|
||||
responsible_rfc: formData.responsible_rfc.trim() || null,
|
||||
position: formData.position.trim() || null,
|
||||
|
||||
manufacturer_id: formData.manufacturer_id.trim() || null,
|
||||
has_express_line: formData.has_express_line,
|
||||
is_service_company: formData.is_service_company,
|
||||
order_format_type: formData.order_format_type.trim() || null,
|
||||
|
||||
ctpat_svi: formData.ctpat_svi.trim() || null,
|
||||
trusted_exporter_number: formData.trusted_exporter_number.trim() || null
|
||||
};
|
||||
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateCompany(item.id, dataToSend);
|
||||
} else {
|
||||
response = await createCompany(dataToSend);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="p-3 text-sm text-red-600 bg-red-50 rounded-md border border-red-200">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
<Tabs.List class="grid w-full grid-cols-4">
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="programas">Programas</Tabs.Trigger>
|
||||
<Tabs.Trigger value="responsable">Responsable</Tabs.Trigger>
|
||||
<Tabs.Trigger value="config">Config</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Content value="general" class="space-y-4 py-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="name">Razón Social <span class="text-destructive">*</span></Label>
|
||||
<Input id="name" bind:value={formData.name} placeholder="Nombre de la empresa" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="rfc">RFC <span class="text-destructive">*</span></Label>
|
||||
<Input id="rfc" bind:value={formData.rfc} maxlength={13} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="curp">CURP</Label>
|
||||
<Input id="curp" bind:value={formData.curp} maxlength={18} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="main_activity">Actividad Principal</Label>
|
||||
<Input id="main_activity" bind:value={formData.main_activity} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="programas" class="space-y-4 py-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="program">Programa (IMMEX)</Label>
|
||||
<Input id="program" bind:value={formData.program} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="program_number">No. Programa</Label>
|
||||
<Input id="program_number" bind:value={formData.program_number} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="prosec">Sector PROSEC (ID)</Label>
|
||||
<Input id="prosec" type="number" bind:value={formData.prosec} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="prosec_auth">Autorización PROSEC</Label>
|
||||
<Input id="prosec_auth" bind:value={formData.prosec_authorization} />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="responsable" class="space-y-4 py-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="resp_name">Nombre</Label>
|
||||
<Input id="resp_name" bind:value={formData.responsible_name} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="resp_last">Apellido Paterno</Label>
|
||||
<Input id="resp_last" bind:value={formData.responsible_last_name} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="resp_mother">Apellido Materno</Label>
|
||||
<Input id="resp_mother" bind:value={formData.responsible_mother_last_name} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="resp_rfc">RFC Responsable</Label>
|
||||
<Input id="resp_rfc" bind:value={formData.responsible_rfc} maxlength={13} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="position">Puesto / Cargo</Label>
|
||||
<Input id="position" bind:value={formData.position} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="config" class="space-y-4 py-4">
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="express" bind:checked={formData.has_express_line} />
|
||||
<Label for="express">Carril Exprés (OEA/NEEC)</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Switch id="service" bind:checked={formData.is_service_company} />
|
||||
<Label for="service">Es Empresa de Servicios</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t my-2"></div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="man_id">Manufacturer ID (MID)</Label>
|
||||
<Input id="man_id" bind:value={formData.manufacturer_id} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="order_format">Formato de Pedido</Label>
|
||||
<Input id="order_format" bind:value={formData.order_format_type} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="ctpat">C-TPAT / SVI</Label>
|
||||
<Input id="ctpat" bind:value={formData.ctpat_svi} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="exporter">No. Exportador Confiable</Label>
|
||||
<Input id="exporter" bind:value={formData.trusted_exporter_number} />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false}>Cancelar</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar Empresa'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,213 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Tabs from "$lib/components/ui/tabs";
|
||||
import { Switch } from "$lib/components/ui/switch";
|
||||
import { createConcept, updateConcept, type Concept } from "$lib/api/dashboard/a76/general_catalogs/concepts";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Concept | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Concepto" : "Nuevo Concepto");
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: '',
|
||||
description_en: '',
|
||||
detailed_description: '',
|
||||
priority: '',
|
||||
priority_ame: '',
|
||||
first_total: '',
|
||||
type: '',
|
||||
is_printed: false,
|
||||
section: '',
|
||||
classification: '',
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Cargar datos al abrir
|
||||
$effect(() => {
|
||||
if (item) {
|
||||
formData = {
|
||||
code: item.code || '',
|
||||
description: item.description || '',
|
||||
description_en: item.description_en || '',
|
||||
detailed_description: item.detailed_description || '',
|
||||
priority: item.priority ? String(item.priority) : '', // Asegurar string
|
||||
priority_ame: item.priority_ame ? String(item.priority_ame) : '',
|
||||
first_total: item.first_total ? String(item.first_total) : '',
|
||||
type: item.type || '',
|
||||
is_printed: item.is_printed || false,
|
||||
section: item.section || '',
|
||||
classification: item.classification || '',
|
||||
};
|
||||
} else {
|
||||
// Limpiar formulario
|
||||
formData = {
|
||||
code: '',
|
||||
description: '',
|
||||
description_en: '',
|
||||
detailed_description: '',
|
||||
priority: '',
|
||||
priority_ame: '',
|
||||
first_total: '',
|
||||
type: '',
|
||||
is_printed: false,
|
||||
section: '',
|
||||
classification: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
loading = true;
|
||||
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
error = "No hay una empresa seleccionada. Recarga la página.";
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
const companyId = companyStore.activeCompany.id;
|
||||
|
||||
try {
|
||||
if (!formData.code.trim()) throw new Error('El código es requerido');
|
||||
|
||||
const dataToSend = {
|
||||
code: formData.code.trim(),
|
||||
company_id: companyId,
|
||||
description: formData.description.trim(),
|
||||
description_en: formData.description_en.trim() || null,
|
||||
detailed_description: formData.detailed_description.trim() || null,
|
||||
priority: formData.priority ? parseInt(formData.priority) : null,
|
||||
priority_ame: formData.priority_ame ? parseInt(formData.priority_ame) : null,
|
||||
section: formData.section ? parseInt(formData.section) : null,
|
||||
first_total: !!formData.first_total,
|
||||
type: formData.type.trim() || null,
|
||||
is_printed: Boolean(formData.is_printed),
|
||||
classification: formData.classification.trim() || null,
|
||||
};
|
||||
|
||||
let response;
|
||||
|
||||
if (isEdit && item) {
|
||||
|
||||
response = await updateConcept(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
|
||||
response = await createConcept(dataToSend, companyId);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error); // Axios a veces devuelve error en el body
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="p-3 text-sm text-red-600 bg-red-50 rounded-md border border-red-200">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
<Tabs.Content value="general" class="space-y-4 py-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="code">Código <span class="text-destructive">*</span></Label>
|
||||
<Input id="code" bind:value={formData.code} maxlength={10} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="priority">Prioridad</Label>
|
||||
<Input id="priority" bind:value={formData.priority} type="number" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="priority_ame">Prioridad AME</Label>
|
||||
<Input id="priority_ame" bind:value={formData.priority_ame} type="number"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="description">Descripción <span class="text-destructive">*</span></Label>
|
||||
<Input id="description" bind:value={formData.description} maxlength={100} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="description_en">Descripción (Inglés)</Label>
|
||||
<Input id="description_en" bind:value={formData.description_en} maxlength={100} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="detailed_description">Descripción Detallada</Label>
|
||||
<Input id="detailed_description" bind:value={formData.detailed_description} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="first_total">Primer Total</Label>
|
||||
<Input id="first_total" bind:value={formData.first_total} type="number" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="type">Tipo</Label>
|
||||
<Input id="type" bind:value={formData.type} maxlength={5} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="section">Sección</Label>
|
||||
<Input id="section" bind:value={formData.section} maxlength={10} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="classification">Clasificación</Label>
|
||||
<Input id="classification" bind:value={formData.classification} maxlength={10} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2 pt-2">
|
||||
<Switch id="is_printed" bind:checked={formData.is_printed} />
|
||||
<Label for="is_printed">¿Se Imprime?</Label>
|
||||
</div>
|
||||
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false}>Cancelar</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar Concepto'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,165 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import {
|
||||
createCustomsBrokerConcept,
|
||||
updateCustomsBrokerConcept,
|
||||
type CustomsBrokerConcept
|
||||
} from "$lib/api/dashboard/a76/general_catalogs/customs-broker-concepts";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: CustomsBrokerConcept | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Concepto AA" : "Nuevo Concepto AA");
|
||||
|
||||
// 3. Estado alineado al modelo de BD
|
||||
let formData = $state({
|
||||
broker_key: '',
|
||||
concept: '',
|
||||
amount: null as number | null,
|
||||
priority: null as number | null
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// 4. Cargar datos al editar
|
||||
$effect(() => {
|
||||
if (item) {
|
||||
formData = {
|
||||
broker_key: item.broker_key || '',
|
||||
concept: item.concept || '',
|
||||
amount: item.amount || null,
|
||||
priority: item.priority || null
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
broker_key: '',
|
||||
concept: '',
|
||||
amount: '',
|
||||
priority: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
// Validaciones
|
||||
if (!formData.broker_key.trim()) throw new Error('La Clave AA es requerida');
|
||||
if (!formData.concept.trim()) throw new Error('El Concepto es requerido');
|
||||
|
||||
// 5. Preparar datos con los tipos correctos (Números)
|
||||
const dataToSend = {
|
||||
broker_key: formData.broker_key.trim(),
|
||||
concept: formData.concept.trim(),
|
||||
amount: formData.amount ? Number(formData.amount) : null,
|
||||
priority: formData.priority ? Number(formData.priority) : null
|
||||
};
|
||||
|
||||
// 6. Corregida la sintaxis de llamada a la API
|
||||
if (isEdit && item) {
|
||||
// UPDATE: (id, data, companyId)
|
||||
await updateCustomsBrokerConcept(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
// CREATE: (data, companyId)
|
||||
await createCustomsBrokerConcept(dataToSend, companyId);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-md max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
{isEdit ? 'Modifica el concepto del agente aduanal' : 'Crea un nuevo concepto'}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="broker_key">Clave AA <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="broker_key"
|
||||
bind:value={formData.broker_key}
|
||||
placeholder="Ej: 550"
|
||||
maxlength={5}
|
||||
disabled={isEdit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="concept">Concepto <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="concept"
|
||||
bind:value={formData.concept}
|
||||
placeholder="Ej: FLETE"
|
||||
maxlength={15}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="amount">Importe</Label>
|
||||
<Input
|
||||
id="amount"
|
||||
type="number"
|
||||
step="0.01"
|
||||
bind:value={formData.amount}
|
||||
placeholder="0.00"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="priority">Prioridad</Label>
|
||||
<Input
|
||||
id="priority"
|
||||
type="number"
|
||||
bind:value={formData.priority}
|
||||
placeholder="Ej: 1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,141 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import {
|
||||
createErrorClassification,
|
||||
updateErrorClassification,
|
||||
type ErrorClassification
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/error-catalogs'; // Ajusta la ruta
|
||||
|
||||
export let open: boolean = false;
|
||||
export let classification: ErrorClassification | null = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let loading = false;
|
||||
|
||||
let formData = {
|
||||
code: '',
|
||||
level: ''
|
||||
};
|
||||
|
||||
function formatDate(dateString?: string) {
|
||||
if (!dateString) return 'N/A';
|
||||
return new Date(dateString).toLocaleString('es-MX', {
|
||||
year: 'numeric', month: 'short', day: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
$: if (classification) {
|
||||
formData = {
|
||||
code: classification.code,
|
||||
level: classification.level || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', level: '' };
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
try {
|
||||
if (classification?.id) {
|
||||
await updateErrorClassification(classification.id, { level: formData.level });
|
||||
} else {
|
||||
await createErrorClassification(formData);
|
||||
}
|
||||
dispatch('save');
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Error al guardar');
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
open = false;
|
||||
dispatch('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
|
||||
|
||||
<div class="w-full max-w-md rounded-xl bg-[#1a1a1a] border border-gray-700 shadow-2xl overflow-hidden">
|
||||
|
||||
<div class="bg-[#1a1a1a] px-6 py-4 border-b border-gray-700 flex justify-between items-center">
|
||||
<h3 class="text-lg font-bold text-white">
|
||||
{classification ? 'Editar Clasificación' : 'Nueva Clasificación'}
|
||||
</h3>
|
||||
<button on:click={closeModal} class="text-gray-400 hover:text-white transition-colors text-2xl">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form on:submit|preventDefault={handleSubmit} class="p-6 space-y-5">
|
||||
|
||||
<div>
|
||||
<label for="code" class="block text-sm font-medium text-gray-300 mb-1">Código *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="code"
|
||||
bind:value={formData.code}
|
||||
required
|
||||
maxlength="100"
|
||||
disabled={!!classification}
|
||||
class="w-full rounded-lg bg-[#252525] border border-gray-600 text-white p-2.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed placeholder-gray-500"
|
||||
placeholder="Ej. SYSTEM_ERROR"
|
||||
/>
|
||||
{#if classification}
|
||||
<p class="text-xs text-gray-500 mt-1">El código no se puede cambiar.</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="level" class="block text-sm font-medium text-gray-300 mb-1">Nivel</label>
|
||||
<input
|
||||
type="text"
|
||||
id="level"
|
||||
bind:value={formData.level}
|
||||
maxlength="3"
|
||||
class="w-full rounded-lg bg-[#252525] border border-gray-600 text-white p-2.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-500"
|
||||
placeholder="Ej. CRT"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if classification}
|
||||
<div class="mt-4 p-3 rounded bg-gray-800/50 border border-gray-700 text-xs text-gray-400 space-y-1">
|
||||
<div class="flex justify-between">
|
||||
<span>Creado:</span>
|
||||
<span class="text-gray-300">{formatDate(classification.created_at)}</span>
|
||||
</div>
|
||||
{#if classification.updated_at}
|
||||
<div class="flex justify-between">
|
||||
<span>Actualizado:</span>
|
||||
<span class="text-gray-300">{formatDate(classification.updated_at)}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4 border-t border-gray-700 mt-2">
|
||||
<button
|
||||
type="button"
|
||||
on:click={closeModal}
|
||||
class="px-4 py-2 text-sm font-medium text-gray-300 bg-transparent border border-gray-600 rounded-lg hover:bg-gray-800 hover:text-white transition-colors"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-blue-900/20"
|
||||
>
|
||||
{loading ? 'Guardando...' : (classification ? 'Guardar Cambios' : 'Crear')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,166 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import {
|
||||
createINPC,
|
||||
updateINPC,
|
||||
type INPC
|
||||
} from "$lib/api/dashboard/a76/general_catalogs/inpc";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: INPC | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar INPC" : "Nuevo INPC");
|
||||
|
||||
// Estado del formulario
|
||||
let formData = $state({
|
||||
year: '',
|
||||
month: '',
|
||||
value: null as number | null
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Cargar datos al abrir
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (item) {
|
||||
formData = {
|
||||
year: item.year,
|
||||
month: item.month,
|
||||
value: item.value || null
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
year: '',
|
||||
month: '',
|
||||
value: null
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
// Validaciones
|
||||
if (!formData.year.trim()) throw new Error('El año es requerido');
|
||||
if (formData.year.length !== 4) throw new Error('El año debe tener 4 dígitos');
|
||||
if (!formData.month.trim()) throw new Error('El mes es requerido');
|
||||
|
||||
// Preparar datos (limpios)
|
||||
const dataToSend = {
|
||||
year: formData.year.trim(),
|
||||
month: formData.month.trim(),
|
||||
value: formData.value // Ya es número o null
|
||||
};
|
||||
|
||||
let response;
|
||||
|
||||
|
||||
if (isEdit && item) {
|
||||
response = await updateINPC(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
|
||||
response = await createINPC(dataToSend, companyId);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar el INPC';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[400px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="year" class="text-right">Año <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="year"
|
||||
bind:value={formData.year}
|
||||
placeholder="Ej: 2025"
|
||||
maxlength={4}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="month" class="text-right">Mes <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="month"
|
||||
bind:value={formData.month}
|
||||
placeholder="Ej: 01"
|
||||
maxlength={2}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Formato MM (Ej: 01, 12)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="value" class="text-right">Valor</Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="value"
|
||||
type="number"
|
||||
step="0.00000001"
|
||||
bind:value={formData.value}
|
||||
placeholder="0.0000"
|
||||
disabled={loading}
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Hasta 8 decimales</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,149 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import {
|
||||
createLegend,
|
||||
updateLegend,
|
||||
type Legend
|
||||
} from "$lib/api/dashboard/a76/general_catalogs/legends";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Legend | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Leyenda" : "Nueva Leyenda");
|
||||
|
||||
// Estado del formulario
|
||||
// code es number | null para manejar el input type="number"
|
||||
let formData = $state({
|
||||
code: null as number | null,
|
||||
description: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Cargar datos al abrir
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (item) {
|
||||
formData = {
|
||||
code: item.code, // Es number
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: null,
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
// Validaciones
|
||||
if (formData.code === null) throw new Error('La Clave (Código) es requerida');
|
||||
|
||||
// Preparar datos
|
||||
const dataToSend = {
|
||||
code: Number(formData.code), // Aseguramos que sea número
|
||||
description: formData.description.trim() || undefined
|
||||
};
|
||||
|
||||
let response;
|
||||
|
||||
|
||||
if (isEdit && item) {
|
||||
response = await updateLegend(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
response = await createLegend(dataToSend, companyId);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar la leyenda';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[500px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Clave <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="code"
|
||||
type="number"
|
||||
bind:value={formData.code}
|
||||
placeholder="Ej: 10"
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Debe ser un número entero.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<div class="col-span-3">
|
||||
<Textarea
|
||||
id="description"
|
||||
bind:value={formData.description}
|
||||
placeholder="Ej: Esta leyenda aplica para..."
|
||||
maxlength={2000}
|
||||
disabled={loading}
|
||||
class="min-h-[100px]"
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1 text-right">
|
||||
{formData.description.length}/2000
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,152 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import {
|
||||
createLocation,
|
||||
updateLocation,
|
||||
type Location
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/locations';
|
||||
|
||||
// Props
|
||||
export let open: boolean = false;
|
||||
export let location: Location | null = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let loading = false;
|
||||
|
||||
// Form Data
|
||||
let formData = {
|
||||
code: '',
|
||||
description: ''
|
||||
};
|
||||
|
||||
// Función para formatear fechas (auditoría)
|
||||
function formatDate(dateString?: string) {
|
||||
if (!dateString) return 'N/A';
|
||||
return new Date(dateString).toLocaleString('es-MX', {
|
||||
year: 'numeric', month: 'short', day: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
// Reactividad: Cargar datos si estamos editando
|
||||
$: if (location) {
|
||||
formData = {
|
||||
code: location.code,
|
||||
description: location.description || ''
|
||||
};
|
||||
} else {
|
||||
// Limpiar si es nuevo
|
||||
formData = { code: '', description: '' };
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
try {
|
||||
if (location?.id) {
|
||||
// EDITAR
|
||||
await updateLocation(location.id, {
|
||||
code: formData.code,
|
||||
description: formData.description
|
||||
});
|
||||
} else {
|
||||
// CREAR
|
||||
await createLocation({
|
||||
code: formData.code,
|
||||
description: formData.description
|
||||
});
|
||||
}
|
||||
dispatch('success'); // Avisamos al padre para que recargue
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
console.error('Error guardando location:', error);
|
||||
// Aquí puedes poner un toast de error si tienes
|
||||
alert('Error al guardar la localización.');
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
open = false;
|
||||
dispatch('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
|
||||
|
||||
<div class="w-full max-w-md rounded-xl bg-[#1a1a1a] border border-gray-700 shadow-2xl overflow-hidden">
|
||||
|
||||
<div class="bg-[#1a1a1a] px-6 py-4 border-b border-gray-700 flex justify-between items-center">
|
||||
<h3 class="text-lg font-bold text-white">
|
||||
{location ? 'Editar Ubicación' : 'Nueva Ubicación'}
|
||||
</h3>
|
||||
<button on:click={closeModal} class="text-gray-400 hover:text-white transition-colors text-2xl">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form on:submit|preventDefault={handleSubmit} class="p-6 space-y-5">
|
||||
|
||||
<div>
|
||||
<label for="code" class="block text-sm font-medium text-gray-300 mb-1">Código *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="code"
|
||||
bind:value={formData.code}
|
||||
required
|
||||
maxlength="5"
|
||||
class="w-full rounded-lg bg-[#252525] border border-gray-600 text-white p-2.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-500"
|
||||
placeholder="Ej. VER"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 mt-1 text-right">Máx. 5 caracteres</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-gray-300 mb-1">Descripción</label>
|
||||
<textarea
|
||||
id="description"
|
||||
bind:value={formData.description}
|
||||
rows="3"
|
||||
maxlength="200"
|
||||
class="w-full rounded-lg bg-[#252525] border border-gray-600 text-white p-2.5 focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-500 resize-none"
|
||||
placeholder="Descripción de la ubicación..."
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
{#if location}
|
||||
<div class="mt-4 p-3 rounded bg-gray-800/50 border border-gray-700 text-xs text-gray-400 space-y-1">
|
||||
<div class="flex justify-between">
|
||||
<span>Creado:</span>
|
||||
<span class="text-gray-300">{formatDate(location.created_at)}</span>
|
||||
</div>
|
||||
{#if location.updated_at}
|
||||
<div class="flex justify-between">
|
||||
<span>Actualizado:</span>
|
||||
<span class="text-gray-300">{formatDate(location.updated_at)}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4 border-t border-gray-700 mt-2">
|
||||
<button
|
||||
type="button"
|
||||
on:click={closeModal}
|
||||
class="px-4 py-2 text-sm font-medium text-gray-300 bg-transparent border border-gray-600 rounded-lg hover:bg-gray-800 hover:text-white transition-colors"
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-blue-900/20"
|
||||
>
|
||||
{loading ? 'Guardando...' : (location ? 'Guardar Cambios' : 'Crear')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,196 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
// 👇 Verifica la ruta de tu archivo TS
|
||||
import {
|
||||
createMultiCurrencyType,
|
||||
updateMultiCurrencyType,
|
||||
type MultiCurrencyType
|
||||
} from "$lib/api/dashboard/a76/general_catalogs/multi-currency-types";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: MultiCurrencyType | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Tipo de Cambio Múltiple" : "Nuevo Tipo de Cambio Múltiple");
|
||||
|
||||
// Estado del formulario
|
||||
let formData = $state({
|
||||
currency_type_code: '',
|
||||
country_key: '',
|
||||
conversion_factor: null as number | null,
|
||||
date_str: '' // Usamos un string temporal para el input type="date"
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Cargar datos al abrir
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (item) {
|
||||
// Truco: Convertir Entero (20251231) -> String ("2025-12-31")
|
||||
let dateFormatted = '';
|
||||
if (item.publication_date) {
|
||||
const s = item.publication_date.toString();
|
||||
if (s.length === 8) {
|
||||
dateFormatted = `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`;
|
||||
}
|
||||
}
|
||||
|
||||
formData = {
|
||||
currency_type_code: item.currency_type_code,
|
||||
country_key: item.country_key || '',
|
||||
conversion_factor: item.conversion_factor,
|
||||
date_str: dateFormatted
|
||||
};
|
||||
} else {
|
||||
// Default: Fecha de hoy
|
||||
formData = {
|
||||
currency_type_code: '',
|
||||
country_key: '',
|
||||
conversion_factor: null,
|
||||
date_str: new Date().toISOString().split('T')[0]
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
// Validaciones
|
||||
if (!formData.currency_type_code.trim()) throw new Error('El código de moneda es requerido');
|
||||
if (!formData.date_str) throw new Error('La fecha de publicación es requerida');
|
||||
|
||||
// Truco: Convertir String ("2025-12-31") -> Entero (20251231)
|
||||
// Quitamos los guiones y parseamos a int
|
||||
const dateInt = parseInt(formData.date_str.replaceAll('-', ''), 10);
|
||||
|
||||
// Preparar datos
|
||||
const dataToSend = {
|
||||
currency_type_code: formData.currency_type_code.trim().toUpperCase(),
|
||||
country_key: formData.country_key.trim().toUpperCase() || null,
|
||||
conversion_factor: formData.conversion_factor ? Number(formData.conversion_factor) : null,
|
||||
publication_date: dateInt // Mandamos el INT que espera Python
|
||||
};
|
||||
|
||||
let response;
|
||||
|
||||
// 👇 companyId por fuera
|
||||
if (isEdit && item) {
|
||||
response = await updateMultiCurrencyType(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
response = await createMultiCurrencyType(dataToSend, companyId);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[500px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="currency_code" class="text-right">Moneda <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="currency_code"
|
||||
bind:value={formData.currency_type_code}
|
||||
placeholder="Ej: USD"
|
||||
maxlength={3}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Código de moneda (FK).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="country_key" class="text-right">País</Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="country_key"
|
||||
bind:value={formData.country_key}
|
||||
placeholder="Ej: MEX"
|
||||
maxlength={3}
|
||||
disabled={loading}
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Clave M3 del país (FK).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="pub_date" class="text-right">Fecha <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="pub_date"
|
||||
type="date"
|
||||
bind:value={formData.date_str}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Se guarda como entero (YYYYMMDD).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="factor" class="text-right">Factor</Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="factor"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
bind:value={formData.conversion_factor}
|
||||
placeholder="0.000000"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,163 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Textarea } from "$lib/components/ui/textarea"; // Para que quepa más texto en la firma
|
||||
// 👇 Verifica tu ruta de importación
|
||||
import {
|
||||
createSignature,
|
||||
updateSignature,
|
||||
type Signature
|
||||
} from "$lib/api/dashboard/a76/general_catalogs/signatures";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Signature | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Firma" : "Nueva Firma");
|
||||
|
||||
// Estado del formulario
|
||||
let formData = $state({
|
||||
code: '',
|
||||
signature: '',
|
||||
photo_path: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Cargar datos al abrir
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (item) {
|
||||
formData = {
|
||||
code: item.code,
|
||||
signature: item.signature || '',
|
||||
photo_path: item.photo_path || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
signature: '',
|
||||
photo_path: ''
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
// Validaciones
|
||||
if (!formData.code.trim()) throw new Error('El código es requerido');
|
||||
|
||||
// Preparar datos
|
||||
const dataToSend = {
|
||||
code: formData.code.trim(),
|
||||
signature: formData.signature.trim() || null,
|
||||
photo_path: formData.photo_path.trim() || null
|
||||
};
|
||||
|
||||
let response;
|
||||
|
||||
// 👇 companyId por fuera (Argumento separado)
|
||||
if (isEdit && item) {
|
||||
response = await updateSignature(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
response = await createSignature(dataToSend, companyId);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar la firma';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[500px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Código <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="code"
|
||||
bind:value={formData.code}
|
||||
placeholder="Ej: REP_LEGAL"
|
||||
maxlength={10}
|
||||
disabled={loading || isEdit}
|
||||
required
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Máximo 10 caracteres.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="signature" class="text-right">Firma / Nombre</Label>
|
||||
<div class="col-span-3">
|
||||
<Textarea
|
||||
id="signature"
|
||||
bind:value={formData.signature}
|
||||
placeholder="Ej: Juan Pérez - Representante Legal"
|
||||
maxlength={1000}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="photo_path" class="text-right">Ruta Foto</Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="photo_path"
|
||||
bind:value={formData.photo_path}
|
||||
placeholder="Ej: /uploads/firmas/juan.png"
|
||||
maxlength={1000}
|
||||
disabled={loading}
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Ruta del archivo (Texto).</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,164 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import {
|
||||
createUnitConversion,
|
||||
updateUnitConversion,
|
||||
type UnitConversion
|
||||
} from "$lib/api/dashboard/a76/general_catalogs/unit-conversions"; // 👈 Asegúrate que la ruta del archivo ts coincida
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: UnitConversion | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Conversión" : "Nueva Conversión");
|
||||
|
||||
// Estado del formulario
|
||||
let formData = $state({
|
||||
from_unit_code: '',
|
||||
to_unit_code: '',
|
||||
conversion_factor: null as number | null
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Cargar datos al abrir
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (item) {
|
||||
formData = {
|
||||
from_unit_code: item.from_unit_code,
|
||||
to_unit_code: item.to_unit_code,
|
||||
conversion_factor: item.conversion_factor
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
from_unit_code: '',
|
||||
to_unit_code: '',
|
||||
conversion_factor: null
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
// Validaciones
|
||||
if (!formData.from_unit_code.trim()) throw new Error('La unidad origen es requerida');
|
||||
if (!formData.to_unit_code.trim()) throw new Error('La unidad destino es requerida');
|
||||
if (formData.conversion_factor === null || formData.conversion_factor === undefined) throw new Error('El factor de conversión es requerido');
|
||||
|
||||
// Preparar datos
|
||||
const dataToSend = {
|
||||
from_unit_code: formData.from_unit_code.trim().toUpperCase(), // Normalizamos a mayúsculas
|
||||
to_unit_code: formData.to_unit_code.trim().toUpperCase(),
|
||||
conversion_factor: Number(formData.conversion_factor)
|
||||
};
|
||||
|
||||
let response;
|
||||
|
||||
// 👇 companyId por fuera (Argumento separado)
|
||||
if (isEdit && item) {
|
||||
response = await updateUnitConversion(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
response = await createUnitConversion(dataToSend, companyId);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar la conversión';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[500px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="from_code" class="text-right">De Unidad <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="from_code"
|
||||
bind:value={formData.from_unit_code}
|
||||
placeholder="Ej: KGM"
|
||||
maxlength={5}
|
||||
disabled={loading}
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Código de la unidad origen.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="to_code" class="text-right">A Unidad <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="to_code"
|
||||
bind:value={formData.to_unit_code}
|
||||
placeholder="Ej: LBR"
|
||||
maxlength={5}
|
||||
disabled={loading}
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Código de la unidad destino.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="factor" class="text-right">Factor <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="factor"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
bind:value={formData.conversion_factor}
|
||||
placeholder="Ej: 2.20462"
|
||||
disabled={loading}
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Numeric(13, 6).</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -1,139 +1,178 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import { createIdentifier, updateIdentifier, type Identifier } from "$lib/api/dashboard/a76/general_catalogs/identifiers";
|
||||
// 👇 Importar tipos correctos
|
||||
import {
|
||||
createIdentifier,
|
||||
updateIdentifier,
|
||||
type Identifier
|
||||
} from "$lib/api/dashboard/a76/general_catalogs/identifiers";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
mode = 'create',
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
mode?: 'create' | 'edit';
|
||||
item?: Identifier | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Identifier | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const title = $derived(isEdit ? "Editar Identificador" : "Nuevo Identificador");
|
||||
// Calculamos si es edición basado en si hay item
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Identificador" : "Nuevo Identificador");
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: '',
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: '',
|
||||
level: '',
|
||||
complement: ''
|
||||
});
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (isEdit && item) {
|
||||
formData = {
|
||||
code: item.code,
|
||||
description: item.description || '',
|
||||
// Efecto para cargar o limpiar datos
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (item) {
|
||||
formData = {
|
||||
code: item.code,
|
||||
description: item.description || '',
|
||||
level: item.level || '',
|
||||
complement: item.complement || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
description: '',
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
description: '',
|
||||
level: '',
|
||||
complement: ''
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
error = 'No hay compañía seleccionada';
|
||||
return;
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateIdentifier(item.id, {
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
level: formData.level || null,
|
||||
complement: formData.complement || null
|
||||
});
|
||||
} else {
|
||||
response = await createIdentifier({
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
level: formData.level || null,
|
||||
complement: formData.complement || null,
|
||||
company_id: companyId
|
||||
});
|
||||
}
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
// Validaciones básicas
|
||||
if (!formData.code.trim()) throw new Error('La clave es requerida');
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
// Preparar payload (SIN company_id adentro)
|
||||
const dataToSend = {
|
||||
code: formData.code.trim(),
|
||||
description: formData.description.trim() || null,
|
||||
level: formData.level.trim() || null,
|
||||
complement: formData.complement.trim() || null
|
||||
};
|
||||
|
||||
let response;
|
||||
// 👇 AQUI ESTA EL CAMBIO IMPORTANTE: companyId va por fuera
|
||||
if (isEdit && item) {
|
||||
response = await updateIdentifier(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
response = await createIdentifier(dataToSend, companyId);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
{#if error}
|
||||
<div class="text-red-500 text-sm mb-2">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Clave</Label>
|
||||
<Input id="code" bind:value={formData.code} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<Textarea id="description" bind:value={formData.description} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
<Dialog.Content class="sm:max-w-[500px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Clave <span class="text-destructive">*</span></Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="code"
|
||||
bind:value={formData.code}
|
||||
disabled={loading || isEdit}
|
||||
placeholder="Ej: CI"
|
||||
maxlength={2}
|
||||
required
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Máximo 2 caracteres.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
bind:value={formData.description}
|
||||
class="col-span-3"
|
||||
disabled={loading}
|
||||
maxlength={1000}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="level" class="text-right">Nivel</Label>
|
||||
<Input id="level" bind:value={formData.level} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="level" class="text-right">Nivel</Label>
|
||||
<div class="col-span-3">
|
||||
<Input
|
||||
id="level"
|
||||
bind:value={formData.level}
|
||||
disabled={loading}
|
||||
placeholder="Ej: G"
|
||||
maxlength={1}
|
||||
/>
|
||||
<p class="text-[10px] text-muted-foreground mt-1">Máximo 1 caracter (G, S, etc).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="complement" class="text-right">Complemento</Label>
|
||||
<Textarea id="complement" bind:value={formData.complement} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="complement" class="text-right">Complemento</Label>
|
||||
<Textarea
|
||||
id="complement"
|
||||
bind:value={formData.complement}
|
||||
class="col-span-3"
|
||||
disabled={loading}
|
||||
maxlength={5000}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="submit" onclick={handleSubmit} disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -3,7 +3,7 @@
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { createPackage, updatePackage, type Package, type PackageCreate, type PackageUpdate } from "$lib/api/dashboard/a76/general_catalogs/packages";
|
||||
import { createPackage, updatePackage, type Package} from "$lib/api/dashboard/a76/general_catalogs/packages";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
@@ -95,9 +95,9 @@
|
||||
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updatePackage(item.id, dataToSend);
|
||||
response = await updatePackage(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
response = await createPackage({ ...dataToSend, company_id: companyId });
|
||||
response = await createPackage(dataToSend, companyId);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import { onMount } from 'svelte';
|
||||
import {
|
||||
type ColumnDef,
|
||||
getCoreRowModel
|
||||
getCoreRowModel,
|
||||
} from "@tanstack/table-core";
|
||||
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
loading: boolean;
|
||||
hasMore: boolean;
|
||||
loadMore: () => void;
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
loading,
|
||||
hasMore,
|
||||
loadMore
|
||||
pageCount,
|
||||
totalItems
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
@@ -28,96 +28,79 @@
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel()
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: pageCount,
|
||||
});
|
||||
|
||||
let scrollContainer = $state<HTMLDivElement>();
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
|
||||
// Intersection Observer para detectar cuando el usuario llega al final
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting && hasMore && !loading) {
|
||||
loadMore();
|
||||
}
|
||||
},
|
||||
{
|
||||
root: scrollContainer,
|
||||
threshold: 0.1
|
||||
}
|
||||
);
|
||||
|
||||
if (loadingTrigger) {
|
||||
observer.observe(loadingTrigger);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="sticky top-0 bg-background z-10">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems}
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) - 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) + 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -81,4 +82,9 @@
|
||||
totalItems={data.classifications?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
title="Crear Nueva Clasificación de Concepto"
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/company/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/company/create-edit-dialog.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/company/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/concepts/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -82,4 +83,9 @@
|
||||
totalItems={data.concepts?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/customs_broker_concepts/create-edite-dialoge.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -82,4 +83,9 @@
|
||||
totalItems={data.concepts?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/error_catalogs/create-edite-dialoge.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import CreateEditDialog from '$lib/components/dashboard/classes/create-edit-dialog.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
@@ -81,4 +83,11 @@
|
||||
totalItems={data.errors?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateDialog
|
||||
open={dialogOpen}
|
||||
on:close={() => dialogOpen = false}
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -211,11 +211,13 @@
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onOpenChange={(open) => (dialogOpen = open)}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/inpc/create-edite-dialoge.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -81,5 +82,10 @@
|
||||
pageCount={data.inpcs?.pages || 0}
|
||||
totalItems={data.inpcs?.total || 0}
|
||||
/>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/legend/create-edite-dialoge.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -81,4 +82,9 @@
|
||||
totalItems={data.legends?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,48 +1,60 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/locations/columns';
|
||||
import DataTable from '$lib/components/dashboard/locations/data-table.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/locations/columns';
|
||||
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/locations/create-edite-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/locations/data-table.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { RefreshCw } from 'lucide-svelte';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let loading = $state(false);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
const columns = createColumns();
|
||||
const columns = createColumns();
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Ubicaciones</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de ubicaciones extraídas de puertos
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Ubicaciones</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de ubicaciones extraídas de puertos
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Localización
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={refreshData}
|
||||
/>
|
||||
|
||||
</div>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/multi_currency_types/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -81,4 +82,10 @@
|
||||
totalItems={data.types?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/signatures/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
@@ -10,7 +11,7 @@
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
|
||||
let searchName = $state($page.url.searchParams.get('name') || '');
|
||||
let searchPosition = $state($page.url.searchParams.get('position') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
@@ -82,4 +83,10 @@
|
||||
totalItems={data.signatures?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
title="Crear Nueva Firma Electrónica"
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateDialogEdit from '$lib/components/dashboard/general_catalogs/unit-conversion/create-edite-dialoge.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
@@ -43,4 +44,10 @@
|
||||
totalItems={data.conversions?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateDialogEdit
|
||||
open={dialogOpen}
|
||||
on:close={() => dialogOpen = false}
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user