feat(client): 草稿 KPI 卡(暗黄)+ 出库详情总售价列 + 明细去¥缩号 + 建单默认值 + 若干列名

- 出入库列表加草稿 KPI 卡(DsKpiTone.warn 新档,审核卡后,点击筛选草稿)
- 出库详情 7 列:售价后加总售价;明细行内价格去 ¥ 且缩一号(合计保留 ¥);
  5/7 列宽重排(系列/数量左移、进价(单瓶)不折行)
- 出库建单:数量默认=可用数量、售价默认=参考售价(product.sale_price)
- 入库详情列名:进价(单瓶)/总价;库存列表「单价」→「总价」(库存×成本);
  商品抽屉:成本价(单瓶)/总成本价
- 公开商品预览页加返回按钮(仅 App 内 canPop 时显示,扫码直达保持纯净)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
wangjia
2026-07-03 14:31:50 +08:00
parent 4d1d69595b
commit 2bf5b98ba2
28 changed files with 132 additions and 35 deletions
@@ -36,6 +36,7 @@ class _PickerItem {
final String spec;
final String unit;
final double? costPrice; // 进价(成本,仅管理员可见列)
final double? salePrice; // 参考售价(入库时填写,存 product.sale_price
final double availableQty;
const _PickerItem({
required this.productId,
@@ -45,6 +46,7 @@ class _PickerItem {
required this.spec,
required this.unit,
this.costPrice,
this.salePrice,
required this.availableQty,
});
}
@@ -64,6 +66,7 @@ List<_PickerItem> _aggregatePickerItems(List<Inventory> rows) {
spec: existing.spec,
unit: existing.unit,
costPrice: existing.costPrice ?? inv.unitPrice,
salePrice: existing.salePrice ?? inv.salePrice,
availableQty: existing.availableQty + inv.quantity,
);
} else {
@@ -75,6 +78,7 @@ List<_PickerItem> _aggregatePickerItems(List<Inventory> rows) {
spec: inv.spec,
unit: inv.unit,
costPrice: inv.unitPrice,
salePrice: inv.salePrice,
availableQty: inv.quantity,
);
}
@@ -264,7 +268,7 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
setState(() {
for (final item in selected) {
if (_items.any((row) => row.productId == item.productId)) continue;
_items.add(_ItemRow(
final row = _ItemRow(
productId: item.productId,
productCode: item.productCode,
productName: item.productName,
@@ -272,7 +276,11 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
spec: item.spec,
costPrice: item.costPrice,
availableQty: item.availableQty,
));
salePrice: item.salePrice, // 售价默认带出入库时填的参考售价
);
// 数量默认带出可用数量(整箱出为主,改少比补多顺手)
row.qtyCtrl.text = item.availableQty.toStringAsFixed(0);
_items.add(row);
}
});
}
@@ -532,6 +532,17 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
ref.read(stockOutListProvider.notifier).setStatus('pending');
},
),
DsKpi(
title: '草稿单数 · 点击筛选',
value: '${summary?.draftCount ?? 0}',
icon: LucideIcons.filePen,
tone: DsKpiTone.warn, // 暗黄(用户拍板)
delta: (summary?.draftCount ?? 0) > 0 ? '待提交审核' : '点击筛选',
onTap: () {
setState(() => _statusFilter = 'draft');
ref.read(stockOutListProvider.notifier).setStatus('draft');
},
),
];
final mobile = context.isMobile;
@@ -845,6 +856,8 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
static final _money =
NumberFormat.currency(locale: 'zh_CN', symbol: '¥', decimalDigits: 0);
// 明细行内数字(去 ¥:列多时更紧凑,符号只留在合计)
static final _num = NumberFormat('#,##0');
static String _fmtQty(double q) =>
q == q.roundToDouble() ? q.toStringAsFixed(0) : q.toString();
@@ -885,10 +898,10 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
series: it.productSeries ?? '',
spec: it.productSpec ?? '',
qty: _fmtQty(it.quantity),
price: pending ? '待定价' : _money.format(it.salePrice),
amount: pending ? '待定价' : _money.format(it.saleAmount),
cost: admin ? _money.format(it.costPrice) : null,
profit: admin ? (pending ? '待定价' : _money.format(profit)) : null,
price: pending ? '待定价' : _num.format(it.salePrice),
amount: pending ? '待定价' : _num.format(it.saleAmount),
cost: admin ? _num.format(it.costPrice) : null,
profit: admin ? (pending ? '待定价' : _num.format(profit)) : null,
pending: pending,
);
}).toList(),