dicom evidence update

This commit is contained in:
2026-03-10 23:22:50 -03:00
parent 57cb27f6cf
commit 92e3a3f0f9
5 changed files with 170 additions and 170 deletions

25
app.py
View File

@@ -178,7 +178,7 @@ def checkout():
@login_required
def dicom():
with sqlite3.connect(DB_FILE) as conn:
debtors = conn.execute('SELECT id, name, amount, notes, datetime(last_updated, "localtime") FROM dicom ORDER BY amount DESC').fetchall()
debtors = conn.execute('SELECT id, name, amount, notes, datetime(last_updated, "localtime"), image_url FROM dicom ORDER BY amount DESC').fetchall()
return render_template('dicom.html', active_page='dicom', user=current_user, debtors=debtors)
@app.route('/sales')
@@ -474,29 +474,26 @@ def update_dicom():
name = data.get('name', '').strip()
amount = float(data.get('amount', 0))
notes = data.get('notes', '')
action = data.get('action') # 'add' or 'pay'
image_url = data.get('image_url', '')
action = data.get('action')
if not name or amount <= 0:
return jsonify({"error": "Nombre y monto válidos son requeridos"}), 400
# If we are giving them credit (Fiar), their balance drops into the negative
if action == 'add':
amount = -amount
try:
with sqlite3.connect(DB_FILE) as conn:
cur = conn.cursor()
# Upsert logic: if they exist, modify debt. If they don't, create them.
cur.execute('''INSERT INTO dicom (name, amount, notes, last_updated)
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
with sqlite3.connect(DB_FILE) as conn:
cur = conn.cursor()
cur.execute('''INSERT INTO dicom (name, amount, notes, image_url, last_updated)
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT(name) DO UPDATE SET
amount = amount + excluded.amount,
notes = excluded.notes,
last_updated = CURRENT_TIMESTAMP''', (name, amount, notes))
conn.commit()
return jsonify({"status": "success"}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
image_url = CASE WHEN excluded.image_url != "" THEN excluded.image_url ELSE dicom.image_url END,
last_updated = CURRENT_TIMESTAMP''', (name, amount, notes, image_url))
conn.commit()
return jsonify({"status": "success"}), 200
@app.route('/api/dicom/<int:debtor_id>', methods=['DELETE'])
@login_required

View File

@@ -1,5 +1,5 @@
{% extends "macros/base.html" %}
{% from 'macros/modals.html' import confirm_modal, scanner_modal %}
{% from 'macros/modals.html' import confirm_modal, scanner_modal, render_receipt %}
{% block title %}Caja{% endblock %}
@@ -16,54 +16,11 @@
-moz-appearance: textfield;
appearance: textfield;
}
@media print {
body * {
visibility: hidden;
}
#receipt-print-zone, #receipt-print-zone * {
visibility: visible;
}
#receipt-print-zone {
position: absolute;
left: 0;
top: 0;
width: 58mm;
padding: 0;
margin: 0;
display: block !important;
font-family: 'Courier New', Courier, monospace;
font-size: 10px;
color: #000;
}
.container-fluid, .main, body {
margin: 0 !important;
padding: 0 !important;
background: #fff !important;
}
}
.receipt-table {
width: 100%;
border-collapse: collapse;
font-family: monospace;
font-size: 12px;
}
.receipt-header {
text-align: center;
margin-bottom: 10px;
border-bottom: 1px dashed #000;
padding-bottom: 5px;
}
</style>
{% endblock %}
{% block content %}
{{ render_receipt() }}
{% call confirm_modal('removeConfirmModal', 'Quitar Producto', 'btn-danger-discord', 'Quitar', 'executeRemoveItem()') %}
¿Estás seguro de que quieres quitar <strong id="removeItemName"></strong> del carrito?
{% endcall %}
@@ -117,41 +74,7 @@
</div>
</div>
</div>
<div id="receipt-print-zone" class="d-none d-print-block">
<div class="receipt-header">
<h3 style="margin: 0; font-weight: 800;">SekiPOS</h3>
<div style="font-size: 10px; margin-bottom: 5px;">Comprobante de Venta</div>
<div style="font-size: 11px; font-weight: bold;">
Ticket Nº <span id="receipt-ticket-id"></span>
</div>
<div id="receipt-date" style="font-size: 11px;"></div>
</div>
<table class="receipt-table">
<thead>
<tr>
<th style="width: 15%;">Cant</th>
<th style="width: 60%; padding-left: 5px;">Desc</th>
<th style="width: 25%; text-align: right;">Total</th>
</tr>
</thead>
<tbody id="receipt-items-print">
</tbody>
</table>
<div class="receipt-total-row d-flex justify-content-between pt-2">
<span>TOTAL:</span>
<span id="receipt-total-print"></span>
</div>
<div class="d-flex justify-content-between">
<span>RECIBIDO:</span>
<span id="receipt-paid-print"></span>
</div>
<div class="d-flex justify-content-between">
<span>VUELTO:</span>
<span id="receipt-change-print"></span>
</div>
<div style="text-align: center; margin-top: 20px; font-size: 10px;">¡Gracias por su compra!</div>
</div>
<div class="modal fade" id="successModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-success">
@@ -488,7 +411,6 @@
document.getElementById('grand-total').innerText = clp.format(total);
// This will actually run now
saveCart();
}

