Compare commits
No commits in common. "cdfd53e8ec50131e11647328b2cc8a9d8aaf0b25" and "7a2b5f8400f83d06116e7cc5800c71a624971f17" have entirely different histories.
cdfd53e8ec
...
7a2b5f8400
@ -10,7 +10,6 @@ services:
|
|||||||
- /var/www/comal-pagos.frontend/node_modules
|
- /var/www/comal-pagos.frontend/node_modules
|
||||||
networks:
|
networks:
|
||||||
- comal-pagos-network
|
- comal-pagos-network
|
||||||
mem_limit: 512m
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
comal-pagos-network:
|
comal-pagos-network:
|
||||||
|
|||||||
@ -11,9 +11,6 @@ const emit = defineEmits(["cash-cut-found", "cash-cut-delivered"]);
|
|||||||
/** Instancias */
|
/** Instancias */
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
|
||||||
/** Control de visibilidad del formulario */
|
|
||||||
const isFormOpen = ref(false);
|
|
||||||
|
|
||||||
/** Refs */
|
/** Refs */
|
||||||
const qr_token = ref("");
|
const qr_token = ref("");
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@ -184,33 +181,12 @@ const clearForm = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mx-4 mt-4 mb-6">
|
<div class="space-y-6 p-6">
|
||||||
<!-- Botón para abrir el formulario -->
|
<div class="bg-white rounded-xl p-6 shadow-lg">
|
||||||
<button
|
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||||
v-if="!isFormOpen"
|
Buscar Corte de Caja
|
||||||
@click="isFormOpen = true"
|
</h3>
|
||||||
class="w-full bg-primary hover:bg-primary/90 text-white font-semibold py-3 px-6 rounded-lg transition-all duration-200 shadow-md hover:shadow-lg flex items-center justify-center space-x-2"
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
>
|
|
||||||
<span class="text-2xl">+</span>
|
|
||||||
<span>Buscar y Entregar Corte de Caja</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Formulario expandible -->
|
|
||||||
<div v-if="isFormOpen" class="space-y-6">
|
|
||||||
<div class="bg-white rounded-xl p-6 shadow-lg">
|
|
||||||
<div class="flex justify-between items-center mb-4">
|
|
||||||
<h3 class="text-xl font-semibold text-gray-800">
|
|
||||||
Buscar Corte de Caja
|
|
||||||
</h3>
|
|
||||||
<button
|
|
||||||
@click="isFormOpen = false"
|
|
||||||
class="text-gray-400 hover:text-gray-600 transition-colors"
|
|
||||||
title="Cerrar"
|
|
||||||
>
|
|
||||||
<span class="text-2xl">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<!-- Scanner QR -->
|
<!-- Scanner QR -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<label class="block text-sm text-gray-600 font-medium mb-2">
|
<label class="block text-sm text-gray-600 font-medium mb-2">
|
||||||
@ -303,8 +279,7 @@ const clearForm = () => {
|
|||||||
<!-- Botón recibir -->
|
<!-- Botón recibir -->
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="submit"
|
||||||
@click="handleDelivery"
|
|
||||||
:disabled="cashCutData.isReceived"
|
:disabled="cashCutData.isReceived"
|
||||||
:class="[
|
:class="[
|
||||||
'w-full font-medium py-3.5 rounded-lg transition-colors',
|
'w-full font-medium py-3.5 rounded-lg transition-colors',
|
||||||
@ -317,7 +292,6 @@ const clearForm = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -23,8 +23,8 @@ function paintBoundingBox(detectedCodes, ctx) {
|
|||||||
boundingBox: { x, y, width, height }
|
boundingBox: { x, y, width, height }
|
||||||
} = detectedCode;
|
} = detectedCode;
|
||||||
|
|
||||||
ctx.lineWidth = 4;
|
ctx.lineWidth = 2;
|
||||||
ctx.strokeStyle = '#10b981';
|
ctx.strokeStyle = '#7a0b3a'; // Color del tema
|
||||||
ctx.strokeRect(x, y, width, height);
|
ctx.strokeRect(x, y, width, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -218,15 +218,6 @@ export default {
|
|||||||
confirm:'Confirmar',
|
confirm:'Confirmar',
|
||||||
copyright:'Todos los derechos reservados.',
|
copyright:'Todos los derechos reservados.',
|
||||||
contact:'Contacto',
|
contact:'Contacto',
|
||||||
checkout: {
|
|
||||||
concept: "Concepto",
|
|
||||||
amount: "Monto total",
|
|
||||||
date: "Fecha",
|
|
||||||
user: "Usuario",
|
|
||||||
list: {
|
|
||||||
empty: "No hay cortes de caja registrados"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
create: 'Crear',
|
create: 'Crear',
|
||||||
created: 'Registro creado',
|
created: 'Registro creado',
|
||||||
created_at: 'Fecha creación',
|
created_at: 'Fecha creación',
|
||||||
|
|||||||
@ -1,227 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, reactive, ref } from "vue";
|
import PageHeader from "@Holos/PageHeader.vue";
|
||||||
import { useSearcher, apiURL } from "@Services/Api";
|
import Checkout from '@App/CheckoutDelivery.vue';
|
||||||
import { transl } from "./Module";
|
|
||||||
|
|
||||||
import PageHeader from "@Holos/PageHeader.vue";
|
|
||||||
import Checkout from "@App/CheckoutDelivery.vue";
|
|
||||||
import Table from "@Holos/Table.vue";
|
|
||||||
|
|
||||||
const models = ref({
|
|
||||||
data: [],
|
|
||||||
total: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const filters = reactive({
|
|
||||||
type: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandedRows = ref(new Set());
|
|
||||||
|
|
||||||
const toggleRow = (id) => {
|
|
||||||
if (expandedRows.value.has(id)) {
|
|
||||||
expandedRows.value.delete(id);
|
|
||||||
} else {
|
|
||||||
expandedRows.value.add(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isRowExpanded = (id) => {
|
|
||||||
return expandedRows.value.has(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getConceptsSummary = (model) => {
|
|
||||||
if (!model.details || model.details.length === 0) return "-";
|
|
||||||
|
|
||||||
const allConcepts = model.details
|
|
||||||
.flatMap(
|
|
||||||
(detail) =>
|
|
||||||
detail.payment?.details?.map((pd) => pd.charge_concept?.name) || []
|
|
||||||
)
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
const uniqueConcepts = [...new Set(allConcepts)];
|
|
||||||
|
|
||||||
if (uniqueConcepts.length === 0) return "-";
|
|
||||||
if (uniqueConcepts.length === 1) return uniqueConcepts[0];
|
|
||||||
|
|
||||||
return `${uniqueConcepts[0]} y ${uniqueConcepts.length - 1} más...`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const searcher = useSearcher({
|
|
||||||
url: apiURL("cash-cuts"),
|
|
||||||
filters,
|
|
||||||
onSuccess: (data) => {
|
|
||||||
models.value = data.models || { data: [] };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
searcher.search();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageHeader title="Entrega de Caja" />
|
<PageHeader title="Entrega de Caja" />
|
||||||
<Checkout />
|
<Checkout />
|
||||||
<div class="mx-4 mb-4">
|
</template>
|
||||||
<div class="bg-white rounded-lg shadow-md p-4 mb-4">
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<label class="text-sm font-medium text-gray-700"
|
|
||||||
>Filtrar por tipo:</label
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
v-model="filters.type"
|
|
||||||
@change="searcher.search()"
|
|
||||||
class="px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
>
|
|
||||||
<option value="">Todos</option>
|
|
||||||
<option value="membership">Membresías</option>
|
|
||||||
<option value="fine">Multas</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
|
||||||
<Table
|
|
||||||
:items="models"
|
|
||||||
:processing="searcher.processing"
|
|
||||||
@send-pagination="(page) => searcher.pagination(page)"
|
|
||||||
>
|
|
||||||
<template #head>
|
|
||||||
<th
|
|
||||||
class="px-6 py-3 text-left text-sm font-semibold"
|
|
||||||
v-text="transl('concept')"
|
|
||||||
/>
|
|
||||||
<th
|
|
||||||
class="px-6 py-3 text-left text-sm font-semibold"
|
|
||||||
v-text="transl('amount')"
|
|
||||||
/>
|
|
||||||
<th
|
|
||||||
class="px-6 py-3 text-left text-sm font-semibold"
|
|
||||||
v-text="transl('user')"
|
|
||||||
/>
|
|
||||||
<th
|
|
||||||
class="px-6 py-3 text-left text-sm font-semibold"
|
|
||||||
v-text="transl('date')"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #body="{ items }">
|
|
||||||
<template v-for="model in items" :key="model.id">
|
|
||||||
<!-- Fila principal -->
|
|
||||||
<tr
|
|
||||||
class="border-b border-gray-200 hover:bg-gray-50 transition-colors cursor-pointer"
|
|
||||||
@click="toggleRow(model.id)"
|
|
||||||
>
|
|
||||||
<td class="px-6 py-4 text-sm text-gray-700">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<svg
|
|
||||||
class="w-4 h-4 transition-transform duration-200"
|
|
||||||
:class="{ 'rotate-90': isRowExpanded(model.id) }"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M9 5l7 7-7 7"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span>{{ getConceptsSummary(model) }}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 text-sm text-gray-700">
|
|
||||||
{{ model.closed_by?.full_name || "-" }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 text-sm text-gray-700">
|
|
||||||
${{ parseFloat(model.total_amount || 0).toFixed(2) }}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 text-sm text-gray-700">
|
|
||||||
{{
|
|
||||||
model.end_at
|
|
||||||
? new Date(model.end_at).toLocaleDateString()
|
|
||||||
: "-"
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Fila expandida con detalles -->
|
|
||||||
<tr v-if="isRowExpanded(model.id)" class="bg-gray-50">
|
|
||||||
<td colspan="4" class="px-6 py-4">
|
|
||||||
<div
|
|
||||||
class="bg-white rounded-lg shadow-sm p-4 border border-gray-200"
|
|
||||||
>
|
|
||||||
<h4 class="font-semibold text-gray-800 mb-3">
|
|
||||||
Detalle del corte de caja
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="model.details && model.details.length > 0"
|
|
||||||
class="space-y-3"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="detail in model.details"
|
|
||||||
:key="detail.id"
|
|
||||||
class="border-b border-gray-200 pb-3 last:border-b-0"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="paymentDetail in detail.payment?.details || []"
|
|
||||||
:key="paymentDetail.id"
|
|
||||||
class="flex justify-between items-center py-2"
|
|
||||||
>
|
|
||||||
<div class="flex-1">
|
|
||||||
<p class="font-medium text-gray-700">
|
|
||||||
{{ paymentDetail.charge_concept?.name || "-" }}
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-gray-500">
|
|
||||||
Cantidad: {{ paymentDetail.quantity || 1 }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="text-right">
|
|
||||||
<p class="font-semibold text-gray-800">
|
|
||||||
${{
|
|
||||||
parseFloat(paymentDetail.amount || 0).toFixed(2)
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="flex justify-between items-center pt-3 border-t-2 border-gray-300"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<p class="font-bold text-gray-800">Total del corte</p>
|
|
||||||
<p class="text-xs text-gray-500">
|
|
||||||
Fecha:
|
|
||||||
{{
|
|
||||||
model.end_at
|
|
||||||
? new Date(model.end_at).toLocaleDateString()
|
|
||||||
: "-"
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p class="text-xl font-bold text-blue-600">
|
|
||||||
${{ parseFloat(model.total_amount || 0).toFixed(2) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="text-center text-gray-500 py-4">
|
|
||||||
No hay detalles disponibles
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template #empty>
|
|
||||||
<td colspan="4" class="px-6 py-8 text-center text-gray-500 text-sm">
|
|
||||||
{{ transl("list.empty") }}
|
|
||||||
</td>
|
|
||||||
</template>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { lang } from '@Lang/i18n';
|
|
||||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
|
||||||
|
|
||||||
// Ruta API
|
|
||||||
const apiTo = (name, params = {}) => route(`checkout.${name}`, params)
|
|
||||||
|
|
||||||
// Ruta visual
|
|
||||||
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `checkout.${name}`, params, query })
|
|
||||||
|
|
||||||
// Obtener traducción del componente
|
|
||||||
const transl = (str) => lang(`checkout.${str}`)
|
|
||||||
|
|
||||||
// Determina si un usuario puede hacer algo no en base a los permisos
|
|
||||||
const can = (permission) => hasPermission(`checkout.${permission}`)
|
|
||||||
|
|
||||||
export {
|
|
||||||
can,
|
|
||||||
viewTo,
|
|
||||||
apiTo,
|
|
||||||
transl
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user