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/fcs/common/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 { 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 doLines = new List(); @override void initState() { super.initState(); var mainModel = Provider.of(context, listen: false); var doModel = Provider.of(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(context); bool isBuyer = mainModel.user.isBuyer(); var logModel = Provider.of(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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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: [ 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(context).isEng ? TextStyle(fontSize: 18) : TextStyle(fontSize: 18, fontFamily: 'MyanmarUnicode')), actions: [ 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.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.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: [ Column( children: [ 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: [ 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 getProductRow(List doLines) { MainModel mainModel = Provider.of(context); ProductModel productModel = Provider.of(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: [ 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 getPOProductRow() { ProductModel productModel = Provider.of(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 _load() async { POSubmissionModel poModel = Provider.of(context, listen: false); DOModel doModel = Provider.of(context, listen: false); var _doSub = await poModel.loadDOLines(doObj); _doSub = await doModel.loadDOPOLines(_doSub); // set po balance List pos = _doSub.getPOs(); for (var po in pos) { List 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(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(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(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(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(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(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(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(context); await doModel.updateDO(doObj, files); Navigator.pop(context, true); } catch (e) { showMsgDialog(context, "Error", e.toString()); } finally { setState(() { _isLoading = false; }); } } }