Clientes API por dominio para los catálogos del SAT, conceptos y emisor. Los catálogos se cachean en un Map del módulo tras la primera carga: son fijos y no cambian durante la sesión. Pantalla de conceptos (/dashboard/fin/conceptos) con tabla, buscador, filtro de activos y alta/edición en diálogo. La clave de producto/servicio se elige con un combobox que consulta el catálogo a partir de 2 caracteres, y el 409 del backend por clave ya asignada se muestra junto al campo. Sección de configuración fiscal (/dashboard/settings/facturacion) con razón social, RFC (misma validación que el backend), régimen fiscal y CP. Si el GET responde 404 se abre en modo alta, no como error; el guardar se deshabilita sin fin.settings.edit. Todo en Svelte 5 con runes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
/**
|
|
* Cliente API — Catálogo de conceptos de facturación.
|
|
*
|
|
* Cada concepto está ligado 1:1 a una clave de producto/servicio del SAT dentro de la
|
|
* empresa; el backend responde 409 si la clave ya está tomada.
|
|
*/
|
|
import { api } from '$lib/api';
|
|
import type { SatCatalogItem, SatUnitOfMeasure } from './catalogs';
|
|
|
|
export interface Concept {
|
|
id: number;
|
|
code: string;
|
|
description: string;
|
|
product_service_id: number;
|
|
unit_of_measure_id: number | null;
|
|
tax_object_id: number | null;
|
|
unit_price: number | null;
|
|
currency: string;
|
|
is_active: boolean;
|
|
notes: string | null;
|
|
product_service: SatCatalogItem | null;
|
|
unit_of_measure: SatUnitOfMeasure | null;
|
|
tax_object: SatCatalogItem | null;
|
|
tenant_id: number;
|
|
company_id: number;
|
|
created_by: string | null;
|
|
updated_by: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface ConceptInput {
|
|
code: string;
|
|
description: string;
|
|
product_service_id: number;
|
|
unit_of_measure_id?: number | null;
|
|
tax_object_id?: number | null;
|
|
unit_price?: number | null;
|
|
currency?: string;
|
|
is_active?: boolean;
|
|
notes?: string | null;
|
|
}
|
|
|
|
export const conceptsAPI = {
|
|
async list(
|
|
companyId: number,
|
|
params?: { search?: string; active_only?: boolean; product_service_id?: number }
|
|
): Promise<Concept[]> {
|
|
const qs = new URLSearchParams({ company_id: String(companyId) });
|
|
if (params?.search) qs.set('search', params.search);
|
|
if (params?.active_only !== undefined) qs.set('active_only', String(params.active_only));
|
|
if (params?.product_service_id !== undefined)
|
|
qs.set('product_service_id', String(params.product_service_id));
|
|
const res = await api.get<Concept[]>(`/v1/fin/concepts?${qs}`);
|
|
if (res.error) throw new Error(res.error);
|
|
return res.data!;
|
|
},
|
|
|
|
async get(id: number, companyId: number): Promise<Concept> {
|
|
const res = await api.get<Concept>(`/v1/fin/concepts/${id}?company_id=${companyId}`);
|
|
if (res.error) throw new Error(res.error);
|
|
return res.data!;
|
|
},
|
|
|
|
async create(data: ConceptInput, companyId: number): Promise<Concept> {
|
|
const res = await api.post<Concept>(`/v1/fin/concepts?company_id=${companyId}`, data);
|
|
if (res.error) throw new Error(res.error);
|
|
return res.data!;
|
|
},
|
|
|
|
async update(id: number, data: Partial<ConceptInput>, companyId: number): Promise<Concept> {
|
|
const res = await api.patch<Concept>(`/v1/fin/concepts/${id}?company_id=${companyId}`, data);
|
|
if (res.error) throw new Error(res.error);
|
|
return res.data!;
|
|
},
|
|
|
|
async remove(id: number, companyId: number): Promise<void> {
|
|
const res = await api.delete(`/v1/fin/concepts/${id}?company_id=${companyId}`);
|
|
if (res.error) throw new Error(res.error);
|
|
}
|
|
};
|