904bfbe67d
照原型 inventory.html 把库存屏从「3 tab」重排为单视图: - 去 3 tab(库存查询/预警/流水)→ 单视图;预警并入 KPI 缺货卡点击筛选;流水移商品详情 - 头部:库存管理 + 共 N 个 SKU·数据实时 + 列设置/导出(盘点/新增入库去掉,走专门菜单) - KPI 4 卡走全店汇总接口:SKU总数/库存货值(Σqty×进价)/在库数量/缺货预警(可点筛选) - 表格 12 列:加 库存(低库存染色)/成本价(进价)/单价(售价);操作列只「查看详情」;状态圆点 - 仓库/备注默认隐藏;工具栏 搜索+状态筛选+重置 - 后端 GET /inventory/summary 全店汇总(守 shop_id);前端 model/repository/provider 配套 - golden_harness:monospace 指向 Noto(消等宽数字黑块,全 golden 受益);库存屏存量 grey 清零 后端编译/库存测试通过;前端 analyze 0 error,flutter test 242 通过;代码端颜色闸 0 违规。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSKEiHsvauyxYUW2itzUXX
167 lines
4.1 KiB
Dart
167 lines
4.1 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../core/api/api_client.dart';
|
|
import '../core/auth/auth_state.dart';
|
|
import '../core/config/app_constants.dart';
|
|
import '../core/models/page_result.dart';
|
|
import '../models/inventory.dart';
|
|
import '../repositories/inventory_repository.dart';
|
|
import 'connectivity_provider.dart';
|
|
|
|
final inventoryRepositoryProvider = Provider<InventoryRepository>((ref) {
|
|
return InventoryRepository(ref.watch(apiClientProvider));
|
|
});
|
|
|
|
final inventoryListProvider =
|
|
AsyncNotifierProvider<InventoryListNotifier, PageResult<Inventory>>(
|
|
InventoryListNotifier.new,
|
|
);
|
|
|
|
/// 全店库存 KPI 汇总(不随分页;换店/网络恢复自动刷新)。
|
|
final inventorySummaryProvider =
|
|
FutureProvider.autoDispose<InventorySummary>((ref) {
|
|
ref.watch(authStateProvider.select((s) => s.user?.shopId));
|
|
ref.watch(networkRecoveryCountProvider);
|
|
return ref.read(inventoryRepositoryProvider).getSummary();
|
|
});
|
|
|
|
class InventoryListNotifier extends AsyncNotifier<PageResult<Inventory>> {
|
|
int _page = 1;
|
|
int _pageSize = AppConstants.defaultPageSize;
|
|
int? _warehouseId;
|
|
String _keyword = '';
|
|
List<String> _series = [];
|
|
List<String> _spec = [];
|
|
PageResult<Inventory>? _cache;
|
|
|
|
@override
|
|
Future<PageResult<Inventory>> build() async {
|
|
ref.watch(authStateProvider.select((s) => s.user?.shopId));
|
|
ref.watch(networkRecoveryCountProvider); // 网络恢复时自动刷新
|
|
try {
|
|
final result = await _fetch();
|
|
_cache = result;
|
|
return result;
|
|
} catch (_) {
|
|
if (_cache != null) return _cache!;
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<PageResult<Inventory>> _fetch() {
|
|
return ref.read(inventoryRepositoryProvider).listInventory(
|
|
warehouseId: _warehouseId,
|
|
keyword: _keyword.isEmpty ? null : _keyword,
|
|
series: _series.isEmpty ? null : _series,
|
|
spec: _spec.isEmpty ? null : _spec,
|
|
page: _page,
|
|
pageSize: _pageSize,
|
|
);
|
|
}
|
|
|
|
void setPage(int page) {
|
|
_page = page;
|
|
reload();
|
|
}
|
|
|
|
void setPageSize(int pageSize) {
|
|
_pageSize = pageSize;
|
|
_page = 1;
|
|
reload();
|
|
}
|
|
|
|
void setWarehouseId(int? id) {
|
|
_warehouseId = id;
|
|
_page = 1;
|
|
reload();
|
|
}
|
|
|
|
void setKeyword(String keyword) {
|
|
_keyword = keyword;
|
|
_page = 1;
|
|
reload();
|
|
}
|
|
|
|
void setSeries(List<String> series) {
|
|
_series = series;
|
|
_page = 1;
|
|
reload();
|
|
}
|
|
|
|
void setSpec(List<String> spec) {
|
|
_spec = spec;
|
|
_page = 1;
|
|
reload();
|
|
}
|
|
|
|
void reload() {
|
|
state = const AsyncValue.loading();
|
|
_fetch().then((result) {
|
|
_cache = result;
|
|
state = AsyncValue.data(result);
|
|
}, onError: (e, st) {
|
|
if (_cache != null) {
|
|
state = AsyncValue.data(_cache!);
|
|
} else {
|
|
state = AsyncValue.error(e, st);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
final inventoryLogProvider =
|
|
AsyncNotifierProvider<InventoryLogNotifier, PageResult<InventoryLog>>(
|
|
InventoryLogNotifier.new,
|
|
);
|
|
|
|
class InventoryLogNotifier extends AsyncNotifier<PageResult<InventoryLog>> {
|
|
int _page = 1;
|
|
int _pageSize = AppConstants.defaultPageSize;
|
|
PageResult<InventoryLog>? _cache;
|
|
|
|
@override
|
|
Future<PageResult<InventoryLog>> build() async {
|
|
ref.watch(authStateProvider.select((s) => s.user?.shopId));
|
|
ref.watch(networkRecoveryCountProvider);
|
|
try {
|
|
final result = await _fetch();
|
|
_cache = result;
|
|
return result;
|
|
} catch (_) {
|
|
if (_cache != null) return _cache!;
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<PageResult<InventoryLog>> _fetch() {
|
|
return ref.read(inventoryRepositoryProvider).listLogs(
|
|
page: _page,
|
|
pageSize: _pageSize,
|
|
);
|
|
}
|
|
|
|
void setPage(int page) {
|
|
_page = page;
|
|
reload();
|
|
}
|
|
|
|
void setPageSize(int pageSize) {
|
|
_pageSize = pageSize;
|
|
_page = 1;
|
|
reload();
|
|
}
|
|
|
|
void reload() {
|
|
state = const AsyncValue.loading();
|
|
_fetch().then((result) {
|
|
_cache = result;
|
|
state = AsyncValue.data(result);
|
|
}, onError: (e, st) {
|
|
if (_cache != null) {
|
|
state = AsyncValue.data(_cache!);
|
|
} else {
|
|
state = AsyncValue.error(e, st);
|
|
}
|
|
});
|
|
}
|
|
}
|