Merge branch 'development' of https://git.aduanasoft.com/ADUANASOFT/anexo76 into fix/clientes-provedores-traduccion
This commit is contained in:
@@ -886,13 +886,21 @@ export const api = {
|
||||
|
||||
// CSV import for Clientes y Proveedores (flujo propio en clients_and_providers/imports)
|
||||
clientProviderImports: {
|
||||
upload: (file: File, companyId: number, uploadOptions?: CsvFormDataUploadOptions) => {
|
||||
upload: (
|
||||
file: File,
|
||||
companyId: number,
|
||||
options?: { actualizar?: boolean; onUploadProgress?: CsvFormDataUploadOptions['onUploadProgress'] }
|
||||
) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const params = new URLSearchParams({ company_id: String(companyId) });
|
||||
if (options?.actualizar !== undefined) params.set('actualizar', String(options.actualizar));
|
||||
const uploadOpts =
|
||||
options?.onUploadProgress != null ? { onUploadProgress: options.onUploadProgress } : undefined;
|
||||
return fetchApiFormDataPost(
|
||||
`/v1/a76/clients-providers/imports/upload?company_id=${companyId}`,
|
||||
`/v1/a76/clients-providers/imports/upload?${params.toString()}`,
|
||||
formData,
|
||||
uploadOptions
|
||||
uploadOpts
|
||||
);
|
||||
},
|
||||
status: (jobId: string) => api.get(`/v1/a76/clients-providers/imports/${jobId}/status`),
|
||||
|
||||
@@ -67,7 +67,19 @@ export interface CreateClientProviderData extends Omit<ClientProvider, 'id' | 'a
|
||||
export interface UpdateClientProviderData extends Partial<CreateClientProviderData> {}
|
||||
|
||||
export const clientsProvidersApi = {
|
||||
list: (companyId: number, page = 1, pageSize = 50, filters?: Record<string, any>) => {
|
||||
list: (
|
||||
companyId: number,
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters?: Partial<{
|
||||
name: string;
|
||||
rfc: string;
|
||||
/** Clave corta; el backend filtra por coincidencia exacta sin distinguir mayúsculas/minúsculas */
|
||||
short_name: string;
|
||||
type: string;
|
||||
active: string | boolean;
|
||||
}>
|
||||
) => {
|
||||
const params = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
|
||||
@@ -117,13 +117,13 @@
|
||||
}
|
||||
|
||||
async function handleDownloadScanErrorsCsv() {
|
||||
const errors = scanResults?.errors;
|
||||
if (!Array.isArray(errors) || errors.length === 0) return;
|
||||
|
||||
const jobId = scanResults?.job_id;
|
||||
const errorCount = Number(scanResults?.error_count) || 0;
|
||||
const errors = scanResults?.errors;
|
||||
|
||||
// Try backend download (sin límite). If it fails, fallback to preview CSV.
|
||||
if (jobId) {
|
||||
// Descarga completa desde el backend (JSONL → CSV) siempre que el scan reportó errores y hay job_id.
|
||||
// No depende del preview acotado en la respuesta JSON.
|
||||
if (jobId && errorCount > 0) {
|
||||
try {
|
||||
let blob: Blob;
|
||||
switch (scanErrorsDownloadType) {
|
||||
@@ -178,17 +178,28 @@
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
return;
|
||||
} catch (e) {
|
||||
// fallback below
|
||||
} catch {
|
||||
// Si falla la API, intentar CSV desde preview si existe.
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(errors) || errors.length === 0) return;
|
||||
|
||||
const filename = `errores_${Date.now()}.csv`;
|
||||
const rows = errors.map((e: Record<string, unknown>) => ({
|
||||
line: e.line,
|
||||
col: e.col,
|
||||
msg: e.msg,
|
||||
solution: e.solution,
|
||||
advertencia: e.warning ? 'si' : 'no',
|
||||
codigo: e.code ?? '',
|
||||
not_found_reason: e.not_found_reason ?? ''
|
||||
}));
|
||||
downloadCsv(
|
||||
filename,
|
||||
['linea', 'columna', 'mensaje', 'solucion'],
|
||||
errors,
|
||||
['line', 'col', 'msg', 'solution']
|
||||
['linea', 'columna', 'mensaje', 'solucion', 'advertencia', 'codigo', 'not_found_reason'],
|
||||
rows,
|
||||
['line', 'col', 'msg', 'solution', 'advertencia', 'codigo', 'not_found_reason']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -334,18 +345,19 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{#if scanResults.errors && scanResults.errors.length > 0}
|
||||
<div class="border rounded-lg overflow-hidden shadow-sm">
|
||||
<div class="bg-muted/50 px-4 py-2 border-b flex justify-between items-center">
|
||||
<h5 class="text-xs font-bold text-foreground uppercase tracking-wide">
|
||||
{csvMsg('modal.errors_heading')}
|
||||
</h5>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="text-[10px] bg-secondary text-secondary-foreground px-2 py-0.5 rounded-full border"
|
||||
>
|
||||
{csvFmt('modal.errors_badge', { shown: scanErrorsShown, total: scanErrorsTotal })}
|
||||
</span>
|
||||
{#if scanResults.errors && scanResults.errors.length > 0}
|
||||
<span
|
||||
class="text-[10px] bg-secondary text-secondary-foreground px-2 py-0.5 rounded-full border"
|
||||
>
|
||||
{csvFmt('modal.errors_badge', { shown: scanErrorsShown, total: scanErrorsTotal })}
|
||||
</span>
|
||||
{/if}
|
||||
<Button
|
||||
variant="ghost"
|
||||
onclick={handleDownloadScanErrorsCsv}
|
||||
@@ -356,41 +368,42 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-h-60 overflow-y-auto bg-card relative">
|
||||
<table class="w-full text-xs text-left">
|
||||
<thead
|
||||
class="text-muted-foreground font-medium bg-muted/30 sticky top-0 z-10 shadow-sm backdrop-blur-sm"
|
||||
>
|
||||
<tr>
|
||||
<th class="px-4 py-2 w-16">{csvMsg('modal.th_line')}</th>
|
||||
<th class="px-4 py-2 w-40">{csvMsg('modal.th_column')}</th>
|
||||
<th class="px-4 py-2">{csvMsg('modal.th_message')}</th>
|
||||
<th class="px-4 py-2 min-w-[240px]">{csvMsg('modal.th_solution')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
{#each scanResults.errors as err}
|
||||
<tr class="hover:bg-muted/30 transition-colors {err.warning ? 'opacity-70' : ''}">
|
||||
<td class="px-4 py-2 font-mono text-muted-foreground">{err.line}</td>
|
||||
<td class="px-4 py-2 font-mono font-medium text-foreground">{err.col || '-'}</td>
|
||||
<td class="px-4 py-2 {err.warning ? 'text-amber-600 dark:text-amber-400' : 'text-destructive'}">{err.msg || '-'}</td>
|
||||
<td class="px-4 py-2 text-muted-foreground whitespace-pre-wrap">{err.solution || '-'}</td>
|
||||
{#if scanResults.errors && scanResults.errors.length > 0}
|
||||
<div class="max-h-60 overflow-y-auto bg-card relative">
|
||||
<table class="w-full text-xs text-left">
|
||||
<thead
|
||||
class="text-muted-foreground font-medium bg-muted/30 sticky top-0 z-10 shadow-sm backdrop-blur-sm"
|
||||
>
|
||||
<tr>
|
||||
<th class="px-4 py-2 w-16">{csvMsg('modal.th_line')}</th>
|
||||
<th class="px-4 py-2 w-40">{csvMsg('modal.th_column')}</th>
|
||||
<th class="px-4 py-2">{csvMsg('modal.th_message')}</th>
|
||||
<th class="px-4 py-2 min-w-[240px]">{csvMsg('modal.th_solution')}</th>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{#if scanErrorsTruncated}
|
||||
<p class="text-xs text-muted-foreground px-4 py-2">
|
||||
{csvMsg('modal.errors_truncated')}
|
||||
</thead>
|
||||
<tbody class="divide-y">
|
||||
{#each scanResults.errors as err}
|
||||
<tr class="hover:bg-muted/30 transition-colors {err.warning ? 'opacity-70' : ''}">
|
||||
<td class="px-4 py-2 font-mono text-muted-foreground">{err.line}</td>
|
||||
<td class="px-4 py-2 font-mono font-medium text-foreground">{err.col || '-'}</td>
|
||||
<td class="px-4 py-2 {err.warning ? 'text-amber-600 dark:text-amber-400' : 'text-destructive'}">{err.msg || '-'}</td>
|
||||
<td class="px-4 py-2 text-muted-foreground whitespace-pre-wrap">{err.solution || '-'}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{#if scanErrorsTruncated}
|
||||
<p class="text-xs text-muted-foreground px-4 py-2">
|
||||
{csvMsg('modal.errors_truncated')}
|
||||
</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<p class="text-sm text-muted-foreground px-4 py-3">
|
||||
{csvFmt('modal.errors_missing_detail', { count: scanResults.error_count })}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-sm text-muted-foreground mt-1">
|
||||
{csvFmt('modal.errors_missing_detail', { count: scanResults.error_count })}
|
||||
</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="rounded-md bg-primary/5 border border-primary/10 p-4 flex items-start gap-3">
|
||||
<CheckCircle2 class="w-5 h-5 text-primary mt-0.5 shrink-0" />
|
||||
|
||||
@@ -649,7 +649,10 @@
|
||||
|
||||
if (useClientProviderImport) {
|
||||
try {
|
||||
const catalogosSettings = allSettings['catalogos'] || {};
|
||||
const actualizar = catalogosSettings['mode'] === 'update';
|
||||
const res = await api.clientProviderImports.upload(file, companyId, {
|
||||
actualizar,
|
||||
onUploadProgress: onCsvFileUploadProgress
|
||||
});
|
||||
if (res.data?.job_id) {
|
||||
|
||||
Reference in New Issue
Block a user