206 lines
9.5 KiB
PHP
206 lines
9.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/pos_app.php';
|
|
|
|
$projectName = pos_project_name();
|
|
$projectDescription = pos_project_description();
|
|
$projectImageUrl = pos_project_image_url();
|
|
$pageTitle = $projectName . ' | Recibo';
|
|
$currentUser = pos_require_login();
|
|
$flashes = pos_pull_flashes();
|
|
$saleId = (int)($_GET['id'] ?? 0);
|
|
$sale = $saleId > 0 ? pos_sale_by_id($saleId) : null;
|
|
$cssVersion = file_exists(__DIR__ . '/assets/css/custom.css') ? (string)filemtime(__DIR__ . '/assets/css/custom.css') : (string)time();
|
|
$jsVersion = file_exists(__DIR__ . '/assets/js/main.js') ? (string)filemtime(__DIR__ . '/assets/js/main.js') : (string)time();
|
|
$flashClassMap = [
|
|
'success' => 'text-bg-success',
|
|
'danger' => 'text-bg-danger',
|
|
'warning' => 'text-bg-warning',
|
|
'secondary' => 'text-bg-secondary',
|
|
'info' => 'text-bg-primary',
|
|
];
|
|
|
|
if (!$sale) {
|
|
http_response_code(404);
|
|
}
|
|
?>
|
|
<!doctype html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title><?= htmlspecialchars($pageTitle) ?></title>
|
|
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
<meta name="robots" content="noindex, nofollow" />
|
|
<meta property="og:title" content="<?= htmlspecialchars($pageTitle) ?>" />
|
|
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
<meta property="twitter:title" content="<?= htmlspecialchars($pageTitle) ?>" />
|
|
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
<?php if ($projectImageUrl !== ''): ?>
|
|
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
<?php endif; ?>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<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">
|
|
<link href="assets/css/custom.css?v=<?= htmlspecialchars($cssVersion) ?>" rel="stylesheet">
|
|
</head>
|
|
<body class="app-body receipt-body">
|
|
<header class="topbar border-bottom">
|
|
<nav class="navbar py-3">
|
|
<div class="container-xxl d-flex justify-content-between align-items-center gap-3">
|
|
<a class="navbar-brand brand-mark" href="index.php">
|
|
<span class="brand-mark__icon"><i class="bi bi-receipt-cutoff"></i></span>
|
|
<span>
|
|
<small class="text-uppercase text-muted d-block">Detalle</small>
|
|
<strong><?= htmlspecialchars($projectName) ?></strong>
|
|
</span>
|
|
</a>
|
|
<div class="d-flex gap-2 flex-wrap justify-content-end">
|
|
<span class="user-chip">
|
|
<strong><?= htmlspecialchars($currentUser['name']) ?></strong>
|
|
<small><?= htmlspecialchars($currentUser['role_label']) ?></small>
|
|
</span>
|
|
<a class="btn btn-outline-secondary" href="index.php#ventas">Volver al POS</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
|
|
<div class="toast-container position-fixed top-0 end-0 p-3">
|
|
<?php foreach ($flashes as $flash): ?>
|
|
<?php $flashClass = $flashClassMap[$flash['type']] ?? 'text-bg-dark'; ?>
|
|
<div class="toast align-items-center border-0 js-app-toast <?= htmlspecialchars($flashClass) ?>" role="status" aria-live="polite" aria-atomic="true" data-bs-delay="3600">
|
|
<div class="d-flex">
|
|
<div class="toast-body"><?= htmlspecialchars($flash['message']) ?></div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Cerrar"></button>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<main class="container-xxl py-4 py-lg-5">
|
|
<?php if (!$sale): ?>
|
|
<section class="panel receipt-panel mx-auto" style="max-width: 760px;">
|
|
<span class="eyebrow">Detalle no disponible</span>
|
|
<h1 class="section-title">No encontramos ese recibo.</h1>
|
|
<p class="section-copy">Puede que el identificador sea incorrecto o que el recibo ya no exista en esta base de datos.</p>
|
|
<a class="btn btn-dark" href="index.php#ventas">Volver a ventas recientes</a>
|
|
</section>
|
|
<?php else: ?>
|
|
<section class="row g-4 align-items-start">
|
|
<div class="col-lg-8">
|
|
<article class="panel receipt-panel printable-area" id="receipt-card">
|
|
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3 mb-4">
|
|
<div>
|
|
<span class="eyebrow">Recibo simple</span>
|
|
<h1 class="display-heading mb-1"><?= htmlspecialchars((string)$sale['receipt_number']) ?></h1>
|
|
<p class="section-copy mb-0">Venta registrada el <?= htmlspecialchars(pos_format_datetime((string)$sale['created_at'])) ?>.</p>
|
|
</div>
|
|
<span class="badge text-bg-light border px-3 py-2"><?= htmlspecialchars((string)$sale['cashier_role']) ?></span>
|
|
</div>
|
|
|
|
<div class="receipt-metadata mb-4">
|
|
<div>
|
|
<span>Cajero</span>
|
|
<strong><?= htmlspecialchars((string)$sale['cashier_name']) ?></strong>
|
|
</div>
|
|
<div>
|
|
<span>Código</span>
|
|
<strong><?= htmlspecialchars((string)$sale['cashier_code']) ?></strong>
|
|
</div>
|
|
<div>
|
|
<span>Items</span>
|
|
<strong><?= htmlspecialchars((string)$sale['item_count']) ?></strong>
|
|
</div>
|
|
<div>
|
|
<span>Total</span>
|
|
<strong><?= htmlspecialchars(pos_format_money((float)$sale['total'])) ?></strong>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-borderless receipt-table align-middle mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Producto</th>
|
|
<th>Categoría</th>
|
|
<th class="text-center">Cantidad</th>
|
|
<th class="text-end">Precio</th>
|
|
<th class="text-end">Importe</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($sale['items'] as $item): ?>
|
|
<tr>
|
|
<td>
|
|
<strong><?= htmlspecialchars((string)($item['name'] ?? 'Producto')) ?></strong>
|
|
<div class="text-muted small"><?= htmlspecialchars((string)($item['unit_label'] ?? 'unidad')) ?></div>
|
|
</td>
|
|
<td><?= htmlspecialchars((string)($item['category'] ?? '—')) ?></td>
|
|
<td class="text-center"><?= htmlspecialchars((string)($item['quantity'] ?? 0)) ?></td>
|
|
<td class="text-end"><?= htmlspecialchars(pos_format_money((float)($item['price'] ?? 0))) ?></td>
|
|
<td class="text-end"><strong><?= htmlspecialchars(pos_format_money((float)($item['line_total'] ?? 0))) ?></strong></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="receipt-total-card mt-4">
|
|
<div>
|
|
<span>Subtotal</span>
|
|
<strong><?= htmlspecialchars(pos_format_money((float)$sale['subtotal'])) ?></strong>
|
|
</div>
|
|
<div>
|
|
<span>Total a pagar</span>
|
|
<strong><?= htmlspecialchars(pos_format_money((float)$sale['total'])) ?></strong>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-print-none d-flex flex-wrap gap-3 mt-4">
|
|
<a class="btn btn-dark btn-lg" href="index.php#catalogo">
|
|
<i class="bi bi-plus-circle me-2"></i>Nueva venta
|
|
</a>
|
|
<button class="btn btn-outline-secondary btn-lg" type="button" id="print-receipt">
|
|
<i class="bi bi-printer me-2"></i>Imprimir recibo
|
|
</button>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
<div class="col-lg-4 d-print-none">
|
|
<aside class="panel h-100">
|
|
<span class="eyebrow">Confirmación</span>
|
|
<h2 class="section-title">La venta quedó registrada.</h2>
|
|
<p class="section-copy">Ya puedes iniciar una nueva venta o volver al listado para revisar otros recibos.</p>
|
|
<div class="mini-grid single-column mt-4">
|
|
<div class="mini-card">
|
|
<i class="bi bi-check2-circle"></i>
|
|
<strong>Stock actualizado</strong>
|
|
<p>El inventario ya se descontó automáticamente.</p>
|
|
</div>
|
|
<div class="mini-card">
|
|
<i class="bi bi-journal-text"></i>
|
|
<strong>Historial guardado</strong>
|
|
<p>El recibo queda visible en “Ventas recientes”.</p>
|
|
</div>
|
|
<div class="mini-card">
|
|
<i class="bi bi-arrow-repeat"></i>
|
|
<strong>Siguiente paso</strong>
|
|
<p>Regresa al catálogo para iniciar otra operación.</p>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
</main>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" defer></script>
|
|
<script src="assets/js/main.js?v=<?= htmlspecialchars($jsVersion) ?>" defer></script>
|
|
</body>
|
|
</html>
|