@@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
import { DevicesService } from './devices.service';
|
import { DevicesService } from './devices.service';
|
||||||
import { SyncDto } from './dto/sync.dto';
|
import { SyncDto } from './dto/sync.dto';
|
||||||
import { createFakePrisma, FakePrisma } from '../testing/fake-prisma';
|
import { createFakePrisma, FakePrisma } from '../testing/fake-prisma';
|
||||||
|
import { StockService } from '../stock/stock.service';
|
||||||
|
|
||||||
type Device = { id: string; venueId: string; revokedAt: null; lastSeenAt: Date };
|
type Device = { id: string; venueId: string; revokedAt: null; lastSeenAt: Date };
|
||||||
type SyncResult = Awaited<ReturnType<DevicesService['sync']>>;
|
type SyncResult = Awaited<ReturnType<DevicesService['sync']>>;
|
||||||
@@ -416,3 +417,117 @@ describe('DevicesService.sync live update signals', () => {
|
|||||||
expect(realtime.notify).toHaveBeenCalledWith('v1', 'device');
|
expect(realtime.notify).toHaveBeenCalledWith('v1', 'device');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DevicesService.sync: website product changes reach the tablets', () => {
|
||||||
|
const admin = { id: 'u1', email: 'admin@example.com', name: 'Admin' };
|
||||||
|
|
||||||
|
function setupWithWebsite() {
|
||||||
|
const venue = setup();
|
||||||
|
const stock = new StockService(venue.prisma as any, { log: jest.fn() } as any, { notify: jest.fn() } as any);
|
||||||
|
return { ...venue, stock };
|
||||||
|
}
|
||||||
|
|
||||||
|
it('sends a new price to every tablet in its next sync', async () => {
|
||||||
|
const { sync, tabletA, tabletB, stock, prisma } = setupWithWebsite();
|
||||||
|
addProduct(prisma);
|
||||||
|
|
||||||
|
await stock.updateProduct('v1', 'cola', { priceInCents: 270 }, admin);
|
||||||
|
const a = await sync(tabletA);
|
||||||
|
const b = await sync(tabletB);
|
||||||
|
|
||||||
|
for (const result of [a, b]) {
|
||||||
|
expect(result.catalog).toEqual([expect.objectContaining({ id: 'cola', priceInCents: 270 })]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps old drinks at their price and puts new ones on a new line', async () => {
|
||||||
|
const { sync, tabletA, tabletB, stock, prisma } = setupWithWebsite();
|
||||||
|
addProduct(prisma);
|
||||||
|
await sync(tabletA, {
|
||||||
|
events: [events.opened('t1', 'Alice'), events.line('line_added', 't1', 2, { lineId: 'old-line' })],
|
||||||
|
});
|
||||||
|
|
||||||
|
await stock.updateProduct('v1', 'cola', { priceInCents: 270 }, admin);
|
||||||
|
// The tablet charges the new price on a line of its own
|
||||||
|
const result = await sync(tabletB, {
|
||||||
|
events: [events.line('line_added', 't1', 1, { lineId: 'new-line', price: 270, minutes: 5 })],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tab(result, 't1')!.lines.map((l) => [l.id, l.unitPriceInCents, l.quantity])).toEqual([
|
||||||
|
['old-line', 250, 2],
|
||||||
|
['new-line', 270, 1],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('settles every line at its own price', async () => {
|
||||||
|
const { sync, tabletA, prisma } = setupWithWebsite();
|
||||||
|
addProduct(prisma);
|
||||||
|
const settled = {
|
||||||
|
...events.settled('t1', 0, 10),
|
||||||
|
amountInCents: 2 * 250 + 270,
|
||||||
|
items: [
|
||||||
|
{ productId: 'cola', productName: 'Cola', quantity: 2, unitPriceInCents: 250 },
|
||||||
|
{ productId: 'cola', productName: 'Cola', quantity: 1, unitPriceInCents: 270 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await sync(tabletA, {
|
||||||
|
events: [
|
||||||
|
events.opened('t1', 'Alice'),
|
||||||
|
events.line('line_added', 't1', 2, { lineId: 'old-line' }),
|
||||||
|
events.line('line_added', 't1', 1, { lineId: 'new-line', price: 270, minutes: 5 }),
|
||||||
|
settled,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tab(result, 't1')!.lines).toEqual([]);
|
||||||
|
expect(result.settlements[0].lines.map((l) => [l.unitPriceInCents, l.quantity])).toEqual([[250, 2], [270, 1]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('counts both price lines down from the one product stock', async () => {
|
||||||
|
const { sync, tabletA, stock, prisma } = setupWithWebsite();
|
||||||
|
addProduct(prisma, 'cola', { stock: 20, countedAt: new Date('2026-01-01') });
|
||||||
|
|
||||||
|
await sync(tabletA, { events: [events.opened('t1', 'Alice'), events.line('line_added', 't1', 2)] });
|
||||||
|
await stock.updateProduct('v1', 'cola', { priceInCents: 270 }, admin);
|
||||||
|
const result = await sync(tabletA, {
|
||||||
|
events: [events.line('line_added', 't1', 1, { lineId: 'new-line', price: 270, minutes: 5 })],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(colaStock(result)).toBe(17);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends a product added on the website with its starting stock', async () => {
|
||||||
|
const { sync, tabletA, stock } = setupWithWebsite();
|
||||||
|
|
||||||
|
const created = await stock.createProduct('v1', { name: 'Ice Tea', category: 'Frisdrank', priceInCents: 280, stock: 24 }, admin);
|
||||||
|
const result = await sync(tabletA);
|
||||||
|
|
||||||
|
expect(result.catalog).toEqual([expect.objectContaining({ id: created.productId, name: 'Ice Tea', priceInCents: 280 })]);
|
||||||
|
expect(result.stock).toEqual([expect.objectContaining({ productId: created.productId, stock: 24 })]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('marks an archived product, and still counts a sale from a tablet that had not synced yet', async () => {
|
||||||
|
const { sync, tabletA, stock, prisma } = setupWithWebsite();
|
||||||
|
addProduct(prisma, 'cola', { stock: 20, countedAt: new Date('2026-01-01') });
|
||||||
|
|
||||||
|
await stock.updateProduct('v1', 'cola', { archived: true }, admin);
|
||||||
|
const result = await sync(tabletA, {
|
||||||
|
events: [events.opened('t1', 'Alice'), events.line('line_added', 't1', 1, { minutes: 1 })],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.catalog).toEqual([expect.objectContaining({ id: 'cola', archived: true })]);
|
||||||
|
expect(tab(result, 't1')!.lines).toMatchObject([{ productId: 'cola', quantity: 1 }]);
|
||||||
|
expect(colaStock(result)).toBe(19);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a renamed product keeps its id, so tablets update it instead of adding one', async () => {
|
||||||
|
const { sync, tabletA, stock, prisma } = setupWithWebsite();
|
||||||
|
addProduct(prisma);
|
||||||
|
|
||||||
|
await stock.updateProduct('v1', 'cola', { name: 'Coca-Cola' }, admin);
|
||||||
|
const result = await sync(tabletA);
|
||||||
|
|
||||||
|
expect(result.catalog).toEqual([expect.objectContaining({ id: 'cola', name: 'Coca-Cola' })]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user