View File

@@ -27,12 +27,29 @@
<label class="small text-muted mb-1">Monto (CLP)</label>
<input type="number" id="dicom-amount" class="form-control" placeholder="Ej: 5000">
</div>
<div class="mb-4">
<div class="mb-3">
<label class="small text-muted mb-1">Nota (Opcional)</label>
<input type="text" id="dicom-notes" class="form-control" placeholder="Ej: Pan y bebida"
onkeydown="if(event.key === 'Enter') submitDicom('add')">
</div>
<div class="mb-4">
<label class="small text-muted mb-1">Foto / Comprobante</label>
<div class="input-group">
<input type="text" id="dicom-image-url" class="form-control" placeholder="URL de imagen" readonly>
<input type="file" id="dicom-camera-input" accept="image/*" capture="environment" style="display: none;"
onchange="handleDicomUpload(this)">
<button class="btn btn-outline-secondary" type="button"
onclick="document.getElementById('dicom-camera-input').click()">
<i class="bi bi-camera"></i>
</button>
</div>
<div id="dicom-img-preview-container" class="mt-2 d-none">
<img id="dicom-img-preview" src="" class="img-thumbnail" style="max-height: 100px;">
</div>
</div>
<div class="d-flex flex-column gap-2">
<button class="btn btn-danger py-2 fw-bold" onclick="submitDicom('add')">
<i class="bi bi-cart-plus me-1"></i> Fiar (Sumar Deuda)
@@ -57,6 +74,7 @@
<table class="table mb-0" id="dicom-table">
<thead>
<tr>
<th>Foto</th>
<th>Nombre</th>
<th>Deuda Total</th>
<th>Última Nota</th>
@@ -67,6 +85,16 @@
<tbody>
{% for d in debtors %}
<tr>
<td>
{% if d[5] %}
<img src="{{ d[5] }}" class="rounded" style="width: 40px; height: 40px; object-fit: cover; cursor: pointer;"
onclick="window.open(this.src, '_blank')">
{% else %}
<div class="bg-secondary rounded" style="width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; opacity: 0.3;">
<i class="bi bi-image text-white"></i>
</div>
{% endif %}
</td>
<td class="fw-bold">{{ d[1] }}</td>
<td class="fw-bold price-cell" data-value="{{ d[2] }}"></td>
<td class="text-muted small">{{ d[3] }}</td>
@@ -112,14 +140,62 @@
}
});
function clearDicomForm() {
document.getElementById('dicom-name').value = '';
document.getElementById('dicom-amount').value = '';
document.getElementById('dicom-notes').value = '';
document.getElementById('dicom-name').focus();
function compressImage(file, maxWidth, quality) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = event => {
const img = new Image();
img.src = event.target.result;
img.onload = () => {
const canvas = document.createElement('canvas');
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob(blob => resolve(blob), 'image/jpeg', quality);
};
};
});
}
// Search Filter
async function handleDicomUpload(input) {
const name = document.getElementById('dicom-name').value;
if (!name) {
alert("Primero ingresa un nombre para asociar la foto.");
input.value = '';
return;
}
const file = input.files[0];
if (!file) return;
try {
const compressedBlob = await compressImage(file, 800, 0.7);
const formData = new FormData();
// Use a unique name for the file based on the debtor
formData.append('image', compressedBlob, `debt_${name}_${Date.now()}.jpg`);
formData.append('barcode', `debt_${name}`); // Reusing the barcode field as a prefix
const res = await fetch('/upload_image', { method: 'POST', body: formData });
const data = await res.json();
if (res.ok) {
document.getElementById('dicom-image-url').value = data.image_url;
document.getElementById('dicom-img-preview').src = data.image_url;
document.getElementById('dicom-img-preview-container').classList.remove('d-none');
}
} catch (e) {
alert("Error procesando imagen.");
}
}
function filterDicom() {
const q = document.getElementById('dicom-search').value.toLowerCase();
document.querySelectorAll('#dicom-table tbody tr').forEach(row => {
@@ -139,6 +215,7 @@
const name = document.getElementById('dicom-name').value;
const amount = document.getElementById('dicom-amount').value;
const notes = document.getElementById('dicom-notes').value;
const image_url = document.getElementById('dicom-image-url').value; // Added
if (!name || amount <= 0) {
alert('Ingresa un nombre y monto válido.');
@@ -149,17 +226,20 @@
const res = await fetch('/api/dicom/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, amount, notes, action })
body: JSON.stringify({ name, amount, notes, action, image_url }) // Added image_url
});
if (res.ok) window.location.reload();
} catch (e) { alert("Error de conexión."); }
}
if (res.ok) {
window.location.reload();
} else {
alert('Error actualizando la base de datos.');
}
} catch (e) {
alert("Error de conexión.");
}
// UPDATE your existing clearDicomForm
function clearDicomForm() {
document.getElementById('dicom-name').value = '';
document.getElementById('dicom-amount').value = '';
document.getElementById('dicom-notes').value = '';
document.getElementById('dicom-image-url').value = ''; // Added
document.getElementById('dicom-img-preview-container').classList.add('d-none'); // Added
document.getElementById('dicom-name').focus();
}
async function forgiveDebt(id, name) {

View File

@@ -43,4 +43,61 @@
</div>
</div>
</div>
{% endmacro %}
{% macro render_receipt(id_suffix="") %}
<div id="receipt-print-zone{{ id_suffix }}" class="d-none d-print-block">
<style>
@media print {
body * { visibility: hidden; }
#receipt-print-zone{{ id_suffix }}, #receipt-print-zone{{ id_suffix }} * { visibility: visible; }
#receipt-print-zone{{ id_suffix }} {
position: absolute; left: 0; top: 0; width: 80mm;
padding: 5mm; margin: 0; display: block !important;
font-family: 'Courier New', Courier, monospace; font-size: 10px; color: #000;
}
}
.receipt-table { width: 100%; border-collapse: collapse; font-family: monospace; font-size: 12px; }
.receipt-header { text-align: center; margin-bottom: 10px; border-bottom: 1px dashed #000; padding-bottom: 5px; }
.receipt-total-row { border-top: 1px dashed #000; margin-top: 5px; padding-top: 5px; font-weight: bold; }
</style>
<div class="receipt-header">
<h3 style="margin: 0; font-weight: 800;">SekiPOS</h3>
<div style="font-size: 10px; margin-bottom: 5px;" id="receipt-type{{ id_suffix }}">Comprobante de Venta</div>
<div style="font-size: 11px; font-weight: bold;">
Ticket Nº <span id="receipt-ticket-id{{ id_suffix }}"></span>
</div>
<div id="receipt-date{{ id_suffix }}" style="font-size: 11px;"></div>
</div>
<table class="receipt-table">
<thead>
<tr>
<th style="width: 15%; text-align: left;">Cant</th>
<th style="width: 60%; padding-left: 5px; text-align: left;">Desc</th>
<th style="width: 25%; text-align: right;">Total</th>
</tr>
</thead>
<tbody id="receipt-items-print{{ id_suffix }}"></tbody>
</table>
<div class="receipt-total-row d-flex justify-content-between">
<span>TOTAL:</span>
<span id="receipt-total-print{{ id_suffix }}"></span>
</div>
<div id="receipt-payment-info{{ id_suffix }}">
<div class="d-flex justify-content-between">
<span>RECIBIDO:</span>
<span id="receipt-paid-print{{ id_suffix }}"></span>
</div>
<div class="d-flex justify-content-between">
<span>VUELTO:</span>
<span id="receipt-change-print{{ id_suffix }}"></span>
</div>
</div>
<div style="text-align: center; margin-top: 20px; font-size: 10px;">¡Gracias por su compra!</div>
</div>
{% endmacro %}

View File

@@ -1,71 +1,14 @@
{% extends "macros/base.html" %}
{% from 'macros/modals.html' import confirm_modal, scanner_modal %}
{% from 'macros/modals.html' import confirm_modal, scanner_modal, render_receipt %}
{% block title %}Ventas{% endblock %}
{% block head %}
<style>
@media print {
body * { visibility: hidden; }
#receipt-print-zone, #receipt-print-zone * { visibility: visible; }
#receipt-print-zone {
position: absolute;
left: 0;
top: 0;
width: 58mm;
margin: 0;
padding: 5mm;
display: block !important;
}
}
@media (max-width: 768px) {
.table td, .table th {
padding: 0.5rem 0.25rem;
font-size: 0.85rem;
}
.btn-sm span {
display: none;
}
}
.table th:last-child,
.table td:last-child {
width: 1%;
white-space: nowrap;
}
</style>
<!--HEAD-->
{% endblock %}
{% block content %}
<div id="receipt-print-zone" class="d-none d-print-block">
<div class="receipt-header" style="text-align: center; margin-bottom: 10px; border-bottom: 1px dashed #000; padding-bottom: 5px;">
<h3 style="margin: 0; font-weight: 800;">SekiPOS</h3>
<div style="font-size: 10px; margin-bottom: 5px;">Re-impresión de Comprobante</div>
<div style="font-size: 11px; font-weight: bold;">
Ticket Nº <span id="receipt-ticket-id"></span>
</div>
<div id="receipt-date" style="font-size: 11px;"></div>
</div>
<table class="receipt-table" style="width: 100%; border-collapse: collapse; font-family: monospace; font-size: 12px;">
<thead>
<tr>
<th style="width: 15%; text-align: left;">Cant</th>
<th style="width: 60%; padding-left: 5px; text-align: left;">Desc</th>
<th style="width: 25%; text-align: right;">Total</th>
</tr>
</thead>
<tbody id="receipt-items-print">
</tbody>
</table>
<div class="receipt-total-row d-flex justify-content-between pt-2" style="display: flex; justify-content: space-between; font-weight: bold; border-top: 1px dashed #000; margin-top: 5px;">
<span>TOTAL:</span>
<span id="receipt-total-print"></span>
</div>
<div style="text-align: center; margin-top: 20px; font-size: 10px;">¡Gracias por su compra!</div>
</div>
{{ render_receipt() }}
<div class="row g-3 mb-3">
<div class="col-12 col-md-4">
@@ -286,6 +229,7 @@
document.getElementById('receipt-ticket-id').innerText = id;
document.getElementById('receipt-total-print').innerText = clp.format(total);
document.getElementById('receipt-date').innerText = new Date(rawDate + " UTC").toLocaleString('es-CL');
document.getElementById('receipt-payment-info').style.display = 'none';
window.print();
} catch (err) {