feature/catalogos-generales-permisos
This commit is contained in:
@@ -70,12 +70,14 @@
|
||||
let {
|
||||
dodaIdParam,
|
||||
defaultLastUser: defaultLastUserProp = '',
|
||||
canMutate = true,
|
||||
onClose,
|
||||
onCreatedNavigateTo
|
||||
}: {
|
||||
/** 'new' o id numérico en string; desde ?doda_id= de la URL */
|
||||
dodaIdParam: string;
|
||||
defaultLastUser?: string;
|
||||
canMutate?: boolean;
|
||||
onClose: () => void;
|
||||
/** Tras crear DODA, actualiza ?doda_id= al id creado (sin desmontar la lista) */
|
||||
onCreatedNavigateTo: (dodaId: number) => void;
|
||||
@@ -170,13 +172,22 @@
|
||||
dodaFormT(dodaLoc, 'shortcuts_scope'),
|
||||
obtenerAtajosFormularioDodaPagina({
|
||||
cambiarPestana: (pestana) => (activeTab = pestana),
|
||||
manejarGuardar: handleSubmit,
|
||||
manejarGuardar: () => {
|
||||
if (!canMutate) return;
|
||||
handleSubmit();
|
||||
},
|
||||
manejarCerrar: () => {
|
||||
if (browser) onClose();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
function ensureCanMutate(): boolean {
|
||||
if (canMutate) return true;
|
||||
error = 'Permission denied: cat_doda.create/edit';
|
||||
return false;
|
||||
}
|
||||
|
||||
function getEmptyForm(): DodaCreate & {
|
||||
pedimentos_detail?: any[];
|
||||
containers?: any[];
|
||||
@@ -400,6 +411,7 @@
|
||||
}
|
||||
|
||||
async function handleAddSeal() {
|
||||
if (!ensureCanMutate()) return;
|
||||
error = null;
|
||||
if (!isEdit || !id) {
|
||||
error = t('seal_save_first');
|
||||
@@ -457,6 +469,7 @@
|
||||
}
|
||||
|
||||
async function handleAddSealFromContainerModal() {
|
||||
if (!ensureCanMutate()) return;
|
||||
const trimmed = newSealValue.trim();
|
||||
if (!trimmed) {
|
||||
error = t('seal_empty');
|
||||
@@ -493,6 +506,7 @@
|
||||
}
|
||||
|
||||
function handleAmericanPedimentoModalConfirm() {
|
||||
if (!ensureCanMutate()) return;
|
||||
error = null;
|
||||
const tipo = americanModalType.trim();
|
||||
const valor = americanModalValue.trim();
|
||||
@@ -539,6 +553,7 @@
|
||||
}
|
||||
|
||||
async function handleDeleteSeal(sealLine: number) {
|
||||
if (!ensureCanMutate()) return;
|
||||
error = null;
|
||||
if (!isEdit || !id) return;
|
||||
if (selectedContainerIndex == null) return;
|
||||
@@ -563,6 +578,7 @@
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!ensureCanMutate()) return;
|
||||
submitAttempted = true;
|
||||
error = null;
|
||||
warning = null;
|
||||
@@ -1621,7 +1637,7 @@
|
||||
<Button
|
||||
size="sm"
|
||||
onclick={handleSubmit}
|
||||
disabled={loading}
|
||||
disabled={loading || !canMutate}
|
||||
class="h-8 min-w-[7rem] px-4 text-sm font-semibold"
|
||||
>
|
||||
{#if loading}
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
canMutate = true,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Location | null;
|
||||
canMutate?: boolean;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
@@ -59,6 +61,10 @@
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!canMutate) {
|
||||
error = 'Permission denied';
|
||||
return;
|
||||
}
|
||||
loading = true;
|
||||
error = null;
|
||||
try {
|
||||
@@ -134,7 +140,7 @@
|
||||
id="clave_localizacion"
|
||||
bind:value={formData.clave_localizacion}
|
||||
maxlength={20}
|
||||
disabled={loading || isEdit}
|
||||
disabled={loading || isEdit || !canMutate}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -147,7 +153,7 @@
|
||||
id="localizacion"
|
||||
bind:value={formData.localizacion}
|
||||
maxlength={200}
|
||||
disabled={loading}
|
||||
disabled={loading || !canMutate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,7 +164,7 @@
|
||||
<select
|
||||
id="system"
|
||||
bind:value={formData.system}
|
||||
disabled={loading}
|
||||
disabled={loading || !canMutate}
|
||||
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
>
|
||||
{#each systemOptions as opt}
|
||||
@@ -173,7 +179,7 @@
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
<Button type="submit" disabled={loading || !canMutate}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { userHasSectorsCatalogAction } from '$lib/permissions/catalogs/sectors-catalog-permissions';
|
||||
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
@@ -10,7 +12,6 @@
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
|
||||
import { currentUser, userHasPermission } from '$lib/auth';
|
||||
|
||||
let { title = 'Sectores' }: { title?: string } = $props();
|
||||
|
||||
let sectors = $state<Sector[]>([]);
|
||||
@@ -19,10 +20,10 @@
|
||||
let status = $state<number>(200);
|
||||
|
||||
// Permisos
|
||||
const canView = $derived(userHasPermission($currentUser, 'cat_sectors.view') || userHasPermission($currentUser, 'frac_sectors.view'));
|
||||
const canCreate = $derived(userHasPermission($currentUser, 'cat_sectors.create'));
|
||||
const canEdit = $derived(userHasPermission($currentUser, 'cat_sectors.edit'));
|
||||
const canDelete = $derived(userHasPermission($currentUser, 'cat_sectors.delete'));
|
||||
const canView = $derived(userHasPermission($currentUser, 'ref_sectors.view'));
|
||||
const canCreate = $derived(userHasSectorsCatalogAction($currentUser, 'create'));
|
||||
const canEdit = $derived(userHasSectorsCatalogAction($currentUser, 'edit'));
|
||||
const canDelete = $derived(userHasSectorsCatalogAction($currentUser, 'delete'));
|
||||
const isError = $derived(!canView || status >= 400 || error);
|
||||
|
||||
let searchTerm = $state('');
|
||||
|
||||
@@ -8,7 +8,11 @@ const SYSTEM_LABELS: Record<string, string> = {
|
||||
inventory: 'Inventario'
|
||||
};
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Location>[] {
|
||||
export function createColumns(
|
||||
onSuccess?: () => void,
|
||||
options: { canEdit?: boolean; canDelete?: boolean } = {}
|
||||
): ColumnDef<Location>[] {
|
||||
const { canEdit = true, canDelete = true } = options;
|
||||
return [
|
||||
{
|
||||
accessorKey: 'clave_localizacion',
|
||||
@@ -30,7 +34,9 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Location>[] {
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit,
|
||||
canDelete
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
@@ -9,10 +9,14 @@
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
onSuccess,
|
||||
canEdit = true,
|
||||
canDelete = true
|
||||
}: {
|
||||
item: Location;
|
||||
onSuccess?: () => void;
|
||||
canEdit?: boolean;
|
||||
canDelete?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -21,6 +25,7 @@
|
||||
let selectedItem = $state<Location | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!canDelete) return;
|
||||
if (!confirm('¿Está seguro de que desea eliminar esta ubicación?')) {
|
||||
return;
|
||||
}
|
||||
@@ -49,6 +54,7 @@
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
if (!canEdit) return;
|
||||
selectedItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
@@ -74,12 +80,12 @@
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<DropdownMenu.Item onclick={handleEdit} disabled={!canEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading || !canDelete}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
@@ -93,5 +99,6 @@
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
canMutate={canEdit}
|
||||
onSuccess={handleDialogSuccess}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { userHasLocationsCatalogAction } from '$lib/permissions/catalogs/locations-catalog-permissions';
|
||||
|
||||
import { createColumns } from '$lib/components/dashboard/locations/columns';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/locations/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/locations/data-table.svelte';
|
||||
@@ -10,7 +12,8 @@
|
||||
type Location,
|
||||
type LocationSystem
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/locations';
|
||||
|
||||
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
|
||||
import { currentUser } from '$lib/auth';
|
||||
let {
|
||||
companyId,
|
||||
system: initialSystem = undefined,
|
||||
@@ -32,14 +35,27 @@
|
||||
let pageSize = $state(50);
|
||||
let pages = $state(0);
|
||||
let loading = $state(true);
|
||||
let status = $state(200);
|
||||
let dialogOpen = $state(false);
|
||||
let searchClave = $state('');
|
||||
let searchLocalizacion = $state('');
|
||||
let filterSystem = $state<LocationSystem | ''>(initialSystem ?? '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const canView = $derived(userHasLocationsCatalogAction($currentUser, 'view'));
|
||||
const canCreate = $derived(userHasLocationsCatalogAction($currentUser, 'create'));
|
||||
const canEdit = $derived(userHasLocationsCatalogAction($currentUser, 'edit'));
|
||||
const canDelete = $derived(userHasLocationsCatalogAction($currentUser, 'delete'));
|
||||
|
||||
async function load() {
|
||||
if (!companyId) return;
|
||||
if (!canView) {
|
||||
loading = false;
|
||||
items = [];
|
||||
total = 0;
|
||||
pages = 0;
|
||||
status = 403;
|
||||
return;
|
||||
}
|
||||
loading = true;
|
||||
try {
|
||||
const filters: Record<string, string | number> = {
|
||||
@@ -52,6 +68,7 @@
|
||||
const res = await getLocations(companyId, filters);
|
||||
items = res.items ?? [];
|
||||
total = res.total ?? 0;
|
||||
status = 200;
|
||||
page = res.page ?? 1;
|
||||
pageSize = res.page_size ?? 50;
|
||||
pages = pageSize > 0 ? Math.ceil(total / pageSize) : 0;
|
||||
@@ -60,6 +77,7 @@
|
||||
items = [];
|
||||
total = 0;
|
||||
pages = 0;
|
||||
status = 500;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -80,11 +98,15 @@
|
||||
$effect(() => {
|
||||
void companyId;
|
||||
void page;
|
||||
void canView;
|
||||
load();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if !canView}
|
||||
<ErrorState status={403} error="Permission denied: cat_locations.view" />
|
||||
{:else}
|
||||
{#if !compact}
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -93,14 +115,14 @@
|
||||
Clave y localización por sistema (FA / Inventario)
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => (dialogOpen = true)} size="sm">
|
||||
<Button onclick={() => (dialogOpen = true)} size="sm" disabled={!canCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center justify-end">
|
||||
<Button onclick={() => (dialogOpen = true)} size="sm" variant="outline">
|
||||
<Button onclick={() => (dialogOpen = true)} size="sm" variant="outline" disabled={!canCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva ubicación
|
||||
</Button>
|
||||
@@ -145,12 +167,13 @@
|
||||
{:else}
|
||||
<DataTable
|
||||
data={items}
|
||||
columns={createColumns(handleSuccess)}
|
||||
columns={createColumns(handleSuccess, { canEdit, canDelete })}
|
||||
pageCount={pages}
|
||||
totalItems={total}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<CreateDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
|
||||
<CreateDialog bind:open={dialogOpen} onSuccess={handleSuccess} canMutate={canCreate} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -198,26 +198,32 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.general_catalogs.company_information"](),
|
||||
url: "/dashboard/general_catalogs/company_information",
|
||||
permission: "cat_company.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.packages"](),
|
||||
url: "/dashboard/general_catalogs/packages",
|
||||
permission: "cat_packages.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.concepts"](),
|
||||
url: "/dashboard/general_catalogs/concepts",
|
||||
permission: "cat_concepts.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.customs_broker_concepts"](),
|
||||
url: "/dashboard/general_catalogs/customs_broker_concepts",
|
||||
permission: "cat_broker_concepts.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.classification"](),
|
||||
url: "/dashboard/general_catalogs/classification_concepts",
|
||||
permission: "cat_classification.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.identifiers"](),
|
||||
url: "/dashboard/general_catalogs/identifiers",
|
||||
permission: "cat_identifiers.view",
|
||||
},
|
||||
// -------------------------------------
|
||||
{
|
||||
@@ -228,14 +234,17 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.general_catalogs.inpc"](),
|
||||
url: "/dashboard/general_catalogs/inpc",
|
||||
permission: "cat_inpc.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.fixed_legends"](),
|
||||
url: "/dashboard/general_catalogs/legends",
|
||||
permission: "cat_legends.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.seals"](),
|
||||
url: "/dashboard/general_catalogs/seal",
|
||||
permission: "cat_seals.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.valuation_methods"](),
|
||||
@@ -250,26 +259,37 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.general_catalogs.ports"](),
|
||||
url: "/dashboard/general_catalogs/ports",
|
||||
permission: "cat_ports.view",
|
||||
},
|
||||
{
|
||||
title: "Ubicaciones",
|
||||
url: "/dashboard/general_catalogs/locations",
|
||||
permission: "cat_locations.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.unit_measures"](),
|
||||
url: "/dashboard/general_catalogs/units_of_measure/general",
|
||||
permission: "cat_um_general.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.um_customs_mex"](),
|
||||
url: "/dashboard/general_catalogs/units_of_measure/customs",
|
||||
permission: "cat_um_customs.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.um_customs_ame"](),
|
||||
url: "/dashboard/general_catalogs/units_of_measure/american",
|
||||
permission: "cat_um_american.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.um_ace"](),
|
||||
url: "/dashboard/general_catalogs/units_of_measure/ace",
|
||||
permission: "cat_um_ace.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.um_oma"](),
|
||||
url: "/dashboard/general_catalogs/units_of_measure/oma",
|
||||
permission: "cat_um_oma.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.conversions"](),
|
||||
@@ -304,10 +324,12 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.general_catalogs.electronic_signatures"](),
|
||||
url: "/dashboard/general_catalogs/signatures",
|
||||
permission: "cat_signatures.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.billing_errors"](),
|
||||
url: "/dashboard/general_catalogs/error_catalogs",
|
||||
permission: "cat_errors.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.customs_warehouses"](),
|
||||
@@ -317,14 +339,12 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.general_catalogs.prevalidators"](),
|
||||
url: "/dashboard/general_catalogs/prevalidators",
|
||||
permission: "cat_prevalidators.view",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.electronic_notices"](),
|
||||
url: "/dashboard/general_catalogs/electronic_notices",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.crossing_notice"](),
|
||||
url: "#",
|
||||
permission: "cat_notices.view",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -361,7 +381,7 @@ export function getSidebarData(): SidebarData {
|
||||
{
|
||||
title: m["sidebar.fractions.sectors"](),
|
||||
url: "/dashboard/general_catalogs/sectors",
|
||||
permission: 'frac_sectors.view',
|
||||
permission: 'ref_sectors.view',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user