diff --git a/backend/internal/handler/partner.go b/backend/internal/handler/partner.go index 56e8e7a..9a8fc3a 100644 --- a/backend/internal/handler/partner.go +++ b/backend/internal/handler/partner.go @@ -1,6 +1,7 @@ package handler import ( + "fmt" "net/http" "strconv" @@ -51,6 +52,18 @@ func (h *PartnerHandler) Create(c *gin.Context) { return } p.ShopID = shopID + // 编码未提供时自动生成:供应商 S001/S002…,客户 C001/C002… + if p.Code == "" { + prefix := "S" + if p.Type == "customer" { + prefix = "C" + } + var count int64 + h.db.Model(&model.Partner{}). + Where("shop_id = ? AND type = ? AND deleted_at IS NULL", shopID, p.Type). + Count(&count) + p.Code = fmt.Sprintf("%s%03d", prefix, count+1) + } if err := h.db.Create(&p).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return diff --git a/client/lib/screens/finance/finance_screen.dart b/client/lib/screens/finance/finance_screen.dart index 6365e36..ac158fe 100644 --- a/client/lib/screens/finance/finance_screen.dart +++ b/client/lib/screens/finance/finance_screen.dart @@ -14,14 +14,14 @@ class FinanceScreen extends ConsumerWidget { return PageScaffold( title: '财务管理', tabs: const [ + Tab(text: '全部记录'), Tab(text: '应付账款'), Tab(text: '应收账款'), - Tab(text: '全部记录'), ], tabViews: [ + _FinanceTab(typeFilter: ''), _FinanceTab(typeFilter: 'payable'), _FinanceTab(typeFilter: 'receivable'), - _FinanceTab(typeFilter: ''), ], ); } diff --git a/client/lib/screens/partners/partners_screen.dart b/client/lib/screens/partners/partners_screen.dart index 766ad09..817dbd8 100644 --- a/client/lib/screens/partners/partners_screen.dart +++ b/client/lib/screens/partners/partners_screen.dart @@ -416,13 +416,15 @@ class _PartnerFormDialogState extends ConsumerState<_PartnerFormDialog> { ), const SizedBox(height: 12), Row(children: [ - Expanded( - child: TextFormField( - controller: _codeCtrl, - decoration: const InputDecoration(labelText: '编码'), + if (isEdit) ...[ + Expanded( + child: TextFormField( + controller: _codeCtrl, + decoration: const InputDecoration(labelText: '编码'), + ), ), - ), - const SizedBox(width: 12), + const SizedBox(width: 12), + ], Expanded( child: TextFormField( controller: _contactCtrl, diff --git a/client/lib/screens/stock_in/stock_in_list_screen.dart b/client/lib/screens/stock_in/stock_in_list_screen.dart index 648c3b9..b08958c 100644 --- a/client/lib/screens/stock_in/stock_in_list_screen.dart +++ b/client/lib/screens/stock_in/stock_in_list_screen.dart @@ -51,7 +51,7 @@ class _StockInListScreenState extends ConsumerState { ], tabViews: [ _buildListTab(filterStatus: 'pending', showNewButton: true), - _buildListTab(filterStatus: null, showNewButton: false), + _buildListTab(filterStatus: 'exclude_pending', showNewButton: false), ], ); } @@ -76,16 +76,19 @@ class _StockInListScreenState extends ConsumerState { ), ), data: (result) { - final orders = filterStatus != null - ? result.data - .where((o) => o.status == filterStatus) - .toList() - : result.data; + final List orders; + if (filterStatus == 'pending') { + orders = result.data.where((o) => o.status == 'pending').toList(); + } else if (filterStatus == 'exclude_pending') { + orders = result.data.where((o) => o.status != 'pending').toList(); + } else { + orders = result.data; + } return _buildOrderTable( orders: orders, - totalCount: filterStatus != null ? orders.length : result.total, + totalCount: orders.length, page: result.page, - showStatusFilter: filterStatus == null, + showStatusFilter: filterStatus == 'exclude_pending', showNewButton: showNewButton, ); }, @@ -611,7 +614,6 @@ class _StatusFilterDropdown extends StatelessWidget { items: [ const DropdownMenuItem(value: '', child: Text('全部状态', style: TextStyle(fontSize: 13))), const DropdownMenuItem(value: 'draft', child: Text('草稿', style: TextStyle(fontSize: 13))), - const DropdownMenuItem(value: 'pending', child: Text('待审核', style: TextStyle(fontSize: 13))), const DropdownMenuItem(value: 'approved', child: Text('已审核', style: TextStyle(fontSize: 13))), const DropdownMenuItem(value: 'rejected', child: Text('已拒绝', style: TextStyle(fontSize: 13))), ], diff --git a/client/lib/screens/stock_out/stock_out_form_screen.dart b/client/lib/screens/stock_out/stock_out_form_screen.dart index f977d2b..0f69d6f 100644 --- a/client/lib/screens/stock_out/stock_out_form_screen.dart +++ b/client/lib/screens/stock_out/stock_out_form_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_theme.dart'; import '../../models/product.dart'; +import '../../providers/inventory_provider.dart'; import '../../providers/partner_provider.dart'; import '../../providers/product_provider.dart'; import '../../providers/stock_out_provider.dart'; @@ -23,6 +24,8 @@ class _StockOutFormScreenState extends ConsumerState { int? _partnerId; DateTime _orderDate = DateTime.now(); bool _submitting = false; + // productId → available quantity in selected warehouse + Map _inventoryMap = {}; final List<_ItemRow> _items = []; @@ -51,6 +54,19 @@ class _StockOutFormScreenState extends ConsumerState { return total; } + Future _loadInventory(int warehouseId) async { + try { + final result = await ref + .read(inventoryRepositoryProvider) + .listInventory(warehouseId: warehouseId, pageSize: 500); + setState(() { + _inventoryMap = { + for (final inv in result.data) inv.productId: inv.quantity + }; + }); + } catch (_) {} + } + void _addItem() { setState(() => _items.add(_ItemRow())); } @@ -219,8 +235,10 @@ class _StockOutFormScreenState extends ConsumerState { style: const TextStyle( fontSize: 13)))) .toList(), - onChanged: (v) => setState( - () => _warehouseId = v), + onChanged: (v) { + setState(() => _warehouseId = v); + if (v != null) _loadInventory(v); + }, validator: (v) => v == null ? '不能为空' : null, decoration: const InputDecoration(), @@ -435,8 +453,8 @@ class _StockOutFormScreenState extends ConsumerState { onChanged: (v) { setState(() { item.productId = v; - // Auto-fill sale price if available if (v != null) { + // Auto-fill sale price final found = result.data.firstWhere( (p) => p.id == v, orElse: () => Product( @@ -445,6 +463,12 @@ class _StockOutFormScreenState extends ConsumerState { item.priceCtrl.text = found.salePrice!.toStringAsFixed(2); } + // Auto-fill max available quantity from inventory + final available = _inventoryMap[v] ?? 0; + if (available > 0) { + item.qtyCtrl.text = + available.toStringAsFixed(0); + } } }); }, diff --git a/client/lib/screens/stock_out/stock_out_list_screen.dart b/client/lib/screens/stock_out/stock_out_list_screen.dart index a377dd3..2fed8e6 100644 --- a/client/lib/screens/stock_out/stock_out_list_screen.dart +++ b/client/lib/screens/stock_out/stock_out_list_screen.dart @@ -52,7 +52,7 @@ class _StockOutListScreenState extends ConsumerState { ], tabViews: [ _buildListTab(filterStatus: 'pending', showNewButton: true), - _buildListTab(filterStatus: null, showNewButton: false), + _buildListTab(filterStatus: 'exclude_pending', showNewButton: false), ], ); } @@ -77,16 +77,19 @@ class _StockOutListScreenState extends ConsumerState { ), ), data: (result) { - final orders = filterStatus != null - ? result.data - .where((o) => o.status == filterStatus) - .toList() - : result.data; + final List orders; + if (filterStatus == 'pending') { + orders = result.data.where((o) => o.status == 'pending').toList(); + } else if (filterStatus == 'exclude_pending') { + orders = result.data.where((o) => o.status != 'pending').toList(); + } else { + orders = result.data; + } return _buildOrderTable( orders: orders, - totalCount: filterStatus != null ? orders.length : result.total, + totalCount: orders.length, page: result.page, - showStatusFilter: filterStatus == null, + showStatusFilter: filterStatus == 'exclude_pending', showNewButton: showNewButton, ); }, @@ -615,7 +618,6 @@ class _StatusFilterDropdown extends StatelessWidget { items: const [ DropdownMenuItem(value: '', child: Text('全部状态', style: TextStyle(fontSize: 13))), DropdownMenuItem(value: 'draft', child: Text('草稿', style: TextStyle(fontSize: 13))), - DropdownMenuItem(value: 'pending', child: Text('待审核', style: TextStyle(fontSize: 13))), DropdownMenuItem(value: 'approved', child: Text('已审核', style: TextStyle(fontSize: 13))), DropdownMenuItem(value: 'rejected', child: Text('已拒绝', style: TextStyle(fontSize: 13))), ],