Major Refactor: Refactor the codebase to improve readability and maintainability
This commit is contained in:
@@ -1,29 +1,20 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from 'macros/modals.html' import confirm_modal, edit_product_modal %}
|
||||
{% from "macros/ui.html" import flashed_messages %}
|
||||
|
||||
{% block title %}Catálogo de Productos{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<!-- HEAD -->
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2 class="mb-4">Catálogo de Productos por Zona</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">{{ message|safe }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{{ flashed_messages() }}
|
||||
|
||||
{{ edit_product_modal(zonas) }}
|
||||
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header bg-primary text-white">Agregar Producto Maestro</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('manage_products') }}">
|
||||
<form method="POST" action="{{ url_for('admin.manage_products') }}">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-10">
|
||||
<input type="text" class="form-control" name="name" placeholder="Nombre del Producto" required>
|
||||
@@ -69,7 +60,7 @@
|
||||
id='deleteProd' ~ prod.id,
|
||||
title='Eliminar Producto',
|
||||
message='¿Eliminar "' ~ prod.name ~ '"? Esto fallará si el producto ya tiene ventas registradas.',
|
||||
action_url=url_for('delete_product', id=prod.id),
|
||||
action_url=url_for('admin.delete_product', id=prod.id),
|
||||
btn_class='btn-danger',
|
||||
btn_text='Eliminar'
|
||||
) }}
|
||||
@@ -85,7 +76,7 @@
|
||||
<div class="modal fade" id="pricesModal{{ prod.id }}" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<form method="POST" action="{{ url_for('update_product_prices', id=prod.id) }}">
|
||||
<form method="POST" action="{{ url_for('admin.update_product_prices', id=prod.id) }}">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Precios: {{ prod.name }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
@@ -156,7 +147,7 @@
|
||||
<td class="align-middle">${{ "{:,.0f}".format(futuro.price).replace(',', '.') }}</td>
|
||||
<td class="align-middle">${{ "{:,.0f}".format(futuro.commission).replace(',', '.') }}</td>
|
||||
<td class="align-middle">
|
||||
<form action="{{ url_for('cancel_scheduled_price', id=futuro.id) }}" method="POST" class="d-inline">
|
||||
<form action="{{ url_for('admin.cancel_scheduled_price', id=futuro.id) }}" method="POST" class="d-inline">
|
||||
<button type="submit" class="btn btn-danger btn-sm py-0 px-2" title="Cancelar este cambio">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
@@ -196,132 +187,6 @@
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
let priceChartInstance = null;
|
||||
|
||||
async function showHistory(prodId, prodName) {
|
||||
// Abrir el modal inmediatamente para que no parezca que la app murió
|
||||
const modal = new bootstrap.Modal(document.getElementById('chartModal'));
|
||||
document.getElementById('chartModalTitle').innerText = 'Fluctuación de Precio: ' + prodName;
|
||||
modal.show();
|
||||
|
||||
// Traer la data desde nuestra nueva API
|
||||
const res = await fetch(`/admin/api/productos/${prodId}/historial`);
|
||||
const data = await res.json();
|
||||
|
||||
// Extraer zonas únicas y fechas únicas (limpiando la hora para el eje X)
|
||||
const zonas = [...new Set(data.map(d => d.zona))];
|
||||
const fechas = [...new Set(data.map(d => d.fecha.split(' ')[0]))].sort();
|
||||
|
||||
const colors = ['#0d6efd', '#198754', '#dc3545', '#ffc107', '#0dcaf0'];
|
||||
|
||||
const datasets = zonas.map((zona, index) => {
|
||||
let lastPrice = 0;
|
||||
|
||||
// Rellenar huecos: Si no hubo cambio un día, se mantiene el precio del día anterior
|
||||
const dataPoints = fechas.map(f => {
|
||||
const hits = data.filter(d => d.zona === zona && d.fecha.startsWith(f));
|
||||
if (hits.length > 0) {
|
||||
lastPrice = hits[hits.length - 1].price; // Tomar el último cambio de ese día
|
||||
}
|
||||
return lastPrice;
|
||||
});
|
||||
|
||||
return {
|
||||
label: zona,
|
||||
data: dataPoints,
|
||||
borderColor: colors[index % colors.length],
|
||||
backgroundColor: colors[index % colors.length],
|
||||
stepped: true, // Hace que se vea como escaleras en vez de curvas raras
|
||||
borderWidth: 2
|
||||
};
|
||||
});
|
||||
|
||||
// Dibujar (o redibujar) el gráfico
|
||||
const ctx = document.getElementById('priceChart').getContext('2d');
|
||||
if (priceChartInstance) {
|
||||
priceChartInstance.destroy();
|
||||
}
|
||||
|
||||
priceChartInstance = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: fechas,
|
||||
datasets: datasets
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: function(value) { return '$' + value.toLocaleString('es-CL'); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const editModal = document.getElementById('editProductModal');
|
||||
if (editModal) {
|
||||
editModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget;
|
||||
|
||||
// Atributos extraídos del botón
|
||||
const id = button.getAttribute('data-id');
|
||||
const name = button.getAttribute('data-name');
|
||||
const price = button.getAttribute('data-price');
|
||||
const commission = button.getAttribute('data-commission');
|
||||
const zonaId = button.getAttribute('data-zona');
|
||||
|
||||
// Actualizamos el destino del formulario
|
||||
const form = editModal.querySelector('#editProductForm');
|
||||
form.action = `/admin/productos/edit/${id}`;
|
||||
|
||||
// Llenamos los inputs
|
||||
editModal.querySelector('#edit_name').value = name;
|
||||
editModal.querySelector('#edit_price').value = price;
|
||||
editModal.querySelector('#edit_commission').value = commission;
|
||||
editModal.querySelector('#edit_zona_id').value = zonaId;
|
||||
|
||||
// Forzamos el formato de miles (puntos) inmediatamente
|
||||
editModal.querySelectorAll('.money-input').forEach(input => {
|
||||
input.dispatchEvent(new Event('input'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.money-input').forEach(function(input) {
|
||||
input.addEventListener('input', function(e) {
|
||||
let value = this.value.replace(/\D/g, '');
|
||||
if (value !== '') {
|
||||
this.value = parseInt(value, 10).toLocaleString('es-CL');
|
||||
} else {
|
||||
this.value = '';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const searchInput = document.getElementById('searchProduct');
|
||||
const productRows = document.querySelectorAll('.product-row');
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function() {
|
||||
const term = this.value.toLowerCase();
|
||||
productRows.forEach(row => {
|
||||
// Asume que el nombre está en la primera celda
|
||||
const name = row.cells[0].textContent.toLowerCase();
|
||||
row.style.display = name.includes(term) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/product-history-chart.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/admin_productos.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -1,11 +1,9 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from 'macros/modals.html' import alert_modal, rendicion_detail_modal, confirm_modal, edit_rendicion_modal %}
|
||||
{% from "macros/ui.html" import flashed_messages %}
|
||||
|
||||
{% block title %}Historial de Rendiciones{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="mb-0">Historial de Rendiciones</h2>
|
||||
@@ -13,7 +11,7 @@
|
||||
|
||||
<div class="card shadow-sm mb-4 border-0">
|
||||
<div class="card-body bg-body-tertiary rounded p-3">
|
||||
<form method="GET" action="{{ url_for('admin_rendiciones') }}" id="filterForm">
|
||||
<form method="GET" action="{{ url_for('admin.admin_rendiciones') }}" id="filterForm">
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small text-muted mb-1">Año</label>
|
||||
@@ -77,13 +75,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">{{ message|safe }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{{ flashed_messages() }}
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
@@ -123,7 +115,7 @@
|
||||
id='deleteRendicion' ~ r[0],
|
||||
title='Eliminar Rendición',
|
||||
message='¿Estás seguro de que deseas eliminar la rendición #' ~ r[0] ~ '? Esta acción no se puede deshacer.',
|
||||
action_url=url_for('delete_rendicion', id=r[0]),
|
||||
action_url=url_for('admin.delete_rendicion', id=r[0]),
|
||||
btn_class='btn-danger',
|
||||
btn_text='Eliminar'
|
||||
) }}
|
||||
@@ -142,171 +134,5 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Función para manejar las etiquetas de jornada (Full/Part Time)
|
||||
function updateBadge(selectElement, badgeId) {
|
||||
const option = selectElement.options[selectElement.selectedIndex];
|
||||
const tipo = option.getAttribute('data-tipo');
|
||||
const badgeDiv = document.getElementById(badgeId);
|
||||
|
||||
if (!badgeDiv) return;
|
||||
|
||||
if (!tipo) {
|
||||
badgeDiv.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const color = (tipo === 'Full Time') ? 'bg-success' : 'bg-secondary';
|
||||
badgeDiv.innerHTML = `<span class="badge ${color}">${tipo}</span>`;
|
||||
}
|
||||
|
||||
function toggleCompDiv(id, select) {
|
||||
const compDiv = document.getElementById(`comp_com_div_${id}`);
|
||||
compDiv.style.display = select.value ? 'flex' : 'none';
|
||||
updateBadge(select, `badge_comp_${id}`);
|
||||
updateComisionToggle(select, `cc_${id}`);
|
||||
}
|
||||
|
||||
function updateComisionToggle(selectElement, toggleId) {
|
||||
const option = selectElement.options[selectElement.selectedIndex];
|
||||
const tipoJornada = option.getAttribute('data-tipo');
|
||||
const toggleSwitch = document.getElementById(toggleId);
|
||||
|
||||
if (toggleSwitch && tipoJornada) {
|
||||
// Explicitly set to true if Full Time, false otherwise
|
||||
toggleSwitch.checked = (tipoJornada === 'Full Time');
|
||||
} else if (toggleSwitch && !selectElement.value) {
|
||||
// If "Sin acompañante" is selected, turn it off
|
||||
toggleSwitch.checked = false;
|
||||
}
|
||||
|
||||
// Actualizar el badge también
|
||||
const baseId = toggleId.split('_')[1];
|
||||
const targetBadge = toggleId.startsWith('wc') ? `badge_worker_${baseId}` : `badge_comp_${baseId}`;
|
||||
updateBadge(selectElement, targetBadge);
|
||||
}
|
||||
|
||||
// Recalcular total de la línea de producto y el total del sistema
|
||||
function recalcProductLine(input) {
|
||||
const qty = parseInt(input.value) || 0;
|
||||
const price = parseInt(input.getAttribute('data-price')) || 0;
|
||||
const rid = input.getAttribute('data-rid');
|
||||
const row = input.closest('tr');
|
||||
|
||||
// Actualizar línea individual
|
||||
const lineTotal = qty * price;
|
||||
row.querySelector('.item-total-line').innerText = '$' + lineTotal.toLocaleString('es-CL');
|
||||
|
||||
// Recalcular total general del sistema en el modal
|
||||
const modal = document.getElementById(`editRendicion${rid}`);
|
||||
let newSysTotal = 0;
|
||||
modal.querySelectorAll('.prod-qty-input').forEach(inp => {
|
||||
newSysTotal += (parseInt(inp.value) || 0) * (parseInt(inp.getAttribute('data-price')) || 0);
|
||||
});
|
||||
document.getElementById(`sys_total_${rid}`).innerText = '$' + newSysTotal.toLocaleString('es-CL');
|
||||
}
|
||||
|
||||
function calcTotalEdit(id) {
|
||||
const getVal = (inputId) => parseInt(document.getElementById(inputId).value.replace(/\D/g, '')) || 0;
|
||||
const total = getVal(`edit_debito_${id}`) + getVal(`edit_credito_${id}`) + getVal(`edit_mp_${id}`) + getVal(`edit_efectivo_${id}`);
|
||||
document.getElementById(`display_nuevo_total_${id}`).innerText = '$' + total.toLocaleString('es-CL');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const editModals = document.querySelectorAll('[id^="editRendicion"]');
|
||||
const editForms = document.querySelectorAll('form[action*="/admin/rendiciones/edit/"]');
|
||||
const errorModalEl = document.getElementById('errorPersonaModal');
|
||||
const errorModal = new bootstrap.Modal(errorModalEl);
|
||||
const errorBody = document.getElementById('errorPersonaModalBody');
|
||||
|
||||
editForms.forEach(form => {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const workerId = this.querySelector('select[name="worker_id"]').value;
|
||||
const companionId = this.querySelector('select[name="companion_id"]').value;
|
||||
|
||||
if (companionId && workerId === companionId) {
|
||||
e.preventDefault();
|
||||
errorBody.innerHTML = "<strong>Error:</strong> El trabajador titular y el acompañante no pueden ser la misma persona. Por favor, selecciona a alguien más.";
|
||||
errorModal.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
editModals.forEach(modal => {
|
||||
// Inicializar badges al abrir
|
||||
modal.addEventListener('show.bs.modal', function() {
|
||||
const rid = this.id.replace('editRendicion', '');
|
||||
updateBadge(this.querySelector('select[name="worker_id"]'), `badge_worker_${rid}`);
|
||||
const compSelect = this.querySelector('select[name="companion_id"]');
|
||||
if (compSelect.value) updateBadge(compSelect, `badge_comp_${rid}`);
|
||||
});
|
||||
|
||||
modal.addEventListener('hidden.bs.modal', function () {
|
||||
const form = this.querySelector('form');
|
||||
if (form) {
|
||||
form.reset();
|
||||
const rid = this.id.replace('editRendicion', '');
|
||||
calcTotalEdit(rid);
|
||||
// Resetear los subtotales visuales de productos
|
||||
this.querySelectorAll('.prod-qty-input').forEach(inp => recalcProductLine(inp));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function validarNombresDiferentes(rendicionId) {
|
||||
const workerSelect = document.querySelector(`select[name="worker_id"]`);
|
||||
const companionSelect = document.querySelector(`select[name="companion_id"]`);
|
||||
|
||||
if (companionSelect.value && workerSelect.value === companionSelect.value) {
|
||||
alert("Error: El trabajador titular y el acompañante no pueden ser la misma persona.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Vincula esta validación al evento submit del formulario de edición
|
||||
document.querySelectorAll('form[action*="edit_rendicion"]').forEach(form => {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const workerSelect = this.querySelector('select[name="worker_id"]');
|
||||
const companionSelect = this.querySelector('select[name="companion_id"]');
|
||||
|
||||
if (companionSelect.value && workerSelect.value === companionSelect.value) {
|
||||
e.preventDefault();
|
||||
alert("Un trabajador no puede ser su propio acompañante. Por favor, corrige la selección.");
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const zonaSelect = document.getElementById('zonaSelect');
|
||||
const moduloSelect = document.getElementById('moduloSelect');
|
||||
const moduloOptions = Array.from(moduloSelect.options);
|
||||
|
||||
function filterModulos() {
|
||||
const selectedZona = zonaSelect.value;
|
||||
|
||||
moduloOptions.forEach(option => {
|
||||
if (option.value === "") {
|
||||
// Siempre mostramos "Todos los Módulos"
|
||||
option.style.display = '';
|
||||
} else if (!selectedZona || option.dataset.zona === selectedZona) {
|
||||
option.style.display = '';
|
||||
} else {
|
||||
option.style.display = 'none';
|
||||
// Si el módulo seleccionado acaba de ocultarse, reseteamos el select
|
||||
if (option.selected) {
|
||||
moduloSelect.value = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
zonaSelect.addEventListener('change', filterModulos);
|
||||
// Ejecutar al cargar la página por si ya viene con una zona filtrada
|
||||
filterModulos();
|
||||
});
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/admin_rendiciones.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -1,48 +1,13 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from "macros/modals.html" import report_filters %}
|
||||
{% from "macros/ui.html" import back_link %}
|
||||
|
||||
{% block title %}Reporte: Centros Comerciales - {{ modulo_name }}{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
.table-container {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.sticky-col {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
background-color: var(--bs-body-bg);
|
||||
border-right: 2px solid var(--bs-border-color) !important;
|
||||
}
|
||||
|
||||
thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: var(--bs-body-bg);
|
||||
box-shadow: inset 0 -1px 0 var(--bs-border-color);
|
||||
}
|
||||
|
||||
thead th.sticky-col {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.numeric-cell {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<a href="{{ url_for('admin_reportes_index') }}" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="bi bi-arrow-left"></i> Volver al Menú
|
||||
</a>
|
||||
{{ back_link(url_for('admin.admin_reportes_index'), 'Volver al Menú') }}
|
||||
<h2>Registro Centros Comerciales</h2>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
@@ -51,7 +16,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ report_filters(
|
||||
url_for('report_modulo_periodo', modulo_id=modulo_id),
|
||||
url_for('admin.report_modulo_periodo', modulo_id=modulo_id),
|
||||
workers_list,
|
||||
worker_actual,
|
||||
dia_actual,
|
||||
|
||||
@@ -1,30 +1,13 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from "macros/modals.html" import report_filters %}
|
||||
{% from "macros/ui.html" import back_link %}
|
||||
|
||||
{% block title %}Reporte: Comisiones - {{ modulo_name }}{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
.numeric-cell {
|
||||
text-align: right;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
.sticky-col {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
background-color: #f8f9fa !important;
|
||||
border-right: 2px solid #dee2e6;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<a href="{{ url_for('admin_reportes_index') }}" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="bi bi-arrow-left"></i> Volver al Menú
|
||||
</a>
|
||||
{{ back_link(url_for('admin.admin_reportes_index'), 'Volver al Menú') }}
|
||||
<h2>Reporte de Comisiones</h2>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
@@ -33,7 +16,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ report_filters(
|
||||
url_for('report_modulo_periodo', modulo_id=modulo_id),
|
||||
url_for('admin.report_modulo_periodo', modulo_id=modulo_id),
|
||||
workers_list,
|
||||
worker_actual,
|
||||
dia_actual,
|
||||
|
||||
@@ -1,44 +1,13 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from "macros/modals.html" import report_filters %}
|
||||
{% from "macros/ui.html" import back_link %}
|
||||
|
||||
{% block title %}Reporte: Horarios - {{ modulo_name }}{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
.table-container {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.sticky-col {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
background-color: var(--bs-body-bg);
|
||||
border-right: 2px solid var(--bs-border-color) !important;
|
||||
}
|
||||
|
||||
thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: var(--bs-body-bg);
|
||||
box-shadow: inset 0 -1px 0 var(--bs-border-color);
|
||||
}
|
||||
|
||||
thead th.sticky-col {
|
||||
z-index: 3;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<a href="{{ url_for('admin_reportes_index') }}" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="bi bi-arrow-left"></i> Volver al Menú
|
||||
</a>
|
||||
{{ back_link(url_for('admin.admin_reportes_index'), 'Volver al Menú') }}
|
||||
<h2>Control de Horarios</h2>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
@@ -47,7 +16,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ report_filters(
|
||||
url_for('report_modulo_periodo', modulo_id=modulo_id),
|
||||
url_for('admin.report_modulo_periodo', modulo_id=modulo_id),
|
||||
workers_list,
|
||||
worker_actual,
|
||||
dia_actual,
|
||||
|
||||
@@ -1,48 +1,13 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from "macros/modals.html" import report_filters %}
|
||||
{% from "macros/ui.html" import back_link %}
|
||||
|
||||
{% block title %}Reporte: Cálculo de IVA - {{ modulo_name }}{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
.table-container {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.sticky-col {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
background-color: var(--bs-body-bg);
|
||||
border-right: 2px solid var(--bs-border-color) !important;
|
||||
}
|
||||
|
||||
thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: var(--bs-body-bg);
|
||||
box-shadow: inset 0 -1px 0 var(--bs-border-color);
|
||||
}
|
||||
|
||||
thead th.sticky-col {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.numeric-cell {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<a href="{{ url_for('admin_reportes_index') }}" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="bi bi-arrow-left"></i> Volver al Menú
|
||||
</a>
|
||||
{{ back_link(url_for('admin.admin_reportes_index'), 'Volver al Menú') }}
|
||||
<h2>Cálculo de IVA</h2>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
@@ -51,7 +16,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ report_filters(
|
||||
url_for('report_modulo_periodo', modulo_id=modulo_id),
|
||||
url_for('admin.report_modulo_periodo', modulo_id=modulo_id),
|
||||
workers_list,
|
||||
worker_actual,
|
||||
dia_actual,
|
||||
|
||||
@@ -1,35 +1,13 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from "macros/modals.html" import report_filters %}
|
||||
{% from "macros/ui.html" import back_link %}
|
||||
|
||||
{% block title %}Reporte: Finanzas - {{ modulo_name }}{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
.numeric-cell {
|
||||
text-align: right;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-weight: 500;
|
||||
}
|
||||
.total-column {
|
||||
font-weight: bold;
|
||||
background-color: #e9ecef !important;
|
||||
}
|
||||
.sticky-col {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
background-color: #f8f9fa !important;
|
||||
border-right: 2px solid #dee2e6;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<a href="{{ url_for('admin_reportes_index') }}" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="bi bi-arrow-left"></i> Volver al Menú
|
||||
</a>
|
||||
{{ back_link(url_for('admin.admin_reportes_index'), 'Volver al Menú') }}
|
||||
<h2>Resumen Financiero y Medios de Pago</h2>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
@@ -74,7 +52,7 @@
|
||||
</div>
|
||||
|
||||
{{ report_filters(
|
||||
url_for('report_modulo_periodo', modulo_id=modulo_id),
|
||||
url_for('admin.report_modulo_periodo', modulo_id=modulo_id),
|
||||
workers_list,
|
||||
worker_actual,
|
||||
dia_actual,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<h4 class="text-info border-bottom border-secondary pb-2 mb-4">
|
||||
<i class="bi bi-geo-alt-fill me-2"></i>Zona: {{ zona_name }}
|
||||
</h4>
|
||||
|
||||
|
||||
<div class="row g-4">
|
||||
{% for mod in lista_modulos %}
|
||||
<div class="col-md-4 col-sm-6">
|
||||
@@ -45,23 +45,4 @@
|
||||
{{ reportes_menu_modal(mod[0], mod[1]) }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
<style>
|
||||
.hover-shadow:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 .5rem 1rem rgba(0,0,0,.3)!important;
|
||||
border-color: #0dcaf0 !important;
|
||||
}
|
||||
.hover-card {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
background-color: #1e2125;
|
||||
}
|
||||
.hover-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 .5rem 1rem rgba(0,0,0,.25)!important;
|
||||
}
|
||||
.transition-all {
|
||||
transition: all .3s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -1,18 +1,13 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% import "macros/modals.html" as modals %}
|
||||
{% from "macros/ui.html" import flashed_messages %}
|
||||
|
||||
{% block title %}Estructura Operativa{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2 class="mb-4">Estructura Operativa</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">{{ message|safe }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{{ flashed_messages() }}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
@@ -21,7 +16,7 @@
|
||||
<h5 class="mb-0">Gestión de Zonas</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('manage_structure') }}" class="mb-4">
|
||||
<form method="POST" action="{{ url_for('admin.manage_structure') }}" class="mb-4">
|
||||
<input type="hidden" name="action" value="add_zona">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="zona_name" placeholder="Nombre de la Zona (ej: Norte)" required>
|
||||
@@ -53,7 +48,7 @@
|
||||
'deleteZona' ~ zona[0],
|
||||
'Eliminar Zona',
|
||||
'¿Estás seguro de eliminar la zona "' ~ zona[1] ~ '"? Esto podría afectar a los módulos asociados.',
|
||||
url_for('delete_structure', type='zona', id=zona[0]),
|
||||
url_for('admin.delete_structure', type='zona', id=zona[0]),
|
||||
'btn-danger',
|
||||
'Eliminar'
|
||||
) }}
|
||||
@@ -76,7 +71,7 @@
|
||||
<h5 class="mb-0">Gestión de Módulos</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('manage_structure') }}" class="mb-4">
|
||||
<form method="POST" action="{{ url_for('admin.manage_structure') }}" class="mb-4">
|
||||
<input type="hidden" name="action" value="add_modulo">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-5">
|
||||
@@ -122,7 +117,7 @@
|
||||
'deleteModulo' ~ modulo[0],
|
||||
'Eliminar Módulo',
|
||||
'¿Deseas eliminar el módulo "' ~ modulo[1] ~ '"?',
|
||||
url_for('delete_structure', type='modulo', id=modulo[0]),
|
||||
url_for('admin.delete_structure', type='modulo', id=modulo[0]),
|
||||
'btn-danger',
|
||||
'Eliminar'
|
||||
) }}
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from 'macros/modals.html' import confirm_modal, edit_worker_modal %}
|
||||
{% from "macros/ui.html" import flashed_messages %}
|
||||
|
||||
{% block title %}Gestión de Trabajadores{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<!-- HEAD -->
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2 class="mb-4">Gestión de Trabajadores</h2>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">{{ message|safe }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{{ flashed_messages() }}
|
||||
|
||||
{{ edit_worker_modal(modulos) }}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">Agregar Nuevo Trabajador</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('manage_workers') }}">
|
||||
<form method="POST" action="{{ url_for('admin.manage_workers') }}">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">RUT</label>
|
||||
@@ -134,7 +125,7 @@
|
||||
id='delWorker' ~ worker[0],
|
||||
title='Eliminar Trabajador',
|
||||
message='¿Eliminar a ' ~ worker[2] ~ '?',
|
||||
action_url=url_for('delete_worker', id=worker[0]),
|
||||
action_url=url_for('admin.delete_worker', id=worker[0]),
|
||||
btn_class='btn-danger'
|
||||
) }}
|
||||
</td>
|
||||
@@ -159,116 +150,5 @@
|
||||
) }}
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
|
||||
<script>
|
||||
const editWorkerModal = document.getElementById('editWorkerModal');
|
||||
const confirmResetModal = document.getElementById('confirmResetPass');
|
||||
|
||||
if (editWorkerModal) {
|
||||
editWorkerModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget;
|
||||
|
||||
// Si el modal se abre desde el botón "Editar" de la tabla
|
||||
if (button && button.hasAttribute('data-id')) {
|
||||
const id = button.getAttribute('data-id');
|
||||
const name = button.getAttribute('data-name');
|
||||
|
||||
const editForm = editWorkerModal.querySelector('#editWorkerForm');
|
||||
const resetForm = confirmResetModal.querySelector('form');
|
||||
|
||||
editForm.action = "/admin/workers/edit/" + id;
|
||||
resetForm.action = "/admin/workers/reset_password/" + id;
|
||||
|
||||
confirmResetModal.querySelector('.modal-body').textContent = `¿Estás seguro de generar una nueva contraseña para ${name}? La anterior dejará de funcionar.`;
|
||||
|
||||
editWorkerModal.querySelector('#edit_worker_rut').value = button.getAttribute('data-rut');
|
||||
editWorkerModal.querySelector('#edit_worker_name').value = name;
|
||||
editWorkerModal.querySelector('#edit_worker_phone').value = button.getAttribute('data-phone');
|
||||
editWorkerModal.querySelector('#edit_worker_modulo').value = button.getAttribute('data-modulo');
|
||||
editWorkerModal.querySelector('#edit_worker_tipo').value = button.getAttribute('data-tipo');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Lógica para reabrir el modal de edición al cancelar el de confirmación
|
||||
if (confirmResetModal) {
|
||||
// Buscamos el botón de cancelar dentro del modal de confirmación
|
||||
const btnCancelar = confirmResetModal.querySelector('.btn-secondary');
|
||||
const btnCerrarX = confirmResetModal.querySelector('.btn-close');
|
||||
|
||||
const reabrirEdicion = () => {
|
||||
const modalEdicion = new bootstrap.Modal(editWorkerModal);
|
||||
modalEdicion.show();
|
||||
};
|
||||
|
||||
btnCancelar.addEventListener('click', reabrirEdicion);
|
||||
btnCerrarX.addEventListener('click', reabrirEdicion);
|
||||
}
|
||||
|
||||
document.getElementById('rutInput').addEventListener('input', function(e) {
|
||||
let value = this.value.replace(/[^0-9kK]/g, '').toUpperCase();
|
||||
|
||||
// Bloquear el largo interno a 9 caracteres (8 cuerpo + 1 dv)
|
||||
if (value.length > 9) {
|
||||
value = value.slice(0, 9);
|
||||
}
|
||||
|
||||
if (value.length > 1) {
|
||||
let body = value.slice(0, -1);
|
||||
let dv = value.slice(-1);
|
||||
body = body.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||
this.value = `${body}-${dv}`;
|
||||
} else {
|
||||
this.value = value;
|
||||
}
|
||||
});
|
||||
|
||||
function formatPhone(input) {
|
||||
let value = input.value.replace(/\D/g, '');
|
||||
if (value.startsWith('56')) value = value.substring(2);
|
||||
value = value.substring(0, 9);
|
||||
if (value.length > 5) input.value = value.replace(/(\d{1})(\d{4})(\d+)/, '$1 $2 $3');
|
||||
else if (value.length > 1) input.value = value.replace(/(\d{1})(\d+)/, '$1 $2');
|
||||
else input.value = value;
|
||||
}
|
||||
|
||||
document.querySelectorAll('.phone-input, #phoneInput').forEach(inp => {
|
||||
inp.addEventListener('input', () => formatPhone(inp));
|
||||
});
|
||||
|
||||
|
||||
// --- LÓGICA DE FILTRADO DE TRABAJADORES ---
|
||||
const searchInputWorker = document.getElementById('searchWorker');
|
||||
const moduleSelectFilter = document.getElementById('filterModule');
|
||||
const typeSelectFilter = document.getElementById('filterType');
|
||||
const workerRows = document.querySelectorAll('.worker-row');
|
||||
|
||||
function filterWorkers() {
|
||||
const searchTerm = searchInputWorker.value.toLowerCase();
|
||||
const selectedModule = moduleSelectFilter.value;
|
||||
const selectedType = typeSelectFilter.value;
|
||||
|
||||
workerRows.forEach(row => {
|
||||
// Asumiendo que celda 0 es RUT y celda 1 es Nombre
|
||||
const rut = row.cells[0].textContent.toLowerCase();
|
||||
const name = row.cells[1].textContent.toLowerCase();
|
||||
const rowModule = row.getAttribute('data-modulo');
|
||||
const rowType = row.getAttribute('data-tipo');
|
||||
|
||||
const matchesSearch = rut.includes(searchTerm) || name.includes(searchTerm);
|
||||
const matchesModule = selectedModule === 'all' || rowModule === selectedModule;
|
||||
const matchesType = selectedType === 'all' || rowType === selectedType;
|
||||
|
||||
if (matchesSearch && matchesModule && matchesType) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
searchInputWorker.addEventListener('input', filterWorkers);
|
||||
moduleSelectFilter.addEventListener('change', filterWorkers);
|
||||
typeSelectFilter.addEventListener('change', filterWorkers);
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/admin_workers.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from "macros/ui.html" import flashed_messages %}
|
||||
|
||||
{% block title %}Inicio de sesión{% endblock %}
|
||||
|
||||
@@ -10,14 +11,8 @@
|
||||
<h4 class="mb-0">Iniciar Sesión</h4>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">{{ message|safe }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{{ flashed_messages() }}
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">RUT</label>
|
||||
@@ -43,22 +38,5 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.getElementById('rutInput').addEventListener('input', function(e) {
|
||||
let value = this.value.replace(/[^0-9kK]/g, '').toUpperCase();
|
||||
|
||||
if (value.length > 9) {
|
||||
value = value.slice(0, 9);
|
||||
}
|
||||
|
||||
if (value.length > 1) {
|
||||
let body = value.slice(0, -1);
|
||||
let dv = value.slice(-1);
|
||||
body = body.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||
this.value = `${body}-${dv}`;
|
||||
} else {
|
||||
this.value = value;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
<script src="{{ url_for('static', filename='js/login.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<title>Sistema de Rendiciones - {% block title %}{% endblock %}</title>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.png') }}" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/components.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/report-tables.css') }}">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
</head>
|
||||
@@ -21,7 +23,9 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='cookieStuff.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='themeStuff.js') }}"></script>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/format-helpers.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/navbar.js') }}"></script>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -275,7 +275,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted d-block mb-1">Observaciones:</span>
|
||||
<p class="mb-0 bg-dark p-2 rounded border border-secondary text-wrap text-break" style="font-size: 0.9em;">
|
||||
<p class="mb-0 bg-body-tertiary p-2 rounded border border-secondary text-wrap text-break" style="font-size: 0.9em;">
|
||||
{{ rendicion[9] if rendicion[9] else "Sin observaciones." }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -301,7 +301,7 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body text-start">
|
||||
<form method="POST" action="{{ url_for('edit_rendicion', id=rendicion[0]) }}" id="editRendicionForm_{{ rendicion[0] }}">
|
||||
<form method="POST" action="{{ url_for('admin.edit_rendicion', id=rendicion[0]) }}" id="editRendicionForm_{{ rendicion[0] }}">
|
||||
<div class="row">
|
||||
<div class="col-md-8 mb-3">
|
||||
<div class="card shadow-sm h-100">
|
||||
@@ -400,32 +400,32 @@
|
||||
<label class="small text-muted mb-0">Débito</label>
|
||||
<input type="text" class="form-control form-control-sm text-end money-input mb-1" id="edit_debito_{{ rendicion[0] }}" name="venta_debito" value="{{ '{:,.0f}'.format(rendicion[4] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text bg-dark border-secondary text-muted" style="font-size: 0.7em;">Boletas</span>
|
||||
<input type="number" class="form-control text-center bg-dark text-white border-secondary" name="boletas_debito" value="{{ rendicion[16] or 0 }}">
|
||||
<span class="input-group-text bg-body-tertiary text-muted" style="font-size: 0.7em;">Boletas</span>
|
||||
<input type="number" class="form-control text-center bg-body text-body border-secondary" name="boletas_debito" value="{{ rendicion[16] or 0 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="small text-muted mb-0">Crédito</label>
|
||||
<input type="text" class="form-control form-control-sm text-end money-input mb-1" id="edit_credito_{{ rendicion[0] }}" name="venta_credito" value="{{ '{:,.0f}'.format(rendicion[5] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text bg-dark border-secondary text-muted" style="font-size: 0.7em;">Boletas</span>
|
||||
<input type="number" class="form-control text-center bg-dark text-white border-secondary" name="boletas_credito" value="{{ rendicion[17] or 0 }}">
|
||||
<span class="input-group-text bg-body-tertiary text-muted" style="font-size: 0.7em;">Boletas</span>
|
||||
<input type="number" class="form-control text-center bg-body text-body border-secondary" name="boletas_credito" value="{{ rendicion[17] or 0 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="small text-muted mb-0">Mercado Pago</label>
|
||||
<input type="text" class="form-control form-control-sm text-end money-input mb-1" id="edit_mp_{{ rendicion[0] }}" name="venta_mp" value="{{ '{:,.0f}'.format(rendicion[6] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text bg-dark border-secondary text-muted" style="font-size: 0.7em;">Boletas</span>
|
||||
<input type="number" class="form-control text-center bg-dark text-white border-secondary" name="boletas_mp" value="{{ rendicion[18] or 0 }}">
|
||||
<span class="input-group-text bg-body-tertiary text-muted" style="font-size: 0.7em;">Boletas</span>
|
||||
<input type="number" class="form-control text-center bg-body text-body border-secondary" name="boletas_mp" value="{{ rendicion[18] or 0 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="small text-muted mb-0">Efectivo</label>
|
||||
<input type="text" class="form-control form-control-sm text-end money-input mb-1" id="edit_efectivo_{{ rendicion[0] }}" name="venta_efectivo" value="{{ '{:,.0f}'.format(rendicion[7] or 0).replace(',', '.') }}" oninput="calcTotalEdit({{ rendicion[0] }})">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text bg-dark border-secondary text-muted" style="font-size: 0.7em;">Boletas</span>
|
||||
<input type="number" class="form-control text-center bg-dark text-white border-secondary" name="boletas_efectivo" value="{{ rendicion[19] or 0 }}">
|
||||
<span class="input-group-text bg-body-tertiary text-muted" style="font-size: 0.7em;">Boletas</span>
|
||||
<input type="number" class="form-control text-center bg-body text-body border-secondary" name="boletas_efectivo" value="{{ rendicion[19] or 0 }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -443,13 +443,13 @@
|
||||
<div class="mb-2">
|
||||
<label class="small text-danger fw-bold">Monto Gastos</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text bg-dark border-danger text-danger">-$</span>
|
||||
<span class="input-group-text bg-danger-subtle border-danger text-danger">-$</span>
|
||||
<input type="text" class="form-control money-input border-danger text-end" name="gastos" value="{{ '{:,.0f}'.format(rendicion[8] or 0).replace(',', '.') }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label class="small text-muted">Observaciones</label>
|
||||
<textarea class="form-control form-control-sm bg-dark text-white" name="observaciones" rows="2">{{ rendicion[9] }}</textarea>
|
||||
<textarea class="form-control form-control-sm bg-body text-body" name="observaciones" rows="2">{{ rendicion[9] }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -486,7 +486,7 @@
|
||||
<h5 class="card-title mb-0">Detalle de Ventas</h5>
|
||||
</div>
|
||||
<p class="text-muted small flex-grow-1">Análisis detallado de ventas diarias, productos vendidos y consolidado mensual.</p>
|
||||
<a href="{{ url_for('report_modulo_periodo', modulo_id=modulo_id) }}" class="btn btn-primary w-100 mt-3">Generar Reporte</a>
|
||||
<a href="{{ url_for('admin.report_modulo_periodo', modulo_id=modulo_id) }}" class="btn btn-primary w-100 mt-3">Generar Reporte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -501,7 +501,7 @@
|
||||
<h5 class="card-title mb-0">Comisiones</h5>
|
||||
</div>
|
||||
<p class="text-muted small flex-grow-1">Cálculo de comisiones generadas por los trabajadores en este módulo.</p>
|
||||
<a href="{{ url_for('report_modulo_comisiones', modulo_id=modulo_id) }}" class="btn btn-success w-100 mt-3">Generar Reporte</a>
|
||||
<a href="{{ url_for('admin.report_modulo_comisiones', modulo_id=modulo_id) }}" class="btn btn-success w-100 mt-3">Generar Reporte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -516,7 +516,7 @@
|
||||
<h5 class="card-title mb-0">Control de Horarios</h5>
|
||||
</div>
|
||||
<p class="text-muted small flex-grow-1">Registro de horas de entrada y salida de los trabajadores y acompañantes.</p>
|
||||
<a href="{{ url_for('report_modulo_horarios', modulo_id=modulo_id) }}" class="btn btn-warning w-100 mt-3">Generar Reporte</a>
|
||||
<a href="{{ url_for('admin.report_modulo_horarios', modulo_id=modulo_id) }}" class="btn btn-warning w-100 mt-3">Generar Reporte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -531,7 +531,7 @@
|
||||
<h5 class="card-title mb-0">Centros Comerciales</h5>
|
||||
</div>
|
||||
<p class="text-muted small flex-grow-1">Reporte de datos exigidos por la administración del centro comercial.</p>
|
||||
<a href="{{ url_for('report_modulo_centros_comerciales', modulo_id=modulo_id) }}" class="btn btn-info w-100 mt-3">Generar Reporte</a>
|
||||
<a href="{{ url_for('admin.report_modulo_centros_comerciales', modulo_id=modulo_id) }}" class="btn btn-info w-100 mt-3">Generar Reporte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -546,7 +546,7 @@
|
||||
<h5 class="card-title mb-0">Cálculo de IVA</h5>
|
||||
</div>
|
||||
<p class="text-muted small flex-grow-1">Proyección de impuestos basados en las ventas declaradas.</p>
|
||||
<a href="{{ url_for('report_modulo_calculo_iva', modulo_id=modulo_id) }}" class="btn btn-secondary w-100 mt-3">Generar Reporte</a>
|
||||
<a href="{{ url_for('admin.report_modulo_calculo_iva', modulo_id=modulo_id) }}" class="btn btn-secondary w-100 mt-3">Generar Reporte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary shadow-sm border-bottom mb-4">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex flex-column align-items-start text-primary-emphasis" href="{{ url_for('index') }}" style="gap: 0;">
|
||||
<a class="navbar-brand d-flex flex-column align-items-start text-primary-emphasis" href="{{ url_for('auth.index') }}" style="gap: 0;">
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<i id="brandIcon" class="bi bi-receipt-cutoff fs-3 text-info me-2"></i>
|
||||
@@ -21,33 +21,33 @@
|
||||
<ul class="navbar-nav me-auto">
|
||||
{% if session.get('is_admin') %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center {{ 'active fw-bold' if 'rendiciones' in request.path }}" href="{{ url_for('admin_rendiciones') }}">
|
||||
<a class="nav-link d-flex align-items-center {{ 'active fw-bold' if 'rendiciones' in request.path }}" href="{{ url_for('admin.admin_rendiciones') }}">
|
||||
<i class="bi bi-journal-text me-1"></i> Rendiciones
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center {{ 'active fw-bold' if request.endpoint == 'manage_workers' }}" href="{{ url_for('manage_workers') }}">
|
||||
<a class="nav-link d-flex align-items-center {{ 'active fw-bold' if request.endpoint == 'admin.manage_workers' }}" href="{{ url_for('admin.manage_workers') }}">
|
||||
<i class="bi bi-people me-1"></i> Trabajadores
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center {{ 'active fw-bold' if request.endpoint == 'manage_structure' }}" href="{{ url_for('manage_structure') }}">
|
||||
<a class="nav-link d-flex align-items-center {{ 'active fw-bold' if request.endpoint == 'admin.manage_structure' }}" href="{{ url_for('admin.manage_structure') }}">
|
||||
<i class="bi bi-diagram-3 me-1"></i> Estructura
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center {{ 'active fw-bold' if request.endpoint == 'manage_products' }}" href="{{ url_for('manage_products') }}">
|
||||
<a class="nav-link d-flex align-items-center {{ 'active fw-bold' if request.endpoint == 'admin.manage_products' }}" href="{{ url_for('admin.manage_products') }}">
|
||||
<i class="bi bi-box-seam me-1"></i> Productos
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint and 'reporte' in request.endpoint %}active fw-bold text-white{% endif %}" href="{{ url_for('admin_reportes_index') }}">
|
||||
<a class="nav-link {% if request.endpoint and 'reporte' in request.endpoint %}active fw-bold text-white{% endif %}" href="{{ url_for('admin.admin_reportes_index') }}">
|
||||
<i class="bi bi-graph-up me-1"></i> Reportes
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ 'active fw-bold' if request.endpoint == 'worker_dashboard' }}" href="{{ url_for('worker_dashboard') }}">
|
||||
<a class="nav-link {{ 'active fw-bold' if request.endpoint == 'worker.worker_dashboard' }}" href="{{ url_for('worker.worker_dashboard') }}">
|
||||
<i class="bi bi-speedometer2 me-1"></i> Mis Rendiciones
|
||||
</a>
|
||||
</li>
|
||||
@@ -63,7 +63,7 @@
|
||||
<i class="bi bi-person-circle me-1 text-info"></i> {{ session.get('rut') }}
|
||||
</span>
|
||||
|
||||
<a href="{{ url_for('logout') }}" class="btn btn-outline-danger btn-sm rounded-pill px-3">
|
||||
<a href="{{ url_for('auth.logout') }}" class="btn btn-outline-danger btn-sm rounded-pill px-3">
|
||||
Salir
|
||||
</a>
|
||||
</div>
|
||||
@@ -72,52 +72,3 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
@keyframes baguetteRoll {
|
||||
0% { transform: rotate(0deg) scale(1.2); }
|
||||
50% { transform: rotate(180deg) scale(1.5); }
|
||||
100% { transform: rotate(360deg) scale(1); }
|
||||
}
|
||||
.baguette-spin {
|
||||
display: inline-block;
|
||||
animation: baguetteRoll 1s ease-in-out;
|
||||
font-style: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const brandIcon = document.getElementById("brandIcon");
|
||||
if (brandIcon) {
|
||||
let clickCount = 0;
|
||||
let clickResetTimer;
|
||||
|
||||
brandIcon.addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
clickCount++;
|
||||
|
||||
clearTimeout(clickResetTimer);
|
||||
clickResetTimer = setTimeout(() => {
|
||||
clickCount = 0;
|
||||
}, 800);
|
||||
|
||||
if (clickCount >= 5) {
|
||||
clickCount = 0;
|
||||
clearTimeout(clickResetTimer);
|
||||
|
||||
const originalClass = this.className;
|
||||
|
||||
this.className = "fs-3 me-2 baguette-spin";
|
||||
this.innerHTML = "🥖";
|
||||
|
||||
setTimeout(() => {
|
||||
this.className = originalClass;
|
||||
this.innerHTML = "";
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
22
templates/macros/ui.html
Normal file
22
templates/macros/ui.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{% macro flashed_messages() %}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">{{ message|safe }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro back_link(url, text='Volver', icon='bi-arrow-left') %}
|
||||
<a href="{{ url }}" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="bi {{ icon }}"></i> {{ text }}
|
||||
</a>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro page_header(title, icon='', actions='') %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>{% if icon %}<i class="bi {{ icon }} me-2"></i>{% endif %}{{ title }}</h2>
|
||||
{% if actions %}{{ actions|safe }}{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,19 +1,14 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from 'macros/modals.html' import confirm_modal, alert_modal %}
|
||||
{% from "macros/ui.html" import flashed_messages, back_link %}
|
||||
|
||||
{% block title %}Rendición de Caja{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<!-- HEAD -->
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<a href="{{ url_for('worker_dashboard') }}" class="btn btn-outline-secondary btn-sm mb-2">
|
||||
<i class="bi bi-arrow-left"></i> Volver al Historial
|
||||
</a>
|
||||
{{ back_link(url_for('worker.worker_dashboard'), 'Volver al Historial') }}
|
||||
<h2>Nueva Rendición de Caja</h2>
|
||||
</div>
|
||||
<div class="text-end text-muted">
|
||||
@@ -22,13 +17,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">{{ message|safe }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{{ flashed_messages() }}
|
||||
|
||||
<form method="POST">
|
||||
<div class="card mb-4 shadow-sm">
|
||||
@@ -241,228 +230,5 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.getElementById('companion_select').addEventListener('change', function() {
|
||||
const timeDiv = document.getElementById('companion_times_div');
|
||||
const compIn = document.getElementById('comp_in');
|
||||
const compOut = document.getElementById('comp_out');
|
||||
if (this.value) {
|
||||
timeDiv.style.display = 'block';
|
||||
compIn.required = true;
|
||||
compOut.required = true;
|
||||
} else {
|
||||
timeDiv.style.display = 'none';
|
||||
compIn.required = false;
|
||||
compOut.required = false;
|
||||
compIn.value = '';
|
||||
compOut.value = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const inputsCantidad = document.querySelectorAll('input[name^="qty_"]');
|
||||
const displayTotalProductos = document.getElementById('total_productos_calc');
|
||||
|
||||
function calcularVentaProductos() {
|
||||
let granTotal = 0;
|
||||
const filas = document.querySelectorAll('tbody tr');
|
||||
|
||||
filas.forEach(fila => {
|
||||
const inputQty = fila.querySelector('input[name^="qty_"]');
|
||||
if (inputQty) {
|
||||
if (parseInt(inputQty.value) < 0) {
|
||||
inputQty.value = 0;
|
||||
}
|
||||
const cantidad = parseInt(inputQty.value) || 0;
|
||||
const precioTexto = fila.cells[1].innerText.replace(/\D/g, '');
|
||||
const precio = parseInt(precioTexto) || 0;
|
||||
granTotal += (cantidad * precio);
|
||||
}
|
||||
});
|
||||
displayTotalProductos.value = granTotal.toLocaleString('es-CL');
|
||||
|
||||
checkWarnings();
|
||||
}
|
||||
|
||||
inputsCantidad.forEach(input => {
|
||||
input.addEventListener('keydown', function(e) {
|
||||
if (['Backspace', 'Tab', 'ArrowLeft', 'ArrowRight', 'Delete', 'Enter'].includes(e.key) || e.ctrlKey || e.metaKey) return;
|
||||
if (e.key < '0' || e.key > '9') {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('input', function() {
|
||||
this.value = this.value.replace(/\D/g, '');
|
||||
calcularVentaProductos();
|
||||
});
|
||||
});
|
||||
|
||||
function checkWarnings() {
|
||||
const getVal = (id) => {
|
||||
const el = document.getElementById(id);
|
||||
if (!el || !el.value.trim() || el.value === '0') return 0;
|
||||
return parseInt(el.value.replace(/\D/g, '')) || 0;
|
||||
};
|
||||
|
||||
const totalProductos = getVal('total_productos_calc');
|
||||
const totalDeclarado = getVal('total_general');
|
||||
const gastos = getVal('gastos');
|
||||
const efectivo = getVal('venta_efectivo');
|
||||
|
||||
let warnings = [];
|
||||
|
||||
// Comprobar diferencias en totales (solo si se ha ingresado algo para no mostrar el error de entrada)
|
||||
if ((totalProductos > 0 || totalDeclarado > 0) && totalProductos !== totalDeclarado) {
|
||||
warnings.push("El <strong>Total Venta por Productos</strong> no coincide con el <strong>Total Ventas Declaradas</strong>.");
|
||||
}
|
||||
|
||||
// Comprobar si los gastos superan el efectivo
|
||||
if (gastos > efectivo) {
|
||||
warnings.push("El <strong>Monto de Gastos</strong> es mayor que el <strong>Efectivo</strong> declarado.");
|
||||
}
|
||||
|
||||
const warningContainer = document.getElementById('discrepancy_warning');
|
||||
const warningText = document.getElementById('discrepancy_text');
|
||||
|
||||
if (warnings.length > 0) {
|
||||
warningText.innerHTML = warnings.join("<br>");
|
||||
warningContainer.style.display = 'block';
|
||||
} else {
|
||||
warningContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const submitModal = document.getElementById('confirmSubmitModal');
|
||||
const mainForm = document.querySelector('form');
|
||||
const alertModalEl = document.getElementById('globalAlertModal');
|
||||
const alertModal = new bootstrap.Modal(alertModalEl);
|
||||
const confirmBtn = submitModal.querySelector('button[type="submit"]');
|
||||
|
||||
function mostrarError(mensaje) {
|
||||
document.getElementById('globalAlertModalBody').textContent = mensaje;
|
||||
alertModal.show();
|
||||
}
|
||||
|
||||
function validarFormulario() {
|
||||
const requiredInputs = mainForm.querySelectorAll('[required]');
|
||||
let valid = true;
|
||||
|
||||
requiredInputs.forEach(input => {
|
||||
const isMoney = input.classList.contains('money-input');
|
||||
if (!input.value.trim() || (isMoney && input.value === '')) {
|
||||
input.classList.add('is-invalid');
|
||||
valid = false;
|
||||
} else {
|
||||
input.classList.remove('is-invalid');
|
||||
}
|
||||
});
|
||||
return valid;
|
||||
}
|
||||
|
||||
confirmBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
if (validarFormulario()) {
|
||||
mainForm.submit();
|
||||
} else {
|
||||
bootstrap.Modal.getInstance(submitModal).hide();
|
||||
mostrarError("Por favor, rellena los campos obligatorios (Fecha y Hora) antes de enviar.");
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.money-input').forEach(input => {
|
||||
if (!input.value.trim()) input.value = '0';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const inputsVenta = document.querySelectorAll('.sale-input');
|
||||
const displayDigital = document.getElementById('total_digital');
|
||||
const displayGeneral = document.getElementById('total_general');
|
||||
|
||||
function calcularTotales() {
|
||||
const getVal = (id) => {
|
||||
const el = document.getElementById(id);
|
||||
if (!el || !el.value.trim() || el.value === '0') return 0;
|
||||
return parseInt(el.value.replace(/\D/g, '')) || 0;
|
||||
};
|
||||
|
||||
const debito = getVal('venta_debito');
|
||||
const credito = getVal('venta_credito');
|
||||
const mp = getVal('venta_mp');
|
||||
const efectivo = getVal('venta_efectivo');
|
||||
|
||||
const totalDigital = debito + credito + mp;
|
||||
const totalGeneral = totalDigital + efectivo; // Ya no restamos los gastos aquí
|
||||
|
||||
document.getElementById('total_digital').value = totalDigital.toLocaleString('es-CL');
|
||||
document.getElementById('total_general').value = totalGeneral.toLocaleString('es-CL');
|
||||
|
||||
checkWarnings();
|
||||
}
|
||||
|
||||
inputsVenta.forEach(input => {
|
||||
input.addEventListener('input', calcularTotales);
|
||||
});
|
||||
|
||||
document.querySelector('form').addEventListener('submit', function(e) {
|
||||
const requiredInputs = this.querySelectorAll('[required]');
|
||||
let valid = true;
|
||||
|
||||
requiredInputs.forEach(input => {
|
||||
const isMoney = input.classList.contains('money-input');
|
||||
if (!input.value.trim() || (isMoney && input.value === '')) {
|
||||
input.classList.add('is-invalid');
|
||||
valid = false;
|
||||
} else {
|
||||
input.classList.remove('is-invalid');
|
||||
}
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
e.preventDefault();
|
||||
const alertModalEl = document.getElementById('globalAlertModal');
|
||||
if (alertModalEl) {
|
||||
const alertModal = bootstrap.Modal.getOrCreateInstance(alertModalEl);
|
||||
document.getElementById('globalAlertModalBody').textContent = "Por favor, rellena todos los campos obligatorios antes de enviar.";
|
||||
alertModal.show();
|
||||
} else {
|
||||
alert("Por favor, rellena todos los campos obligatorios.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.money-input').forEach(function(input) {
|
||||
input.addEventListener('keydown', function(e) {
|
||||
if (['Backspace', 'Tab', 'ArrowLeft', 'ArrowRight', 'Delete', 'Enter'].includes(e.key) || e.ctrlKey || e.metaKey) return;
|
||||
if (e.key < '0' || e.key > '9') {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('focus', function() {
|
||||
if (this.value === '0') this.value = '';
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function() {
|
||||
if (this.value.trim() === '' || this.value.trim() === '0') {
|
||||
this.value = '0';
|
||||
}
|
||||
calcularTotales();
|
||||
});
|
||||
|
||||
input.addEventListener('input', function() {
|
||||
let value = this.value.replace(/\D/g, '');
|
||||
if (value !== '') {
|
||||
this.value = parseInt(value, 10).toLocaleString('es-CL');
|
||||
}
|
||||
calcularTotales();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/worker_dashboard.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -1,23 +1,18 @@
|
||||
{% extends "macros/base.html" %}
|
||||
{% from 'macros/modals.html' import rendicion_detail_modal %}
|
||||
{% from "macros/ui.html" import flashed_messages %}
|
||||
|
||||
{% block title %}Mis Rendiciones{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-4 gap-3">
|
||||
<h2 class="mb-0">Mis Rendiciones</h2>
|
||||
<a href="{{ url_for('new_rendicion') }}" class="btn btn-success shadow-sm align-self-start align-self-md-auto">
|
||||
<a href="{{ url_for('worker.new_rendicion') }}" class="btn btn-success shadow-sm align-self-start align-self-md-auto">
|
||||
<i class="bi bi-plus-circle me-2"></i>Nueva Rendición
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">{{ message|safe }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{{ flashed_messages() }}
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-0">
|
||||
|
||||
Reference in New Issue
Block a user