Merge branch 'development' of https://git.aduanasoft.com/ADUANASOFT/anexo76 into fix/modulos-ingles
This commit is contained in:
@@ -598,8 +598,9 @@ export const invoicesApi = {
|
||||
);
|
||||
},
|
||||
|
||||
copyInvoice: (invoiceId: number, companyId: number) => {
|
||||
copyInvoice: (invoiceId: number, companyId: number, newInvoiceNumber?: string) => {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
if (newInvoiceNumber) params.set('new_invoice_number', newInvoiceNumber);
|
||||
return api.post<Invoice>(`/v1/a76/invoices/${invoiceId}/copy?${params.toString()}`, {});
|
||||
},
|
||||
copyInvoiceHeader: (invoiceId: number, companyId: number) => {
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import type { InvoiceType } from '$lib/api/dashboard/reference_data/invoice_types';
|
||||
|
||||
export type InvoiceListFilters = {
|
||||
operation_type?: string | null;
|
||||
invoice_type?: string | null;
|
||||
};
|
||||
|
||||
/** Claves de importación expuestas en el menú lateral. */
|
||||
export const IMPORT_INVOICE_TYPE_KEYS = ['TEM', 'DEF', 'MEX', 'CR', 'REP'] as const;
|
||||
|
||||
/** Claves de exportación relevantes para configuración (Exportación y Reparación). */
|
||||
export const EXPORT_INVOICE_TYPE_KEYS = ['EXDEF', 'REPAR'] as const;
|
||||
|
||||
/**
|
||||
* Filtra tipos de factura por operación (imp/exp), incluyendo tipos marcados como `both`.
|
||||
*/
|
||||
export function filterInvoiceTypesByOperation(
|
||||
invoiceTypes: InvoiceType[],
|
||||
operationType: string
|
||||
): InvoiceType[] {
|
||||
if (!operationType) return [];
|
||||
|
||||
return invoiceTypes.filter(
|
||||
(type) => !type.operation || type.operation === 'both' || type.operation === operationType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tipos visibles en parámetros.
|
||||
*
|
||||
* - Para importación: se limitan a las claves expuestas en el menú lateral (TEM, DEF, MEX, CR, REP).
|
||||
* - Para exportación: se permiten todos los tipos cuyo `operation` sea `exp` (catálogo completo).
|
||||
*/
|
||||
export function getInvoiceTypesForSettings(
|
||||
invoiceTypes: InvoiceType[],
|
||||
operationType: string
|
||||
): InvoiceType[] {
|
||||
const filtered = filterInvoiceTypesByOperation(invoiceTypes, operationType);
|
||||
if (!operationType) return filtered;
|
||||
|
||||
// Importación: solo los tipos configurables desde el menú lateral
|
||||
if (operationType === 'imp') {
|
||||
const byKey = new Map(filtered.map((type) => [type.key, type]));
|
||||
return IMPORT_INVOICE_TYPE_KEYS.map((key) => byKey.get(key)).filter(
|
||||
(type): type is InvoiceType => !!type
|
||||
);
|
||||
}
|
||||
|
||||
if (operationType === 'exp') {
|
||||
const byKey = new Map(filtered.map((type) => [type.key, type]));
|
||||
const fromBackend = EXPORT_INVOICE_TYPE_KEYS.map((key) => byKey.get(key)).filter(
|
||||
(type): type is InvoiceType => !!type
|
||||
);
|
||||
|
||||
// Si el backend devolvió ambos tipos, usar sus descripciones reales.
|
||||
if (fromBackend.length > 0) {
|
||||
return fromBackend;
|
||||
}
|
||||
|
||||
// Fallback mínimo para no dejar el combo vacío
|
||||
const fallback: InvoiceType[] = [
|
||||
{ key: 'EXDEF', description: 'EXPORTACIÓN DEFINITIVA', operation: 'exp' },
|
||||
{ key: 'REPAR', description: 'REPARACIÓN', operation: 'exp' }
|
||||
];
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// Otra operación válida: devolvemos lo filtrado
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tipo de factura por defecto cuando la URL no lo incluye.
|
||||
*/
|
||||
export function getDefaultInvoiceTypeForOperation(
|
||||
operationType: string,
|
||||
invoiceTypes: InvoiceType[]
|
||||
): string {
|
||||
const options = getInvoiceTypesForSettings(invoiceTypes, operationType);
|
||||
if (options.length === 0) return '';
|
||||
|
||||
if (operationType === 'imp') {
|
||||
return options.find((type) => type.key === 'TEM')?.key ?? options[0].key;
|
||||
}
|
||||
|
||||
if (operationType === 'exp') {
|
||||
return (
|
||||
options.find((type) => type.key === 'EXDEF')?.key ??
|
||||
options.find((type) => type.key === 'REPAR')?.key ??
|
||||
options[0].key
|
||||
);
|
||||
}
|
||||
|
||||
return options[0].key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construye la ruta del listado de facturas preservando query params y evitando
|
||||
* navegar sin `operation_type` (el SSR del listado redirige al dashboard).
|
||||
*/
|
||||
export function buildInvoicesListPath(
|
||||
searchParams: URLSearchParams,
|
||||
fallbacks?: InvoiceListFilters
|
||||
): string {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
|
||||
if (!params.has('operation_type') && fallbacks?.operation_type) {
|
||||
params.set('operation_type', fallbacks.operation_type);
|
||||
}
|
||||
|
||||
if (!params.has('invoice_type') && fallbacks?.invoice_type) {
|
||||
params.set('invoice_type', fallbacks.invoice_type);
|
||||
}
|
||||
|
||||
const operationType = params.get('operation_type');
|
||||
if (!operationType) {
|
||||
return '/dashboard';
|
||||
}
|
||||
|
||||
const qs = params.toString();
|
||||
return qs ? `/dashboard/invoices?${qs}` : `/dashboard/invoices?operation_type=${operationType}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construye la ruta de parámetros de factura conservando el contexto del listado.
|
||||
*/
|
||||
export function buildInvoicesSettingsPath(searchParams: URLSearchParams): string {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
const qs = params.toString();
|
||||
return qs ? `/dashboard/invoices/settings?${qs}` : '/dashboard/invoices/settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resuelve operación y tipo inicial para la pantalla de parámetros.
|
||||
*/
|
||||
export function resolveInvoiceSettingsContext(
|
||||
searchParams: URLSearchParams,
|
||||
invoiceTypes: InvoiceType[]
|
||||
): { operationType: string; invoiceType: string } {
|
||||
const operationType = searchParams.get('operation_type') ?? '';
|
||||
let invoiceType = searchParams.get('invoice_type') ?? '';
|
||||
|
||||
if (operationType && !invoiceType) {
|
||||
invoiceType = getDefaultInvoiceTypeForOperation(operationType, invoiceTypes);
|
||||
}
|
||||
|
||||
return { operationType, invoiceType };
|
||||
}
|
||||
@@ -38,14 +38,14 @@
|
||||
{#snippet child({ props })}
|
||||
<button
|
||||
{...props}
|
||||
class="inline-flex size-8 shrink-0 items-center justify-center rounded-md
|
||||
class="inline-flex size-10 shrink-0 select-none items-center justify-center rounded-md
|
||||
text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground
|
||||
transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring
|
||||
data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
title="Aplicaciones"
|
||||
aria-label="Abrir selector de aplicaciones"
|
||||
>
|
||||
<LayoutGridIcon class="size-4" />
|
||||
<LayoutGridIcon class="size-5" />
|
||||
</button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
@@ -84,6 +84,21 @@
|
||||
inventory: BarChart3Icon,
|
||||
};
|
||||
|
||||
const SYSTEM_COLORS: Record<SystemType, { border: string; bg: string; iconBg: string; iconText: string }> = {
|
||||
fixed_asset: {
|
||||
border: 'border-amber-500/40',
|
||||
bg: 'bg-amber-500/5 dark:bg-amber-500/10',
|
||||
iconBg: 'bg-amber-500/10',
|
||||
iconText: 'text-amber-600 dark:text-amber-400',
|
||||
},
|
||||
inventory: {
|
||||
border: 'border-emerald-500/40',
|
||||
bg: 'bg-emerald-500/5 dark:bg-emerald-500/10',
|
||||
iconBg: 'bg-emerald-500/10',
|
||||
iconText: 'text-emerald-600 dark:text-emerald-400',
|
||||
},
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const expandForKeyboardNav = () => {
|
||||
sidebar.setOpen(true);
|
||||
@@ -104,15 +119,15 @@
|
||||
{@const activeLabel = systemStore.activeLabel}
|
||||
{@const activeSystem = systemStore.activeSystem}
|
||||
{@const Icon = activeSystem ? ACTIVE_ICONS[activeSystem] : null}
|
||||
{@const colors = activeSystem ? SYSTEM_COLORS[activeSystem] : null}
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 rounded-lg border border-primary/40 bg-primary/5 px-3 py-2 text-xs shadow-sm
|
||||
dark:bg-primary/10"
|
||||
class="flex items-center gap-2 rounded-lg border {colors?.border} {colors?.bg} px-3 py-2 text-xs shadow-sm"
|
||||
aria-label={`Aplicación activa: ${activeLabel.name}`}
|
||||
title={`Aplicación activa: ${activeLabel.name}`}
|
||||
>
|
||||
{#if Icon}
|
||||
<div class="flex size-8 items-center justify-center rounded-md bg-primary/10 text-primary">
|
||||
<div class="flex size-8 items-center justify-center rounded-md {colors?.iconBg} {colors?.iconText}">
|
||||
<Icon class="size-4" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user