feat: agregar gestión de proveedores en la creación y edición de facturas
This commit is contained in:
parent
5dbb52a9e9
commit
53a5208cdf
@ -18,6 +18,7 @@ export default {
|
||||
title: 'Editar Factura',
|
||||
description: 'Actualiza los datos de la factura. Si subes un nuevo archivo, el anterior será reemplazado.',
|
||||
},
|
||||
supplier: 'Proveedor',
|
||||
paid: 'Pagada',
|
||||
pending: 'Pendiente',
|
||||
export: 'Exportar pendientes',
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
<script setup>
|
||||
import { useForm, apiURL } from '@Services/Api';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useForm, useApi, apiURL } from '@Services/Api';
|
||||
import { transl } from './Module';
|
||||
|
||||
import Modal from '@Holos/Modal.vue';
|
||||
import FormInput from '@Holos/Form/Input.vue';
|
||||
import FormError from '@Holos/Form/Elements/Error.vue';
|
||||
import SingleFile from '@Holos/Form/SingleFile.vue';
|
||||
import Modal from '@Holos/Modal.vue';
|
||||
import FormInput from '@Holos/Form/Input.vue';
|
||||
import FormError from '@Holos/Form/Elements/Error.vue';
|
||||
import SingleFile from '@Holos/Form/SingleFile.vue';
|
||||
import Selectable from '@Holos/Form/Selectable.vue';
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
import SupplierCreate from '@Pages/POS/Suppliers/Create.vue';
|
||||
|
||||
/** Eventos */
|
||||
const emit = defineEmits(['close', 'created']);
|
||||
@ -15,17 +19,25 @@ defineProps({
|
||||
show: Boolean
|
||||
});
|
||||
|
||||
/** Estado */
|
||||
const api = useApi();
|
||||
const suppliers = ref([]);
|
||||
const selectedSupplier = ref(null);
|
||||
const showSupplierModal = ref(false);
|
||||
|
||||
/** Formulario */
|
||||
const form = useForm({
|
||||
name: '',
|
||||
cost: '',
|
||||
deadline: '',
|
||||
supplier_id: null,
|
||||
paid: false,
|
||||
file: null,
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
const create = () => {
|
||||
form.supplier_id = selectedSupplier.value?.id ?? null;
|
||||
form.post(apiURL('bills'), {
|
||||
onSuccess: (data) => {
|
||||
window.Notify.success(Lang('register.create.onSuccess'));
|
||||
@ -37,8 +49,18 @@ const create = () => {
|
||||
|
||||
const closeModal = () => {
|
||||
form.reset();
|
||||
selectedSupplier.value = null;
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const loadSuppliers = () => {
|
||||
api.get(apiURL('proveedores'), {
|
||||
onSuccess: (data) => suppliers.value = data.suppliers?.data ?? [],
|
||||
});
|
||||
};
|
||||
|
||||
/** Ciclo de vida */
|
||||
onMounted(loadSuppliers);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -62,6 +84,31 @@ const closeModal = () => {
|
||||
<!-- Formulario -->
|
||||
<form @submit.prevent="create" class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- Proveedor -->
|
||||
<div class="col-span-2">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<label class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase">
|
||||
{{ transl('supplier') }}
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center justify-center w-6 h-6 rounded-full bg-indigo-600 hover:bg-indigo-700 text-white transition-colors"
|
||||
:title="$t('crud.create')"
|
||||
@click="showSupplierModal = true"
|
||||
>
|
||||
<GoogleIcon name="add" class="text-sm" />
|
||||
</button>
|
||||
</div>
|
||||
<Selectable
|
||||
v-model="selectedSupplier"
|
||||
:options="suppliers"
|
||||
label="business_name"
|
||||
track-by="id"
|
||||
:placeholder="transl('supplier')"
|
||||
/>
|
||||
<FormError :message="form.errors?.supplier_id" />
|
||||
</div>
|
||||
|
||||
<!-- Nombre -->
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase mb-1.5">
|
||||
@ -150,4 +197,10 @@ const closeModal = () => {
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<SupplierCreate
|
||||
:show="showSupplierModal"
|
||||
@close="showSupplierModal = false"
|
||||
@created="() => { showSupplierModal = false; loadSuppliers(); }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { watch } from 'vue';
|
||||
import { useForm, apiURL } from '@Services/Api';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useForm, useApi, apiURL } from '@Services/Api';
|
||||
import { transl } from './Module';
|
||||
|
||||
import Modal from '@Holos/Modal.vue';
|
||||
@ -8,6 +8,8 @@ import FormInput from '@Holos/Form/Input.vue';
|
||||
import FormError from '@Holos/Form/Elements/Error.vue';
|
||||
import SingleFile from '@Holos/Form/SingleFile.vue';
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
import Selectable from '@Holos/Form/Selectable.vue';
|
||||
import SupplierCreate from '@Pages/POS/Suppliers/Create.vue';
|
||||
|
||||
/** Eventos */
|
||||
const emit = defineEmits(['close', 'updated']);
|
||||
@ -18,17 +20,31 @@ const props = defineProps({
|
||||
bill: Object
|
||||
});
|
||||
|
||||
/** Estado */
|
||||
const api = useApi();
|
||||
const suppliers = ref([]);
|
||||
const selectedSupplier = ref(null);
|
||||
const showSupplierModal = ref(false);
|
||||
|
||||
const loadSuppliers = () => {
|
||||
api.get(apiURL('proveedores'), {
|
||||
onSuccess: (data) => suppliers.value = data.suppliers?.data ?? [],
|
||||
});
|
||||
};
|
||||
|
||||
/** Formulario */
|
||||
const form = useForm({
|
||||
name: '',
|
||||
cost: '',
|
||||
deadline: '',
|
||||
supplier_id: null,
|
||||
paid: false,
|
||||
file: null,
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
const update = () => {
|
||||
form.supplier_id = selectedSupplier.value?.id ?? null;
|
||||
form.post(apiURL(`bills/${props.bill.id}`), {
|
||||
onSuccess: () => {
|
||||
window.Notify.success(Lang('register.edit.onSuccess'));
|
||||
@ -40,9 +56,13 @@ const update = () => {
|
||||
|
||||
const closeModal = () => {
|
||||
form.reset();
|
||||
selectedSupplier.value = null;
|
||||
emit('close');
|
||||
};
|
||||
|
||||
/** Ciclo de vida */
|
||||
onMounted(loadSuppliers);
|
||||
|
||||
/** Observadores */
|
||||
watch(() => props.bill, (bill) => {
|
||||
if (bill) {
|
||||
@ -51,6 +71,7 @@ watch(() => props.bill, (bill) => {
|
||||
form.deadline = bill.deadline || '';
|
||||
form.paid = !!bill.paid;
|
||||
form.file = null;
|
||||
selectedSupplier.value = bill.supplier ?? null;
|
||||
}
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
@ -76,6 +97,31 @@ watch(() => props.bill, (bill) => {
|
||||
<!-- Formulario -->
|
||||
<form @submit.prevent="update" class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- Proveedor -->
|
||||
<div class="col-span-2">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<label class="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase">
|
||||
{{ transl('supplier') }}
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center justify-center w-6 h-6 rounded-full bg-indigo-600 hover:bg-indigo-700 text-white transition-colors"
|
||||
:title="$t('crud.create')"
|
||||
@click="showSupplierModal = true"
|
||||
>
|
||||
<GoogleIcon name="add" class="text-sm" />
|
||||
</button>
|
||||
</div>
|
||||
<Selectable
|
||||
v-model="selectedSupplier"
|
||||
:options="suppliers"
|
||||
label="business_name"
|
||||
track-by="id"
|
||||
:placeholder="transl('supplier')"
|
||||
/>
|
||||
<FormError :message="form.errors?.supplier_id" />
|
||||
</div>
|
||||
|
||||
<!-- Nombre -->
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase mb-1.5">
|
||||
@ -178,4 +224,10 @@ watch(() => props.bill, (bill) => {
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<SupplierCreate
|
||||
:show="showSupplierModal"
|
||||
@close="showSupplierModal = false"
|
||||
@created="() => { showSupplierModal = false; loadSuppliers(); }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@ -81,7 +81,11 @@ const togglePaid = (bill) => {
|
||||
};
|
||||
|
||||
const exportPending = () => {
|
||||
api.download(apiURL('bills/pending/excel'), 'Facturas_Pendientes.xlsx');
|
||||
const now = new Date();
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const date = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`;
|
||||
const time = `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
||||
api.download(apiURL('bills/pending/excel'), `Facturas_Pendientes_${date}_${time}.xlsx`);
|
||||
};
|
||||
|
||||
/** Ciclo de vida */
|
||||
@ -122,6 +126,9 @@ onMounted(() => searcher.search());
|
||||
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
{{ transl('name') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
{{ transl('supplier') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
{{ transl('cost') }}
|
||||
</th>
|
||||
@ -148,6 +155,12 @@ onMounted(() => searcher.search());
|
||||
<td class="px-6 py-4 text-center">
|
||||
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ bill.name }}</p>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<p v-if="bill.supplier" class="text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ bill.supplier.business_name }}
|
||||
</p>
|
||||
<span v-else class="text-sm text-gray-400">—</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ formatCurrency(bill.cost) }}
|
||||
@ -208,7 +221,7 @@ onMounted(() => searcher.search());
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
<td colspan="6" class="table-cell text-center">
|
||||
<td colspan="7" class="table-cell text-center">
|
||||
<div class="flex flex-col items-center justify-center py-8 text-gray-500">
|
||||
<GoogleIcon name="receipt_long" class="text-6xl mb-2 opacity-50" />
|
||||
<p class="font-semibold">{{ $t('registers.empty') }}</p>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user