Files
fcs/lib/pages/do/do_approve.dart

905 lines
27 KiB
Dart
Raw Normal View History

2020-05-29 07:45:27 +06:30
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:quiver/async.dart';
import 'package:fcs/model/do_model.dart';
import 'package:fcs/model/language_model.dart';
import 'package:fcs/model/log_model.dart';
import 'package:fcs/model/main_model.dart';
import 'package:fcs/model/po_model.dart';
import 'package:fcs/model/product_model.dart';
import 'package:fcs/pages/do/photo_page.dart';
import 'package:fcs/theme/theme.dart';
import 'package:fcs/vo/do.dart';
import 'package:fcs/vo/po.dart';
import 'package:fcs/widget/img_file.dart';
import 'package:fcs/widget/local_text.dart';
import 'package:fcs/widget/localization/app_translations.dart';
import 'package:fcs/widget/my_data_table.dart';
import 'package:fcs/widget/number_cell.dart';
import 'package:fcs/widget/progress.dart';
import '../document_log_page.dart';
import '../util.dart';
import 'do_files.dart';
import 'do_storage_item.dart';
class DOApproval extends StatefulWidget {
final DOSubmission doSubmission;
const DOApproval({this.doSubmission});
@override
_DOApprovalState createState() => _DOApprovalState();
}
class _DOApprovalState extends State<DOApproval> {
var dateFormatter = new DateFormat('dd MMM yyyy');
final numberFormatter = new NumberFormat("#,###");
var doDateFormatter = new DateFormat('dd MMM yyyy - hh:mm a');
bool _isLoading = false;
TextEditingController _date = new TextEditingController();
TextEditingController _doDate = new TextEditingController();
TextEditingController _number = new TextEditingController();
TextEditingController _licence = new TextEditingController();
TextEditingController _driver = new TextEditingController();
TextEditingController _carNo = new TextEditingController();
TextEditingController _type = new TextEditingController();
TextEditingController _name = new TextEditingController();
TextEditingController _bizName = new TextEditingController();
TextEditingController _storage = new TextEditingController();
TextEditingController _comment = new TextEditingController();
DOSubmission doObj = DOSubmission();
int _count;
DateTime _result;
DOFiles files = DOFiles();
List<DOLine> doLines = new List();
@override
void initState() {
super.initState();
var mainModel = Provider.of<MainModel>(context, listen: false);
var doModel = Provider.of<DOModel>(context, listen: false);
doObj = widget.doSubmission;
_date.text = doObj.deliveryDate != null
? dateFormatter.format(doObj.deliveryDate)
: "";
_doDate.text =
doObj.doDate != null ? doDateFormatter.format(doObj.doDate) : "";
_number.text = doObj.doNumber.toString();
_licence.text = doObj.driverLicenseNumber;
_driver.text = doObj.driverName;
_carNo.text = doObj.carNo;
_type.text = doObj.type;
_name.text = doObj.userName;
_bizName.text = doObj.bizName;
_storage.text = doObj.storageCharge == null
? ""
: numberFormatter.format(doObj.storageCharge);
_comment.text = doObj.comment;
if (doObj.deliveryStatus == 'initiated') {
_count = doModel.timber;
Duration diff = DateTime.now().difference(doObj.deliveryInitiatedTime);
if (diff.inMinutes < mainModel.setting.deliveryStartWaitMin) {
var time = mainModel.setting.deliveryStartWaitMin - diff.inMinutes;
new CountdownTimer(
new Duration(minutes: time), new Duration(seconds: 1))
.listen((data) {
if (mounted) {
setState(() {
_count = data.remaining.inSeconds;
doModel.addTimber(_count);
});
}
});
}
} else {
_count = 0;
}
_load();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
MainModel mainModel = Provider.of<MainModel>(context);
bool isBuyer = mainModel.user.isBuyer();
var logModel = Provider.of<LogModel>(context);
String formattedTime;
if (doObj.deliveryStatus == 'initiated') {
_result = DateTime(
doObj.deliveryInitiatedTime.year,
doObj.deliveryInitiatedTime.month,
doObj.deliveryInitiatedTime.day,
doObj.deliveryInitiatedTime.hour,
doObj.deliveryInitiatedTime.minute,
_count);
formattedTime = DateFormat.ms().format(_result);
}
final doDateBox = Container(
padding: EdgeInsets.only(left: 20, top: 15),
child: Row(
children: <Widget>[
LocalText(context, "do.do_date"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_doDate.text,
style: textStyle,
),
)
],
),
);
final dateBox = Container(
padding: EdgeInsets.only(left: 20, top: 8),
child: Row(
children: <Widget>[
LocalText(context, "do.date"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_date.text,
style: textStyle,
),
)
],
),
);
final numberBox = Container(
padding: EdgeInsets.only(left: 20, top: 5),
child: Row(
children: <Widget>[
LocalText(context, "do.do_num"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_number.text,
style: textStyle,
),
)
],
),
);
final driverBox = Container(
padding: EdgeInsets.only(left: 20, top: 5),
child: Row(
children: <Widget>[
LocalText(context, "do.driver"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_driver.text,
style: textStyle,
),
)
],
),
);
final carNoBox = Container(
padding: EdgeInsets.only(left: 20, top: 5),
child: Row(
children: <Widget>[
LocalText(context, "do.car"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_carNo.text,
style: textStyle,
),
)
],
),
);
final licenceBox = Container(
padding: EdgeInsets.only(left: 20, top: 5),
child: Row(
children: <Widget>[
LocalText(context, "do.licence"),
ImageFile(
enabled: false,
title: "Image",
initialImgUrl: doObj.driverLicenceUrl,
onFile: (file) {}),
],
),
);
final statusBox = Container(
padding: EdgeInsets.only(left: 20, top: 5),
child: Row(
children: <Widget>[
LocalText(context, "do.status"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
doObj.status,
style: doObj.isPending
? textHighlightBlueStyle
: doObj.isApproved
? textHighlightGreenStyle
: textHighlightRedStyle,
),
),
],
),
);
final deliveryStatusBox = Container(
padding: EdgeInsets.only(left: 20, top: 5),
child: Row(
children: <Widget>[
LocalText(context, "do.delivery.status"),
Container(
padding: EdgeInsets.only(left: 10, right: 15),
child: Text(
doObj.getDeliveryStatus,
style: textStyle,
),
),
doObj.deliveryStatus == 'initiated'
? Text(
"(can start in $formattedTime)",
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
)
: Container()
],
),
);
final typeBox = Container(
padding: EdgeInsets.only(left: 20, top: 5),
child: Row(
children: <Widget>[
LocalText(context, "do.type"),
Container(
padding: EdgeInsets.only(left: 20),
child: Text(
_type.text,
style: textStyle,
),
)
],
),
);
final userNameBox = Container(
padding: EdgeInsets.only(top: 5, left: 20),
child: Row(
children: <Widget>[
LocalText(context, "do.name"),
Container(
padding: EdgeInsets.only(left: 20),
child: Text(
_name.text,
style: textStyle,
),
)
],
),
);
final bizNameBox = Container(
padding: EdgeInsets.only(left: 20, top: 5),
child: Row(
children: <Widget>[
LocalText(context, "do.biz"),
Container(
padding: EdgeInsets.only(left: 20),
child: Text(
_bizName.text,
style: textStyle,
),
)
],
),
);
final driverImgUrlBox = Container(
padding: EdgeInsets.only(left: 20),
child: Row(children: <Widget>[
LocalText(context, "do.driver.image"),
Container(
padding: EdgeInsets.only(left: 10),
child: ImageFile(
enabled: !isBuyer && doObj.deliveryStatus == null,
initialImgUrl: doObj.driverImgUrl,
title: "Image",
imageSource: ImageSource.camera,
onFile: (file) {
doObj.driverImg = file;
}),
),
]));
final receiptImagebox = Container(
padding: EdgeInsets.only(left: 20, top: 0),
child: Row(children: <Widget>[
LocalText(context, "do.receipt"),
Container(
padding: EdgeInsets.only(left: 10),
child: ImageFile(
enabled: false,
initialImgUrl: doObj.doReceiptUrl,
title: "Receipt",
),
),
]));
final deliveryInitTimeBox = Container(
padding: EdgeInsets.only(left: 20, top: 5),
child: Row(
children: <Widget>[
LocalText(context, "do.delivery.init.time"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
doObj.deliveryInitTime,
style: textStyle,
),
)
],
),
);
final storageBox = Container(
padding: EdgeInsets.only(left: 20),
child: Row(
children: <Widget>[
LocalText(context, "do.storage_charge"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_storage.text,
style: textStyle,
),
)
],
),
);
final storagePaymentBox = Container(
padding: EdgeInsets.only(left: 20),
child: Row(children: <Widget>[
LocalText(context, "do.storage_receipt"),
ImageFile(
enabled: mainModel.user.isBuyer() ? true : false,
title: "Receipt File",
initialImgUrl: this.doObj.storageReceiptUrl,
onFile: (file) {
this.files.setStorageChargeFile = file;
}),
]));
final commentBox = Container(
padding: EdgeInsets.only(top: 5, left: 20),
child: Row(
children: <Widget>[
LocalText(context, "do.comment"),
Container(
padding: EdgeInsets.only(left: 10),
child: Text(
_comment.text,
style: textStyle,
),
)
],
),
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("do"),
style: Provider.of<LanguageModel>(context).isEng
? TextStyle(fontSize: 18)
: TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')),
actions: <Widget>[
mainModel.showHistoryBtn()
? IconButton(
icon: Icon(Icons.history),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DocumentLogPage(docID: doObj.id)),
);
},
)
: Container(),
isBuyer
? doObj.isPending
? PopupMenuButton(
onSelected: _selectBuyer,
itemBuilder: (context) => List<PopupMenuEntry>.from([
PopupMenuItem(
enabled: this.doObj.isPending &&
this.doObj.storageCharge > 0,
value: 1,
child: Text("Update DO"),
),
PopupMenuItem(
enabled: this.doObj.isPending,
value: 2,
child: Text("Cancel DO"),
),
]))
: Container()
: PopupMenuButton(
onSelected: _select,
itemBuilder: (context) => List<PopupMenuEntry>.from([
PopupMenuItem(
enabled: this.doObj.isPending,
value: 1,
child: Text("Approve DO"),
),
PopupMenuItem(
enabled: this.doObj.isPending,
value: 2,
child: Text("Reject DO"),
),
PopupMenuItem(
enabled:
this.doObj.isApproved && mainModel.user.isOwner(),
value: 6,
child: Text("Cancel DO"),
)
]),
),
],
),
body: Container(
padding:
EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10),
child: Card(
elevation: 23,
child: ListView(
children: <Widget>[
Column(
children: <Widget>[
doDateBox,
Divider(),
dateBox,
Divider(),
numberBox,
Divider(),
userNameBox,
Divider(),
bizNameBox,
Divider(),
typeBox,
Divider(),
statusBox,
Divider(),
doObj.comment == null || doObj.comment == ''
? Container()
: commentBox,
doObj.comment == null || doObj.comment == ''
? Container()
: Divider(),
driverBox,
Divider(),
carNoBox,
Divider(),
licenceBox,
Divider(),
receiptImagebox,
Divider(),
doObj.hasStorageCharge() ? storageBox : Container(),
doObj.hasStorageCharge() ? Divider() : Container(),
doObj.hasStorageCharge()
? storagePaymentBox
: Container(),
doObj.isApproved || doObj.isClosed
? deliveryStatusBox
: Container(),
doObj.isApproved || doObj.isClosed
? Divider()
: Container(),
Container(
padding: EdgeInsets.only(top: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Center(
child: LocalText(
context,
'do.products',
fontSize: 14,
fontWeight: FontWeight.bold,
underline: true,
color: secondaryColor,
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: MyDataTable(
headingRowHeight: 40,
columnSpacing: 40,
columns: [
MyDataColumn(
label: LocalText(context, "do.product"),
),
MyDataColumn(
label: LocalText(context, "do.storage"),
),
MyDataColumn(
label: LocalText(context, "do.quantity"),numeric: true
),
],
rows: getProductRow(doObj.doLines),
),
),
],
),
),
SizedBox(
height: 15,
),
getPOProductTable()
],
),
],
),
)),
));
}
List<MyDataRow> getProductRow(List<DOLine> doLines) {
MainModel mainModel = Provider.of<MainModel>(context);
ProductModel productModel = Provider.of<ProductModel>(context);
bool isBuyer = mainModel.user.isBuyer();
if (doLines.isNotEmpty) {
doLines.forEach((d) {
productModel.products.forEach((p) {
if (p.id == d.productID) {
d.displayOrder = p.displayOrder;
} else {
return;
}
});
});
doLines.sort((p1, p2) => p1.displayOrder.compareTo(p2.displayOrder));
}
return doLines.map((d) {
return MyDataRow(
onSelectChanged: (bool selected) async {
if (isBuyer) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DOStorageItem(
doLine: d,
onSave: (storageID, storageName) {
setState(() {
d.storageID = storageID;
d.storageName = storageName;
});
},
)),
);
},
cells: [
MyDataCell(
new Text(
d.productName,
style: textStyle,
),
),
MyDataCell(
new Text(d.storageName, style: textStyle),
),
MyDataCell(NumberCell(d.qty)),
],
);
}).toList();
}
Widget getPOProductTable() {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: <Widget>[
Center(
child: LocalText(context, 'po.info',
fontSize: 14,
fontWeight: FontWeight.bold,
underline: true,
color: secondaryColor),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: MyDataTable(
headingRowHeight: 40,
columnSpacing: 20,
columns: [
MyDataColumn(label: LocalText(context, "po.number")),
MyDataColumn(label: LocalText(context, "po.product")),
MyDataColumn(
label: LocalText(context, "do.po_qty"),numeric: true,
),
MyDataColumn(
label: LocalText(context, "do.po_balance_qty"),numeric: true,
),
MyDataColumn(
label: LocalText(context, "po.retrieved.amount"),numeric: true,
),
MyDataColumn(
label: LocalText(context, "do.do_qty"),numeric: true,
),
],
rows: getPOProductRow(),
),
),
],
),
);
}
List<MyDataRow> getPOProductRow() {
ProductModel productModel = Provider.of<ProductModel>(context);
if (doObj.dopoLies.isNotEmpty) {
doObj.dopoLies.sort((p1, p2) => p1.poNumber.compareTo(p2.poNumber));
doObj.dopoLies.forEach((d) {
productModel.products.forEach((p) {
if (p.id == d.productID) {
d.displayOrder = p.displayOrder;
} else {
return;
}
});
});
doObj.dopoLies.sort((p1, p2) {
if (p1.displayOrder != p2.displayOrder)
return p1.displayOrder.compareTo(p2.displayOrder);
return p1.poNumber.compareTo(p2.poNumber);
});
}
return doObj.dopoLies.map((d) {
return MyDataRow(
cells: [
MyDataCell(
new Text(
d.poNumber,
style: textStyle,
),
),
MyDataCell(
new Text(
d.productName,
style: textStyle,
),
),
MyDataCell(NumberCell(d.poQty)),
MyDataCell(NumberCell(d.poBalAtCreate)),
MyDataCell(NumberCell(d.getPoBalanceQtyAtCreate)),
MyDataCell(NumberCell(d.doQty)),
],
);
}).toList();
}
_select(s) {
if (s == 1) {
showConfirmDialog(context, "do.approve.confirm", () {
_approve();
});
} else if (s == 2) {
showCommentDialog(context, (comment) {
doObj.comment = comment;
_reject();
});
} else if (s == 5) {
showConfirmDialog(context, "do.end.confirm", () {
_endDelivery();
});
} else if (s == 6) {
showConfirmDialog(context, "do.cancel.confirm", () {
_cancelDelivery();
});
}
}
_selectBuyer(s) {
if (s == 1) {
showConfirmDialog(context, "do.confirm", () {
_submit();
});
} else if (s == 2) {
showConfirmDialog(context, "do.cancel.confirm", () {
_cancelDelivery();
});
}
}
Future<void> _load() async {
POSubmissionModel poModel =
Provider.of<POSubmissionModel>(context, listen: false);
DOModel doModel = Provider.of<DOModel>(context, listen: false);
var _doSub = await poModel.loadDOLines(doObj);
_doSub = await doModel.loadDOPOLines(_doSub);
// set po balance
List<String> pos = _doSub.getPOs();
for (var po in pos) {
List<POLine> poLines = await poModel.loadPOLines(po);
_doSub.setDOPOLineBalance(po, poLines);
}
if (mounted) {
setState(() {
doObj.doLines = _doSub.doLines;
doObj.dopoLies = _doSub.dopoLies;
});
}
}
_approve() async {
if (doObj.doLines.any((l) => l.storageID.isEmpty)) {
showMsgDialog(context, "Error", "Storage required for every product");
return;
}
setState(() {
_isLoading = true;
});
try {
DOModel doModel = Provider.of<DOModel>(context);
await doModel.approveDO(doObj);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_reject() async {
setState(() {
_isLoading = true;
});
try {
DOModel doModel = Provider.of<DOModel>(context);
await doModel.rejectDO(doObj);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_initDelivery() async {
if (doObj.driverImg == null) {
showMsgDialog(context, "Error", "Please attach driver image");
return;
}
setState(() {
_isLoading = true;
});
try {
DOModel doModel = Provider.of<DOModel>(context);
await doModel.initDelivery(doObj);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_startDelivery() async {
MainModel mainModel = Provider.of<MainModel>(context);
Duration diff = DateTime.now().difference(doObj.deliveryInitiatedTime);
if (diff.inMinutes < mainModel.setting.deliveryStartWaitMin) {
showMsgDialog(context, "Waiting...",
"Can not start delivery, wait for ${mainModel.setting.deliveryStartWaitMin} minutes");
return;
}
setState(() {
_isLoading = true;
});
try {
DOModel doModel = Provider.of<DOModel>(context);
await doModel.startDelivery(doObj);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_endDelivery() async {
var photo = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => PhotoPage()),
);
if (photo == null) {
return;
}
Uint8List bytesPhoto = photo.readAsBytesSync() as Uint8List;
setState(() {
_isLoading = true;
});
try {
DOModel doModel = Provider.of<DOModel>(context);
await doModel.endDelivery(doObj, bytesPhoto);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_cancelDelivery() async {
setState(() {
_isLoading = true;
});
try {
DOModel doModel = Provider.of<DOModel>(context);
await doModel.cancelDO(doObj);
Navigator.pop(context);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_submit() async {
if (doObj.hasStorageCharge()) {
if (files.storageChargeFile == null && doObj.storageReceiptUrl == '') {
showMsgDialog(context, "Error", "Please insert storage receipt");
return;
}
}
setState(() {
_isLoading = true;
});
try {
DOModel doModel = Provider.of<DOModel>(context);
await doModel.updateDO(doObj, files);
Navigator.pop<bool>(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}