feat: 完成7项功能任务
后端: - fix(backend): InventoryLog 增加 Product/Warehouse 关联,Logs 接口加 Preload 修复流水记录显示"-" - feat(backend): 新增 /inventory/batches 批次追踪接口 - feat(backend): 新增财务记录、编号规则接口(finance、number_rule handler) - fix(backend): service/stock_test.go 修复 model.Date 类型错误 前端: - feat(client): 入库/出库管理 Tab 调换顺序(审核在前),审核 Tab 加新建按钮,列表 Tab 无按钮 - feat(client): 入库单/出库单增加详情弹窗,显示完整单据信息和商品明细 - feat(client): 完善出库单新建表单(StockOutFormScreen),支持选客户/仓库/商品 - fix(client): StockInItem/StockOutItem.fromJson 修复读取嵌套 product 对象而非平铺字段 - feat(client): 批次追踪页面(BatchTrackingScreen)及侧边栏入口 - feat(client): 财务管理、盘点、编号规则接入真实后端 API - fix(client): 入库/出库审核通过后 invalidate inventoryListProvider 刷新库存 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../models/number_rule.dart';
|
||||
import '../../models/user.dart';
|
||||
import '../../models/warehouse.dart';
|
||||
import '../../providers/number_rule_provider.dart';
|
||||
import '../../providers/user_provider.dart';
|
||||
import '../../providers/warehouse_provider.dart';
|
||||
|
||||
@@ -311,67 +313,149 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
||||
}
|
||||
|
||||
Widget _buildNumberRulesTab() {
|
||||
final rules = [
|
||||
{'type': '入库单', 'prefix': 'RK', 'format': 'RK{年}{月}{日}{序号4}', 'example': 'RK20260404001', 'currentNo': 4},
|
||||
{'type': '出库单', 'prefix': 'CK', 'format': 'CK{年}{月}{日}{序号4}', 'example': 'CK20260404001', 'currentNo': 2},
|
||||
{'type': '盘点单', 'prefix': 'PD', 'format': 'PD{年}{月}{日}{序号4}', 'example': 'PD20260404001', 'currentNo': 1},
|
||||
{'type': '商品编码', 'prefix': 'SP', 'format': 'SP{序号3}', 'example': 'SP001', 'currentNo': 12},
|
||||
];
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('编号规则配置',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 4),
|
||||
const Text('配置各类单据的自动编号规则,修改后对新建单据生效',
|
||||
style: TextStyle(fontSize: 13, color: AppTheme.textSecondary)),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
child: DataTable(
|
||||
headingRowColor:
|
||||
WidgetStateProperty.all(const Color(0xFFF0F4FF)),
|
||||
columns: const [
|
||||
DataColumn(label: Text('单据类型')),
|
||||
DataColumn(label: Text('前缀')),
|
||||
DataColumn(label: Text('格式')),
|
||||
DataColumn(label: Text('示例')),
|
||||
DataColumn(label: Text('当前序号'), numeric: true),
|
||||
DataColumn(label: Text('操作')),
|
||||
],
|
||||
rows: rules
|
||||
.map((r) => DataRow(cells: [
|
||||
DataCell(Text(r['type'] as String,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500))),
|
||||
DataCell(Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(r['prefix'] as String,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primary,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600)),
|
||||
)),
|
||||
DataCell(Text(r['format'] as String,
|
||||
style: const TextStyle(
|
||||
fontSize: 12, color: AppTheme.textSecondary))),
|
||||
DataCell(Text(r['example'] as String,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace', fontSize: 12))),
|
||||
DataCell(Text('${r['currentNo']}')),
|
||||
DataCell(TextButton(
|
||||
onPressed: () {},
|
||||
child: const Text('编辑', style: TextStyle(fontSize: 12)))),
|
||||
]))
|
||||
.toList(),
|
||||
final asyncRules = ref.watch(numberRuleListProvider);
|
||||
return asyncRules.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, _) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('加载失败:$e',
|
||||
style: const TextStyle(color: AppTheme.danger)),
|
||||
const SizedBox(height: 12),
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
ref.read(numberRuleListProvider.notifier).reload(),
|
||||
child: const Text('重试'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
data: (rules) => SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('编号规则配置',
|
||||
style:
|
||||
TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 4),
|
||||
const Text('配置各类单据的自动编号规则,修改后对新建单据生效',
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: AppTheme.textSecondary)),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
child: DataTable(
|
||||
headingRowColor:
|
||||
WidgetStateProperty.all(const Color(0xFFF0F4FF)),
|
||||
columns: const [
|
||||
DataColumn(label: Text('单据类型')),
|
||||
DataColumn(label: Text('前缀')),
|
||||
DataColumn(label: Text('日期格式')),
|
||||
DataColumn(label: Text('下一编号示例')),
|
||||
DataColumn(label: Text('当前序号'), numeric: true),
|
||||
DataColumn(label: Text('操作')),
|
||||
],
|
||||
rows: rules
|
||||
.map((r) => DataRow(cells: [
|
||||
DataCell(Text(r.typeLabel,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500))),
|
||||
DataCell(Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(r.prefix,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primary,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600)),
|
||||
)),
|
||||
DataCell(Text(r.dateFormat,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppTheme.textSecondary))),
|
||||
DataCell(Text(r.exampleNo,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace', fontSize: 12))),
|
||||
DataCell(Text('${r.currentNo}')),
|
||||
DataCell(TextButton(
|
||||
onPressed: () =>
|
||||
_showNumberRuleDialog(context, r),
|
||||
child: const Text('编辑',
|
||||
style: TextStyle(fontSize: 12)))),
|
||||
]))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showNumberRuleDialog(BuildContext context, NumberRule rule) {
|
||||
final prefixCtrl = TextEditingController(text: rule.prefix);
|
||||
final currentNoCtrl =
|
||||
TextEditingController(text: '${rule.currentNo}');
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text('编辑编号规则 — ${rule.typeLabel}'),
|
||||
content: SizedBox(
|
||||
width: 360,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: prefixCtrl,
|
||||
decoration: const InputDecoration(labelText: '前缀'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: currentNoCtrl,
|
||||
decoration: const InputDecoration(labelText: '当前序号'),
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
child: const Text('取消')),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(ctx).pop();
|
||||
try {
|
||||
await ref
|
||||
.read(numberRuleListProvider.notifier)
|
||||
.updateRule(rule.id, {
|
||||
'prefix': prefixCtrl.text.trim(),
|
||||
'current_no': int.tryParse(currentNoCtrl.text) ?? rule.currentNo,
|
||||
});
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('编号规则已更新'),
|
||||
backgroundColor: AppTheme.success),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('更新失败:$e'),
|
||||
backgroundColor: AppTheme.danger),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const Text('保存'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user