@@ -1,9 +1,10 @@
|
||||
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { RealtimeService } from '../realtime/realtime.service';
|
||||
import { customerNameKey } from '../planned-tabs/customer-name';
|
||||
import { tabSummary } from './tab-state';
|
||||
import { SettledLine, tabSummary } from './tab-state';
|
||||
import { VenueLedger } from './venue-ledger';
|
||||
|
||||
@Injectable()
|
||||
@@ -37,6 +38,65 @@ export class TabsService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The venue's paid tabs, newest first, as the terminals' history shows them: every settlement made on a terminal
|
||||
* or on the dashboard. [search] matches the customer name.
|
||||
*/
|
||||
async history(
|
||||
venueId: string,
|
||||
options: { search?: string; paymentMethod?: string; limit: number; offset: number },
|
||||
) {
|
||||
const search = options.search?.trim();
|
||||
const where: Prisma.SettlementWhereInput = {
|
||||
venueId,
|
||||
...(search ? { customerName: { contains: search, mode: 'insensitive' as const } } : {}),
|
||||
...(options.paymentMethod ? { paymentMethod: options.paymentMethod } : {}),
|
||||
};
|
||||
|
||||
const [rows, total, sum] = await Promise.all([
|
||||
this.prisma.settlement.findMany({
|
||||
where,
|
||||
orderBy: [{ settledAt: 'desc' }, { sequence: 'desc' }],
|
||||
skip: options.offset,
|
||||
take: options.limit,
|
||||
}),
|
||||
this.prisma.settlement.count({ where }),
|
||||
this.prisma.settlement.aggregate({ where, _sum: { amountInCents: true } }),
|
||||
]);
|
||||
|
||||
const deviceIds = [...new Set(rows.map((row) => row.deviceId).filter((id): id is string => id !== null))];
|
||||
const devices = deviceIds.length > 0
|
||||
? await this.prisma.device.findMany({ where: { id: { in: deviceIds } }, select: { id: true, name: true } })
|
||||
: [];
|
||||
const deviceNames = new Map(devices.map((device) => [device.id, device.name]));
|
||||
|
||||
return {
|
||||
settlements: rows.map((row) => {
|
||||
const items = (row.lines as unknown as SettledLine[]).map((line) => ({
|
||||
productName: line.productName,
|
||||
quantity: line.quantity,
|
||||
unitPriceInCents: line.unitPriceInCents,
|
||||
// Rung up and taken off again before paying
|
||||
removed: line.units.filter((unit) => unit.removedAt !== null).length,
|
||||
}));
|
||||
return {
|
||||
id: row.id,
|
||||
tabId: row.tabId,
|
||||
customerName: row.customerName,
|
||||
settledAt: row.settledAt,
|
||||
paymentMethod: row.paymentMethod,
|
||||
amountInCents: row.amountInCents,
|
||||
itemCount: items.reduce((count, item) => count + item.quantity, 0),
|
||||
items,
|
||||
settledBy: (row.deviceId ? deviceNames.get(row.deviceId) : row.requestedBy) ?? null,
|
||||
onDashboard: row.deviceId === null,
|
||||
};
|
||||
}),
|
||||
total,
|
||||
totalAmountInCents: sum._sum.amountInCents ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a tab without settling it, as tab_deleted does on a terminal: what is still on it is not paid and stays
|
||||
* out of the stock. The terminals drop it on their next sync; one that still has unsent drinks for it brings it
|
||||
|
||||
Reference in New Issue
Block a user