feat: make server own products

This commit is contained in:
2026-10-21 12:10:42 +02:00
parent 93acfe15a9
commit b15d94a805
21 changed files with 1717 additions and 106 deletions
+349
View File
@@ -245,6 +245,355 @@ void main() {
});
});
// The website changes prices: drinks already on a tab keep theirs, the
// next one is charged the new price
group('addProductToTab after a price or name change', () {
test(
'a new price starts a new line; earlier drinks keep theirs',
() async {
final tab = await service.createTab(customerName: 'John');
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(priceInCents: 450),
);
final items = (await service.getTabById(tab.id))!.items;
expect([
for (final item in items) (item.unitPriceInCents, item.quantity),
], unorderedEquals([(400, 2), (450, 1)]));
expect((await service.getTabById(tab.id))!.totalInCents, 1250);
},
);
test('more drinks at the new price go on the new line', () async {
final tab = await service.createTab(customerName: 'John');
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
for (var i = 0; i < 3; i++) {
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(priceInCents: 450),
);
}
final items = (await service.getTabById(tab.id))!.items;
expect(items, hasLength(2));
expect(
items.firstWhere((item) => item.unitPriceInCents == 450).quantity,
3,
);
});
test('a price going back joins the line at that price again', () async {
final tab = await service.createTab(customerName: 'John');
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(priceInCents: 300),
);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
final items = (await service.getTabById(tab.id))!.items;
expect([
for (final item in items) (item.unitPriceInCents, item.quantity),
], unorderedEquals([(400, 2), (300, 1)]));
});
test('a renamed product starts a new line under its new name', () async {
final tab = await service.createTab(customerName: 'John');
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(name: 'Pils'),
);
final items = (await service.getTabById(tab.id))!.items;
expect([
for (final item in items) item.productName,
], unorderedEquals(['Test Beer', 'Pils']));
});
test('both lines count down the stock of the one product', () async {
final tab = await service.createTab(customerName: 'John');
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(priceInCents: 450),
);
final stored = await (database.select(
database.products,
)..where((row) => row.id.equals('prod-1'))).getSingle();
expect(stored.stockQuantity, 98);
});
test('settling charges each line at its own price', () async {
final tab = await service.createTab(customerName: 'John');
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(priceInCents: 450),
);
await service.closeTab(tab.id);
final closed = (await service.getClosedTabs()).single;
expect(closed.totalInCents, 850);
expect([
for (final item in closed.items) item.unitPriceInCents,
], unorderedEquals([400, 450]));
});
});
// Foo has 2 at the old price; the website changes the price; staff tap +
// on Foo's line. The 2 stay at the old price, every new one is charged
// the new price.
group('+ on a line after a price change', () {
Future<void> setPrice(int priceInCents, {String? name}) async {
await (database.update(
database.products,
)..where((row) => row.id.equals('prod-1'))).write(
ProductsCompanion(
priceInCents: Value(priceInCents),
name: name == null ? const Value.absent() : Value(name),
),
);
}
Future<String> fooWithTwoAtOldPrice() async {
final tab = await service.createTab(customerName: 'Foo');
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await setPrice(450);
return tab.id;
}
Future<List<(int, int)>> lines(String tabId) async {
return [
for (final item in (await service.getTabById(tabId))!.items)
(item.unitPriceInCents, item.quantity),
];
}
Future<String> lineAt(String tabId, int priceInCents) async {
return (await service.getTabById(tabId))!.items
.firstWhere((item) => item.unitPriceInCents == priceInCents)
.id;
}
test(
'the old drinks keep their price, the new one gets the new price',
() async {
final tabId = await fooWithTwoAtOldPrice();
final added = await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 400),
delta: 1,
);
expect(added, 1);
expect(await lines(tabId), unorderedEquals([(400, 2), (450, 1)]));
expect((await service.getTabById(tabId))!.totalInCents, 1250);
},
);
test(
'tapping + on the old line again keeps adding at the new price',
() async {
final tabId = await fooWithTwoAtOldPrice();
final oldLine = await lineAt(tabId, 400);
await service.adjustTabItemQuantity(tabItemId: oldLine, delta: 1);
await service.adjustTabItemQuantity(tabItemId: oldLine, delta: 1);
expect(await lines(tabId), unorderedEquals([(400, 2), (450, 2)]));
},
);
test('+ on the line at the new price adds there', () async {
final tabId = await fooWithTwoAtOldPrice();
await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 400),
delta: 1,
);
await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 450),
delta: 1,
);
expect(await lines(tabId), unorderedEquals([(400, 2), (450, 2)]));
});
test('the tile and + end up on the same new line', () async {
final tabId = await fooWithTwoAtOldPrice();
await service.addProductToTab(
tabId: tabId,
product: createTestProduct(priceInCents: 450),
);
await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 400),
delta: 1,
);
expect(await lines(tabId), unorderedEquals([(400, 2), (450, 2)]));
});
test('+ several at once adds them all at the new price', () async {
final tabId = await fooWithTwoAtOldPrice();
final added = await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 400),
delta: 3,
);
expect(added, 3);
expect(await lines(tabId), unorderedEquals([(400, 2), (450, 3)]));
});
test('− on the old line takes off a drink at the old price', () async {
final tabId = await fooWithTwoAtOldPrice();
await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 400),
delta: 1,
);
await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 400),
delta: -1,
);
expect(await lines(tabId), unorderedEquals([(400, 1), (450, 1)]));
});
test('a price going back makes + add to the old line again', () async {
final tabId = await fooWithTwoAtOldPrice();
await setPrice(400);
await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 400),
delta: 1,
);
expect(await lines(tabId), [(400, 3)]);
});
test('a renamed product gets a new line under its new name', () async {
final tab = await service.createTab(customerName: 'Foo');
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await setPrice(400, name: 'Pils');
await service.adjustTabItemQuantity(
tabItemId: (await service.getTabById(tab.id))!.items.single.id,
delta: 1,
);
final names = [
for (final item in (await service.getTabById(tab.id))!.items)
(item.productName, item.quantity),
];
expect(names, unorderedEquals([('Test Beer', 1), ('Pils', 1)]));
});
test(
'a hidden (archived) product is still charged its latest price',
() async {
final tabId = await fooWithTwoAtOldPrice();
await (database.update(database.products)
..where((row) => row.id.equals('prod-1')))
.write(const ProductsCompanion(active: Value(false)));
await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 400),
delta: 1,
);
expect(await lines(tabId), unorderedEquals([(400, 2), (450, 1)]));
},
);
test('a line whose product is gone keeps its own price', () async {
final tab = await service.createTab(customerName: 'Foo');
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await database.delete(database.products).go();
await service.adjustTabItemQuantity(
tabItemId: (await service.getTabById(tab.id))!.items.single.id,
delta: 1,
);
expect(await lines(tab.id), [(400, 2)]);
});
test('another tab without the product gets the new price', () async {
await fooWithTwoAtOldPrice();
final bar = await service.createTab(customerName: 'Bar');
await service.addProductToTab(
tabId: bar.id,
product: createTestProduct(priceInCents: 450),
);
expect(await lines(bar.id), [(450, 1)]);
});
test('every drink counts down the stock of the one product', () async {
final tabId = await fooWithTwoAtOldPrice();
await service.adjustTabItemQuantity(
tabItemId: await lineAt(tabId, 400),
delta: 2,
);
final stored = await (database.select(
database.products,
)..where((row) => row.id.equals('prod-1'))).getSingle();
expect(stored.stockQuantity, 96);
});
});
group('adjustTabItemQuantity', () {
test('updates item quantity', () async {
final tab = await service.createTab(customerName: 'John');