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>
This commit is contained in:
efrilm
2026-09-06 23:03:31 +07:00
co-authored by Claude Opus 5
parent 9777f63426
commit 1505a83dd3
72 changed files with 5388 additions and 584 deletions
+51 -24
View File
@@ -88,6 +88,8 @@ import 'package:apskel_pos_flutter_v2/common/network/network_client.dart'
import 'package:apskel_pos_flutter_v2/common/service/device_info_service.dart'
as _i288;
import 'package:apskel_pos_flutter_v2/common/service/fcm_service.dart' as _i312;
import 'package:apskel_pos_flutter_v2/common/service/unit_registry.dart'
as _i584;
import 'package:apskel_pos_flutter_v2/domain/analytic/analytic.dart' as _i346;
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
import 'package:apskel_pos_flutter_v2/domain/category/category.dart' as _i502;
@@ -99,6 +101,7 @@ import 'package:apskel_pos_flutter_v2/domain/payment_method/payment_method.dart'
import 'package:apskel_pos_flutter_v2/domain/printer/printer.dart' as _i104;
import 'package:apskel_pos_flutter_v2/domain/product/product.dart' as _i44;
import 'package:apskel_pos_flutter_v2/domain/table/table.dart' as _i983;
import 'package:apskel_pos_flutter_v2/domain/unit/unit.dart' as _i320;
import 'package:apskel_pos_flutter_v2/env.dart' as _i923;
import 'package:apskel_pos_flutter_v2/infrastructure/analytic/datasources/remote_data_provider.dart'
as _i708;
@@ -148,6 +151,12 @@ import 'package:apskel_pos_flutter_v2/infrastructure/table/datasources/remote_da
as _i95;
import 'package:apskel_pos_flutter_v2/infrastructure/table/repositories/table_repository.dart'
as _i824;
import 'package:apskel_pos_flutter_v2/infrastructure/unit/datasources/local_data_provider.dart'
as _i393;
import 'package:apskel_pos_flutter_v2/infrastructure/unit/datasources/remote_data_provider.dart'
as _i1016;
import 'package:apskel_pos_flutter_v2/infrastructure/unit/repositories/unit_repository.dart'
as _i228;
import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart'
as _i800;
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
@@ -229,6 +238,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i693.OutletLocalDatasource>(
() => _i693.OutletLocalDatasource(gh<_i460.SharedPreferences>()),
);
gh.factory<_i393.UnitLocalDataProvider>(
() => _i393.UnitLocalDataProvider(gh<_i460.SharedPreferences>()),
);
gh.lazySingleton<_i457.ApiClient>(
() => _i457.ApiClient(gh<_i361.Dio>(), gh<_i923.Env>()),
);
@@ -279,6 +291,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i95.TableRemoteDataProvider>(
() => _i95.TableRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i1016.UnitRemoteDataProvider>(
() => _i1016.UnitRemoteDataProvider(gh<_i457.ApiClient>()),
);
gh.factory<_i776.IAuthRepository>(
() => _i941.AuthRepository(
gh<_i370.AuthRemoteDataProvider>(),
@@ -291,13 +306,6 @@ extension GetItInjectableX on _i174.GetIt {
gh<_i708.CategoryLocalDataProvider>(),
),
);
gh.factory<_i46.LoginFormBloc>(
() => _i46.LoginFormBloc(
gh<_i776.IAuthRepository>(),
gh<_i312.FcmService>(),
gh<_i288.DeviceInfoService>(),
),
);
gh.factory<_i983.ITableRepository>(
() => _i824.TableRepository(
gh<_i95.TableRemoteDataProvider>(),
@@ -313,12 +321,6 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i424.TableLoaderBloc>(
() => _i424.TableLoaderBloc(gh<_i983.ITableRepository>()),
);
gh.factory<_i44.IProductRepository>(
() => _i763.ProductRepository(
gh<_i707.ProductRemoteDataProvider>(),
gh<_i464.ProductLocalDataProvider>(),
),
);
gh.factory<_i552.IOutletRepository>(
() => _i845.OutletRepository(
gh<_i132.OutletRemoteDataProvider>(),
@@ -342,6 +344,12 @@ extension GetItInjectableX on _i174.GetIt {
gh<_i552.IOutletRepository>(),
),
);
gh.factory<_i320.IUnitRepository>(
() => _i228.UnitRepository(
gh<_i1016.UnitRemoteDataProvider>(),
gh<_i393.UnitLocalDataProvider>(),
),
);
gh.factory<_i143.ICustomerRepository>(
() => _i385.CustomerRepository(gh<_i841.CustomerRemoteDataProvider>()),
);
@@ -363,6 +371,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i822.VoidFormBloc>(
() => _i822.VoidFormBloc(gh<_i299.IOrderRepository>()),
);
gh.lazySingleton<_i584.UnitRegistry>(
() => _i584.UnitRegistry(gh<_i320.IUnitRepository>()),
);
gh.factory<_i683.CustomerLoaderBloc>(
() => _i683.CustomerLoaderBloc(gh<_i143.ICustomerRepository>()),
);
@@ -381,19 +392,19 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i76.OutletLoaderBloc>(
() => _i76.OutletLoaderBloc(gh<_i552.IOutletRepository>()),
);
gh.factory<_i13.ProductLoaderBloc>(
() => _i13.ProductLoaderBloc(gh<_i44.IProductRepository>()),
);
gh.factory<_i741.SyncBloc>(
() => _i741.SyncBloc(
gh<_i44.IProductRepository>(),
gh<_i502.ICategoryRepository>(),
gh.factory<_i46.LoginFormBloc>(
() => _i46.LoginFormBloc(
gh<_i776.IAuthRepository>(),
gh<_i312.FcmService>(),
gh<_i288.DeviceInfoService>(),
gh<_i584.UnitRegistry>(),
),
);
gh.factory<_i729.SyncSettingBloc>(
() => _i729.SyncSettingBloc(
gh<_i44.IProductRepository>(),
gh<_i502.ICategoryRepository>(),
gh.factory<_i44.IProductRepository>(
() => _i763.ProductRepository(
gh<_i707.ProductRemoteDataProvider>(),
gh<_i464.ProductLocalDataProvider>(),
gh<_i584.UnitRegistry>(),
),
);
gh.factory<_i911.CategoryAnalyticLoaderBloc>(
@@ -419,6 +430,22 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i413.SalesAnalyticLoaderBloc>(
() => _i413.SalesAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()),
);
gh.factory<_i729.SyncSettingBloc>(
() => _i729.SyncSettingBloc(
gh<_i44.IProductRepository>(),
gh<_i502.ICategoryRepository>(),
gh<_i584.UnitRegistry>(),
),
);
gh.factory<_i13.ProductLoaderBloc>(
() => _i13.ProductLoaderBloc(gh<_i44.IProductRepository>()),
);
gh.factory<_i741.SyncBloc>(
() => _i741.SyncBloc(
gh<_i44.IProductRepository>(),
gh<_i502.ICategoryRepository>(),
),
);
return this;
}
}