feat: 4项需求实现
1. 供应商/客户编码自动生成(S001/C001...),新建表单隐藏编码字段,编辑保留 2. 财务管理"全部记录"移为第一个 tab 3. 入库单列表 tab 排除 pending 单据,提交后自动移入审核 tab 4. 出库单列表同上;出库表单按仓库+商品自动填最大可用库存数量 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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: ''),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -416,6 +416,7 @@ class _PartnerFormDialogState extends ConsumerState<_PartnerFormDialog> {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(children: [
|
||||
if (isEdit) ...[
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _codeCtrl,
|
||||
@@ -423,6 +424,7 @@ class _PartnerFormDialogState extends ConsumerState<_PartnerFormDialog> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _contactCtrl,
|
||||
|
||||
@@ -51,7 +51,7 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
|
||||
],
|
||||
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<StockInListScreen> {
|
||||
),
|
||||
),
|
||||
data: (result) {
|
||||
final orders = filterStatus != null
|
||||
? result.data
|
||||
.where((o) => o.status == filterStatus)
|
||||
.toList()
|
||||
: result.data;
|
||||
final List<StockInOrder> 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))),
|
||||
],
|
||||
|
||||
@@ -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<StockOutFormScreen> {
|
||||
int? _partnerId;
|
||||
DateTime _orderDate = DateTime.now();
|
||||
bool _submitting = false;
|
||||
// productId → available quantity in selected warehouse
|
||||
Map<int, double> _inventoryMap = {};
|
||||
|
||||
final List<_ItemRow> _items = [];
|
||||
|
||||
@@ -51,6 +54,19 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
|
||||
return total;
|
||||
}
|
||||
|
||||
Future<void> _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<StockOutFormScreen> {
|
||||
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<StockOutFormScreen> {
|
||||
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<StockOutFormScreen> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -52,7 +52,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
|
||||
],
|
||||
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<StockOutListScreen> {
|
||||
),
|
||||
),
|
||||
data: (result) {
|
||||
final orders = filterStatus != null
|
||||
? result.data
|
||||
.where((o) => o.status == filterStatus)
|
||||
.toList()
|
||||
: result.data;
|
||||
final List<StockOutOrder> 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))),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user