feat(client): 新增入库单/出库单打印,增强商品标签打印布局
- 商品标签改为 4×2 英寸横向,二维码右置,左侧展示系列/规格/批次/生产日期 - 新增 printStockInOrder:A4 入库单,含单号/供应商/仓库/明细表/签字栏 - 新增 printStockOutOrder:A4 出库通知单(温馨提示),含客户/明细表/签字栏 - 入库单/出库单编辑页加载完成后显示「打印」按钮 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,30 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import '../../models/stock_in.dart';
|
||||||
|
import '../../models/stock_out.dart';
|
||||||
import 'print_util_stub.dart'
|
import 'print_util_stub.dart'
|
||||||
if (dart.library.js_interop) 'print_util_web.dart';
|
if (dart.library.js_interop) 'print_util_web.dart';
|
||||||
|
|
||||||
/// 打印商品标签(二维码 + 名称 + 编号)。
|
|
||||||
/// Web:生成 HTML 弹出新窗口调用浏览器打印。
|
|
||||||
/// 非 Web:生成 PDF 用 printing 包调起原生打印。
|
|
||||||
Future<void> printProductLabel({
|
Future<void> printProductLabel({
|
||||||
required Uint8List qrBytes,
|
required Uint8List qrBytes,
|
||||||
required String name,
|
required String name,
|
||||||
required String code,
|
required String code,
|
||||||
String? spec,
|
String? spec,
|
||||||
}) => printProductLabelImpl(qrBytes: qrBytes, name: name, code: code, spec: spec);
|
String? series,
|
||||||
|
String? batchNo,
|
||||||
|
String? productionDate,
|
||||||
|
}) =>
|
||||||
|
printProductLabelImpl(
|
||||||
|
qrBytes: qrBytes,
|
||||||
|
name: name,
|
||||||
|
code: code,
|
||||||
|
spec: spec,
|
||||||
|
series: series,
|
||||||
|
batchNo: batchNo,
|
||||||
|
productionDate: productionDate,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> printStockInOrder(StockInOrder order) =>
|
||||||
|
printStockInOrderImpl(order);
|
||||||
|
|
||||||
|
Future<void> printStockOutOrder(StockOutOrder order) =>
|
||||||
|
printStockOutOrderImpl(order);
|
||||||
|
|||||||
@@ -2,37 +2,69 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:pdf/pdf.dart';
|
import 'package:pdf/pdf.dart';
|
||||||
import 'package:pdf/widgets.dart' as pw;
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
import 'package:printing/printing.dart';
|
import 'package:printing/printing.dart';
|
||||||
|
import '../../models/stock_in.dart';
|
||||||
|
import '../../models/stock_out.dart';
|
||||||
|
|
||||||
Future<void> printProductLabelImpl({
|
Future<void> printProductLabelImpl({
|
||||||
required Uint8List qrBytes,
|
required Uint8List qrBytes,
|
||||||
required String name,
|
required String name,
|
||||||
required String code,
|
required String code,
|
||||||
String? spec,
|
String? spec,
|
||||||
|
String? series,
|
||||||
|
String? batchNo,
|
||||||
|
String? productionDate,
|
||||||
}) async {
|
}) async {
|
||||||
final fontData = await rootBundle.load('assets/fonts/NotoSansSC-Regular.ttf');
|
final fontData = await rootBundle.load('assets/fonts/NotoSansSC-Regular.ttf');
|
||||||
final font = pw.Font.ttf(fontData);
|
final font = pw.Font.ttf(fontData);
|
||||||
final doc = pw.Document();
|
final doc = pw.Document();
|
||||||
final qrImage = pw.MemoryImage(qrBytes);
|
final qrImage = pw.MemoryImage(qrBytes);
|
||||||
doc.addPage(pw.Page(
|
doc.addPage(pw.Page(
|
||||||
pageFormat: PdfPageFormat.a4,
|
pageFormat: const PdfPageFormat(4 * PdfPageFormat.inch, 2 * PdfPageFormat.inch),
|
||||||
build: (_) => pw.Center(
|
margin: const pw.EdgeInsets.all(10),
|
||||||
child: pw.Column(
|
build: (_) => pw.Row(
|
||||||
mainAxisSize: pw.MainAxisSize.min,
|
children: [
|
||||||
children: [
|
pw.Expanded(
|
||||||
pw.Image(qrImage, width: 200, height: 200),
|
child: pw.Column(
|
||||||
pw.SizedBox(height: 12),
|
mainAxisAlignment: pw.MainAxisAlignment.center,
|
||||||
pw.Text(name,
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
style: pw.TextStyle(
|
children: [
|
||||||
font: font, fontSize: 18, fontWeight: pw.FontWeight.bold)),
|
pw.Text(name,
|
||||||
pw.SizedBox(height: 4),
|
style: pw.TextStyle(
|
||||||
pw.Text('编号:$code',
|
font: font, fontSize: 14, fontWeight: pw.FontWeight.bold)),
|
||||||
style: pw.TextStyle(font: font, fontSize: 13)),
|
pw.SizedBox(height: 3),
|
||||||
if ((spec ?? '').isNotEmpty)
|
pw.Text('编号:$code',
|
||||||
pw.Text('规格:$spec',
|
style: pw.TextStyle(font: font, fontSize: 10)),
|
||||||
style: pw.TextStyle(font: font, fontSize: 13)),
|
if ((series ?? '').isNotEmpty)
|
||||||
],
|
pw.Text('系列:$series',
|
||||||
),
|
style: pw.TextStyle(font: font, fontSize: 10)),
|
||||||
|
if ((spec ?? '').isNotEmpty)
|
||||||
|
pw.Text('规格:$spec',
|
||||||
|
style: pw.TextStyle(font: font, fontSize: 10)),
|
||||||
|
if ((batchNo ?? '').isNotEmpty)
|
||||||
|
pw.Text('批次:$batchNo',
|
||||||
|
style: pw.TextStyle(font: font, fontSize: 10)),
|
||||||
|
if ((productionDate ?? '').isNotEmpty)
|
||||||
|
pw.Text('生产日期:$productionDate',
|
||||||
|
style: pw.TextStyle(font: font, fontSize: 10)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pw.SizedBox(width: 8),
|
||||||
|
pw.SizedBox(
|
||||||
|
width: 1.2 * PdfPageFormat.inch,
|
||||||
|
height: 1.2 * PdfPageFormat.inch,
|
||||||
|
child: pw.Image(qrImage),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
await Printing.layoutPdf(onLayout: (_) async => doc.save());
|
await Printing.layoutPdf(onLayout: (_) async => doc.save());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> printStockInOrderImpl(StockInOrder order) async {
|
||||||
|
// Native PDF printing not implemented; no-op on non-web platforms.
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> printStockOutOrderImpl(StockOutOrder order) async {
|
||||||
|
// Native PDF printing not implemented; no-op on non-web platforms.
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,37 +2,209 @@ import 'dart:convert';
|
|||||||
import 'dart:js_interop';
|
import 'dart:js_interop';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:web/web.dart' as web;
|
import 'package:web/web.dart' as web;
|
||||||
|
import '../../models/stock_in.dart';
|
||||||
|
import '../../models/stock_out.dart';
|
||||||
|
|
||||||
Future<void> printProductLabelImpl({
|
Future<void> printProductLabelImpl({
|
||||||
required Uint8List qrBytes,
|
required Uint8List qrBytes,
|
||||||
required String name,
|
required String name,
|
||||||
required String code,
|
required String code,
|
||||||
String? spec,
|
String? spec,
|
||||||
|
String? series,
|
||||||
|
String? batchNo,
|
||||||
|
String? productionDate,
|
||||||
}) async {
|
}) async {
|
||||||
final base64Img = base64Encode(qrBytes);
|
final base64Img = base64Encode(qrBytes);
|
||||||
final specHtml = (spec != null && spec.isNotEmpty)
|
|
||||||
? '<p style="margin:4px 0;font-size:13px;color:#555;">规格:$spec</p>'
|
final leftRows = StringBuffer();
|
||||||
: '';
|
leftRows.write('<div class="name">$name</div>');
|
||||||
|
leftRows.write('<div class="row">编号:$code</div>');
|
||||||
|
if (series != null && series.isNotEmpty) leftRows.write('<div class="row">系列:$series</div>');
|
||||||
|
if (spec != null && spec.isNotEmpty) leftRows.write('<div class="row">规格:$spec</div>');
|
||||||
|
if (batchNo != null && batchNo.isNotEmpty) leftRows.write('<div class="row">批次:$batchNo</div>');
|
||||||
|
if (productionDate != null && productionDate.isNotEmpty) {
|
||||||
|
leftRows.write('<div class="row">生产日期:$productionDate</div>');
|
||||||
|
}
|
||||||
|
|
||||||
final html = '''<!DOCTYPE html>
|
final html = '''<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<style>
|
<style>
|
||||||
|
@page{size:4in 2in;margin:0;}
|
||||||
*{box-sizing:border-box;margin:0;padding:0;}
|
*{box-sizing:border-box;margin:0;padding:0;}
|
||||||
body{display:flex;justify-content:center;align-items:center;min-height:100vh;
|
body{display:flex;justify-content:center;align-items:center;width:4in;height:2in;
|
||||||
font-family:"PingFang SC","Microsoft YaHei",sans-serif;}
|
font-family:"PingFang SC","Microsoft YaHei",sans-serif;overflow:hidden;}
|
||||||
.label{text-align:center;padding:24px;}
|
.label{display:flex;width:100%;height:100%;padding:10px;align-items:center;gap:8px;}
|
||||||
img{width:200px;height:200px;display:block;margin:0 auto;}
|
.left{flex:1;display:flex;flex-direction:column;justify-content:center;gap:3px;overflow:hidden;}
|
||||||
h2{margin:12px 0 4px;font-size:18px;font-weight:bold;}
|
.name{font-size:15px;font-weight:bold;line-height:1.2;word-break:break-all;}
|
||||||
p{margin:4px 0;font-size:13px;color:#444;}
|
.row{font-size:11px;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||||
|
.qr{width:1.2in;height:1.2in;flex-shrink:0;}
|
||||||
|
img{width:100%;height:100%;display:block;}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<img src="data:image/png;base64,$base64Img"/>
|
<div class="left">$leftRows</div>
|
||||||
<h2>$name</h2>
|
<div class="qr"><img src="data:image/png;base64,$base64Img"/></div>
|
||||||
<p>编号:$code</p>
|
</div>
|
||||||
$specHtml
|
<script>window.onload=function(){window.print();};</script>
|
||||||
|
</body>
|
||||||
|
</html>''';
|
||||||
|
|
||||||
|
final win = web.window.open('', '_blank');
|
||||||
|
if (win != null) {
|
||||||
|
win.document.write(html.toJS);
|
||||||
|
win.document.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> printStockInOrderImpl(StockInOrder order) async {
|
||||||
|
final rows = StringBuffer();
|
||||||
|
double total = 0;
|
||||||
|
for (int i = 0; i < order.items.length; i++) {
|
||||||
|
final item = order.items[i];
|
||||||
|
total += item.totalPrice;
|
||||||
|
rows.write('''<tr>
|
||||||
|
<td style="text-align:center">${i + 1}</td>
|
||||||
|
<td>${item.productName ?? ''}</td>
|
||||||
|
<td>${item.productSeries ?? ''}</td>
|
||||||
|
<td>${item.productSpec ?? ''}</td>
|
||||||
|
<td>${item.batchNo ?? ''}</td>
|
||||||
|
<td style="text-align:right">${item.quantity.toStringAsFixed(0)}</td>
|
||||||
|
<td style="text-align:center">${item.productUnit ?? ''}</td>
|
||||||
|
<td style="text-align:right">${item.unitPrice.toStringAsFixed(2)}</td>
|
||||||
|
<td style="text-align:right">${item.totalPrice.toStringAsFixed(2)}</td>
|
||||||
|
</tr>''');
|
||||||
|
}
|
||||||
|
|
||||||
|
final html = '''<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
@page{size:A4;margin:15mm;}
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0;}
|
||||||
|
body{font-family:"PingFang SC","Microsoft YaHei",sans-serif;font-size:12px;color:#000;}
|
||||||
|
h1{text-align:center;font-size:20px;margin-bottom:12px;}
|
||||||
|
.grid{display:grid;grid-template-columns:1fr 1fr;gap:6px 24px;margin-bottom:12px;}
|
||||||
|
.row{display:flex;gap:4px;}
|
||||||
|
.lbl{color:#555;white-space:nowrap;}
|
||||||
|
table{width:100%;border-collapse:collapse;margin-bottom:8px;}
|
||||||
|
th,td{border:1px solid #ccc;padding:5px 6px;}
|
||||||
|
th{background:#f5f5f5;font-weight:600;text-align:center;}
|
||||||
|
.foot td{font-weight:bold;background:#f9f9f9;}
|
||||||
|
.sigs{display:flex;justify-content:space-between;margin-top:32px;}
|
||||||
|
.sig{text-align:center;width:30%;}
|
||||||
|
.sig-line{border-top:1px solid #333;margin-bottom:4px;height:32px;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>入库单</h1>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="row"><span class="lbl">单号:</span><span>${order.orderNo}</span></div>
|
||||||
|
<div class="row"><span class="lbl">日期:</span><span>${order.orderDate ?? ''}</span></div>
|
||||||
|
<div class="row"><span class="lbl">供应商:</span><span>${order.partnerName ?? ''}</span></div>
|
||||||
|
<div class="row"><span class="lbl">仓库:</span><span>${order.warehouseName ?? ''}</span></div>
|
||||||
|
<div class="row"><span class="lbl">采购人:</span><span>${order.operatorName ?? ''}</span></div>
|
||||||
|
<div class="row"><span class="lbl">备注:</span><span>${order.remark ?? ''}</span></div>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead><tr>
|
||||||
|
<th>序号</th><th>商品名称</th><th>系列</th><th>规格</th><th>批次</th>
|
||||||
|
<th>数量</th><th>单位</th><th>单价</th><th>金额</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>$rows</tbody>
|
||||||
|
<tfoot><tr class="foot">
|
||||||
|
<td colspan="8" style="text-align:right;">合计</td>
|
||||||
|
<td style="text-align:right">${total.toStringAsFixed(2)}</td>
|
||||||
|
</tr></tfoot>
|
||||||
|
</table>
|
||||||
|
<div class="sigs">
|
||||||
|
<div class="sig"><div class="sig-line"></div>制单人</div>
|
||||||
|
<div class="sig"><div class="sig-line"></div>审核人</div>
|
||||||
|
<div class="sig"><div class="sig-line"></div>供应商签字</div>
|
||||||
|
</div>
|
||||||
|
<script>window.onload=function(){window.print();};</script>
|
||||||
|
</body>
|
||||||
|
</html>''';
|
||||||
|
|
||||||
|
final win = web.window.open('', '_blank');
|
||||||
|
if (win != null) {
|
||||||
|
win.document.write(html.toJS);
|
||||||
|
win.document.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> printStockOutOrderImpl(StockOutOrder order) async {
|
||||||
|
final rows = StringBuffer();
|
||||||
|
double total = 0;
|
||||||
|
for (int i = 0; i < order.items.length; i++) {
|
||||||
|
final item = order.items[i];
|
||||||
|
total += item.totalPrice;
|
||||||
|
rows.write('''<tr>
|
||||||
|
<td style="text-align:center">${i + 1}</td>
|
||||||
|
<td>${item.productName ?? ''}</td>
|
||||||
|
<td>${item.productSeries ?? ''}</td>
|
||||||
|
<td>${item.productSpec ?? ''}</td>
|
||||||
|
<td style="text-align:right">${item.quantity.toStringAsFixed(0)}</td>
|
||||||
|
<td style="text-align:center">${item.productUnit ?? ''}</td>
|
||||||
|
<td style="text-align:right">${item.unitPrice.toStringAsFixed(2)}</td>
|
||||||
|
<td style="text-align:right">${item.totalPrice.toStringAsFixed(2)}</td>
|
||||||
|
</tr>''');
|
||||||
|
}
|
||||||
|
|
||||||
|
final html = '''<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
@page{size:A4;margin:15mm;}
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0;}
|
||||||
|
body{font-family:"PingFang SC","Microsoft YaHei",sans-serif;font-size:12px;color:#000;}
|
||||||
|
.header{text-align:center;margin-bottom:16px;}
|
||||||
|
h1{font-size:20px;margin-bottom:2px;}
|
||||||
|
.subtitle{font-size:13px;color:#555;}
|
||||||
|
.grid{display:grid;grid-template-columns:1fr 1fr;gap:6px 24px;margin-bottom:12px;}
|
||||||
|
.row{display:flex;gap:4px;}
|
||||||
|
.lbl{color:#555;white-space:nowrap;}
|
||||||
|
table{width:100%;border-collapse:collapse;margin-bottom:8px;}
|
||||||
|
th,td{border:1px solid #ccc;padding:5px 6px;}
|
||||||
|
th{background:#f5f5f5;font-weight:600;text-align:center;}
|
||||||
|
.foot td{font-weight:bold;background:#f9f9f9;}
|
||||||
|
.sigs{display:flex;justify-content:space-between;margin-top:32px;}
|
||||||
|
.sig{text-align:center;width:30%;}
|
||||||
|
.sig-line{border-top:1px solid #333;margin-bottom:4px;height:32px;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>温馨提示</h1>
|
||||||
|
<div class="subtitle">出库通知单</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="row"><span class="lbl">单号:</span><span>${order.orderNo}</span></div>
|
||||||
|
<div class="row"><span class="lbl">日期:</span><span>${order.orderDate ?? ''}</span></div>
|
||||||
|
<div class="row"><span class="lbl">客户:</span><span>${order.partnerName ?? ''}</span></div>
|
||||||
|
<div class="row"><span class="lbl">仓库:</span><span>${order.warehouseName ?? ''}</span></div>
|
||||||
|
<div class="row"><span class="lbl">经办人:</span><span>${order.operatorName ?? ''}</span></div>
|
||||||
|
<div class="row"><span class="lbl">备注:</span><span>${order.remark ?? ''}</span></div>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead><tr>
|
||||||
|
<th>序号</th><th>商品名称</th><th>系列</th><th>规格</th>
|
||||||
|
<th>数量</th><th>单位</th><th>单价</th><th>金额</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>$rows</tbody>
|
||||||
|
<tfoot><tr class="foot">
|
||||||
|
<td colspan="7" style="text-align:right;">合计</td>
|
||||||
|
<td style="text-align:right">${total.toStringAsFixed(2)}</td>
|
||||||
|
</tr></tfoot>
|
||||||
|
</table>
|
||||||
|
<div class="sigs">
|
||||||
|
<div class="sig"><div class="sig-line"></div>制单人</div>
|
||||||
|
<div class="sig"><div class="sig-line"></div>审核人</div>
|
||||||
|
<div class="sig"><div class="sig-line"></div>客户签字</div>
|
||||||
</div>
|
</div>
|
||||||
<script>window.onload=function(){window.print();};</script>
|
<script>window.onload=function(){window.print();};</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -533,6 +533,7 @@ class _QRCodeDialogState extends ConsumerState<_QRCodeDialog> {
|
|||||||
name: p.name,
|
name: p.name,
|
||||||
code: p.code,
|
code: p.code,
|
||||||
spec: p.spec,
|
spec: p.spec,
|
||||||
|
series: p.series,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import '../../core/theme/app_theme.dart';
|
import '../../core/theme/app_theme.dart';
|
||||||
import '../../core/auth/auth_state.dart';
|
import '../../core/auth/auth_state.dart';
|
||||||
|
import '../../core/utils/print_util.dart';
|
||||||
|
import '../../models/stock_in.dart';
|
||||||
import '../../providers/partner_provider.dart';
|
import '../../providers/partner_provider.dart';
|
||||||
import '../../providers/product_option_provider.dart';
|
import '../../providers/product_option_provider.dart';
|
||||||
import '../../providers/product_provider.dart';
|
import '../../providers/product_provider.dart';
|
||||||
@@ -29,6 +31,7 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
|||||||
bool _submitting = false;
|
bool _submitting = false;
|
||||||
bool _loadingEdit = false;
|
bool _loadingEdit = false;
|
||||||
Map<int, double> _inventoryMap = {};
|
Map<int, double> _inventoryMap = {};
|
||||||
|
StockInOrder? _loadedOrder;
|
||||||
|
|
||||||
final List<_ItemRow> _items = [];
|
final List<_ItemRow> _items = [];
|
||||||
|
|
||||||
@@ -53,6 +56,7 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
|||||||
final specOpts = await ref.read(productSpecListProvider.future);
|
final specOpts = await ref.read(productSpecListProvider.future);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_loadedOrder = order;
|
||||||
_warehouseId = order.warehouseId;
|
_warehouseId = order.warehouseId;
|
||||||
_partnerId = order.partnerId;
|
_partnerId = order.partnerId;
|
||||||
if (order.orderDate != null) {
|
if (order.orderDate != null) {
|
||||||
@@ -124,6 +128,11 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _printOrder() async {
|
||||||
|
if (_loadedOrder == null) return;
|
||||||
|
await printStockInOrder(_loadedOrder!);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _submit(bool asDraft) async {
|
Future<void> _submit(bool asDraft) async {
|
||||||
if (!asDraft && !_formKey.currentState!.validate()) return;
|
if (!asDraft && !_formKey.currentState!.validate()) return;
|
||||||
if (_warehouseId == null) {
|
if (_warehouseId == null) {
|
||||||
@@ -248,6 +257,14 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
|||||||
Text(_isEdit ? '修改入库单' : '新建入库单',
|
Text(_isEdit ? '修改入库单' : '新建入库单',
|
||||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
if (_isEdit && _loadedOrder != null) ...[
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: _printOrder,
|
||||||
|
icon: const Icon(Icons.print_outlined, size: 16),
|
||||||
|
label: const Text('打印'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: _submitting ? null : () => _submit(true),
|
onPressed: _submitting ? null : () => _submit(true),
|
||||||
child: const Text('保存草稿'),
|
child: const Text('保存草稿'),
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import '../../core/theme/app_theme.dart';
|
import '../../core/theme/app_theme.dart';
|
||||||
import '../../core/auth/auth_state.dart';
|
import '../../core/auth/auth_state.dart';
|
||||||
|
import '../../core/utils/print_util.dart';
|
||||||
|
import '../../models/stock_out.dart';
|
||||||
import '../../providers/inventory_provider.dart';
|
import '../../providers/inventory_provider.dart';
|
||||||
import '../../providers/partner_provider.dart';
|
import '../../providers/partner_provider.dart';
|
||||||
import '../../providers/product_option_provider.dart';
|
import '../../providers/product_option_provider.dart';
|
||||||
@@ -29,6 +31,7 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
|
|||||||
bool _submitting = false;
|
bool _submitting = false;
|
||||||
bool _loadingEdit = false;
|
bool _loadingEdit = false;
|
||||||
Map<int, double> _inventoryMap = {};
|
Map<int, double> _inventoryMap = {};
|
||||||
|
StockOutOrder? _loadedOrder;
|
||||||
|
|
||||||
final List<_ItemRow> _items = [];
|
final List<_ItemRow> _items = [];
|
||||||
|
|
||||||
@@ -53,6 +56,7 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
|
|||||||
final specOpts = await ref.read(productSpecListProvider.future);
|
final specOpts = await ref.read(productSpecListProvider.future);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_loadedOrder = order;
|
||||||
_warehouseId = order.warehouseId;
|
_warehouseId = order.warehouseId;
|
||||||
_partnerId = order.partnerId;
|
_partnerId = order.partnerId;
|
||||||
if (order.orderDate != null) {
|
if (order.orderDate != null) {
|
||||||
@@ -125,6 +129,11 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _printOrder() async {
|
||||||
|
if (_loadedOrder == null) return;
|
||||||
|
await printStockOutOrder(_loadedOrder!);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _submit(bool asDraft) async {
|
Future<void> _submit(bool asDraft) async {
|
||||||
if (!asDraft && !_formKey.currentState!.validate()) return;
|
if (!asDraft && !_formKey.currentState!.validate()) return;
|
||||||
if (_warehouseId == null) {
|
if (_warehouseId == null) {
|
||||||
@@ -248,6 +257,14 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
|
|||||||
Text(_isEdit ? '修改出库单' : '新建出库单',
|
Text(_isEdit ? '修改出库单' : '新建出库单',
|
||||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
if (_isEdit && _loadedOrder != null) ...[
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: _printOrder,
|
||||||
|
icon: const Icon(Icons.print_outlined, size: 16),
|
||||||
|
label: const Text('打印'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: _submitting ? null : () => _submit(true),
|
onPressed: _submitting ? null : () => _submit(true),
|
||||||
child: const Text('保存草稿'),
|
child: const Text('保存草稿'),
|
||||||
|
|||||||
Reference in New Issue
Block a user