diff --git a/src/components/POS/CartItem.vue b/src/components/POS/CartItem.vue index 89b19fb..07723aa 100644 --- a/src/components/POS/CartItem.vue +++ b/src/components/POS/CartItem.vue @@ -129,8 +129,18 @@ const remove = () => { + +
+ + + + + {{ item.unit_name }} + +
+ -
+

{{ item.unit_of_measure.name }} ({{ item.unit_of_measure.abbreviation }}) - Permite decimales

diff --git a/src/components/POS/CfdiSelector.vue b/src/components/POS/CfdiSelector.vue new file mode 100644 index 0000000..5a4e2c7 --- /dev/null +++ b/src/components/POS/CfdiSelector.vue @@ -0,0 +1,38 @@ + + + \ No newline at end of file diff --git a/src/components/POS/RegimenSelecto.vue b/src/components/POS/RegimenSelecto.vue new file mode 100644 index 0000000..350e852 --- /dev/null +++ b/src/components/POS/RegimenSelecto.vue @@ -0,0 +1,38 @@ + + + \ No newline at end of file diff --git a/src/components/POS/UnitEquivalenceSelector.vue b/src/components/POS/UnitEquivalenceSelector.vue new file mode 100644 index 0000000..c8b9970 --- /dev/null +++ b/src/components/POS/UnitEquivalenceSelector.vue @@ -0,0 +1,167 @@ + + + diff --git a/src/lang/es.js b/src/lang/es.js index d9d60a7..c15bdb8 100644 --- a/src/lang/es.js +++ b/src/lang/es.js @@ -462,6 +462,7 @@ export default { returns: 'Devoluciones', clients: 'Clientes', suppliers: 'Proveedores', + unitMeasure: 'Unidades de medida', clientTiers: 'Niveles de Clientes', billingRequests: 'Solicitudes de Facturación', warehouses: 'Almacenes', diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue index 871f77f..5db9b45 100644 --- a/src/layouts/AppLayout.vue +++ b/src/layouts/AppLayout.vue @@ -75,6 +75,11 @@ onMounted(() => { name="pos.suppliers" to="pos.suppliers.index" /> + { }); /** Métodos */ -const calculateTax = () => { - if (form.retail_price && !form.tax) { - form.tax = (parseFloat(form.retail_price) * 0.16).toFixed(2); - } -}; - const handleProductSelect = (product) => { if (selectedProducts.value.find(item => item.product.id === product.id)) { Notify.warning('Este producto ya está agregado'); @@ -213,7 +207,6 @@ watch(() => props.show, (val) => { { }); /** Métodos */ -const calculateTax = () => { - if (form.retail_price && !form.tax) { - form.tax = (parseFloat(form.retail_price) * 0.16).toFixed(2); - } -}; - const handleProductSelect = (product) => { if (selectedProducts.value.find(item => item.product.id === product.id)) { Notify.warning('Este producto ya está agregado'); @@ -72,7 +66,6 @@ const updateQuantity = (index, quantity) => { const useSuggestedPrice = () => { form.retail_price = suggestedPrice.value.toFixed(2); - calculateTax(); }; const updateBundle = () => { @@ -229,7 +222,6 @@ watch(() => props.bundle, (bundle) => { {
-
- - - -
+
@@ -180,18 +174,10 @@ const closeModal = () => {
-
- - - -
+
diff --git a/src/pages/POS/Clients/Edit.vue b/src/pages/POS/Clients/Edit.vue index b7e1846..5d9769e 100644 --- a/src/pages/POS/Clients/Edit.vue +++ b/src/pages/POS/Clients/Edit.vue @@ -5,6 +5,8 @@ import { useForm, apiURL } from '@Services/Api'; import Modal from '@Holos/Modal.vue'; import FormInput from '@Holos/Form/Input.vue'; import FormError from '@Holos/Form/Elements/Error.vue'; +import SelectUsoCfdi from '@Components/POS/CfdiSelector.vue'; +import SelectRegimenFiscal from '@Components/POS/RegimenSelecto.vue'; /** Eventos */ const emit = defineEmits(['close', 'updated']); @@ -168,21 +170,7 @@ watch(() => props.client, (newClient) => { /> - - -
- - - -
- +
+ + + + -
- - - -
+ diff --git a/src/pages/POS/Clients/Index.vue b/src/pages/POS/Clients/Index.vue index d55b016..8954331 100644 --- a/src/pages/POS/Clients/Index.vue +++ b/src/pages/POS/Clients/Index.vue @@ -2,6 +2,10 @@ import { onMounted, ref } from 'vue'; import { useSearcher, apiURL } from '@Services/Api'; import { can } from './Module.js'; +import { regimenFiscalOptions, usoCfdiOptions } from '@/utils/fiscalData'; + +const regimenFiscalLabel = (value) => regimenFiscalOptions.find(o => o.value === value)?.label ?? value; +const usoCfdiLabel = (value) => usoCfdiOptions.find(o => o.value === value)?.label ?? value; import SearcherHead from '@Holos/Searcher.vue'; import ExcelModal from '@Components/POS/ExcelClient.vue'; @@ -198,13 +202,13 @@ onMounted(() => {

{{ client.razon_social }}

-

{{ client.regimen_fiscal }}

+

{{ regimenFiscalLabel(client.regimen_fiscal) }}

{{ client.cp_fiscal }}

-

{{ client.uso_cfdi }}

+

{{ usoCfdiLabel(client.uso_cfdi) }}

diff --git a/src/pages/POS/Factura/Index.vue b/src/pages/POS/Factura/Index.vue index c34b029..523c164 100644 --- a/src/pages/POS/Factura/Index.vue +++ b/src/pages/POS/Factura/Index.vue @@ -8,6 +8,8 @@ import GoogleIcon from '@Shared/GoogleIcon.vue'; import Loader from '@Shared/Loader.vue'; import Input from '@Holos/Form/Input.vue'; import PrimaryButton from '@Holos/Button/Primary.vue'; +import SelectUsoCfdi from '@Components/POS/CfdiSelector.vue'; +import SelectRegimenFiscal from '@Components/POS/RegimenSelector.vue'; /** Definidores */ const route = useRoute(); @@ -43,32 +45,6 @@ const paymentMethods = [ { value: 'debit_card', label: 'Tarjeta de Débito' } ]; -const usoCfdiOptions = [ - { value: 'G01', label: 'G01 - Adquisición de mercancías' }, - { value: 'G02', label: 'G02 - Devoluciones, descuentos o bonificaciones' }, - { value: 'G03', label: 'G03 - Gastos en general' }, - { value: 'I01', label: 'I01 - Construcciones' }, - { value: 'I02', label: 'I02 - Mobiliario y equipo de oficina por inversiones' }, - { value: 'I03', label: 'I03 - Equipo de transporte' }, - { value: 'I04', label: 'I04 - Equipo de computo y accesorios' }, - { value: 'I05', label: 'I05 - Dados, troqueles, moldes, matrices y herramental' }, - { value: 'I06', label: 'I06 - Comunicaciones telefónicas' }, - { value: 'I07', label: 'I07 - Comunicaciones satelitales' }, - { value: 'I08', label: 'I08 - Otra maquinaria y equipo' }, - { value: 'S01', label: 'S01 - Sin efectos fiscales' } -]; - -const regimenFiscalOptions = [ - { value: '601', label: '601 - General de Ley Personas Morales' }, - { value: '603', label: '603 - Personas Morales con Fines no Lucrativos' }, - { value: '610', label: '610 - Residentes en el Extranjero sin Establecimiento Permanente en México' }, - { value: '620', label: '620 - Sociedades Cooperativas de Producción que optan por diferir sus ingresos' }, - { value: '622', label: '622 - Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras' }, - { value: '623', label: '623 - Opcional para Grupos de Sociedades' }, - { value: '624', label: '624 - Coordinados' }, - { value: '626', label: '626 - Régimen Simplificado de Confianza' } -]; - const paymentMethodLabel = computed(() => { const method = paymentMethods.find(m => saleData.value?.payment_method?.includes(m.value)); return method?.label || saleData.value?.payment_method || 'N/A'; @@ -93,16 +69,9 @@ const canRequestInvoice = computed(() => { return latestRequest.value.status === 'rejected'; }); -/** Helpers para mostrar labels legibles */ -const getRegimenFiscalLabel = (value) => { - const option = regimenFiscalOptions.find(o => o.value === value); - return option ? option.label : value || 'No registrado'; -}; - -const getUsoCfdiLabel = (value) => { - const option = usoCfdiOptions.find(o => o.value === value); - return option ? option.label : value || 'No registrado'; -}; +/** Helpers */ +const getRegimenFiscalLabel = (value) => value || 'No registrado'; +const getUsoCfdiLabel = (value) => value || 'No registrado'; /** Métodos */ const fetchSaleData = () => { @@ -113,8 +82,6 @@ const fetchSaleData = () => { .then(({ data }) => { if (data.status === 'success') { saleData.value = data.data.sale; - - // Si la venta ya tiene un cliente asociado, cargar sus datos if (data.data.client) { clientData.value = data.data.client; fillFormWithClient(data.data.client); @@ -136,9 +103,6 @@ const fetchSaleData = () => { }); }; -/** - * Llenar el formulario con los datos del cliente - */ const fillFormWithClient = (client) => { form.value = { name: client.name || '', @@ -153,9 +117,6 @@ const fillFormWithClient = (client) => { }; }; -/** - * Buscar cliente por RFC - */ const searchClientByRfc = () => { const rfc = rfcSearch.value?.trim().toUpperCase(); @@ -174,7 +135,6 @@ const searchClientByRfc = () => { window.axios.get(apiURL(`facturacion/check-rfc?rfc=${rfc}`)) .then(({ data }) => { - // La respuesta viene: { status: 'success', data: { exists: true, client: {...} } } if (data.status === 'success' && data.data?.exists && data.data?.client) { clientData.value = data.data.client; fillFormWithClient(data.data.client); @@ -195,9 +155,6 @@ const searchClientByRfc = () => { }); }; -/** - * Manejar Enter en el input de búsqueda - */ const handleSearchKeypress = (event) => { if (event.key === 'Enter') { event.preventDefault(); @@ -205,9 +162,6 @@ const handleSearchKeypress = (event) => { } }; -/** - * Limpiar datos del cliente y volver al formulario limpio - */ const clearFoundClient = () => { clientData.value = null; rfcSearchError.value = ''; @@ -230,7 +184,7 @@ const submitForm = () => { formErrors.value = {}; window.axios.post(apiURL(`facturacion/${invoiceNumber.value}`), form.value) - .then(({ data }) => { + .then(() => { submitted.value = true; }) .catch(({ response }) => { @@ -247,7 +201,6 @@ const submitForm = () => { }); }; -/** Ciclos */ onMounted(() => { fetchSaleData(); }); @@ -649,30 +602,11 @@ onMounted(() => { :onError="formErrors.razon_social" /> - -
- - -

- {{ formErrors.regimen_fiscal[0] }} -

-
+ { :onError="formErrors.cp_fiscal" /> - -
- - -

- {{ formErrors.uso_cfdi[0] }} -

-
+
{ }; const createProduct = () => { - form.post(apiURL('inventario'), { + form.transform((data) => ({ + ...data, + track_serials: selectedUnit.value ? !selectedUnit.value.allows_decimals && !!data.track_serials : false + })).post(apiURL('inventario'), { onSuccess: () => { Notify.success('Producto creado exitosamente'); emit('created'); @@ -167,10 +170,9 @@ watch(() => form.track_serials, () => {
@@ -251,25 +253,6 @@ watch(() => form.track_serials, () => {

- -
- -

- No se pueden usar números de serie con esta unidad de medida. -

- -
-