Files
apskel-pos-flutter-v2/test/weight_based_products_test.dart
efrilmandClaude Opus 5 1505a83dd3 feat: dukung penjualan produk per timbangan
Produk kini punya cara jual `unit` (cacah) atau `weight` (timbangan),
mengikuti kontrak backend di docs/integration-weight-based-products.md.

- tipe SellBy, domain/infrastructure unit baru, dan endpoint /api/v1/units
- dialog input berat saat produk timbangan ditambahkan ke keranjang
- tiap penimbangan jadi baris keranjang sendiri; total baris dihitung
  berat x harga satuan, sedangkan produk cacah tetap digabung
- request ke backend mengirim quantity 1 dan membawa berat
- migrasi DB v3: kolom sell_by, unit_id, unit_name, unit_abbreviation
  di tabel products supaya satuan tetap tampil saat offline
- tampilan keranjang, struk, split bill, void, dan refund memakai berat
- test/weight_based_products_test.dart menutup perilaku di atas (9 test)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 23:03:31 +07:00

140 lines
4.1 KiB
Dart

import 'package:apskel_pos_flutter_v2/application/checkout/checkout_form/checkout_form_bloc.dart';
import 'package:apskel_pos_flutter_v2/common/types/sell_by.dart';
import 'package:apskel_pos_flutter_v2/domain/order/order.dart';
import 'package:apskel_pos_flutter_v2/domain/product/product.dart';
import 'package:flutter_test/flutter_test.dart';
/// Ikan Tude — Rp 4.500 per ons.
final ikanTude = Product.empty().copyWith(
id: 'ikan-tude',
name: 'Ikan Tude',
price: 4500,
isActive: true,
sellBy: SellBy.weight,
unitId: 'unit-ons',
unitName: 'Ons',
unitAbbreviation: 'ons',
);
final nasiGoreng = Product.empty().copyWith(
id: 'nasi-goreng',
name: 'Nasi Goreng',
price: 25000,
isActive: true,
);
void main() {
group('keranjang produk timbangan', () {
test('dua penimbangan produk yang sama menjadi dua baris', () async {
final bloc = CheckoutFormBloc();
bloc.add(CheckoutFormEvent.addWeightedItem(ikanTude, null, 4.2));
bloc.add(CheckoutFormEvent.addWeightedItem(ikanTude, null, 5.6));
await bloc.stream.firstWhere((s) => s.items.length == 2);
final items = bloc.state.items;
expect(items.length, 2);
expect(items.map((e) => e.weight), [4.2, 5.6]);
expect(items.every((e) => e.quantity == 1), isTrue);
expect(items[0].lineId, isNot(items[1].lineId));
});
test('produk cacah tetap digabung jadi satu baris', () async {
final bloc = CheckoutFormBloc();
bloc.add(CheckoutFormEvent.addItem(nasiGoreng, null));
bloc.add(CheckoutFormEvent.addItem(nasiGoreng, null));
await bloc.stream.firstWhere((s) => s.items.isNotEmpty && s.items.first.quantity == 2);
expect(bloc.state.items.length, 1);
expect(bloc.state.items.first.quantity, 2);
});
test('produk timbangan tidak bisa masuk tanpa berat', () async {
final bloc = CheckoutFormBloc();
bloc.add(CheckoutFormEvent.addItem(ikanTude, null));
await Future<void>.delayed(Duration.zero);
expect(bloc.state.items, isEmpty);
});
test('total baris dihitung berat x harga satuan', () {
final line = ProductQuantity(
product: ikanTude,
quantity: 1,
notes: '',
weight: 4.2,
);
expect(line.lineTotal, closeTo(18900, 0.001));
expect(line.quantityLabel, '4,2 ons');
});
test('total keranjang mencampur baris berbobot dan baris cacah', () {
final items = [
ProductQuantity(
product: ikanTude,
quantity: 1,
notes: '',
weight: 4.2,
),
ProductQuantity(product: nasiGoreng, quantity: 2, notes: ''),
];
expect(items.cartTotal, closeTo(18900 + 50000, 0.001));
expect(items.cartItemCount, 3);
});
});
group('permintaan ke backend', () {
test('baris berbobot dikirim quantity 1 dan membawa berat', () {
final request = OrderItemRequest.fromProductQuantity(
ProductQuantity(
product: ikanTude,
quantity: 1,
notes: '',
weight: 4.2,
),
);
expect(request.quantity, 1);
expect(request.weight, 4.2);
expect(request.unitPrice, 4500);
});
test('produk cacah tidak membawa berat sama sekali', () {
final request = OrderItemRequest.fromProductQuantity(
ProductQuantity(product: nasiGoreng, quantity: 2, notes: ''),
);
expect(request.quantity, 2);
expect(request.weight, isNull);
});
});
group('tampilan baris pesanan', () {
test('baris berbobot memakai berat, bukan quantity', () {
final item = OrderItem.empty().copyWith(
quantity: 1,
unitPrice: 4500,
totalPrice: 18900,
weight: 4.2,
unitAbbreviation: 'ons',
);
expect(item.isWeighted, isTrue);
expect(item.quantityLabel, '4,2 ons');
expect(item.quantityPriceLabel, '4,2 ons x Rp 4.500');
});
test('baris biasa tetap memakai quantity', () {
final item = OrderItem.empty().copyWith(quantity: 2, unitPrice: 25000);
expect(item.isWeighted, isFalse);
expect(item.quantityLabel, '2');
expect(item.quantityPriceLabel, '2 x Rp 25.000');
});
});
}