fix UI
This commit is contained in:
@@ -8,6 +8,8 @@ PODS:
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- printing (1.0.0):
|
||||
- Flutter
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -17,6 +19,7 @@ DEPENDENCIES:
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- printing (from `.symlinks/plugins/printing/ios`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@@ -28,15 +31,18 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
printing:
|
||||
:path: ".symlinks/plugins/printing/ios"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||
mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
printing: 233e1b73bd1f4a05615548e9b5a324c98588640b
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
|
||||
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
||||
|
||||
|
||||
368
lib/core/services/print_service.dart
Normal file
368
lib/core/services/print_service.dart
Normal file
@@ -0,0 +1,368 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
import 'package:printing/printing.dart';
|
||||
|
||||
/// Service for generating and printing warehouse export forms
|
||||
class PrintService {
|
||||
/// Generate and print a warehouse export form
|
||||
static Future<void> printWarehouseExport({
|
||||
required BuildContext context,
|
||||
required String warehouseName,
|
||||
required int productId,
|
||||
required String productCode,
|
||||
required String productName,
|
||||
String? stageName,
|
||||
required double passedKg,
|
||||
required int passedPcs,
|
||||
required double issuedKg,
|
||||
required int issuedPcs,
|
||||
String? responsibleName,
|
||||
String? barcodeData,
|
||||
}) async {
|
||||
final pdf = pw.Document();
|
||||
|
||||
// Format current date
|
||||
final dt = DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now());
|
||||
|
||||
// Add page to PDF
|
||||
pdf.addPage(
|
||||
pw.Page(
|
||||
pageFormat: PdfPageFormat.a4,
|
||||
margin: const pw.EdgeInsets.all(12),
|
||||
build: (pw.Context pdfContext) {
|
||||
return pw.Container(
|
||||
padding: const pw.EdgeInsets.all(12),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title
|
||||
pw.Center(
|
||||
child: pw.Column(
|
||||
children: [
|
||||
pw.Text(
|
||||
'PHIẾU XUẤT KHO',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Text(
|
||||
'Công ty TNHH Cơ Khí Chính Xác Minh Thư',
|
||||
style: const pw.TextStyle(fontSize: 16),
|
||||
),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text(
|
||||
warehouseName,
|
||||
style: const pw.TextStyle(fontSize: 14),
|
||||
),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text(
|
||||
'Ngày: $dt',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
pw.SizedBox(height: 16),
|
||||
|
||||
// Product information box
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border.all(color: PdfColors.black, width: 0.5),
|
||||
borderRadius: pw.BorderRadius.circular(8),
|
||||
),
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Column(
|
||||
children: [
|
||||
pw.Row(
|
||||
children: [
|
||||
pw.Expanded(
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'ProductId',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
'$productId',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.Expanded(
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Mã sản phẩm',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
productCode,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Row(
|
||||
children: [
|
||||
pw.Expanded(
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Tên sản phẩm',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
productName,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
pw.Expanded(
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Công đoạn',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
stageName ?? '-',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
pw.SizedBox(height: 12),
|
||||
|
||||
// Quantities box
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border.all(color: PdfColors.black, width: 0.5),
|
||||
borderRadius: pw.BorderRadius.circular(8),
|
||||
),
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Số lượng:',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 6),
|
||||
pw.Table(
|
||||
border: pw.TableBorder.all(
|
||||
color: PdfColors.black,
|
||||
width: 0.5,
|
||||
),
|
||||
children: [
|
||||
// Header
|
||||
pw.TableRow(
|
||||
decoration: const pw.BoxDecoration(
|
||||
color: PdfColors.grey300,
|
||||
),
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(
|
||||
'Loại',
|
||||
style: pw.TextStyle(
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(
|
||||
'KG',
|
||||
style: pw.TextStyle(
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(
|
||||
'PCS',
|
||||
style: pw.TextStyle(
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Passed quantity row
|
||||
pw.TableRow(
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text('Hàng đạt'),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(passedKg.toStringAsFixed(2)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text('$passedPcs'),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Issued quantity row
|
||||
pw.TableRow(
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text('Hàng lỗi'),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(issuedKg.toStringAsFixed(2)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text('$issuedPcs'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
pw.SizedBox(height: 12),
|
||||
|
||||
// Responsible person box
|
||||
pw.Container(
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border.all(color: PdfColors.black, width: 0.5),
|
||||
borderRadius: pw.BorderRadius.circular(8),
|
||||
),
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Row(
|
||||
children: [
|
||||
pw.Text(
|
||||
'Nhân viên kho: ',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
),
|
||||
pw.Text(
|
||||
responsibleName ?? '-',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
pw.SizedBox(height: 12),
|
||||
|
||||
// Barcode section
|
||||
if (barcodeData != null && barcodeData.isNotEmpty)
|
||||
pw.Center(
|
||||
child: pw.BarcodeWidget(
|
||||
barcode: pw.Barcode.code128(),
|
||||
data: barcodeData,
|
||||
width: 200,
|
||||
height: 60,
|
||||
),
|
||||
),
|
||||
|
||||
pw.Spacer(),
|
||||
|
||||
// Footer signature section
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.center,
|
||||
children: [
|
||||
pw.Container(
|
||||
width: 150,
|
||||
child: pw.Column(
|
||||
children: [
|
||||
pw.Text(
|
||||
'Người nhận',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 40),
|
||||
pw.Container(
|
||||
height: 1,
|
||||
color: PdfColors.grey700,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Show print preview dialog
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (PdfPageFormat format) async => pdf.save(),
|
||||
name: 'warehouse_export_${productCode}_${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
);
|
||||
}
|
||||
}
|
||||
106
lib/docs/import.html
Normal file
106
lib/docs/import.html
Normal file
@@ -0,0 +1,106 @@
|
||||
<!doctype html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Phiếu xuất kho</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<style>
|
||||
:root { --fg:#111; --muted:#666; --border:#000; --primary:#2563eb; }
|
||||
html,body { margin:0; padding:0; font:14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif; color:var(--fg); }
|
||||
.wrap { max-width:720px; margin:0 auto; padding:12px; }
|
||||
.actions { position: sticky; top:0; background:#fff; padding:8px 0; display:flex; gap:8px; justify-content:flex-end; border-bottom:0.1mm solid var(--border); }
|
||||
.actions button { padding:6px 12px; cursor:pointer; border:0.1mm solid var(--border); background:#fff; border-radius:6px; }
|
||||
h1 { font-size:20px; margin:8px 0 4px; text-align:center; }
|
||||
.meta { text-align:center; color:var(--muted); margin-bottom:8px; }
|
||||
.box { border:0.1mm solid var(--border); border-radius:8px; padding:8px; margin:8px 0; }
|
||||
.row { display:flex; gap:8px; margin:6px 0; }
|
||||
.row > div { flex:1; }
|
||||
.label { color:var(--muted); font-size:12px; }
|
||||
.value { font-weight:600; }
|
||||
table { width:100%; border-collapse:collapse; margin-top:6px; }
|
||||
th, td { border:0.1mm solid var(--border); padding:8px; text-align:left; }
|
||||
th { background:#f8fafc; }
|
||||
.barcode { text-align:center; margin:12px 0; }
|
||||
.footer { display:flex; gap:12px; margin:8px 0 4px; }
|
||||
.sign { flex:1; text-align:center; color:var(--muted); padding-top:24px; }
|
||||
/* Ensure printer keeps border colors/thickness */
|
||||
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||
/* Print margins and padding */
|
||||
@page {
|
||||
size: auto;
|
||||
margin: 3mm 0mm; /* outer page margin */
|
||||
}
|
||||
@media print {
|
||||
.actions { display:none; }
|
||||
.wrap { padding:0 4px ; }
|
||||
th { background:#eee; } /* light gray still visible on most printers */
|
||||
/* Force black borders on print */
|
||||
.box, table, th, td { border-color:#000 !important; border-width:0.1mm !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="actions">
|
||||
<button onclick="printAndClose()">In</button>
|
||||
<button onclick="window.close()">Đóng</button>
|
||||
</div>
|
||||
|
||||
<h1>PHIẾU XUẤT KHO</h1>
|
||||
<h3>Công ty TNHH Cơ Khí Chính Xác Minh Thư</h3>
|
||||
<h4>${wareHouseText}</h4>
|
||||
<div class="meta">Ngày: ${dt}</div>
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
<div><div class="label">ProductId</div><div class="value">${productId}</div></div>
|
||||
<div><div class="label">Mã sản phẩm</div><div class="value">${productCode}</div></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div><div class="label">Tên sản phẩm</div><div class="value">${productName}</div></div>
|
||||
<div><div class="label">Công đoạn</div><div class="value">${stageName || '-'}</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="label">Số lượng:</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Loại</th><th>KG</th><th>PCS</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hàng đạt</td>
|
||||
<td>${Number(qty.passedKg || 0)}</td>
|
||||
<td>${Number(qty.passedPcs || 0)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hàng lỗi</td>
|
||||
<td>${Number(qty.issuedKg || 0)}</td>
|
||||
<td>${Number(qty.issuedPcs || 0)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
<div><div class="label">Nhân viên kho</div><div class="value">${responsibleName || '-'}</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="barcode">
|
||||
${barcodeDataUrl ? `<img alt="Barcode" src="${barcodeDataUrl}" />` : ''}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="sign">
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let printed = false;
|
||||
function printAndClose() { printed = true; window.print(); }
|
||||
window.addEventListener('afterprint', () => setTimeout(() => window.close(), 200));
|
||||
window.addEventListener('focus', () => { if (printed) setTimeout(() => window.close(), 400); });
|
||||
window.onload = () => { printAndClose(); };
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../core/di/providers.dart';
|
||||
import '../../../../core/services/print_service.dart';
|
||||
import '../../../users/domain/entities/user_entity.dart';
|
||||
import '../../data/models/create_product_warehouse_request.dart';
|
||||
import '../../domain/entities/product_stage_entity.dart';
|
||||
@@ -118,21 +119,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
operationTitle,
|
||||
style: textTheme.titleMedium,
|
||||
),
|
||||
Text(
|
||||
productName,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: Text('${operationTitle} ${productName}'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
@@ -149,6 +136,11 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
selectedIndex: selectedIndex,
|
||||
theme: theme,
|
||||
),
|
||||
bottomNavigationBar: _buildBottomActionBar(
|
||||
selectedStage: selectedStage,
|
||||
stages: stages,
|
||||
theme: theme,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -277,7 +269,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
if (displayStages.isNotEmpty)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
border: Border(
|
||||
@@ -289,39 +281,6 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
widget.stageId != null
|
||||
? 'Công đoạn'
|
||||
: 'Công đoạn (${displayStages.length})',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (widget.stageId != null) ...[
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'ID: ${widget.stageId}',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
@@ -376,43 +335,24 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 16,
|
||||
spacing: 8,
|
||||
children: [
|
||||
// Stage header
|
||||
_buildStageHeader(stageToShow, theme),
|
||||
|
||||
_buildSectionCard(
|
||||
theme: theme,
|
||||
title: 'Thông tin công đoạn',
|
||||
icon: Icons.info_outlined,
|
||||
children: [
|
||||
_buildInfoRow('Mã sản phẩm', '${stageToShow.productId}'),
|
||||
if (stageToShow.productStageId != null)
|
||||
_buildInfoRow('Mã công đoạn', '${stageToShow.productStageId}'),
|
||||
if (stageToShow.actionTypeId != null)
|
||||
_buildInfoRow('Mã loại thao tác', '${stageToShow.actionTypeId}'),
|
||||
_buildInfoRow('Tên công đoạn', stageToShow.displayName),
|
||||
],
|
||||
),
|
||||
|
||||
// Current Quantity information
|
||||
_buildSectionCard(
|
||||
theme: theme,
|
||||
title: 'Số lượng hiện tại',
|
||||
icon: Icons.info_outlined,
|
||||
children: [
|
||||
_buildInfoRow('Số lượng đạt', '${stageToShow.passedQuantity}'),
|
||||
_buildInfoRow(
|
||||
'Khối lượng đạt',
|
||||
'${stageToShow.passedQuantityWeight.toStringAsFixed(2)} kg',
|
||||
),
|
||||
_buildInfoRow('Số lượng lỗi', '${stageToShow.issuedQuantity}'),
|
||||
_buildInfoRow(
|
||||
'Khối lượng lỗi',
|
||||
'${stageToShow.issuedQuantityWeight.toStringAsFixed(2)} kg',
|
||||
),
|
||||
],
|
||||
),
|
||||
// _buildStageHeader(stageToShow, theme),
|
||||
//
|
||||
// _buildSectionCard(
|
||||
// theme: theme,
|
||||
// title: 'Thông tin công đoạn',
|
||||
// icon: Icons.info_outlined,
|
||||
// children: [
|
||||
// _buildInfoRow('Mã sản phẩm', '${stageToShow.productId}'),
|
||||
// if (stageToShow.productStageId != null)
|
||||
// _buildInfoRow('Mã công đoạn', '${stageToShow.productStageId}'),
|
||||
// if (stageToShow.actionTypeId != null)
|
||||
// _buildInfoRow('Mã loại thao tác', '${stageToShow.actionTypeId}'),
|
||||
// _buildInfoRow('Tên công đoạn', stageToShow.displayName),
|
||||
// ],
|
||||
// ),
|
||||
|
||||
// Add New Quantities section
|
||||
_buildSectionCard(
|
||||
@@ -462,6 +402,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
},
|
||||
theme: theme,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// All Employees Dropdown
|
||||
_buildUserDropdown(
|
||||
label: 'Nhân viên',
|
||||
@@ -476,30 +417,21 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
),
|
||||
]),
|
||||
|
||||
// Action buttons
|
||||
Row(
|
||||
spacing: 12,
|
||||
// Current Quantity information
|
||||
_buildSectionCard(
|
||||
theme: theme,
|
||||
title: 'Số lượng hiện tại',
|
||||
icon: Icons.info_outlined,
|
||||
children: [
|
||||
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _printQuantities(stageToShow),
|
||||
icon: const Icon(Icons.print),
|
||||
label: const Text('In'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => _addNewQuantities(stageToShow),
|
||||
icon: const Icon(Icons.save),
|
||||
label: const Text('Lưu'),
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
_buildInfoRow('Số lượng đạt', '${stageToShow.passedQuantity}'),
|
||||
_buildInfoRow(
|
||||
'Khối lượng đạt',
|
||||
'${stageToShow.passedQuantityWeight.toStringAsFixed(2)} kg',
|
||||
),
|
||||
_buildInfoRow('Số lượng lỗi', '${stageToShow.issuedQuantity}'),
|
||||
_buildInfoRow(
|
||||
'Khối lượng lỗi',
|
||||
'${stageToShow.issuedQuantityWeight.toStringAsFixed(2)} kg',
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -513,15 +445,56 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
);
|
||||
}
|
||||
|
||||
void _printQuantities(ProductStageEntity stage) {
|
||||
// TODO: Implement print functionality
|
||||
Future<void> _printQuantities(ProductStageEntity stage) async {
|
||||
// Get the current quantity values (entered by user or use current values)
|
||||
final passedQuantity = int.tryParse(_passedQuantityController.text) ?? 0;
|
||||
final passedWeight = double.tryParse(_passedWeightController.text) ?? 0.0;
|
||||
final issuedQuantity = int.tryParse(_issuedQuantityController.text) ?? 0;
|
||||
final issuedWeight = double.tryParse(_issuedWeightController.text) ?? 0.0;
|
||||
|
||||
// Use entered values if available, otherwise use current stock values
|
||||
final finalPassedPcs = passedQuantity > 0 ? passedQuantity : stage.passedQuantity;
|
||||
final finalPassedKg = passedWeight > 0.0 ? passedWeight : stage.passedQuantityWeight;
|
||||
final finalIssuedPcs = issuedQuantity > 0 ? issuedQuantity : stage.issuedQuantity;
|
||||
final finalIssuedKg = issuedWeight > 0.0 ? issuedWeight : stage.issuedQuantityWeight;
|
||||
|
||||
// Get responsible user name
|
||||
final responsibleName = _selectedWarehouseUser != null
|
||||
? '${_selectedWarehouseUser!.name} ${_selectedWarehouseUser!.firstName}'
|
||||
: null;
|
||||
|
||||
// Generate barcode data (using product code or product ID)
|
||||
final barcodeData = stage.productCode.isNotEmpty
|
||||
? stage.productCode
|
||||
: 'P${stage.productId}';
|
||||
|
||||
try {
|
||||
await PrintService.printWarehouseExport(
|
||||
context: context,
|
||||
warehouseName: widget.warehouseName,
|
||||
productId: stage.productId,
|
||||
productCode: stage.productCode,
|
||||
productName: stage.productName,
|
||||
stageName: stage.displayName,
|
||||
passedKg: finalPassedKg,
|
||||
passedPcs: finalPassedPcs,
|
||||
issuedKg: finalIssuedKg,
|
||||
issuedPcs: finalIssuedPcs,
|
||||
responsibleName: responsibleName,
|
||||
barcodeData: barcodeData,
|
||||
);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Tính năng in đang phát triển'),
|
||||
duration: Duration(seconds: 2),
|
||||
SnackBar(
|
||||
content: Text('Error printing: ${e.toString()}'),
|
||||
backgroundColor: Colors.red,
|
||||
duration: const Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addNewQuantities(ProductStageEntity stage) async {
|
||||
// Parse the values from text fields
|
||||
@@ -708,28 +681,11 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
}) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 12,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...children,
|
||||
],
|
||||
),
|
||||
@@ -921,4 +877,69 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget? _buildBottomActionBar({
|
||||
required ProductStageEntity? selectedStage,
|
||||
required List<ProductStageEntity> stages,
|
||||
required ThemeData theme,
|
||||
}) {
|
||||
// Determine which stage to show
|
||||
// When stageId is provided, use the filtered stage
|
||||
final displayStages = widget.stageId != null
|
||||
? stages.where((stage) => stage.productStageId == widget.stageId).toList()
|
||||
: stages;
|
||||
|
||||
final stageToShow = widget.stageId != null && displayStages.isNotEmpty
|
||||
? displayStages.first
|
||||
: selectedStage;
|
||||
|
||||
// Don't show action bar if there's no stage to work with
|
||||
if (stageToShow == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: theme.colorScheme.shadow.withValues(alpha: 0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _printQuantities(stageToShow),
|
||||
icon: const Icon(Icons.print),
|
||||
label: const Text('In'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => _addNewQuantities(stageToShow),
|
||||
icon: const Icon(Icons.save),
|
||||
label: const Text('Lưu'),
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
64
pubspec.lock
64
pubspec.lock
@@ -49,6 +49,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
barcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: barcode
|
||||
sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.9"
|
||||
barcode_widget:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: barcode_widget
|
||||
sha256: "6f2c5b08659b1a5f4d88d183e6007133ea2f96e50e7b8bb628f03266c3931427"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
bidi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bidi
|
||||
sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -664,6 +688,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -712,6 +744,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
pdf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pdf
|
||||
sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.3"
|
||||
pdf_widget_wrapper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pdf_widget_wrapper
|
||||
sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -752,6 +800,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
printing:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: printing
|
||||
sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.14.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -768,6 +824,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -36,6 +36,11 @@ dependencies:
|
||||
cached_network_image: ^3.3.1
|
||||
cupertino_icons: ^1.0.6
|
||||
|
||||
# Printing & PDF
|
||||
printing: ^5.13.4
|
||||
pdf: ^3.11.3
|
||||
barcode_widget: ^2.0.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Reference in New Issue
Block a user