The office still issues cash receipts from Access (EFE MENU -> EFE). This is that form: cliente, fecha, monto + moneda, concepto, elaboró (the logged-in user), fecha de depósito, and a folio that continues the legacy book. - cash_receipts, folio unique. Next folio = one past the highest of the platform's receipts and the imported utilities EFECTIVO journal, so after the cutover import it lands on the office's next number (13935 on prod today). A typed folio the legacy book already used is refused. - Issuing a receipt posts its credit in the same database transaction, as legacy posted to DATOS2: reference C<folio>, CASH DEPOSIT, pseudo check CASH<MMDD><P|D>. Dollars with a rate credit a pesos ledger (1,500 USD at 16.80 -> 25,200 MXN, as C13830 did). Voiding that row voids the receipt. - /efectivo: issue, search, record the deposit date; /efectivo/<folio> prints the receipt. - Reporte de efectivo now lists platform receipts by receipt, not by check, so each is counted once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
3.8 KiB
TypeScript
99 lines
3.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { getCashReceipt } from "@/lib/api";
|
|
import { formatDate, formatMoney } from "@/lib/labels";
|
|
import type { CashReceipt, CompanyHeader } from "@/lib/types";
|
|
|
|
/**
|
|
* One EFE receipt, laid out to print and hand to the customer. No app shell:
|
|
* the page is the paper.
|
|
*/
|
|
export default function CashReceiptPrint({ params }: { params: { folio: string } }) {
|
|
const [r, setR] = useState<(CashReceipt & { company: CompanyHeader }) | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
getCashReceipt(Number(params.folio))
|
|
.then(setR)
|
|
.catch((e) => setError((e as Error)?.message ?? "No se encontró el recibo."));
|
|
}, [params.folio]);
|
|
|
|
if (error) return <div className="state-box state-error">{error}</div>;
|
|
if (!r) return <div className="state-box">Cargando…</div>;
|
|
|
|
const c = r.company;
|
|
const voided = r.transaction.voidedAt != null;
|
|
const converted = r.exchangeRate != null;
|
|
|
|
return (
|
|
<div style={{ maxWidth: 720, margin: "24px auto", padding: 24, fontSize: 14 }}>
|
|
<style>{"@media print { .no-print { display: none } body { background: #fff } }"}</style>
|
|
<div className="no-print" style={{ marginBottom: 16 }}>
|
|
<button type="button" className="btn btn-primary" onClick={() => window.print()}>
|
|
Imprimir
|
|
</button>
|
|
</div>
|
|
|
|
<div style={{ border: "1px solid #333", padding: 24 }}>
|
|
<div style={{ display: "flex", justifyContent: "space-between", gap: 16 }}>
|
|
<div>
|
|
<strong style={{ fontSize: 18 }}>{c.name}</strong>
|
|
<div>{c.addressLine1}</div>
|
|
{c.addressLine2 && <div>{c.addressLine2}</div>}
|
|
<div>{c.cityState}</div>
|
|
{c.mailingAddress && <div>{c.mailingAddress}</div>}
|
|
<div>
|
|
{c.phone} · {c.email}
|
|
</div>
|
|
{c.taxId && <div>RFC {c.taxId}</div>}
|
|
</div>
|
|
<div style={{ textAlign: "right" }}>
|
|
<div style={{ fontSize: 18, fontWeight: 700 }}>RECIBO DE EFECTIVO</div>
|
|
<div style={{ fontSize: 22, fontWeight: 700 }}>Folio {r.folio}</div>
|
|
<div>{formatDate(r.receivedAt)}</div>
|
|
{voided && <div style={{ color: "#b00", fontWeight: 700 }}>ANULADO</div>}
|
|
</div>
|
|
</div>
|
|
|
|
<table style={{ width: "100%", marginTop: 24, borderCollapse: "collapse" }}>
|
|
<tbody>
|
|
<Row label="Cliente">
|
|
{r.customer.name}
|
|
{r.customer.folderNumber != null && ` · Carpeta ${r.customer.folderNumber}`}
|
|
</Row>
|
|
<Row label="Monto">
|
|
<strong>{formatMoney(r.amount, r.currency)}</strong>
|
|
{converted &&
|
|
` (abonado ${formatMoney(r.transaction.amount, r.transaction.currency)} a T.C. ${Number(r.exchangeRate).toFixed(4)})`}
|
|
</Row>
|
|
<Row label="Concepto">{r.concept}</Row>
|
|
<Row label="Elaboró">{r.preparedBy.name}</Row>
|
|
{r.depositedAt && <Row label="Fecha de depósito">{formatDate(r.depositedAt)}</Row>}
|
|
</tbody>
|
|
</table>
|
|
|
|
<div style={{ marginTop: 56, display: "flex", justifyContent: "space-between" }}>
|
|
<div style={{ borderTop: "1px solid #333", width: 240, textAlign: "center", paddingTop: 4 }}>
|
|
Recibí
|
|
</div>
|
|
<div style={{ borderTop: "1px solid #333", width: 240, textAlign: "center", paddingTop: 4 }}>
|
|
Entregó
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Row({ label, children }: { label: string; children: React.ReactNode }) {
|
|
return (
|
|
<tr>
|
|
<td style={{ padding: "6px 12px 6px 0", width: 160, color: "#555", verticalAlign: "top" }}>
|
|
{label}
|
|
</td>
|
|
<td style={{ padding: "6px 0" }}>{children}</td>
|
|
</tr>
|
|
);
|
|
}
|