chore: release client-v1.0.71
Deploy Client / build-client-web (push) Successful in 47s
Deploy Client / build-macos (push) Successful in 2m11s
Deploy Client / build-android (push) Successful in 1m11s
Deploy Client / build-ios (push) Successful in 2m29s
Deploy Client / build-windows (push) Successful in 2m27s
Deploy Client / release-deploy-client (push) Successful in 1m27s

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZ4DskSRKsSiheQonFtQvx
This commit is contained in:
wangjia
2026-06-21 22:17:50 +08:00
parent 20f9e3a410
commit 51acee2705
11 changed files with 650 additions and 4 deletions
@@ -25,6 +25,7 @@ import '../../providers/finance_provider.dart' show financeRepositoryProvider;
import '../../providers/shop_provider.dart' show shopInfoProvider;
import '../../widgets/write_guard.dart';
import '../../widgets/order_row_actions.dart';
import '../../widgets/order_return_dialog.dart';
import '../../core/auth/auth_state.dart'
show isAdminProvider, currentUserIdProvider;
@@ -268,7 +269,11 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
? '¥${o.totalAmount!.toStringAsFixed(2)}'
: '-'));
case 'status':
return DataCell(StatusBadge(_apiStatusToEnum(o.status)));
final rb = returnStateBadge(o.returnState);
return DataCell(Row(mainAxisSize: MainAxisSize.min, children: [
StatusBadge(_apiStatusToEnum(o.status)),
if (rb != null) ...[const SizedBox(width: 4), rb],
]));
case 'date':
return DataCell(Text(o.orderDate?.substring(0, 10) ?? '-'));
case 'operator':
@@ -531,6 +536,9 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
onApprove: () => _confirmApprove(context, o),
onReject: () => _confirmReject(context, o),
onWithdraw: () => _confirmWithdraw(context, o),
// 入库退单:仅管理员/超管
canReturn: ref.watch(isAdminProvider),
onReturn: () => _confirmReturn(context, o),
);
}
@@ -540,7 +548,13 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
onTap: () => _showDetail(context, o.id),
title: Text(o.orderNo,
style: const TextStyle(fontFamily: 'monospace', fontSize: 14)),
trailing: StatusBadge(_apiStatusToEnum(o.status)),
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
StatusBadge(_apiStatusToEnum(o.status)),
if (returnStateBadge(o.returnState) != null) ...[
const SizedBox(width: 4),
returnStateBadge(o.returnState)!,
],
]),
fields: [
MobileCardField('供应商', o.partnerName ?? '-'),
MobileCardField('仓库', o.warehouseName ?? '-'),
@@ -797,6 +811,68 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
}
}
}
Future<void> _confirmReturn(BuildContext context, StockInOrder o) async {
final go = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('退单'),
content: Text('确认对已审核入库单「${o.orderNo}」发起退单?将打开退单明细窗口选择要退的商品。'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.danger, foregroundColor: Colors.white),
child: const Text('确认'),
),
],
),
);
if (go != true || !mounted) return;
final StockInOrder full;
try {
full = await ref.read(stockInRepositoryProvider).get(o.id);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('加载明细失败:$e'), backgroundColor: AppTheme.danger));
}
return;
}
if (!mounted) return;
final lines = full.items
.where((it) => it.id != null)
.map((it) => ReturnLine(
itemId: it.id!,
code: it.productCode ?? '',
name: it.productName ?? '',
series: it.productSeries ?? '',
spec: it.productSpec ?? '',
quantity: it.quantity,
unitPrice: it.unitPrice,
totalPrice: it.totalPrice,
alreadyReturned: it.isReturned,
))
.toList();
await showOrderReturnDialog(
context: context,
title: '入库单退单 — ${o.orderNo}',
isOut: false,
meta: [
('供应商', o.partnerName ?? '-'),
('仓库', o.warehouseName ?? '-'),
('入库日期', o.orderDate != null && o.orderDate!.length >= 10
? o.orderDate!.substring(0, 10)
: (o.orderDate ?? '-')),
],
lines: lines,
onSubmit: (ids) =>
ref.read(stockInListProvider.notifier).returnItems(o.id, ids),
);
}
}
// Detail dialog — fetches full order with items
@@ -23,6 +23,7 @@ import '../../providers/product_provider.dart';
import '../../providers/finance_provider.dart' show financeRepositoryProvider;
import '../../widgets/write_guard.dart';
import '../../widgets/order_row_actions.dart';
import '../../widgets/order_return_dialog.dart';
import '../../core/auth/auth_state.dart'
show isAdminProvider, currentUserIdProvider;
@@ -269,7 +270,11 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
? '¥${o.totalAmount!.toStringAsFixed(2)}'
: '-'));
case 'status':
return DataCell(StatusBadge(_apiStatusToEnum(o.status)));
final rb = returnStateBadge(o.returnState);
return DataCell(Row(mainAxisSize: MainAxisSize.min, children: [
StatusBadge(_apiStatusToEnum(o.status)),
if (rb != null) ...[const SizedBox(width: 4), rb],
]));
case 'date':
return DataCell(Text(o.orderDate?.substring(0, 10) ?? '-'));
case 'created_at':
@@ -505,6 +510,11 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
onApprove: () => _confirmApprove(context, o),
onReject: () => _confirmReject(context, o),
onWithdraw: () => _confirmWithdraw(context, o),
// 出库退单:管理员/超管 或 本人单
canReturn: ref.watch(isAdminProvider) ||
(o.operatorId != null &&
o.operatorId == ref.watch(currentUserIdProvider)),
onReturn: () => _confirmReturn(context, o),
);
}
@@ -514,7 +524,13 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
onTap: () => _showDetail(context, o.id),
title: Text(o.orderNo,
style: const TextStyle(fontFamily: 'monospace', fontSize: 14)),
trailing: StatusBadge(_apiStatusToEnum(o.status)),
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
StatusBadge(_apiStatusToEnum(o.status)),
if (returnStateBadge(o.returnState) != null) ...[
const SizedBox(width: 4),
returnStateBadge(o.returnState)!,
],
]),
fields: [
MobileCardField('客户', o.partnerName ?? '-'),
MobileCardField('仓库', o.warehouseName ?? '-'),
@@ -776,6 +792,68 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
}
}
}
Future<void> _confirmReturn(BuildContext context, StockOutOrder o) async {
final go = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('退单'),
content: Text('确认对已审核出库单「${o.orderNo}」发起退单?将打开退单明细窗口选择要退的商品。'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.danger, foregroundColor: Colors.white),
child: const Text('确认'),
),
],
),
);
if (go != true || !mounted) return;
final StockOutOrder full;
try {
full = await ref.read(stockOutRepositoryProvider).get(o.id);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('加载明细失败:$e'), backgroundColor: AppTheme.danger));
}
return;
}
if (!mounted) return;
final lines = full.items
.where((it) => it.id != null)
.map((it) => ReturnLine(
itemId: it.id!,
code: it.productCode ?? '',
name: it.productName ?? '',
series: it.productSeries ?? '',
spec: it.productSpec ?? '',
quantity: it.quantity,
unitPrice: it.unitPrice,
totalPrice: it.totalPrice,
alreadyReturned: it.isReturned,
))
.toList();
await showOrderReturnDialog(
context: context,
title: '出库单退单 — ${o.orderNo}',
isOut: true,
meta: [
('客户', o.partnerName ?? '-'),
('仓库', o.warehouseName ?? '-'),
('出库日期', o.orderDate != null && o.orderDate!.length >= 10
? o.orderDate!.substring(0, 10)
: (o.orderDate ?? '-')),
],
lines: lines,
onSubmit: (ids) =>
ref.read(stockOutListProvider.notifier).returnItems(o.id, ids),
);
}
}
// Detail dialog — fetches full order with items