feat: change email template + add list of items

This commit is contained in:
2026-10-09 09:30:26 +02:00
parent 6f237a937a
commit 5246aeabc1
17 changed files with 474 additions and 55 deletions
@@ -1,3 +1,4 @@
import { Prisma } from '@prisma/client';
import { DevicesService } from './devices.service';
type Snapshot = { tabId: string; customerName: string; version: number; totalInCents: number; itemCount: number };
@@ -101,3 +102,26 @@ describe('DevicesService.sync live update signals', () => {
expect(realtime.notify).toHaveBeenCalledWith('v1', 'device');
});
});
describe('DevicesService.sync tab lines', () => {
const base = { id: 't1', customerName: 'Bram', version: 4, totalInCents: 2500, itemCount: 10 };
it('stores the lines a terminal reports', async () => {
const { service, tx } = createService();
const items = [{ productName: 'Cola', quantity: 10, unitPriceInCents: 250 }];
await service.sync(device, { deviceId: 'd1', openTabs: [{ ...base, items }] });
const { create, update } = tx.openTabSnapshot.upsert.mock.calls[0][0];
expect(create.items).toEqual(items);
expect(update.items).toEqual(items);
});
it('stores no lines for app versions that do not report them', async () => {
const { service, tx } = createService();
await service.sync(device, { deviceId: 'd1', openTabs: [base] });
expect(tx.openTabSnapshot.upsert.mock.calls[0][0].update.items).toBe(Prisma.JsonNull);
});
});
+11 -2
View File
@@ -9,7 +9,8 @@ import { PrismaService } from '../prisma/prisma.service';
import { AuditService } from '../audit/audit.service';
import { RealtimeService, RealtimeTopic } from '../realtime/realtime.service';
import { DEVICE_ONLINE_THRESHOLD_MS } from '@kooltap/shared';
import { SyncDto } from './dto/sync.dto';
import { Prisma } from '@prisma/client';
import { OpenTabDto, SyncDto } from './dto/sync.dto';
import { AckCommandDto } from './dto/ack-command.dto';
@Injectable()
@@ -82,6 +83,7 @@ export class DevicesService {
version: tab.version,
totalInCents: tab.totalInCents,
itemCount: tab.itemCount,
items: snapshotItems(tab),
reportedAt: now,
},
update: {
@@ -89,6 +91,7 @@ export class DevicesService {
version: tab.version,
totalInCents: tab.totalInCents,
itemCount: tab.itemCount,
items: snapshotItems(tab),
reportedAt: now,
},
});
@@ -274,4 +277,10 @@ export class DevicesService {
return { ok: true };
}
}
}
/** The tab's lines in the stored shape, or JSON null when the terminal didn't report them. */
function snapshotItems(tab: OpenTabDto) {
if (!tab.items) return Prisma.JsonNull;
return tab.items.map(({ productName, quantity, unitPriceInCents }) => ({ productName, quantity, unitPriceInCents }));
}
+28 -2
View File
@@ -1,8 +1,26 @@
import { IsString, IsOptional, IsArray, IsInt, Min, IsUUID, ValidateNested, ArrayMaxSize, IsNotEmpty } from 'class-validator';
import { IsString, IsOptional, IsArray, IsInt, Min, IsUUID, ValidateNested, ArrayMaxSize, IsNotEmpty, MaxLength } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
class OpenTabDto {
export class OpenTabItemDto {
@ApiProperty({ example: 'Cola' })
@IsString()
@IsNotEmpty()
@MaxLength(200)
productName: string;
@ApiProperty({ example: 10 })
@IsInt()
@Min(0)
quantity: number;
@ApiProperty({ example: 250 })
@IsInt()
@Min(0)
unitPriceInCents: number;
}
export class OpenTabDto {
@ApiProperty({ example: 'local-tab-uuid' })
@IsString()
@IsNotEmpty()
@@ -26,6 +44,14 @@ class OpenTabDto {
@IsInt()
@Min(0)
itemCount: number;
@ApiPropertyOptional({ type: [OpenTabItemDto], description: 'Lines on the tab; omitted by older app versions' })
@IsOptional()
@IsArray()
@ArrayMaxSize(500)
@ValidateNested({ each: true })
@Type(() => OpenTabItemDto)
items?: OpenTabItemDto[];
}
export class SyncDto {