ADD: Mejora en la gestión de direcciones y conceptos, incluyendo validaciones y traducciones
This commit is contained in:
parent
740f7f5b78
commit
7f82f57588
@ -11,7 +11,5 @@ COPY . .
|
|||||||
COPY install.sh /usr/local/bin/install.sh
|
COPY install.sh /usr/local/bin/install.sh
|
||||||
RUN chmod +x /usr/local/bin/install.sh
|
RUN chmod +x /usr/local/bin/install.sh
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/install.sh"]
|
ENTRYPOINT ["/usr/local/bin/install.sh"]
|
||||||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||||
@ -10,15 +10,15 @@ const addressName = ref("");
|
|||||||
/** Métodos */
|
/** Métodos */
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (!addressName.value.trim()) {
|
if (!addressName.value.trim()) {
|
||||||
alert("Por favor ingresa un nombre de dirección");
|
window.Notify.warning(window.Lang('address.validation.required'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emitir evento al padre con la nueva dirección
|
// Emitir evento al padre con la nueva dirección
|
||||||
emit('address-created', {
|
emit('address-created', {
|
||||||
name: addressName.value
|
name: addressName.value
|
||||||
});
|
});
|
||||||
|
|
||||||
// Limpiar el formulario
|
// Limpiar el formulario
|
||||||
addressName.value = "";
|
addressName.value = "";
|
||||||
};
|
};
|
||||||
@ -27,7 +27,7 @@ const handleSubmit = () => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg mb-10">
|
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg mb-10">
|
||||||
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||||
Crear nueva dirección
|
{{ $t('address.create.title') }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<form @submit.prevent="handleSubmit">
|
<form @submit.prevent="handleSubmit">
|
||||||
@ -36,12 +36,13 @@ const handleSubmit = () => {
|
|||||||
for="addressName"
|
for="addressName"
|
||||||
class="block text-sm text-gray-600 font-medium mb-2"
|
class="block text-sm text-gray-600 font-medium mb-2"
|
||||||
>
|
>
|
||||||
Nombre de la Dirección:
|
{{ $t('address.name') }}:
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="adddressName"
|
id="addressName"
|
||||||
v-model="addressName"
|
v-model="addressName"
|
||||||
|
:placeholder="$t('address.placeholder')"
|
||||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -50,7 +51,7 @@ const handleSubmit = () => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
class="w-1/4 bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium py-3.5 rounded-lg transition-colors"
|
class="w-1/4 bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium py-3.5 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
Guardar
|
{{ $t('save') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,136 +1,194 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { useForm } from "@Services/Api";
|
import { useForm, api, apiURL } from "@Services/Api";
|
||||||
|
|
||||||
import Input from "@Holos/Form/Input.vue";
|
import Input from "@Holos/Form/Input.vue";
|
||||||
import Textarea from "@Holos/Form/Textarea.vue";
|
import Textarea from "@Holos/Form/Textarea.vue";
|
||||||
import Selectable from "@Holos/Form/Selectable.vue";
|
import Selectable from "@Holos/Form/Selectable.vue";
|
||||||
|
|
||||||
|
/** Eventos */
|
||||||
|
const emit = defineEmits(['concept-created']);
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
address: [],
|
direction_id: null,
|
||||||
shortName: "",
|
unit_id: null,
|
||||||
|
short_name: "",
|
||||||
name: "",
|
name: "",
|
||||||
legal: "",
|
legal_instrument: "",
|
||||||
article: "",
|
article: "",
|
||||||
description: "",
|
content: "",
|
||||||
minimumAmount: "",
|
min_amount_uma: "",
|
||||||
maximumAmount: "",
|
max_amount_uma: "",
|
||||||
sizeUnit: [],
|
min_amount_peso: "",
|
||||||
|
max_amount_peso: "",
|
||||||
|
unit_cost_uma: "",
|
||||||
|
unit_cost_peso: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const addresses = ref([]);
|
const addresses = ref([]);
|
||||||
const sizeUnit = ref([]);
|
const units = ref([]);
|
||||||
|
|
||||||
|
/** Cargar cosas */
|
||||||
|
onMounted(async () => {
|
||||||
|
api.get(apiURL('directions'), {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
addresses.value = data.models?.data || data.data || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.get(apiURL('units'), {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
units.value = data.models?.data || data.data || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const transformedData = {
|
||||||
|
direction_id: typeof modelModal.value.direction === 'object' ? modelModal.value.direction.id : Number(modelModal.value.direction_id),
|
||||||
|
unit_id: modelModal.value.unit ? (typeof modelModal.value.unit === 'object' ? modelModal.value.unit.id : Number(modelModal.value.unit)) : null,
|
||||||
|
short_name: modelModal.value.short_name,
|
||||||
|
name: modelModal.value.name,
|
||||||
|
legal_instrument: modelModal.value.legal_instrument,
|
||||||
|
article: modelModal.value.article,
|
||||||
|
content: modelModal.value.content,
|
||||||
|
min_amount_uma: modelModal.value.min_amount_uma ? Number(modelModal.value.min_amount_uma) : null,
|
||||||
|
max_amount_uma: modelModal.value.max_amount_uma ? Number(modelModal.value.max_amount_uma) : null,
|
||||||
|
min_amount_peso: modelModal.value.min_amount_peso ? Number(modelModal.value.min_amount_peso) : null,
|
||||||
|
max_amount_peso: modelModal.value.max_amount_peso ? Number(modelModal.value.max_amount_peso) : null,
|
||||||
|
unit_cost_uma: form.unit_cost_uma ? Number(form.unit_cost_uma) : null,
|
||||||
|
unit_cost_peso: form.unit_cost_peso ? Number(form.unit_cost_peso) : null,
|
||||||
|
};
|
||||||
|
|
||||||
|
emit('concept-created', transformedData);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg mb-10">
|
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg mb-10">
|
||||||
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||||
Crear nuevo concepto de cobro
|
{{ $t('concept.create.title') }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<form>
|
<form @submit.prevent="handleSubmit">
|
||||||
<div class="mb-5 grid grid-cols-2 gap-4">
|
<div class="mb-5 grid grid-cols-2 gap-4">
|
||||||
<Selectable
|
<Selectable
|
||||||
v-model="form.address"
|
v-model="form.direction"
|
||||||
label="description"
|
label="name"
|
||||||
title="Direcciones"
|
value="id"
|
||||||
|
:title="$t('concept.direction')"
|
||||||
:options="addresses"
|
:options="addresses"
|
||||||
multiple
|
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model="form.shortName"
|
v-model="form.short_name"
|
||||||
class="col-span-2"
|
class="col-span-2"
|
||||||
id="Nombre corto"
|
:id="$t('concept.shortName')"
|
||||||
type="text"
|
type="text"
|
||||||
:onError="form.errors.shortName"
|
:onError="form.errors.short_name"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
v-model="form.name"
|
v-model="form.name"
|
||||||
class="col-span-2"
|
class="col-span-2"
|
||||||
id="Nombre"
|
:id="$t('concept.name')"
|
||||||
type="text"
|
type="text"
|
||||||
:onError="form.errors.name"
|
:onError="form.errors.name"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<div class="mb-5 grid grid-cols-2 gap-4 py-2">
|
<div class="mb-5 grid grid-cols-2 gap-4 py-2">
|
||||||
<Input
|
<Input
|
||||||
v-model="form.legal"
|
v-model="form.legal_instrument"
|
||||||
class="col-span-2"
|
class="col-span-2"
|
||||||
id="Instrumento Legal"
|
:id="$t('concept.legal')"
|
||||||
type="text"
|
type="text"
|
||||||
:onError="form.errors.legal"
|
:onError="form.errors.legal_instrument"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model="form.article"
|
v-model="form.article"
|
||||||
class="col-span-2"
|
class="col-span-2"
|
||||||
id="Articulado"
|
:id="$t('concept.article')"
|
||||||
type="text"
|
type="text"
|
||||||
:onError="form.errors.article"
|
:onError="form.errors.article"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Textarea
|
<Textarea
|
||||||
v-model="form.description"
|
v-model="form.content"
|
||||||
class="col-span-2"
|
class="col-span-2"
|
||||||
id="Descripción"
|
:id="$t('concept.description')"
|
||||||
:onError="form.errors.description"
|
:onError="form.errors.content"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<div class="mb-5 grid grid-cols-2 gap-4 py-2">
|
<div class="mb-5 grid grid-cols-2 gap-4 py-2">
|
||||||
<Input
|
<Input
|
||||||
v-model="form.minimumAmount"
|
v-model="form.min_amount_uma"
|
||||||
class="col-span-2"
|
:id="$t('concept.minimumAmountUma')"
|
||||||
id="Monto mínimo (UMAs)"
|
type="number"
|
||||||
type="text"
|
step="0.01"
|
||||||
:onError="form.errors.minimumAmount"
|
:onError="form.errors.min_amount_uma"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model="form.maximumAmount"
|
v-model="form.max_amount_uma"
|
||||||
class="col-span-2"
|
:id="$t('concept.maximumAmountUma')"
|
||||||
id="Monto máximo (UMAs)"
|
type="number"
|
||||||
type="text"
|
step="0.01"
|
||||||
:onError="form.errors.maximumAmount"
|
:onError="form.errors.max_amount_uma"
|
||||||
required
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-5 grid grid-cols-2 gap-4 py-2">
|
||||||
|
<Input
|
||||||
|
v-model="form.min_amount_peso"
|
||||||
|
:id="$t('concept.minimumAmountPeso')"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
:onError="form.errors.min_amount_peso"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="form.max_amount_peso"
|
||||||
|
:id="$t('concept.maximumAmountPeso')"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
:onError="form.errors.max_amount_peso"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<hr class="my-4 border-gray-300" />
|
<hr class="my-4 border-gray-300" />
|
||||||
|
<h4 class="text-lg font-semibold mb-4 text-gray-700">
|
||||||
|
{{ $t('concept.tabulator') }}
|
||||||
|
</h4>
|
||||||
<div class="mb-5 grid grid-cols-3 gap-4 py-2">
|
<div class="mb-5 grid grid-cols-3 gap-4 py-2">
|
||||||
<Selectable
|
<Selectable
|
||||||
v-model="form.sizeUnit"
|
v-model="form.unit"
|
||||||
label="description"
|
label="name"
|
||||||
title="Unidad de medida"
|
value="id"
|
||||||
:options="sizeUnit"
|
:title="$t('concept.sizeUnit')"
|
||||||
multiple
|
:options="units"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model="form.costMin"
|
v-model="form.unit_cost_uma"
|
||||||
class="col-span-2"
|
:id="$t('concept.costUnitUma')"
|
||||||
id="Costo mínimo (UMAs)"
|
type="number"
|
||||||
type="text"
|
step="0.01"
|
||||||
:onError="form.errors.costMin"
|
:onError="form.errors.unit_cost_uma"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model="form.costMax"
|
v-model="form.unit_cost_peso"
|
||||||
class="col-span-2"
|
:id="$t('concept.costUnitPeso')"
|
||||||
id="Costo máximo (UMAs)"
|
type="number"
|
||||||
type="text"
|
step="0.01"
|
||||||
:onError="form.errors.costMax"
|
:onError="form.errors.unit_cost_peso"
|
||||||
required
|
/>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 p-7 flex justify-center">
|
<div class="mb-3 p-7 flex justify-center">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="w-1/4 bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium py-3.5 rounded-lg transition-colors"
|
:disabled="form.processing"
|
||||||
|
class="w-1/4 bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium py-3.5 rounded-lg transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Guardar
|
{{ form.processing ? 'Guardando...' : $t('save') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -82,8 +82,13 @@ export default {
|
|||||||
},
|
},
|
||||||
address: {
|
address: {
|
||||||
title: 'Direcciones',
|
title: 'Direcciones',
|
||||||
|
name: 'Nombre de la Dirección',
|
||||||
|
placeholder: 'Ingrese el nombre de la dirección',
|
||||||
|
validation: {
|
||||||
|
required: 'Por favor ingresa un nombre de dirección'
|
||||||
|
},
|
||||||
create: {
|
create: {
|
||||||
title: 'Crear dirección',
|
title: 'Crear nueva dirección',
|
||||||
description: 'Permite crear nuevas direcciones.',
|
description: 'Permite crear nuevas direcciones.',
|
||||||
onSuccess: 'Dirección creada exitosamente',
|
onSuccess: 'Dirección creada exitosamente',
|
||||||
onError: 'Error al crear la dirección'
|
onError: 'Error al crear la dirección'
|
||||||
@ -94,11 +99,55 @@ export default {
|
|||||||
onSuccess: 'Dirección actualizada exitosamente',
|
onSuccess: 'Dirección actualizada exitosamente',
|
||||||
onError: 'Error al actualizar la dirección'
|
onError: 'Error al actualizar la dirección'
|
||||||
},
|
},
|
||||||
|
list: {
|
||||||
|
title: 'Listado de Direcciones',
|
||||||
|
empty: 'No hay direcciones registradas'
|
||||||
|
},
|
||||||
deleted: 'Dirección eliminada',
|
deleted: 'Dirección eliminada',
|
||||||
permissions: {
|
permissions: {
|
||||||
title: 'Permisos de dirección'
|
title: 'Permisos de dirección'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
concept: {
|
||||||
|
title: 'Conceptos de Cobro',
|
||||||
|
direction: 'Dirección',
|
||||||
|
shortName: 'Nombre Corto',
|
||||||
|
name: 'Nombre',
|
||||||
|
legal: 'Instrumento Legal',
|
||||||
|
article: 'Articulado',
|
||||||
|
description: 'Contenido',
|
||||||
|
minimumAmountUma: 'Monto Mínimo (UMAs)',
|
||||||
|
maximumAmountUma: 'Monto Máximo (UMAs)',
|
||||||
|
minimumAmountPeso: 'Monto Mínimo (Pesos)',
|
||||||
|
maximumAmountPeso: 'Monto Máximo (Pesos)',
|
||||||
|
tabulator: 'Tabulador',
|
||||||
|
sizeUnit: 'Unidad de Medida',
|
||||||
|
costUnitUma: 'Costo Unitario (UMAs)',
|
||||||
|
costUnitPeso: 'Costo Unitario (Pesos)',
|
||||||
|
validation: {
|
||||||
|
required: 'Por favor complete todos los campos requeridos'
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
title: 'Nuevo Concepto de Cobro',
|
||||||
|
description: 'Permite crear nuevos conceptos de cobro.',
|
||||||
|
onSuccess: 'Concepto creado exitosamente',
|
||||||
|
onError: 'Error al crear el concepto'
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
title: 'Editar concepto',
|
||||||
|
description: 'Permite editar conceptos existentes.',
|
||||||
|
onSuccess: 'Concepto actualizado exitosamente',
|
||||||
|
onError: 'Error al actualizar el concepto'
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
title: 'Listado de Conceptos',
|
||||||
|
empty: 'No hay conceptos registrados'
|
||||||
|
},
|
||||||
|
deleted: 'Concepto eliminado',
|
||||||
|
permissions: {
|
||||||
|
title: 'Permisos de concepto'
|
||||||
|
}
|
||||||
|
},
|
||||||
membership: {
|
membership: {
|
||||||
title: 'Membresías',
|
title: 'Membresías',
|
||||||
create: {
|
create: {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { useSearcher, useForm } from "@Services/Api";
|
import { useSearcher, useForm } from "@Services/Api";
|
||||||
import { can, apiTo, viewTo, transl } from "./Module";
|
import { apiTo, transl } from "./Module";
|
||||||
|
|
||||||
import Table from "@Holos/Table.vue";
|
import Table from "@Holos/Table.vue";
|
||||||
import IconButton from "@Holos/Button/Icon.vue";
|
import IconButton from "@Holos/Button/Icon.vue";
|
||||||
@ -45,8 +45,12 @@ const handleAddressCreated = async (address) => {
|
|||||||
|
|
||||||
form.post(apiTo("store"), {
|
form.post(apiTo("store"), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
Notify.success(transl('create.onSuccess'));
|
||||||
searcher.refresh();
|
searcher.refresh();
|
||||||
},
|
},
|
||||||
|
onError: () => {
|
||||||
|
Notify.error(transl('create.onError'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,17 +59,21 @@ const handleAddressUpdated = async () => {
|
|||||||
name: modelModal.value.name,
|
name: modelModal.value.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
form.put(apiTo("update", { id: modelModal.value.id }), {
|
form.put(apiTo("update", { direction: modelModal.value.id }), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
Notify.success(transl('edit.onSuccess'));
|
||||||
searcher.refresh();
|
searcher.refresh();
|
||||||
Modal.switchEditModal();
|
Modal.switchEditModal();
|
||||||
},
|
},
|
||||||
|
onError: () => {
|
||||||
|
Notify.error(transl('edit.onError'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageHeader title="Creación de Dirección" />
|
<PageHeader :title="transl('title')" />
|
||||||
<AddressForm @address-created="handleAddressCreated" />
|
<AddressForm @address-created="handleAddressCreated" />
|
||||||
<div class="m-3">
|
<div class="m-3">
|
||||||
<Table
|
<Table
|
||||||
@ -102,7 +110,7 @@ const handleAddressUpdated = async () => {
|
|||||||
</template>
|
</template>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<td colspan="2" class="table-cell text-center py-4 text-gray-500">
|
<td colspan="2" class="table-cell text-center py-4 text-gray-500">
|
||||||
No hay direcciones registradas
|
{{ transl('list.empty') }}
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</Table>
|
||||||
@ -111,7 +119,7 @@ const handleAddressUpdated = async () => {
|
|||||||
subtitle=""
|
subtitle=""
|
||||||
:model="modelModal"
|
:model="modelModal"
|
||||||
:show="destroyModal"
|
:show="destroyModal"
|
||||||
:to="(address) => apiTo('destroy', { id: address.id })"
|
:to="(id) => apiTo('destroy', { direction: id })"
|
||||||
@close="Modal.switchDestroyModal"
|
@close="Modal.switchDestroyModal"
|
||||||
@update="
|
@update="
|
||||||
() => {
|
() => {
|
||||||
@ -125,7 +133,7 @@ const handleAddressUpdated = async () => {
|
|||||||
/>
|
/>
|
||||||
<Editview
|
<Editview
|
||||||
:show="editModal"
|
:show="editModal"
|
||||||
:title="$t('crud.edit')"
|
:title="transl('edit.title')"
|
||||||
@close="Modal.switchEditModal"
|
@close="Modal.switchEditModal"
|
||||||
@update="handleAddressUpdated"
|
@update="handleAddressUpdated"
|
||||||
>
|
>
|
||||||
@ -134,12 +142,13 @@ const handleAddressUpdated = async () => {
|
|||||||
for="editAddressName"
|
for="editAddressName"
|
||||||
class="block text-sm text-gray-600 font-medium mb-2"
|
class="block text-sm text-gray-600 font-medium mb-2"
|
||||||
>
|
>
|
||||||
Nombre de la Dirección:
|
{{ transl('name') }}:
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="editAddressName"
|
id="editAddressName"
|
||||||
v-model="modelModal.name"
|
v-model="modelModal.name"
|
||||||
|
:placeholder="transl('placeholder')"
|
||||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { RouterLink } from "vue-router";
|
import { useSearcher, useForm, api, apiURL } from "@Services/Api";
|
||||||
import { useSearcher } from "@Services/Api";
|
import { apiTo, transl } from "./Module";
|
||||||
import { can, apiTo, viewTo, transl } from "./Module";
|
|
||||||
|
|
||||||
import Table from "@Holos/Table.vue";
|
import Table from "@Holos/Table.vue";
|
||||||
import IconButton from "@Holos/Button/Icon.vue";
|
import IconButton from "@Holos/Button/Icon.vue";
|
||||||
@ -10,6 +9,10 @@ import PageHeader from "@Holos/PageHeader.vue";
|
|||||||
import ConceptForm from "@App/ConceptSection.vue";
|
import ConceptForm from "@App/ConceptSection.vue";
|
||||||
import DestroyView from "@Holos/Modal/Template/Destroy.vue";
|
import DestroyView from "@Holos/Modal/Template/Destroy.vue";
|
||||||
import ModalController from "@Controllers/ModalController.js";
|
import ModalController from "@Controllers/ModalController.js";
|
||||||
|
import Editview from "@Holos/Modal/Edit.vue";
|
||||||
|
import Input from "@Holos/Form/Input.vue";
|
||||||
|
import Textarea from "@Holos/Form/Textarea.vue";
|
||||||
|
import Selectable from "@Holos/Form/Selectable.vue";
|
||||||
|
|
||||||
/** Controladores */
|
/** Controladores */
|
||||||
const Modal = new ModalController();
|
const Modal = new ModalController();
|
||||||
@ -19,67 +22,133 @@ const destroyModal = ref(Modal.destroyModal);
|
|||||||
const editModal = ref(Modal.editModal);
|
const editModal = ref(Modal.editModal);
|
||||||
const modelModal = ref(Modal.modelModal);
|
const modelModal = ref(Modal.modelModal);
|
||||||
|
|
||||||
/** Inicializar searcher */
|
const addresses = ref([]);
|
||||||
//const models = ref(searcher.models);
|
const units = ref([]);
|
||||||
|
|
||||||
const models = ref({
|
const models = ref({
|
||||||
data: [],
|
data: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const processing = ref(false);
|
|
||||||
//const searcher = useSearcher(apiTo("index"));
|
/** Inicializar searcher */
|
||||||
const searcher = {
|
const searcher = useSearcher({
|
||||||
models,
|
url: apiTo("index"),
|
||||||
processing,
|
filters: {},
|
||||||
pagination: () => {},
|
onSuccess: (data) => {
|
||||||
search: () => {},
|
models.value = data.models;
|
||||||
};
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
api.get(apiURL("directions"), {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
addresses.value = data.models?.data || data.data || [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
api.get(apiURL("units"), {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
units.value = data.models?.data || data.data || [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
/** Cargar datos iniciales */
|
/** Cargar datos iniciales */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
searcher.search();
|
searcher.search();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** crear concepto */
|
||||||
|
const handleConceptCreated = async (conceptData) => {
|
||||||
|
const form = useForm(conceptData);
|
||||||
|
|
||||||
|
form.post(apiTo("store"), {
|
||||||
|
onSuccess: () => {
|
||||||
|
Notify.success(transl("create.onSuccess"));
|
||||||
|
searcher.refresh();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
Notify.error(transl("create.onError"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConceptUpdated = async () => {
|
||||||
|
const transformedData = {
|
||||||
|
direction_id: typeof modelModal.value.direction === 'object' ? modelModal.value.direction.id : Number(modelModal.value.direction_id),
|
||||||
|
unit_id: modelModal.value.unit ? (typeof modelModal.value.unit === 'object' ? modelModal.value.unit.id : Number(modelModal.value.unit)) : null,
|
||||||
|
short_name: modelModal.value.short_name,
|
||||||
|
name: modelModal.value.name,
|
||||||
|
legal_instrument: modelModal.value.legal_instrument,
|
||||||
|
article: modelModal.value.article,
|
||||||
|
content: modelModal.value.content,
|
||||||
|
min_amount_uma: modelModal.value.min_amount_uma ? Number(modelModal.value.min_amount_uma) : null,
|
||||||
|
max_amount_uma: modelModal.value.max_amount_uma ? Number(modelModal.value.max_amount_uma) : null,
|
||||||
|
min_amount_peso: modelModal.value.min_amount_peso ? Number(modelModal.value.min_amount_peso) : null,
|
||||||
|
max_amount_peso: modelModal.value.max_amount_peso ? Number(modelModal.value.max_amount_peso) : null,
|
||||||
|
unit_cost_uma: modelModal.value.unit_cost_uma ? Number(modelModal.value.unit_cost_uma) : null,
|
||||||
|
unit_cost_peso: modelModal.value.unit_cost_peso ? Number(modelModal.value.unit_cost_peso) : null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = useForm(transformedData);
|
||||||
|
|
||||||
|
form.put(apiTo("update", { charge_concept: modelModal.value.id }), {
|
||||||
|
onSuccess: () => {
|
||||||
|
Notify.success(transl("edit.onSuccess"));
|
||||||
|
searcher.refresh();
|
||||||
|
Modal.switchEditModal();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
Notify.error(transl("edit.onError"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<PageHeader title="Creación de Concepto" />
|
<PageHeader :title="transl('title')" />
|
||||||
<ConceptForm />
|
<ConceptForm @concept-created="handleConceptCreated" />
|
||||||
<div class="m-3">
|
<div class="m-3">
|
||||||
<Table
|
<Table
|
||||||
:items="models"
|
:items="models"
|
||||||
:processing="searcher.processing.value"
|
:processing="searcher.processing"
|
||||||
@send-pagination="(page) => searcher.pagination(page)"
|
@send-pagination="(page) => searcher.pagination(page)"
|
||||||
>
|
>
|
||||||
<template #head>
|
<template #head>
|
||||||
<th v-text="'Dirección'" />
|
<th v-text="transl('direction')" />
|
||||||
<th v-text="'Nombre corto'" />
|
<th v-text="transl('shortName')" />
|
||||||
<th v-text="'Nombre'" />
|
<th v-text="transl('name')" />
|
||||||
<th v-text="'Instrumento Legal'" />
|
<th v-text="transl('legal')" />
|
||||||
<th v-text="'Articulado'" />
|
<th v-text="transl('article')" />
|
||||||
<th v-text="'Descripción'" />
|
<th v-text="transl('description')" />
|
||||||
<th v-text="$t('actions')" class="w-32 text-center" />
|
<th v-text="$t('actions')" class="w-32 text-center" />
|
||||||
</template>
|
</template>
|
||||||
<template #body="{ items }">
|
<template #body="{ items }">
|
||||||
<tr v-for="model in items" :key="model.id" class="table-row">
|
<tr v-for="model in items" :key="model.id" class="table-row">
|
||||||
<td class="table-cell border">
|
<td class="table-cell border">
|
||||||
{{ model.description }}
|
{{ model.direction?.name || "-" }}
|
||||||
|
</td>
|
||||||
|
<td class="table-cell border">
|
||||||
|
{{ model.short_name }}
|
||||||
|
</td>
|
||||||
|
<td class="table-cell border">
|
||||||
|
{{ model.name }}
|
||||||
|
</td>
|
||||||
|
<td class="table-cell border">
|
||||||
|
{{ model.legal_instrument }}
|
||||||
|
</td>
|
||||||
|
<td class="table-cell border">
|
||||||
|
{{ model.article }}
|
||||||
|
</td>
|
||||||
|
<td class="table-cell border">
|
||||||
|
{{ model.content }}
|
||||||
</td>
|
</td>
|
||||||
<td class="table-cell">
|
<td class="table-cell">
|
||||||
<div class="table-actions">
|
<div class="table-actions">
|
||||||
<IconButton
|
<IconButton
|
||||||
v-if="can('edit') && ![1, 2].includes(model.id)"
|
icon="edit"
|
||||||
icon="license"
|
:title="$t('crud.edit')"
|
||||||
:title="transl('permissions.title')"
|
|
||||||
@click="Modal.switchEditModal(model)"
|
@click="Modal.switchEditModal(model)"
|
||||||
outline
|
outline
|
||||||
/>
|
/>
|
||||||
<RouterLink
|
|
||||||
v-if="can('edit')"
|
|
||||||
class="h-fit"
|
|
||||||
:to="viewTo({ name: 'edit', params: { id: model.id } })"
|
|
||||||
>
|
|
||||||
<IconButton icon="edit" :title="$t('crud.edit')" outline />
|
|
||||||
</RouterLink>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
v-if="can('destroy') && ![1, 2].includes(model.id)"
|
|
||||||
icon="delete"
|
icon="delete"
|
||||||
:title="$t('crud.destroy')"
|
:title="$t('crud.destroy')"
|
||||||
@click="Modal.switchDestroyModal(model)"
|
@click="Modal.switchDestroyModal(model)"
|
||||||
@ -90,18 +159,17 @@ onMounted(() => {
|
|||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<td colspan="2" class="table-cell text-center py-4 text-gray-500">
|
<td colspan="7" class="table-cell text-center py-4 text-gray-500">
|
||||||
No hay direcciones registradas
|
{{ transl("list.empty") }}
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</Table>
|
||||||
<DestroyView
|
<DestroyView
|
||||||
v-if="can('destroy')"
|
title="name"
|
||||||
title="description"
|
subtitle="content"
|
||||||
subtitle=""
|
|
||||||
:model="modelModal"
|
:model="modelModal"
|
||||||
:show="destroyModal"
|
:show="destroyModal"
|
||||||
:to="(address) => apiTo('destroy', { address: address.id })"
|
:to="(id) => apiTo('destroy', { charge_concept: id })"
|
||||||
@close="Modal.switchDestroyModal"
|
@close="Modal.switchDestroyModal"
|
||||||
@update="
|
@update="
|
||||||
() => {
|
() => {
|
||||||
@ -113,5 +181,104 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
<Editview
|
||||||
|
:show="editModal"
|
||||||
|
:title="transl('edit.title')"
|
||||||
|
@close="Modal.switchEditModal"
|
||||||
|
@update="handleConceptUpdated"
|
||||||
|
>
|
||||||
|
<div class="p-4 space-y-4">
|
||||||
|
<Selectable
|
||||||
|
v-model="modelModal.direction"
|
||||||
|
label="name"
|
||||||
|
value="id"
|
||||||
|
:title="$t('concept.direction')"
|
||||||
|
:options="addresses"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.short_name"
|
||||||
|
:id="$t('concept.shortName')"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.name"
|
||||||
|
:id="$t('concept.name')"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.legal_instrument"
|
||||||
|
:id="$t('concept.legal')"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.article"
|
||||||
|
:id="$t('concept.article')"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
v-model="modelModal.content"
|
||||||
|
:id="$t('concept.description')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.min_amount_uma"
|
||||||
|
:id="$t('concept.minimumAmountUma')"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.max_amount_uma"
|
||||||
|
:id="$t('concept.maximumAmountUma')"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.min_amount_peso"
|
||||||
|
:id="$t('concept.minimumAmountPeso')"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.max_amount_peso"
|
||||||
|
:id="$t('concept.maximumAmountPeso')"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr class="my-4 border-gray-300" />
|
||||||
|
<h4 class="text-lg font-semibold mb-2 text-gray-700">
|
||||||
|
{{ $t('concept.tabulator') }}
|
||||||
|
</h4>
|
||||||
|
<Selectable
|
||||||
|
v-model="modelModal.unit"
|
||||||
|
label="name"
|
||||||
|
value="id"
|
||||||
|
:title="$t('concept.sizeUnit')"
|
||||||
|
:options="units"
|
||||||
|
/>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.unit_cost_uma"
|
||||||
|
:id="$t('concept.costUnitUma')"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="modelModal.unit_cost_peso"
|
||||||
|
:id="$t('concept.costUnitPeso')"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Editview>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { lang } from '@Lang/i18n';
|
|||||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||||
|
|
||||||
// Ruta API
|
// Ruta API
|
||||||
const apiTo = (name, params = {}) => route(`concept.${name}`, params)
|
const apiTo = (name, params = {}) => route(`charge-concepts.${name}`, params)
|
||||||
|
|
||||||
// Ruta visual
|
// Ruta visual
|
||||||
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `concept.${name}`, params, query })
|
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `concept.${name}`, params, query })
|
||||||
|
|||||||
21
src/pages/App/Fine/Module.js
Normal file
21
src/pages/App/Fine/Module.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { lang } from '@Lang/i18n';
|
||||||
|
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||||
|
|
||||||
|
// Ruta API
|
||||||
|
const apiTo = (name, params = {}) => route(`fines.${name}`, params)
|
||||||
|
|
||||||
|
// Ruta visual
|
||||||
|
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `fine.${name}`, params, query })
|
||||||
|
|
||||||
|
// Obtener traducción del componente
|
||||||
|
const transl = (str) => lang(`fine.${str}`)
|
||||||
|
|
||||||
|
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||||
|
const can = (permission) => hasPermission(`fine.${permission}`)
|
||||||
|
|
||||||
|
export {
|
||||||
|
can,
|
||||||
|
viewTo,
|
||||||
|
apiTo,
|
||||||
|
transl
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user