feat(customers): the customer file's estado de cuenta follows the same year rule
`/clientes/:id` carries its own "Estado de cuenta" card, fed by CustomersService.detail rather than by BillingService.statement, so the previous commit left it reading the old way: the last 100 movements of all time, newest first. Two pages, one label, two orders. It now covers the current calendar year oldest-first, like the statement and like the sheet the office prints. The `take: 100` is gone with it — capping a descending list hid the oldest rows, but capping an ascending one would hide the newest, and a single year is small (365 rows for the heaviest customer in the book). The per-domain totals above the list are untouched and still historical: they are an unfloored groupBy, so they double-count every customer's opening balance. That is a separate bug from this one; the card now says out loud that those figures are lifetime, not this year's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,13 @@ export class CustomersService {
|
|||||||
|
|
||||||
/** Full unified customer view: identity + both business lines + ledger. */
|
/** Full unified customer view: identity + both business lines + ledger. */
|
||||||
async detail(id: string) {
|
async detail(id: string) {
|
||||||
|
// The movement list on the customer file is the same statement the office
|
||||||
|
// prints, so it follows the same rule as BillingService.statement: this
|
||||||
|
// calendar year, oldest-first. No `take` any more — the cap used to hide
|
||||||
|
// the end of a busy customer's year once the order flipped, and a single
|
||||||
|
// year is small (365 rows for the heaviest customer in the book).
|
||||||
|
const yearStart = new Date(Date.UTC(new Date().getUTCFullYear(), 0, 1));
|
||||||
|
|
||||||
const customer = await this.prisma.customer.findUnique({
|
const customer = await this.prisma.customer.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
@@ -117,8 +124,8 @@ export class CustomersService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
transactions: {
|
transactions: {
|
||||||
orderBy: { transactionDate: "desc" },
|
where: { transactionDate: { gte: yearStart } },
|
||||||
take: 100,
|
orderBy: [{ transactionDate: "asc" }, { id: "asc" }],
|
||||||
include: { type: true },
|
include: { type: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -140,6 +147,8 @@ export class CustomersService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...customer,
|
...customer,
|
||||||
|
/** Calendar year the movement list covers. */
|
||||||
|
transactionYear: yearStart.getUTCFullYear(),
|
||||||
transactionSummary: summary.map((s) => ({
|
transactionSummary: summary.map((s) => ({
|
||||||
domain: s.domain,
|
domain: s.domain,
|
||||||
currency: s.currency,
|
currency: s.currency,
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ function Detail({ id }: { id: string }) {
|
|||||||
customerId={data.id}
|
customerId={data.id}
|
||||||
summary={data.transactionSummary}
|
summary={data.transactionSummary}
|
||||||
transactions={data.transactions}
|
transactions={data.transactions}
|
||||||
|
year={data.transactionYear}
|
||||||
/>
|
/>
|
||||||
<DocumentosSection data={data} />
|
<DocumentosSection data={data} />
|
||||||
</div>
|
</div>
|
||||||
@@ -746,16 +747,18 @@ function EstadoCuentaSection({
|
|||||||
customerId,
|
customerId,
|
||||||
summary,
|
summary,
|
||||||
transactions,
|
transactions,
|
||||||
|
year,
|
||||||
}: {
|
}: {
|
||||||
customerId: string;
|
customerId: string;
|
||||||
summary: TransactionSummaryRow[];
|
summary: TransactionSummaryRow[];
|
||||||
transactions: Transaction[];
|
transactions: Transaction[];
|
||||||
|
year: number;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section className="section">
|
<section className="section">
|
||||||
<SectionHead
|
<SectionHead
|
||||||
rule="cuenta"
|
rule="cuenta"
|
||||||
title="Estado de cuenta"
|
title={`Estado de cuenta ${year}`}
|
||||||
count={transactions.length}
|
count={transactions.length}
|
||||||
countSuffix="movimientos"
|
countSuffix="movimientos"
|
||||||
/>
|
/>
|
||||||
@@ -782,7 +785,7 @@ function EstadoCuentaSection({
|
|||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
{transactions.length === 0 ? (
|
{transactions.length === 0 ? (
|
||||||
<div className="empty-inline">Sin movimientos registrados.</div>
|
<div className="empty-inline">Sin movimientos en {year}.</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="tx-scroll">
|
<div className="tx-scroll">
|
||||||
<table className="tx-table">
|
<table className="tx-table">
|
||||||
@@ -804,11 +807,10 @@ function EstadoCuentaSection({
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{transactions.length >= 100 && (
|
<div className="section-note" style={{ padding: "0 16px 14px" }}>
|
||||||
<div className="section-note" style={{ padding: "0 16px 14px" }}>
|
Movimientos de {year}, del más antiguo al más reciente. Los saldos de
|
||||||
Mostrando los 100 movimientos más recientes.
|
arriba son el acumulado histórico, no el del año.
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{transactions.length > 0 && (
|
{transactions.length > 0 && (
|
||||||
<p className="section-note">
|
<p className="section-note">
|
||||||
|
|||||||
@@ -1129,6 +1129,8 @@ export interface CustomerDetail {
|
|||||||
properties: Property[];
|
properties: Property[];
|
||||||
policies: Policy[];
|
policies: Policy[];
|
||||||
transactions: Transaction[];
|
transactions: Transaction[];
|
||||||
|
/** Calendar year `transactions` covers. */
|
||||||
|
transactionYear: number;
|
||||||
transactionSummary: TransactionSummaryRow[];
|
transactionSummary: TransactionSummaryRow[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user