feature/api-doda-update
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -552,6 +552,43 @@ async function fetchApiFormDataPost<T = any>(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convierte cuerpos de error (JSON o texto) en un mensaje legible para toasts/UX.
|
||||
* Evita mostrar JSON crudo p. ej. `{"error":"HTTP_ERROR","message":"..."}`.
|
||||
*/
|
||||
function messageFromBlobErrorResponse(text: string, status: number): string {
|
||||
const raw = (text || '').trim();
|
||||
if (!raw) {
|
||||
return status === 404
|
||||
? 'No se encontró el recurso. Prueba otro rango o vuelve a intentar.'
|
||||
: `Error ${status} al descargar el archivo.`;
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(raw) as Record<string, unknown>;
|
||||
if (typeof data.message === 'string' && data.message.trim()) {
|
||||
return data.message.trim();
|
||||
}
|
||||
const d = data.detail;
|
||||
if (typeof d === 'string' && d.trim()) {
|
||||
return d.trim();
|
||||
}
|
||||
if (Array.isArray(d) && d[0] && typeof (d[0] as { msg?: string }).msg === 'string') {
|
||||
return String((d[0] as { msg: string }).msg).trim();
|
||||
}
|
||||
} catch {
|
||||
// no es JSON: usar texto plano si es corto y legible
|
||||
}
|
||||
if (raw.length < 500 && !raw.startsWith('{')) {
|
||||
return raw;
|
||||
}
|
||||
if (raw.startsWith('{')) {
|
||||
return status === 404
|
||||
? 'No se encontró información para exportar. Prueba otras fechas o amplía el rango.'
|
||||
: `Error ${status} al descargar el archivo.`;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
async function fetchBlob(endpoint: string, options: RequestInit = {}): Promise<Blob> {
|
||||
const token = getToken();
|
||||
const headers: Record<string, string> = {
|
||||
@@ -568,9 +605,8 @@ async function fetchBlob(endpoint: string, options: RequestInit = {}): Promise<B
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Try to extract some useful message for debugging/UI.
|
||||
const text = await response.text().catch(() => '');
|
||||
throw new Error(text || `Error ${response.status} descargando archivo`);
|
||||
throw new Error(messageFromBlobErrorResponse(text, response.status));
|
||||
}
|
||||
return await response.blob();
|
||||
}
|
||||
|
||||
@@ -398,6 +398,29 @@ export async function exportDodaList(
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/a76/doda/export/pedimentos/{id} — líneas de pedimento del DODA (TSV/csv/txt).
|
||||
*/
|
||||
export async function exportDodaPedimentosDetail(
|
||||
dodaId: number,
|
||||
companyId: number,
|
||||
format: DodaExportFileFormat = 'xls'
|
||||
): Promise<void> {
|
||||
const params = new URLSearchParams({
|
||||
company_id: String(companyId),
|
||||
format
|
||||
});
|
||||
const blob = await api.getBlob(`/v1/a76/doda/export/pedimentos/${dodaId}?${params.toString()}`);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `doda_pedimentos_${dodaId}.${format}`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// ── Alta DODA API ─────────────────────────────────────────────────────────── //
|
||||
|
||||
export interface DodaAltaResponse {
|
||||
|
||||
@@ -73,7 +73,13 @@
|
||||
toast.success(m['sidebar.doda_alta.export_excel_success']());
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
toast.error(msg || m['sidebar.doda_alta.export_excel_error']());
|
||||
const isEmpty =
|
||||
/no existen doda|no se encontr[óo] informaci[óo]n para exportar|no doda.*range/i.test(
|
||||
String(msg)
|
||||
);
|
||||
toast.error(
|
||||
isEmpty ? m['sidebar.doda_alta.export_no_data']() : (msg || m['sidebar.doda_alta.export_excel_error']())
|
||||
);
|
||||
} finally {
|
||||
busy = false;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, Pencil, Trash2, Inbox } from 'lucide-svelte';
|
||||
import { cn } from '$lib/utils';
|
||||
import { dodaFormT } from '$lib/i18n/doda-form-strings';
|
||||
|
||||
interface Column {
|
||||
header: string;
|
||||
@@ -12,6 +13,8 @@
|
||||
|
||||
let {
|
||||
title = '',
|
||||
/** `en` / `es` (viene del padre; evita leer `page` aquí, más seguro con SSR) */
|
||||
locale: localeProp = 'es',
|
||||
columns = [],
|
||||
data = [],
|
||||
onAdd,
|
||||
@@ -24,6 +27,7 @@
|
||||
class: className = ''
|
||||
}: {
|
||||
title?: string;
|
||||
locale?: 'en' | 'es';
|
||||
columns: Column[];
|
||||
data: any[];
|
||||
onAdd?: () => void;
|
||||
@@ -34,6 +38,8 @@
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
const dodaLoc = $derived((localeProp === 'en' ? 'en' : 'es') as 'en' | 'es');
|
||||
|
||||
let selectedIndex = $state<number | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
@@ -86,7 +92,7 @@
|
||||
class="flex flex-col items-center justify-center gap-1 text-muted-foreground"
|
||||
>
|
||||
<Inbox class="h-5 w-5 opacity-45" />
|
||||
<span class="text-xs">Sin filas. «Nuevo» para añadir.</span>
|
||||
<span class="text-xs">{dodaFormT(dodaLoc, 'child_empty')}</span>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
@@ -133,7 +139,7 @@
|
||||
<div class="flex flex-wrap items-center justify-end gap-2 border-t border-border/30 bg-muted/20 px-3 py-2 sm:px-4">
|
||||
<Button variant="outline" size="sm" onclick={onAdd} class="h-8 gap-1.5 px-3 text-xs font-medium">
|
||||
<Plus class="h-3.5 w-3.5" />
|
||||
Nuevo
|
||||
{dodaFormT(dodaLoc, 'child_new')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -147,7 +153,7 @@
|
||||
disabled={data.length === 0 || selectedIndex == null}
|
||||
>
|
||||
<Pencil class="h-3.5 w-3.5" />
|
||||
Editar
|
||||
{dodaFormT(dodaLoc, 'child_edit')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -161,7 +167,7 @@
|
||||
disabled={data.length === 0 || selectedIndex == null}
|
||||
>
|
||||
<Trash2 class="h-3.5 w-3.5" />
|
||||
Borrar
|
||||
{dodaFormT(dodaLoc, 'child_delete')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { Doda } from '$lib/api/dashboard/a76/general_catalogs/doda';
|
||||
import { renderSnippet } from '$lib/components/ui/data-table';
|
||||
import { createRawSnippet } from 'svelte';
|
||||
import { getLocale } from '$lib/paraglide/runtime';
|
||||
import { dodaFormT, type DodaFormKey } from '$lib/i18n/doda-form-strings';
|
||||
|
||||
/**
|
||||
* doda_date se almacena como Integer con formato YYYYMMDD (ej. 20180409).
|
||||
@@ -18,7 +20,7 @@ function formatDodaDate(val?: number | string | null): string {
|
||||
}
|
||||
// Fallback: ISO string
|
||||
try {
|
||||
return new Date(s).toLocaleDateString('es-MX', {
|
||||
return new Date(s).toLocaleDateString(getLocale() === 'en' ? 'en-US' : 'es-MX', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
@@ -36,11 +38,12 @@ const STATUS_CLASSES: Record<string, string> = {
|
||||
ELIMINADO: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300',
|
||||
};
|
||||
|
||||
export function createColumns(): ColumnDef<Doda>[] {
|
||||
export function createColumns(loc: 'en' | 'es'): ColumnDef<Doda>[] {
|
||||
const t = (k: DodaFormKey) => dodaFormT(loc, k);
|
||||
return [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'Folio',
|
||||
header: t('list_col_folio'),
|
||||
size: 70,
|
||||
cell: ({ row }) => {
|
||||
const n = row.original.id;
|
||||
@@ -53,7 +56,7 @@ export function createColumns(): ColumnDef<Doda>[] {
|
||||
},
|
||||
{
|
||||
accessorKey: 'doda_date',
|
||||
header: 'Fecha Doda',
|
||||
header: t('list_col_doda_date'),
|
||||
size: 100,
|
||||
cell: ({ row }) => {
|
||||
const d = formatDodaDate(row.original.doda_date);
|
||||
@@ -65,19 +68,19 @@ export function createColumns(): ColumnDef<Doda>[] {
|
||||
},
|
||||
{
|
||||
accessorKey: 'dispatch_customs',
|
||||
header: 'Desp.',
|
||||
header: t('list_col_desp'),
|
||||
size: 60,
|
||||
cell: ({ row }) => row.original.dispatch_customs || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'patent',
|
||||
header: 'Patente',
|
||||
header: t('list_col_patent'),
|
||||
size: 70,
|
||||
cell: ({ row }) => row.original.patent || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'pedimentos',
|
||||
header: 'Pedimento(s)',
|
||||
header: t('list_col_pedimentos'),
|
||||
cell: ({ row }) => {
|
||||
const v = row.original.pedimentos || '-';
|
||||
const s = createRawSnippet(() => ({
|
||||
@@ -89,13 +92,13 @@ export function createColumns(): ColumnDef<Doda>[] {
|
||||
},
|
||||
{
|
||||
accessorKey: 'shipments',
|
||||
header: 'Remesa(s)',
|
||||
header: t('list_col_remesas'),
|
||||
size: 90,
|
||||
cell: ({ row }) => row.original.shipments || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'integration_number',
|
||||
header: 'Integración',
|
||||
header: t('list_col_integracion'),
|
||||
size: 110,
|
||||
cell: ({ row }) => {
|
||||
const v = row.original.integration_number;
|
||||
@@ -110,7 +113,7 @@ export function createColumns(): ColumnDef<Doda>[] {
|
||||
},
|
||||
{
|
||||
accessorKey: 'transaction_number',
|
||||
header: 'No. Transacción',
|
||||
header: t('list_col_trans'),
|
||||
cell: ({ row }) => {
|
||||
const v = row.original.transaction_number || '-';
|
||||
const s = createRawSnippet(() => ({
|
||||
@@ -122,25 +125,25 @@ export function createColumns(): ColumnDef<Doda>[] {
|
||||
},
|
||||
{
|
||||
accessorKey: 'transport_identification',
|
||||
header: 'Id. Transporte',
|
||||
header: t('list_col_id_transport'),
|
||||
size: 120,
|
||||
cell: ({ row }) => row.original.transport_identification || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'caat',
|
||||
header: 'CAAT',
|
||||
header: t('list_col_caat'),
|
||||
size: 70,
|
||||
cell: ({ row }) => row.original.caat || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'last_user',
|
||||
header: 'Usuario',
|
||||
header: t('list_col_user'),
|
||||
size: 90,
|
||||
cell: ({ row }) => row.original.last_user || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: 'Estatus',
|
||||
header: t('list_col_status'),
|
||||
size: 110,
|
||||
cell: ({ row }) => {
|
||||
const status = (row.original.status || '').toUpperCase();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { type ColumnDef, 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 { dodaFormT } from '$lib/i18n/doda-form-strings';
|
||||
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
@@ -13,6 +14,8 @@
|
||||
selectedId?: number | null;
|
||||
onRowClick?: (row: TData) => void;
|
||||
onRowDoubleClick?: (row: TData) => void;
|
||||
/** Solo catálogo DODA: textos i18n de carga / vacío */
|
||||
locale?: 'en' | 'es';
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -23,9 +26,12 @@
|
||||
loadMore,
|
||||
selectedId = null,
|
||||
onRowClick,
|
||||
onRowDoubleClick
|
||||
onRowDoubleClick,
|
||||
locale = 'es'
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const loc = $derived((locale === 'en' ? 'en' : 'es') as 'en' | 'es');
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
@@ -108,7 +114,7 @@
|
||||
{:else}
|
||||
<Table.Row inTabOrder={false}>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center text-sm text-muted-foreground">
|
||||
No hay resultados.
|
||||
{dodaFormT(loc, 'list_no_results')}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
@@ -122,10 +128,10 @@
|
||||
<div
|
||||
class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"
|
||||
></div>
|
||||
<span class="text-sm text-muted-foreground">Cargando más...</span>
|
||||
<span class="text-sm text-muted-foreground">{dodaFormT(loc, 'list_loading_more')}</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-muted-foreground">Desplázate para cargar más</div>
|
||||
<div class="text-sm text-muted-foreground">{dodaFormT(loc, 'list_scroll_for_more')}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
|
||||
@@ -72,6 +72,9 @@ export function isPitaCustomsClearance(customsClearance: number | undefined | nu
|
||||
return customsClearance === 1;
|
||||
}
|
||||
|
||||
/** Códigos de error para mapear a i18n (`sidebar.doda_form.*`). */
|
||||
export type AmericanPedimentoTipoError = 'required' | 'import_range' | 'export_range' | 'op_undefined';
|
||||
|
||||
/**
|
||||
* Valida `american_pedimento_type` frente a `operation_type` (legacy Clarion).
|
||||
* Importación: tipos 1–5. Exportación: tipos 6–8.
|
||||
@@ -79,9 +82,9 @@ export function isPitaCustomsClearance(customsClearance: number | undefined | nu
|
||||
export function validateAmericanPedimentoTipo(
|
||||
operationType: string | undefined,
|
||||
tipo: string | undefined
|
||||
): string | null {
|
||||
): AmericanPedimentoTipoError | null {
|
||||
const t = (tipo || '').trim();
|
||||
if (!t) return 'El tipo de pedimento americano es obligatorio.';
|
||||
if (!t) return 'required';
|
||||
|
||||
const op = (operationType || '').trim().toUpperCase();
|
||||
const isImport = op === 'I' || op === '1';
|
||||
@@ -89,14 +92,14 @@ export function validateAmericanPedimentoTipo(
|
||||
|
||||
if (isImport) {
|
||||
if (!['1', '2', '3', '4', '5'].includes(t)) {
|
||||
return 'El tipo de pedimento americano no es correcto para importación (debe ser 1, 2, 3, 4 o 5).';
|
||||
return 'import_range';
|
||||
}
|
||||
} else if (isExport) {
|
||||
if (!['6', '7', '8'].includes(t)) {
|
||||
return 'El tipo de pedimento americano no es correcto para exportación (debe ser 6, 7 u 8).';
|
||||
return 'export_range';
|
||||
}
|
||||
} else {
|
||||
return 'Define el tipo de operación (I/E) antes de validar el pedimento americano.';
|
||||
return 'op_undefined';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
60
frontend/src/lib/i18n/doda-form-strings.ts
Normal file
60
frontend/src/lib/i18n/doda-form-strings.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Catálogo duplicado desde `messages/{locale}.json` → `sidebar.doda_form`.
|
||||
* Los JSON viven bajo `src/` para que Vite los empaquete; importar `../../../messages/*`
|
||||
* provocaba `GET /messages/*.json?import` → 403 con la config actual del dev server.
|
||||
*/
|
||||
import { getLocale } from '$lib/paraglide/runtime';
|
||||
import esDoda from './doda-form/messages.es.json';
|
||||
import enDoda from './doda-form/messages.en.json';
|
||||
import type { AmericanPedimentoTipoError } from '$lib/components/dashboard/general_catalogs/doda/doda-form-helpers';
|
||||
|
||||
const esBlock = esDoda;
|
||||
const enBlock = enDoda ?? esBlock;
|
||||
if (!esBlock) {
|
||||
throw new Error('doda-form: empty messages.es.json');
|
||||
}
|
||||
const blocks = { es: esBlock, en: enBlock! };
|
||||
|
||||
export type DodaFormKey = keyof typeof esDoda;
|
||||
|
||||
const AMERICAN_TIPO_ERR: Record<AmericanPedimentoTipoError, DodaFormKey> = {
|
||||
required: 'err_american_tipo_required',
|
||||
import_range: 'err_american_tipo_import',
|
||||
export_range: 'err_american_tipo_export',
|
||||
op_undefined: 'err_american_op_undefined'
|
||||
};
|
||||
|
||||
function interpolate(template: string, params?: Record<string, string | number>) {
|
||||
if (!params) return template;
|
||||
return template.replace(/\{(\w+)\}/g, (_, k) => String(params[k] ?? ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Idioma real de la app (cookie / Paraglie). No usar solo el path: el dashboard suele
|
||||
* ser `/dashboard/...` con locale en `PARAGLIDE_LOCALE`, no `/en/dashboard/...`.
|
||||
*/
|
||||
export function dodaFormLocaleFromRuntime(): 'en' | 'es' {
|
||||
return getLocale() === 'en' ? 'en' : 'es';
|
||||
}
|
||||
|
||||
/** Si alguna ruta usara prefijo URL `/en/...`, puedes combinar con runtime. */
|
||||
export function dodaFormLocaleFromPathname(pathname: string): 'en' | 'es' {
|
||||
return pathname.split('/').filter(Boolean)[0] === 'en' ? 'en' : 'es';
|
||||
}
|
||||
|
||||
export function dodaFormT(
|
||||
locale: 'en' | 'es',
|
||||
key: DodaFormKey,
|
||||
params?: Record<string, string | number>
|
||||
): string {
|
||||
const block = blocks[locale] ?? blocks.es;
|
||||
const raw = block[key] ?? blocks.es[key] ?? String(key);
|
||||
return interpolate(String(raw), params);
|
||||
}
|
||||
|
||||
export function dodaFormAmericanTipoMessage(
|
||||
locale: 'en' | 'es',
|
||||
code: AmericanPedimentoTipoError
|
||||
): string {
|
||||
return dodaFormT(locale, AMERICAN_TIPO_ERR[code]);
|
||||
}
|
||||
195
frontend/src/lib/i18n/doda-form/messages.en.json
Normal file
195
frontend/src/lib/i18n/doda-form/messages.en.json
Normal file
@@ -0,0 +1,195 @@
|
||||
{
|
||||
"shortcuts_scope": "DODA form",
|
||||
"title_new": "New DODA",
|
||||
"title_edit": "Edit DODA",
|
||||
"description_catalog": "Catalogs · DODA",
|
||||
"tab_general": "General",
|
||||
"tab_seals_sat": "Seals and SAT",
|
||||
"shortcuts_hint": "Alt+1/2 · Ctrl+S save · Esc cancel",
|
||||
"btn_cancel": "Cancel",
|
||||
"btn_save": "Save",
|
||||
"btn_saving": "Saving...",
|
||||
"btn_save_changes": "Save changes",
|
||||
"btn_create_doda": "Create DODA",
|
||||
"btn_accept": "OK",
|
||||
"card_broker_customs": "Customs agent and office",
|
||||
"card_transport": "Transport",
|
||||
"card_control": "Control and dispatch",
|
||||
"card_sat_chain": "Original chain and signatures (SAT)",
|
||||
"label_responsible": "Broker",
|
||||
"label_patent": "Patent",
|
||||
"label_dispatch": "Dispatch office",
|
||||
"label_section_es": "E/S section",
|
||||
"label_operation_type": "Operation type",
|
||||
"label_transporter": "Carrier",
|
||||
"label_transport_id": "Transport ID",
|
||||
"label_caat": "CAAT",
|
||||
"label_doda_date": "DODA date",
|
||||
"label_status": "Status",
|
||||
"label_dispatch_type": "Dispatch type",
|
||||
"label_unique_badge": "Unique badge",
|
||||
"label_integration_num": "Integration No.",
|
||||
"label_transaction_num": "Transaction No.",
|
||||
"label_fast_id": "Fast ID",
|
||||
"label_last_user": "Last user",
|
||||
"label_original_chain": "Original chain",
|
||||
"label_serial_cert": "Serial (certificate)",
|
||||
"label_uuid_cp": "Carta porte UUID",
|
||||
"label_electronic_sig": "Electronic signature",
|
||||
"label_sat_cert": "SAT certificate",
|
||||
"label_sat_chain": "Original SAT chain",
|
||||
"ph_aga": "AGA key",
|
||||
"ph_0000": "0000",
|
||||
"ph_000": "000",
|
||||
"ph_select": "Select",
|
||||
"ph_plate": "Plate / vehicle ID",
|
||||
"ph_dash": "—",
|
||||
"ph_yyyymmdd": "YYYYMMDD",
|
||||
"ph_badge_pita": "N/A — PITA",
|
||||
"ph_badge_num": "Badge no.",
|
||||
"ph_example_container": "E.g. 53056",
|
||||
"op_import": "I — Import",
|
||||
"op_export": "E — Export",
|
||||
"type_pita": "PITA",
|
||||
"type_doda": "DODA",
|
||||
"vu_checking": "Verifying agent VU DODA…",
|
||||
"vu_incomplete": "VU DODA incomplete: agent needs .cer, .key, and DODA FIEL password.",
|
||||
"vu_complete": "VU DODA complete for API submission.",
|
||||
"badge_required_hint": "Required for DODA filing API.",
|
||||
"pedimentos": "Pedimentos",
|
||||
"lines": "lines",
|
||||
"containers": "Containers",
|
||||
"american_pedimentos": "U.S. pedimentos",
|
||||
"seals_block_title": "Seals — total in DODA: {n} / 8",
|
||||
"seals_help": "Select a container. Maximum 8 seals per DODA (SCAII).",
|
||||
"seals_select_container": "Select a container in the table to view or edit its seals.",
|
||||
"container_no_id_warning": "Container not saved on server. Enter value, press Save; new containers are sent and reloaded with id for seals.",
|
||||
"container_line_info": "Container:",
|
||||
"seal_on_line": "seal(s) on this line",
|
||||
"line_word": "Line",
|
||||
"btn_add_seal": "Add seal",
|
||||
"btn_seal_delete": "Delete",
|
||||
"seals_empty_line": "No seals on this container.",
|
||||
"col_line": "Line",
|
||||
"col_auth_patent": "Auth. patent",
|
||||
"col_document": "Document",
|
||||
"col_remesa": "Shipment",
|
||||
"col_cove": "COVE",
|
||||
"col_umc": "UMC",
|
||||
"col_cash_usd": "Cash USD",
|
||||
"col_diff_usd": "Difference USD",
|
||||
"col_dta_niu": "DTA NIU",
|
||||
"col_art7": "Art. 7",
|
||||
"col_container": "Container",
|
||||
"col_seals": "Seals",
|
||||
"col_seal_value": "Seal",
|
||||
"col_american_type": "Type",
|
||||
"col_american_ped": "U.S. pedimento",
|
||||
"col_pedimento_only": "U.S. pedimento",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"child_empty": "No rows. “New” to add.",
|
||||
"child_new": "New",
|
||||
"child_edit": "Edit",
|
||||
"child_delete": "Delete",
|
||||
"modal_container_new": "New container",
|
||||
"modal_container_edit": "Edit container",
|
||||
"modal_container_desc": "Enter the container value for the DODA declaration.",
|
||||
"label_container_value": "Container value",
|
||||
"modal_seals_in_container": "Seals in container",
|
||||
"seal_modal_title": "Containers > Seal",
|
||||
"seal_modal_desc": "Enter the seal value for the selected container.",
|
||||
"label_seal": "Seal",
|
||||
"ph_seal": "Seal value",
|
||||
"american_modal_title": "U.S. pedimento",
|
||||
"american_modal_desc": "Enter type and value of the U.S. pedimento.",
|
||||
"label_american_type_short": "U.S. type",
|
||||
"label_american_value": "U.S. pedimento",
|
||||
"ph_american_value": "U.S. pedimento value",
|
||||
"line_label": "Line:",
|
||||
"select_type": "Select type",
|
||||
"american_cat_6": "AMERICAN PEDIMENTO",
|
||||
"american_cat_7": "SELF-DECLARATION",
|
||||
"american_cat_8": "NOT PRESENT",
|
||||
"err_american_tipo_required": "U.S. pedimento type is required.",
|
||||
"err_american_tipo_import": "U.S. pedimento type is not valid for import (must be 1, 2, 3, 4, or 5).",
|
||||
"err_american_tipo_export": "U.S. pedimento type is not valid for export (must be 6, 7, or 8).",
|
||||
"err_american_op_undefined": "Set operation type (I/E) before validating the U.S. pedimento.",
|
||||
"err_company": "Select a company",
|
||||
"err_responsible": "Broker is required",
|
||||
"err_patent": "Patent is required",
|
||||
"err_transport": "Transport ID is required. Select a vehicle.",
|
||||
"err_badge": "Unique badge number is required for DODA filing.",
|
||||
"err_vu_wait": "Wait for agent VU DODA check to finish, then try again.",
|
||||
"err_vu_config": "The customs agent does not have full VU DODA config (.cer, .key, DODA FIEL password).",
|
||||
"err_min_containers": "Add at least one container for API submission.",
|
||||
"err_american_new_lines": "Enter the U.S. pedimento value for each new line.",
|
||||
"err_save": "Error saving",
|
||||
"toast_saved": "Changes saved successfully.",
|
||||
"toast_created": "DODA created successfully.",
|
||||
"load_error": "Could not load DODA",
|
||||
"warn_vu_incomplete": "This DODA’s agent does not have full VU DODA (.cer, .key, DODA FIEL password).",
|
||||
"warn_vu_fetch": "Could not validate the agent’s VU settings.",
|
||||
"warn_broker_select": "Selected agent has incomplete VU DODA. Configure in Customs agents before generating.",
|
||||
"seal_save_first": "Save the DODA before managing seals.",
|
||||
"seal_pick_container": "Select a container in the table.",
|
||||
"seal_not_persisted": "This container is not on the server yet. Save the DODA and reload.",
|
||||
"seal_empty": "Seal cannot be empty.",
|
||||
"seal_max": "DODA already has the maximum 8 seals.",
|
||||
"seal_add_err": "Error adding seal",
|
||||
"seal_delete_err": "Error removing seal",
|
||||
"pedimento_remove_blocked": "Cannot remove pedimentos already saved on the server here.",
|
||||
"container_delete_err": "Error deleting container",
|
||||
"american_delete_err": "Error deleting U.S. pedimento",
|
||||
"container_update_err": "Error updating container",
|
||||
"american_cannot_edit_persisted": "To change saved U.S. pedimentos, remove and add again.",
|
||||
"err_american_value": "Enter the U.S. pedimento value.",
|
||||
"err_american_type_or_value": "Enter type and/or U.S. pedimento value.",
|
||||
"err_containers_max": "A DODA can have at most 4 containers.",
|
||||
"err_container_empty": "Container value cannot be empty.",
|
||||
"err_container_not_found": "Container to edit not found.",
|
||||
"pedimento_selector_title": "Containers > Seal",
|
||||
"list_page_subtitle": "Manage your Customs Operation Documents (DODA)",
|
||||
"list_btn_new": "New DODA",
|
||||
"list_card_title": "DODA list",
|
||||
"list_ph_folio": "Folio",
|
||||
"list_ph_patent": "Patent",
|
||||
"list_filter_status_ph": "Status",
|
||||
"list_filter_status_all": "All",
|
||||
"list_filter_op_import": "Import",
|
||||
"list_filter_op_export": "Export",
|
||||
"list_filter_op": "Operation",
|
||||
"list_filter_op_all": "All",
|
||||
"list_btn_clear": "Clear",
|
||||
"list_showing": "Showing {a} of {b} records",
|
||||
"list_active_filters": "Active filters: {n}",
|
||||
"list_btn_edit": "Edit",
|
||||
"list_btn_print": "Print",
|
||||
"list_toast_reload_error": "Error reloading data",
|
||||
"list_elig_error_prefix": "Error checking eligibility: ",
|
||||
"list_elig_not_meet": "This DODA does not meet the filing requirements.",
|
||||
"list_alta_error_prefix": "Error sending DODA filing: ",
|
||||
"list_print_error": "Error generating DODA PDF",
|
||||
"list_alta_complete": "DODA filing completed successfully",
|
||||
"list_shortcuts_scope": "DODA list",
|
||||
"list_col_folio": "Folio",
|
||||
"list_col_doda_date": "DODA date",
|
||||
"list_col_desp": "Cstm.",
|
||||
"list_col_patent": "Patent",
|
||||
"list_col_pedimentos": "Pedimento(s)",
|
||||
"list_col_remesas": "Shipment(s)",
|
||||
"list_col_integracion": "Integration",
|
||||
"list_col_trans": "Trans. no.",
|
||||
"list_col_id_transport": "Transport ID",
|
||||
"list_col_caat": "CAAT",
|
||||
"list_col_user": "User",
|
||||
"list_col_status": "Status",
|
||||
"list_loading_more": "Loading more...",
|
||||
"list_scroll_for_more": "Scroll to load more",
|
||||
"list_confirm_delete": "Are you sure you want to delete this DODA record?",
|
||||
"list_toast_delete_ok": "DODA deleted successfully",
|
||||
"list_toast_delete_err": "Error deleting DODA",
|
||||
"list_filter_i": "I — Import",
|
||||
"list_filter_e": "E — Export",
|
||||
"list_no_results": "No results."
|
||||
}
|
||||
195
frontend/src/lib/i18n/doda-form/messages.es.json
Normal file
195
frontend/src/lib/i18n/doda-form/messages.es.json
Normal file
@@ -0,0 +1,195 @@
|
||||
{
|
||||
"shortcuts_scope": "Formulario DODA",
|
||||
"title_new": "Nuevo DODA",
|
||||
"title_edit": "Editar DODA",
|
||||
"description_catalog": "Catálogos · DODA",
|
||||
"tab_general": "General",
|
||||
"tab_seals_sat": "Sellos y SAT",
|
||||
"shortcuts_hint": "Alt+1/2 · Ctrl+S guardar · Esc cancelar",
|
||||
"btn_cancel": "Cancelar",
|
||||
"btn_save": "Guardar",
|
||||
"btn_saving": "Guardando...",
|
||||
"btn_save_changes": "Guardar cambios",
|
||||
"btn_create_doda": "Crear DODA",
|
||||
"btn_accept": "Aceptar",
|
||||
"card_broker_customs": "Agente aduanal y aduana",
|
||||
"card_transport": "Transporte",
|
||||
"card_control": "Control y despacho",
|
||||
"card_sat_chain": "Cadena original y firmas (SAT)",
|
||||
"label_responsible": "Responsable",
|
||||
"label_patent": "Patente",
|
||||
"label_dispatch": "Aduana despacho",
|
||||
"label_section_es": "Aduana sección E/S",
|
||||
"label_operation_type": "Tipo operación",
|
||||
"label_transporter": "Transportista",
|
||||
"label_transport_id": "ID transporte",
|
||||
"label_caat": "CAAT",
|
||||
"label_doda_date": "Fecha DODA",
|
||||
"label_status": "Estatus",
|
||||
"label_dispatch_type": "Tipo despacho",
|
||||
"label_unique_badge": "Gafete único",
|
||||
"label_integration_num": "Núm. integración",
|
||||
"label_transaction_num": "Núm. transacción",
|
||||
"label_fast_id": "Fast ID",
|
||||
"label_last_user": "Último usuario",
|
||||
"label_original_chain": "Cadena original",
|
||||
"label_serial_cert": "Núm. serie (certificado)",
|
||||
"label_uuid_cp": "UUID carta porte",
|
||||
"label_electronic_sig": "Firma electrónica",
|
||||
"label_sat_cert": "Certificado SAT",
|
||||
"label_sat_chain": "Cadena original SAT",
|
||||
"ph_aga": "Clave AGA",
|
||||
"ph_0000": "0000",
|
||||
"ph_000": "000",
|
||||
"ph_select": "Seleccionar",
|
||||
"ph_plate": "Placa / ID vehículo",
|
||||
"ph_dash": "—",
|
||||
"ph_yyyymmdd": "AAAAMMDD",
|
||||
"ph_badge_pita": "N/A — PITA",
|
||||
"ph_badge_num": "Núm. gafete",
|
||||
"ph_example_container": "Ej. 53056",
|
||||
"op_import": "I — Importación",
|
||||
"op_export": "E — Exportación",
|
||||
"type_pita": "PITA",
|
||||
"type_doda": "DODA",
|
||||
"vu_checking": "Verificando VU DODA del agente…",
|
||||
"vu_incomplete": "VU DODA incompleta: se requiere .cer, .key y clave FIEL DODA del agente.",
|
||||
"vu_complete": "VU DODA completa para envío a API.",
|
||||
"badge_required_hint": "Requerido para alta DODA en API.",
|
||||
"pedimentos": "Pedimentos",
|
||||
"lines": "líneas",
|
||||
"containers": "Contenedores",
|
||||
"american_pedimentos": "Pedimentos americanos",
|
||||
"seals_block_title": "Precintos (candados) — total en el DODA: {n} / 8",
|
||||
"seals_help": "Selecciona un contenedor en la tabla. Máximo 8 precintos en todo el DODA (regla SCAII).",
|
||||
"seals_select_container": "Selecciona un contenedor en la tabla de contenedores para ver o editar sus precintos.",
|
||||
"container_no_id_warning": "Contenedor sin id en el servidor. Completa el valor, pulsa Guardar (arriba); al guardar se envían contenedores nuevos y se recargan con id para precintos.",
|
||||
"container_line_info": "Contenedor:",
|
||||
"seal_on_line": "precinto(s) en esta línea",
|
||||
"line_word": "Línea",
|
||||
"btn_add_seal": "Agregar precinto",
|
||||
"btn_seal_delete": "Eliminar",
|
||||
"seals_empty_line": "Sin precintos en este contenedor.",
|
||||
"col_line": "Línea",
|
||||
"col_auth_patent": "Patente auth.",
|
||||
"col_document": "Documento",
|
||||
"col_remesa": "Remesa",
|
||||
"col_cove": "COVE",
|
||||
"col_umc": "UMC",
|
||||
"col_cash_usd": "Efectivo USD",
|
||||
"col_diff_usd": "Diferencia USD",
|
||||
"col_dta_niu": "DTA NIU",
|
||||
"col_art7": "Art. 7",
|
||||
"col_container": "Contenedor",
|
||||
"col_seals": "Precintos",
|
||||
"col_seal_value": "Precinto",
|
||||
"col_american_type": "Tipo",
|
||||
"col_american_ped": "Pedimento americano",
|
||||
"col_pedimento_only": "Pedimento americano",
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
"child_empty": "Sin filas. «Nuevo» para añadir.",
|
||||
"child_new": "Nuevo",
|
||||
"child_edit": "Editar",
|
||||
"child_delete": "Borrar",
|
||||
"modal_container_new": "Nuevo contenedor",
|
||||
"modal_container_edit": "Editar contenedor",
|
||||
"modal_container_desc": "Captura el valor del contenedor para la declaración DODA.",
|
||||
"label_container_value": "Valor contenedor",
|
||||
"modal_seals_in_container": "Precintos del contenedor",
|
||||
"seal_modal_title": "Contenedores > Precinto",
|
||||
"seal_modal_desc": "Captura el valor del precinto para el contenedor seleccionado.",
|
||||
"label_seal": "Precinto",
|
||||
"ph_seal": "Valor del precinto",
|
||||
"american_modal_title": "Pedimento Americano",
|
||||
"american_modal_desc": "Captura el tipo y valor del pedimento americano.",
|
||||
"label_american_type_short": "Tipo Ped. Americano",
|
||||
"label_american_value": "Pedimento Americano",
|
||||
"ph_american_value": "Valor pedimento americano",
|
||||
"line_label": "Línea:",
|
||||
"select_type": "Selecciona tipo",
|
||||
"american_cat_6": "PEDIMENTO AMERICANO",
|
||||
"american_cat_7": "AUTODECLARACION",
|
||||
"american_cat_8": "NO PRESENTA",
|
||||
"err_american_tipo_required": "El tipo de pedimento americano es obligatorio.",
|
||||
"err_american_tipo_import": "El tipo de pedimento americano no es correcto para importación (debe ser 1, 2, 3, 4 o 5).",
|
||||
"err_american_tipo_export": "El tipo de pedimento americano no es correcto para exportación (debe ser 6, 7 u 8).",
|
||||
"err_american_op_undefined": "Define el tipo de operación (I/E) antes de validar el pedimento americano.",
|
||||
"err_company": "Selecciona una compañía",
|
||||
"err_responsible": "El Responsable es requerido",
|
||||
"err_patent": "El Agente Aduanal (Patente) es requerido",
|
||||
"err_transport": "La Identificación de Transporte es requerida. Selecciona un vehículo.",
|
||||
"err_badge": "El Número de Gafete Único es requerido para Alta DODA.",
|
||||
"err_vu_wait": "Espera a que termine la verificación VU DODA del agente e intenta de nuevo.",
|
||||
"err_vu_config": "El agente aduanal no tiene configuración VU DODA completa (.cer, .key y clave FIEL DODA).",
|
||||
"err_min_containers": "Agrega al menos un contenedor con valor para el envío a API.",
|
||||
"err_american_new_lines": "Indique el valor del pedimento americano en cada línea nueva.",
|
||||
"err_save": "Error al guardar",
|
||||
"toast_saved": "Cambios guardados correctamente.",
|
||||
"toast_created": "DODA creado correctamente.",
|
||||
"load_error": "No se pudo cargar la información del DODA",
|
||||
"warn_vu_incomplete": "El agente aduanal de este DODA no tiene VU DODA completa (.cer, .key y clave FIEL DODA).",
|
||||
"warn_vu_fetch": "No se pudo validar la configuración VU del agente aduanal.",
|
||||
"warn_broker_select": "El agente seleccionado no tiene VU DODA completa (.cer, .key y clave FIEL DODA). Configúralo en Agentes Aduanales antes de generar.",
|
||||
"seal_save_first": "Guarda el DODA antes de gestionar precintos.",
|
||||
"seal_pick_container": "Selecciona un contenedor en la tabla.",
|
||||
"seal_not_persisted": "Este contenedor aún no está guardado en el servidor. Guarda el DODA (Guardar) y vuelve a abrir o recarga.",
|
||||
"seal_empty": "El precinto no puede estar vacío.",
|
||||
"seal_max": "El DODA ya tiene el máximo de 8 precintos.",
|
||||
"seal_add_err": "Error al agregar el precinto",
|
||||
"seal_delete_err": "Error al eliminar el precinto",
|
||||
"pedimento_remove_blocked": "Los pedimentos guardados en servidor no se pueden quitar aquí.",
|
||||
"container_delete_err": "Error al eliminar el contenedor",
|
||||
"american_delete_err": "Error al eliminar el pedimento americano",
|
||||
"container_update_err": "Error al actualizar el contenedor",
|
||||
"american_cannot_edit_persisted": "Para editar pedimentos americanos guardados, elimínalo y créalo nuevamente.",
|
||||
"err_american_value": "Indique el valor del pedimento americano.",
|
||||
"err_american_type_or_value": "Capture tipo o valor del pedimento americano.",
|
||||
"err_containers_max": "El DODA solo puede tener máximo 4 contenedores.",
|
||||
"err_container_empty": "El valor del contenedor no puede estar vacío.",
|
||||
"err_container_not_found": "No se encontró el contenedor a editar.",
|
||||
"pedimento_selector_title": "Contenedores > Precinto",
|
||||
"list_page_subtitle": "Gestiona tus Documentos de Operación Aduanera (DODA)",
|
||||
"list_btn_new": "Nuevo DODA",
|
||||
"list_card_title": "Listado de DODA",
|
||||
"list_ph_folio": "Folio",
|
||||
"list_ph_patent": "Patente",
|
||||
"list_filter_status_ph": "Estatus",
|
||||
"list_filter_status_all": "Todos",
|
||||
"list_filter_op_import": "Importación",
|
||||
"list_filter_op_export": "Exportación",
|
||||
"list_filter_op": "Operación",
|
||||
"list_filter_op_all": "Todas",
|
||||
"list_btn_clear": "Limpiar",
|
||||
"list_showing": "Mostrando {a} de {b} registros",
|
||||
"list_active_filters": "Filtros activos: {n}",
|
||||
"list_btn_edit": "Editar",
|
||||
"list_btn_print": "Imprimir",
|
||||
"list_toast_reload_error": "Error al recargar datos",
|
||||
"list_elig_error_prefix": "Error al verificar elegibilidad: ",
|
||||
"list_elig_not_meet": "El DODA no cumple con los requisitos de alta.",
|
||||
"list_alta_error_prefix": "Error al enviar alta DODA: ",
|
||||
"list_print_error": "Error al generar el PDF del DODA",
|
||||
"list_alta_complete": "Alta DODA completada correctamente",
|
||||
"list_shortcuts_scope": "Lista DODA",
|
||||
"list_col_folio": "Folio",
|
||||
"list_col_doda_date": "Fecha DODA",
|
||||
"list_col_desp": "Desp.",
|
||||
"list_col_patent": "Patente",
|
||||
"list_col_pedimentos": "Pedimento(s)",
|
||||
"list_col_remesas": "Remesa(s)",
|
||||
"list_col_integracion": "Integración",
|
||||
"list_col_trans": "Núm. Transacción",
|
||||
"list_col_id_transport": "Id. Transporte",
|
||||
"list_col_caat": "CAAT",
|
||||
"list_col_user": "Usuario",
|
||||
"list_col_status": "Estatus",
|
||||
"list_loading_more": "Cargando más...",
|
||||
"list_scroll_for_more": "Desplázate para cargar más",
|
||||
"list_confirm_delete": "¿Está seguro de eliminar este registro DODA?",
|
||||
"list_toast_delete_ok": "DODA eliminado correctamente",
|
||||
"list_toast_delete_err": "Error al eliminar DODA",
|
||||
"list_filter_i": "I - Importación",
|
||||
"list_filter_e": "E - Exportación",
|
||||
"list_no_results": "No hay resultados."
|
||||
}
|
||||
@@ -13,7 +13,8 @@
|
||||
RotateCcw,
|
||||
Send,
|
||||
Loader2,
|
||||
FileSpreadsheet
|
||||
FileSpreadsheet,
|
||||
Table
|
||||
} from 'lucide-svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
@@ -32,6 +33,7 @@
|
||||
import {
|
||||
getDodas,
|
||||
deleteDoda,
|
||||
exportDodaPedimentosDetail,
|
||||
postDodaAlta,
|
||||
getDodaElegibilidad,
|
||||
type Doda
|
||||
@@ -71,6 +73,7 @@
|
||||
let currentVariant = $state<'doda' | 'pita'>('doda');
|
||||
let altaLoading = $state(false);
|
||||
let deleteLoading = $state(false);
|
||||
let pedimentosExportLoading = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
if (data.dodas) {
|
||||
@@ -164,6 +167,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleExportPedimentos() {
|
||||
if (pedimentosExportLoading) return;
|
||||
if (!companyStore.activeCompany) {
|
||||
toast.error(m['sidebar.doda_alta.delete_missing_company']());
|
||||
return;
|
||||
}
|
||||
if (selectedDodaIds.length !== 1 || !selectedDoda) {
|
||||
toast.error(m['sidebar.doda_alta.delete_select_one']());
|
||||
return;
|
||||
}
|
||||
pedimentosExportLoading = true;
|
||||
try {
|
||||
await exportDodaPedimentosDetail(selectedDoda.id, companyStore.activeCompany.id, 'xls');
|
||||
toast.success(m['sidebar.doda_alta.export_pedimentos_success']());
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : m['sidebar.doda_alta.export_pedimentos_error']();
|
||||
toast.error(msg || m['sidebar.doda_alta.export_pedimentos_error']());
|
||||
} finally {
|
||||
pedimentosExportLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
filters = { integration_number: '', patent: '', status: '', operation_type: '' };
|
||||
}
|
||||
@@ -341,6 +366,21 @@
|
||||
<FileSpreadsheet size={16} class="mr-2" />
|
||||
{m['sidebar.doda_alta.action_export_excel']()}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
class="border border-primary/25 bg-primary/10 text-primary hover:bg-primary/15"
|
||||
onclick={handleExportPedimentos}
|
||||
disabled={selectedDodaIds.length !== 1 || pedimentosExportLoading || !companyStore.activeCompany}
|
||||
title={m['sidebar.doda_alta.action_export_pedimentos']()}
|
||||
>
|
||||
{#if pedimentosExportLoading}
|
||||
<Loader2 size={16} class="mr-2 animate-spin" />
|
||||
{:else}
|
||||
<Table size={16} class="mr-2" />
|
||||
{/if}
|
||||
{m['sidebar.doda_alta.action_export_pedimentos']()}
|
||||
</Button>
|
||||
<Separator orientation="vertical" class="mx-1 h-8 hidden sm:block" />
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import {
|
||||
getDodas,
|
||||
deleteDoda,
|
||||
exportDodaPedimentosDetail,
|
||||
printDoda,
|
||||
postDodaAlta,
|
||||
getDodaElegibilidad,
|
||||
@@ -12,6 +13,11 @@
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/doda';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { m } from '$lib/i18n/messages';
|
||||
import {
|
||||
dodaFormT,
|
||||
dodaFormLocaleFromRuntime,
|
||||
type DodaFormKey
|
||||
} from '$lib/i18n/doda-form-strings';
|
||||
import { isPitaCustomsClearance } from '$lib/components/dashboard/general_catalogs/doda/doda-form-helpers';
|
||||
import {
|
||||
Plus,
|
||||
@@ -24,7 +30,8 @@
|
||||
LayoutGrid,
|
||||
Printer,
|
||||
Send,
|
||||
Loader2
|
||||
Loader2,
|
||||
Table
|
||||
} from 'lucide-svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
@@ -42,25 +49,36 @@
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const dodaIdFromUrl = $derived($page.url.searchParams.get('doda_id') ?? '');
|
||||
const dodaLoc = $derived.by(() => {
|
||||
void page.url.pathname;
|
||||
return dodaFormLocaleFromRuntime();
|
||||
});
|
||||
|
||||
function dt(key: DodaFormKey, params?: Record<string, string | number>) {
|
||||
return dodaFormT(dodaFormLocaleFromRuntime(), key, params);
|
||||
}
|
||||
|
||||
const tableColumns = $derived(createColumns(dodaLoc));
|
||||
|
||||
const dodaIdFromUrl = $derived(page.url.searchParams.get('doda_id') ?? '');
|
||||
|
||||
function closeDodaForm() {
|
||||
if (!browser) return;
|
||||
const u = new URL($page.url);
|
||||
const u = new URL(page.url);
|
||||
u.searchParams.delete('doda_id');
|
||||
const q = u.searchParams.toString();
|
||||
void goto(u.pathname + (q ? `?${q}` : ''), { replaceState: true, noScroll: true });
|
||||
}
|
||||
|
||||
function setDodaIdQuery(id: number | 'new') {
|
||||
const u = new URL($page.url);
|
||||
const u = new URL(page.url);
|
||||
u.searchParams.set('doda_id', id === 'new' ? 'new' : String(id));
|
||||
const q = u.searchParams.toString();
|
||||
void goto(`${u.pathname}?${q}`, { noScroll: true });
|
||||
}
|
||||
|
||||
function onCreatedDodaToEdit(dodaId: number) {
|
||||
const u = new URL($page.url);
|
||||
const u = new URL(page.url);
|
||||
u.searchParams.set('doda_id', String(dodaId));
|
||||
const q = u.searchParams.toString();
|
||||
void goto(`${u.pathname}?${q}`, { replaceState: true, noScroll: true });
|
||||
@@ -69,10 +87,10 @@
|
||||
|
||||
// Filtros centralizados
|
||||
let filters = $state({
|
||||
integration_number: $page.url.searchParams.get('integration_number') || '',
|
||||
patent: $page.url.searchParams.get('patent') || '',
|
||||
status: $page.url.searchParams.get('status') || '',
|
||||
operation_type: $page.url.searchParams.get('operation_type') || ''
|
||||
integration_number: page.url.searchParams.get('integration_number') || '',
|
||||
patent: page.url.searchParams.get('patent') || '',
|
||||
status: page.url.searchParams.get('status') || '',
|
||||
operation_type: page.url.searchParams.get('operation_type') || ''
|
||||
});
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
@@ -96,6 +114,7 @@
|
||||
let altaLoading = $state(false);
|
||||
let printLoading = $state(false);
|
||||
let deleteLoading = $state(false);
|
||||
let pedimentosExportLoading = $state(false);
|
||||
let progressDialogOpen = $state(false);
|
||||
let currentTaskId = $state('');
|
||||
let currentVariant = $state<'doda' | 'pita'>('doda');
|
||||
@@ -121,7 +140,7 @@
|
||||
if (filters.status) params.set('status', filters.status);
|
||||
if (filters.operation_type) params.set('operation_type', filters.operation_type);
|
||||
|
||||
const modalId = $page.url.searchParams.get('doda_id');
|
||||
const modalId = page.url.searchParams.get('doda_id');
|
||||
if (modalId) params.set('doda_id', modalId);
|
||||
|
||||
const queryString = params.toString();
|
||||
@@ -146,7 +165,7 @@
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista DODA',
|
||||
dodaFormT(dodaLoc, 'list_shortcuts_scope'),
|
||||
obtenerAtajosListaDoda({
|
||||
manejarNuevo: handleCreateClick,
|
||||
manejarActualizar: reloadData
|
||||
@@ -202,7 +221,7 @@
|
||||
} catch (e) {
|
||||
console.error('Error reloading DODAs:', e);
|
||||
// Evitar mostrar error si es solo carga inicial y falla por falta de login etc
|
||||
if (allItems.length > 0) toast.error('Error al recargar datos');
|
||||
if (allItems.length > 0) toast.error(dt('list_toast_reload_error'));
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -260,6 +279,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleExportPedimentos() {
|
||||
if (pedimentosExportLoading) return;
|
||||
if (!companyStore.activeCompany) {
|
||||
toast.error(m['sidebar.doda_alta.delete_missing_company']());
|
||||
return;
|
||||
}
|
||||
if (selectedIds.length !== 1 || !selectedDoda) {
|
||||
toast.error(m['sidebar.doda_alta.delete_select_one']());
|
||||
return;
|
||||
}
|
||||
pedimentosExportLoading = true;
|
||||
try {
|
||||
await exportDodaPedimentosDetail(selectedDoda.id, companyStore.activeCompany.id, 'xls');
|
||||
toast.success(m['sidebar.doda_alta.export_pedimentos_success']());
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : m['sidebar.doda_alta.export_pedimentos_error']();
|
||||
toast.error(msg || m['sidebar.doda_alta.export_pedimentos_error']());
|
||||
} finally {
|
||||
pedimentosExportLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAlta() {
|
||||
if (!selectedDoda || !companyStore.activeCompany) return;
|
||||
const companyId = companyStore.activeCompany.id;
|
||||
@@ -269,18 +310,18 @@
|
||||
try {
|
||||
const elig = await getDodaElegibilidad(dodaId, companyId, variant);
|
||||
if (elig.error) {
|
||||
toast.error(`Error al verificar elegibilidad: ${elig.error}`);
|
||||
toast.error(`${dt('list_elig_error_prefix')}${elig.error}`);
|
||||
return;
|
||||
}
|
||||
if (elig.data && !elig.data.can_alta) {
|
||||
const msgs = elig.data.reasons.map((r) => `- ${r.message}`).join('\n');
|
||||
toast.error(msgs || 'El DODA no cumple con los requisitos de alta.');
|
||||
toast.error(msgs || dt('list_elig_not_meet'));
|
||||
return;
|
||||
}
|
||||
|
||||
const resp = await postDodaAlta(dodaId, companyId, variant);
|
||||
if (resp.error) {
|
||||
toast.error(`Error al enviar alta DODA: ${resp.error}`);
|
||||
toast.error(`${dt('list_alta_error_prefix')}${resp.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,7 +341,7 @@
|
||||
try {
|
||||
await printDoda(dodaId, companyId);
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : 'Error al generar el PDF del DODA';
|
||||
const msg = e instanceof Error ? e.message : dt('list_print_error');
|
||||
toast.error(msg);
|
||||
} finally {
|
||||
printLoading = false;
|
||||
@@ -310,7 +351,7 @@
|
||||
function onAltaComplete() {
|
||||
progressDialogOpen = false;
|
||||
reloadData();
|
||||
toast.success('Alta DODA completada correctamente');
|
||||
toast.success(dt('list_alta_complete'));
|
||||
}
|
||||
|
||||
function handleRowClick(row: Doda) {
|
||||
@@ -323,12 +364,12 @@
|
||||
<div class="flex-none flex items-center justify-between">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-2xl font-bold tracking-tight">DODA</h1>
|
||||
<p class="text-muted-foreground">Gestiona tus Documentos de Operación Aduanera (DODA)</p>
|
||||
<p class="text-muted-foreground">{dodaFormT(dodaLoc, 'list_page_subtitle')}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button class="h-9" onclick={handleCreateClick}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo DODA
|
||||
{dodaFormT(dodaLoc, 'list_btn_new')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -336,27 +377,31 @@
|
||||
<Card.Root class="flex min-h-0 flex-1 flex-col border bg-background">
|
||||
<Card.Header>
|
||||
<div class="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
|
||||
<Card.Title>Listado de DODA</Card.Title>
|
||||
<Card.Title>{dodaFormT(dodaLoc, 'list_card_title')}</Card.Title>
|
||||
<div class="grid gap-2 sm:grid-cols-2 xl:grid-cols-[220px_180px_170px_170px_auto] xl:items-center">
|
||||
<div class="relative">
|
||||
<Search class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Folio"
|
||||
placeholder={dodaFormT(dodaLoc, 'list_ph_folio')}
|
||||
bind:value={filters.integration_number}
|
||||
class="h-9 bg-card pl-9"
|
||||
/>
|
||||
</div>
|
||||
<Input placeholder="Patente" bind:value={filters.patent} class="h-9 bg-card" />
|
||||
<Input
|
||||
placeholder={dodaFormT(dodaLoc, 'list_ph_patent')}
|
||||
bind:value={filters.patent}
|
||||
class="h-9 bg-card"
|
||||
/>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={filters.status}
|
||||
onValueChange={(v) => (filters.status = v)}
|
||||
>
|
||||
<Select.Trigger class="h-9 w-full bg-card">
|
||||
{filters.status || 'Estatus'}
|
||||
{filters.status || dodaFormT(dodaLoc, 'list_filter_status_ph')}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="">Todos</Select.Item>
|
||||
<Select.Item value="">{dodaFormT(dodaLoc, 'list_filter_status_all')}</Select.Item>
|
||||
<Select.Item value="PENDIENTE">PENDIENTE</Select.Item>
|
||||
<Select.Item value="GENERADO">GENERADO</Select.Item>
|
||||
<Select.Item value="VALIDADO">VALIDADO</Select.Item>
|
||||
@@ -370,20 +415,20 @@
|
||||
>
|
||||
<Select.Trigger class="h-9 w-full bg-card">
|
||||
{filters.operation_type === 'I'
|
||||
? 'Importación'
|
||||
? dodaFormT(dodaLoc, 'list_filter_op_import')
|
||||
: filters.operation_type === 'E'
|
||||
? 'Exportación'
|
||||
: 'Operación'}
|
||||
? dodaFormT(dodaLoc, 'list_filter_op_export')
|
||||
: dodaFormT(dodaLoc, 'list_filter_op')}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="">Todas</Select.Item>
|
||||
<Select.Item value="I">I - Importación</Select.Item>
|
||||
<Select.Item value="E">E - Exportación</Select.Item>
|
||||
<Select.Item value="">{dodaFormT(dodaLoc, 'list_filter_op_all')}</Select.Item>
|
||||
<Select.Item value="I">{dodaFormT(dodaLoc, 'list_filter_i')}</Select.Item>
|
||||
<Select.Item value="E">{dodaFormT(dodaLoc, 'list_filter_e')}</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={clearFilters}>
|
||||
<RotateCcw class="mr-2 h-4 w-4" />
|
||||
Limpiar
|
||||
{dodaFormT(dodaLoc, 'list_btn_clear')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -391,8 +436,9 @@
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="rounded-md border bg-background">
|
||||
<DataTable
|
||||
locale={dodaLoc}
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
columns={tableColumns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
@@ -405,9 +451,13 @@
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
{dodaFormT(dodaLoc, 'list_showing', { a: allItems.length, b: totalItems })}
|
||||
<span class="ml-2">•</span>
|
||||
<span class="ml-2">Filtros activos: {Object.values(filters).filter((value) => value !== '').length}</span>
|
||||
<span class="ml-2"
|
||||
>{dodaFormT(dodaLoc, 'list_active_filters', {
|
||||
n: Object.values(filters).filter((value) => value !== '').length
|
||||
})}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="h-20"></div>
|
||||
@@ -426,7 +476,7 @@
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
{dodaFormT(dodaLoc, 'list_btn_edit')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -442,6 +492,21 @@
|
||||
{/if}
|
||||
{m['sidebar.doda_alta.action_delete']()}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
class="border border-primary/25 bg-primary/10 text-primary hover:bg-primary/15"
|
||||
onclick={handleExportPedimentos}
|
||||
disabled={selectedIds.length !== 1 || pedimentosExportLoading || !companyStore.activeCompany}
|
||||
title={m['sidebar.doda_alta.action_export_pedimentos']()}
|
||||
>
|
||||
{#if pedimentosExportLoading}
|
||||
<Loader2 size={16} class="mr-2 animate-spin" />
|
||||
{:else}
|
||||
<Table size={16} class="mr-2" />
|
||||
{/if}
|
||||
{m['sidebar.doda_alta.action_export_pedimentos']()}
|
||||
</Button>
|
||||
<Separator orientation="vertical" class="mx-1 h-8" />
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -454,7 +519,7 @@
|
||||
{:else}
|
||||
<Printer size={16} class="mr-2" />
|
||||
{/if}
|
||||
Imprimir
|
||||
{dodaFormT(dodaLoc, 'list_btn_print')}
|
||||
</Button>
|
||||
<Separator orientation="vertical" class="mx-1 h-8" />
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user