263 lines
8.0 KiB
Svelte
263 lines
8.0 KiB
Svelte
<script lang="ts">
|
|
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 { Switch } from '$lib/components/ui/switch';
|
|
import {
|
|
createHistoricalFraction,
|
|
updateHistoricalFraction,
|
|
type HistoricalFraction,
|
|
type HistoricalFractionCreate,
|
|
type HistoricalFractionUpdate
|
|
} from '$lib/api/dashboard/a76/general_catalogs/historical-tariff-fractions';
|
|
import { companyStore } from '$lib/stores/company.svelte';
|
|
import { toast } from 'svelte-sonner';
|
|
import { Loader2 } from 'lucide-svelte';
|
|
|
|
let {
|
|
open = $bindable(false),
|
|
fraction = null, // If null, create mode. If set, edit mode.
|
|
onSuccess
|
|
}: {
|
|
open: boolean;
|
|
fraction?: HistoricalFraction | null;
|
|
onSuccess: () => void;
|
|
} = $props();
|
|
|
|
let isLoading = $state(false);
|
|
|
|
// Form fields
|
|
let historicalFractionCode = $state('');
|
|
let unitOfMeasureCode = $state('');
|
|
let country = $state('');
|
|
let fractionType = $state('');
|
|
let sector = $state('');
|
|
let importTaxRate = $state('');
|
|
let exportTaxRate = $state('');
|
|
let publicationDate = $state('');
|
|
let endDate = $state('');
|
|
let isImmex = $state(false);
|
|
let normalTemporality = $state(false);
|
|
let servicesTemporality = $state(false);
|
|
let certifiedTemporality = $state(false);
|
|
let byLog = $state(false);
|
|
|
|
// Load data on open/fraction change
|
|
$effect(() => {
|
|
if (open) {
|
|
if (fraction) {
|
|
// Edit mode
|
|
historicalFractionCode = fraction.historical_fraction || '';
|
|
unitOfMeasureCode = fraction.unit_of_measure_code || '';
|
|
country = fraction.country || '';
|
|
fractionType = fraction.fraction_type || '';
|
|
sector = fraction.sector || '';
|
|
importTaxRate = fraction.import_tax_rate?.toString() || '';
|
|
exportTaxRate = fraction.export_tax_rate?.toString() || '';
|
|
publicationDate = fraction.publication_date ? fraction.publication_date.split('T')[0] : '';
|
|
endDate = fraction.end_date ? fraction.end_date.split('T')[0] : '';
|
|
isImmex = fraction.is_immex || false;
|
|
normalTemporality = fraction.normal_temporality || false;
|
|
servicesTemporality = fraction.services_temporality || false;
|
|
certifiedTemporality = fraction.certified_temporality || false;
|
|
byLog = fraction.by_log || false;
|
|
} else {
|
|
// Create mode - reset
|
|
historicalFractionCode = '';
|
|
unitOfMeasureCode = '';
|
|
country = '';
|
|
fractionType = '';
|
|
sector = '';
|
|
importTaxRate = '';
|
|
exportTaxRate = '';
|
|
publicationDate = '';
|
|
endDate = '';
|
|
isImmex = false;
|
|
normalTemporality = false;
|
|
servicesTemporality = false;
|
|
certifiedTemporality = false;
|
|
byLog = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
async function handleSubmit() {
|
|
const companyId = companyStore.activeCompany?.id;
|
|
if (!companyId) return;
|
|
|
|
// Validation
|
|
if (!historicalFractionCode) {
|
|
toast.error('La fracción es requerida');
|
|
return;
|
|
}
|
|
|
|
isLoading = true;
|
|
try {
|
|
const importRateNum = importTaxRate ? parseFloat(importTaxRate) : undefined;
|
|
const exportRateNum = exportTaxRate ? parseFloat(exportTaxRate) : undefined;
|
|
|
|
const baseData = {
|
|
historical_fraction: historicalFractionCode,
|
|
unit_of_measure_code: unitOfMeasureCode || null,
|
|
country: country || null,
|
|
fraction_type: fractionType || null,
|
|
sector: sector || null,
|
|
import_tax_rate: importRateNum,
|
|
export_tax_rate: exportRateNum,
|
|
publication_date: publicationDate || null,
|
|
end_date: endDate || null,
|
|
is_immex: isImmex,
|
|
normal_temporality: normalTemporality,
|
|
services_temporality: servicesTemporality,
|
|
certified_temporality: certifiedTemporality,
|
|
by_log: byLog
|
|
};
|
|
|
|
if (fraction) {
|
|
// Update
|
|
const updateData: HistoricalFractionUpdate = baseData;
|
|
await updateHistoricalFraction(companyId, fraction.id, updateData);
|
|
toast.success('Fracción actualizada correctamente');
|
|
} else {
|
|
// Create
|
|
const createData: HistoricalFractionCreate = {
|
|
...baseData,
|
|
historical_fraction: historicalFractionCode // Required in create
|
|
};
|
|
await createHistoricalFraction(companyId, createData);
|
|
toast.success('Fracción creada correctamente');
|
|
}
|
|
onSuccess();
|
|
open = false;
|
|
} catch (error) {
|
|
console.error('Error saving historical fraction:', error);
|
|
toast.error('Error al guardar la fracción');
|
|
} finally {
|
|
isLoading = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<Dialog.Root bind:open>
|
|
<Dialog.Content class="sm:max-w-[700px]">
|
|
<Dialog.Header>
|
|
<Dialog.Title>{fraction ? 'Editar' : 'Crear'} Fracción Histórica</Dialog.Title>
|
|
<Dialog.Description>
|
|
{fraction
|
|
? 'Modifica los detalles de la fracción seleccionada.'
|
|
: 'Ingresa los datos para la nueva fracción.'}
|
|
</Dialog.Description>
|
|
</Dialog.Header>
|
|
|
|
<div class="grid max-h-[70vh] gap-4 overflow-y-auto py-4 pr-2">
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="space-y-2">
|
|
<Label for="fraction">Fracción</Label>
|
|
<Input
|
|
id="fraction"
|
|
bind:value={historicalFractionCode}
|
|
placeholder="Ej. 01010101"
|
|
maxlength={8}
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="unit">Unidad de Medida</Label>
|
|
<Input id="unit" bind:value={unitOfMeasureCode} placeholder="Ej. 01" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="space-y-2">
|
|
<Label for="country">País</Label>
|
|
<Input id="country" bind:value={country} placeholder="Ej. MEX" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="type">Tipo</Label>
|
|
<Input id="type" bind:value={fractionType} placeholder="Ej. General" maxlength={7} />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="space-y-2">
|
|
<Label for="sector">Sector</Label>
|
|
<Input id="sector" bind:value={sector} placeholder="Sectores..." maxlength={5} />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="by-log">Por Bitácora</Label>
|
|
<div class="flex items-center space-x-2 pt-2">
|
|
<Switch id="by-log" bind:checked={byLog} />
|
|
<span class="text-sm text-muted-foreground">{byLog ? 'Sí' : 'No'}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="space-y-2">
|
|
<Label for="import-tax">Tasa IGI (%)</Label>
|
|
<Input
|
|
id="import-tax"
|
|
type="number"
|
|
step="0.01"
|
|
bind:value={importTaxRate}
|
|
placeholder="0.00"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="export-tax">Tasa IGE (%)</Label>
|
|
<Input
|
|
id="export-tax"
|
|
type="number"
|
|
step="0.01"
|
|
bind:value={exportTaxRate}
|
|
placeholder="0.00"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="space-y-2">
|
|
<Label for="pub-date">Fecha Publicación</Label>
|
|
<Input id="pub-date" type="date" bind:value={publicationDate} />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="end-date">Fecha Fin</Label>
|
|
<Input id="end-date" type="date" bind:value={endDate} />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4 border-t pt-4">
|
|
<div class="flex items-center justify-between space-x-2">
|
|
<Label for="is-immex">IMMEX</Label>
|
|
<Switch id="is-immex" bind:checked={isImmex} />
|
|
</div>
|
|
<div class="flex items-center justify-between space-x-2">
|
|
<Label for="normal-temp">Temp. Normal</Label>
|
|
<Switch id="normal-temp" bind:checked={normalTemporality} />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="flex items-center justify-between space-x-2">
|
|
<Label for="services-temp">Temp. Servicios</Label>
|
|
<Switch id="services-temp" bind:checked={servicesTemporality} />
|
|
</div>
|
|
<div class="flex items-center justify-between space-x-2">
|
|
<Label for="certified-temp">Temp. Certificada</Label>
|
|
<Switch id="certified-temp" bind:checked={certifiedTemporality} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Dialog.Footer>
|
|
<Button variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
|
<Button onclick={handleSubmit} disabled={isLoading}>
|
|
{#if isLoading}
|
|
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
|
{/if}
|
|
Guardar
|
|
</Button>
|
|
</Dialog.Footer>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|