From d0d664e00427dc963fc917b2664e9a003da1bd63 Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Fri, 23 Oct 2020 01:27:23 +0630 Subject: [PATCH 01/10] fix issues --- lib/pages/carton/carton_info.dart | 68 +------------------- lib/pages/delivery/delivery_info.dart | 3 +- lib/pages/discount/discount_list.dart | 2 +- lib/pages/package/model/package_model.dart | 1 - lib/pages/shipment/model/shipment_model.dart | 9 +-- 5 files changed, 10 insertions(+), 73 deletions(-) diff --git a/lib/pages/carton/carton_info.dart b/lib/pages/carton/carton_info.dart index 2c23bd0..37daf5c 100644 --- a/lib/pages/carton/carton_info.dart +++ b/lib/pages/carton/carton_info.dart @@ -1,5 +1,4 @@ import 'package:fcs/domain/constants.dart'; -import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/vo/delivery_address.dart'; @@ -23,7 +22,6 @@ import 'package:provider/provider.dart'; import 'carton_cargo_table.dart'; import 'carton_editor.dart'; -import 'carton_mix_table.dart'; import 'carton_package_table.dart'; import 'model/carton_model.dart'; import 'widgets.dart'; @@ -41,11 +39,6 @@ class CartonInfo extends StatefulWidget { class _CartonInfoState extends State { bool _isLoading = false; Carton _box; - String _selectedCartonType; - List _packages = []; - List _mixBoxes = []; - Carton _selectedShipmentBox = new Carton(); - List _cargoTypes = []; DeliveryAddress _deliveryAddress = new DeliveryAddress(); TextEditingController _widthController = new TextEditingController(); TextEditingController _heightController = new TextEditingController(); @@ -63,7 +56,6 @@ class _CartonInfoState extends State { void initState() { super.initState(); _box = widget.box; - _selectedCartonType = _box.cartonType; //for shipment weight volumetricRatio = Provider.of(context, listen: false) @@ -81,7 +73,6 @@ class _CartonInfoState extends State { _widthController.text = _box.width.toString(); _heightController.text = _box.height.toString(); _lengthController.text = _box.length.toString(); - _cargoTypes = _box.cargoTypes; _deliveryAddress = _box.deliveryAddress; isMixBox = _box.cartonType == carton_mix_box; isFromShipments = _box.cartonType == carton_from_shipments; @@ -101,7 +92,8 @@ class _CartonInfoState extends State { List packages = await packageModel.getPackages(_box.userID, [ package_processed_status, package_packed_status, - package_shipped_status + package_shipped_status, + package_delivered_status ]); packages = packages.where((p) => _box.packageIDs.contains(p.id)).toList(); packages.forEach((p) { @@ -155,38 +147,6 @@ class _CartonInfoState extends State { iconData: Icons.person, ); - final shipmentBoxTitle = Container( - padding: EdgeInsets.only(left: 15, right: 10.0, top: 20), - child: Row( - children: [ - Expanded( - child: - LocalText(context, 'box.shipment_number', color: Colors.grey), - ), - LocalText(context, 'box.shipment.desc', color: Colors.grey), - ], - ), - ); - - final shipmentBoxRow = Container( - padding: EdgeInsets.only(left: 15.0, right: 10.0, top: 5.0, bottom: 5.0), - child: Row( - children: [ - Expanded( - child: new Text( - _selectedShipmentBox.shipmentNumber == null - ? "" - : _selectedShipmentBox.shipmentNumber, - style: textStyle, - )), - new Text( - _selectedShipmentBox.desc == null ? "" : _selectedShipmentBox.desc, - style: textStyle, - ), - ], - ), - ); - final lengthBox = LengthPicker( controller: _lengthController, lableKey: "box.length", @@ -276,30 +236,6 @@ class _CartonInfoState extends State { packages: _box.packages, ) : Container(), - isFromPackages - ? Container() - : isFromShipments - ? Column( - children: [ - LocalTitle(textKey: "box.shipment.boxes"), - shipmentBoxTitle, - Divider( - color: Colors.grey[400], - ), - shipmentBoxRow - ], - ) - : Container(), - // : _selectedCartonType == "Mix carton" - // ? CartonMixTable( - // cartons: _box.cartons, - // onSelect: (c, check) { - // setState(() { - // c.isChecked = check; - // }); - // }, - // ) - // : Container(), isMixBox ? Container() : LocalTitle(textKey: "box.cargo.type"), isMixBox ? Container() : cargoTableBox, ...(isFromPackages diff --git a/lib/pages/delivery/delivery_info.dart b/lib/pages/delivery/delivery_info.dart index a12b8d0..dc0a5c4 100644 --- a/lib/pages/delivery/delivery_info.dart +++ b/lib/pages/delivery/delivery_info.dart @@ -99,7 +99,8 @@ class _DeliveryInfoState extends State { List packages = await packageModel.getPackages(_box.userID, [ package_processed_status, package_packed_status, - package_shipped_status + package_shipped_status, + package_delivered_status ]); packages = packages.where((p) => _box.packageIDs.contains(p.id)).toList(); packages.forEach((p) { diff --git a/lib/pages/discount/discount_list.dart b/lib/pages/discount/discount_list.dart index e8cf374..ca2845a 100644 --- a/lib/pages/discount/discount_list.dart +++ b/lib/pages/discount/discount_list.dart @@ -16,7 +16,7 @@ import 'discount_editor.dart'; class DiscountList extends StatefulWidget { final bool selectionMode; - const DiscountList({Key key, this.selectionMode}) : super(key: key); + const DiscountList({Key key, this.selectionMode = false}) : super(key: key); @override _DiscountListState createState() => _DiscountListState(); } diff --git a/lib/pages/package/model/package_model.dart b/lib/pages/package/model/package_model.dart index 9593ace..f1608d6 100644 --- a/lib/pages/package/model/package_model.dart +++ b/lib/pages/package/model/package_model.dart @@ -186,7 +186,6 @@ class PackageModel extends BaseModel { .where("status", whereIn: status) .where("user_id", isEqualTo: userID) .where("is_deleted", isEqualTo: false) - .where("is_delivered", isEqualTo: false) .getDocuments(source: Source.server); packages = snaps.documents.map((documentSnapshot) { var p = diff --git a/lib/pages/shipment/model/shipment_model.dart b/lib/pages/shipment/model/shipment_model.dart index 8e33777..8e8cbe5 100644 --- a/lib/pages/shipment/model/shipment_model.dart +++ b/lib/pages/shipment/model/shipment_model.dart @@ -1,13 +1,9 @@ import 'dart:async'; -import 'dart:io'; -import 'package:fcs/helpers/firebase_helper.dart'; -import 'package:path/path.dart' as Path; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fcs/data/services/services.dart'; import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/shipment.dart'; -import 'package:fcs/domain/vo/message.dart'; import 'package:fcs/helpers/paginator.dart'; import 'package:fcs/pages/main/model/base_model.dart'; import 'package:logging/logging.dart'; @@ -132,6 +128,11 @@ class ShipmentModel extends BaseModel { shipment_courier_dropoff ]; + Shipment getActiveShipment(String shipmentID) { + return _shipments?.firstWhere((e) => e.id == shipmentID, + orElse: () => null); + } + Future getShipment(String shipmentID) async { String path = "/$shipments_collection"; try { From feec3c8687ce392825830bf9811d292fdfa0070e Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Sat, 24 Oct 2020 06:14:07 +0630 Subject: [PATCH 02/10] update invoice page --- assets/local/localization_en.json | 19 +- assets/local/localization_mu.json | 11 +- lib/domain/entities/cargo_type.dart | 5 +- lib/domain/entities/carton.dart | 28 +- lib/domain/entities/invoice.dart | 132 +++--- lib/domain/entities/receipt.dart | 2 - lib/domain/entities/shipment.dart | 10 + lib/pages/carton/carton_editor.dart | 41 +- lib/pages/carton/model/carton_model.dart | 5 +- lib/pages/discount/model/discount_model.dart | 25 +- lib/pages/invoice/invoice_cargo_table.dart | 350 ---------------- lib/pages/invoice/invoice_carton_table.dart | 7 +- lib/pages/invoice/invoice_discount_table.dart | 88 ++++ lib/pages/invoice/invoice_editor.dart | 342 ++++++++++------ lib/pages/invoice/invoice_info.dart | 6 +- lib/pages/invoice/invoice_shipment_table.dart | 105 +++++ lib/pages/invoice/invoice_table.dart | 377 ++++++++++++++++++ lib/pages/invoice/model/invoice_model.dart | 43 +- lib/pages/invoice/payment_page.dart | 2 +- lib/pages/shipment/model/shipment_model.dart | 24 ++ lib/pages/widgets/fcs_icons.dart | 5 + .../widgets/local_popup_menu_button.dart | 6 +- 22 files changed, 996 insertions(+), 637 deletions(-) delete mode 100644 lib/pages/invoice/invoice_cargo_table.dart create mode 100644 lib/pages/invoice/invoice_discount_table.dart create mode 100644 lib/pages/invoice/invoice_shipment_table.dart create mode 100644 lib/pages/invoice/invoice_table.dart create mode 100644 lib/pages/widgets/fcs_icons.dart diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index 0ae209d..17d9baf 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -393,13 +393,13 @@ "rate.delivery_fee":"Delivery fees", "rate.total_estimated_amount":"Total estimated amount", "rate.volumetric_ratio":"Volumetric ratio", - "rate.custom.form.title":"CUSTOM", + "rate.custom.form.title":"Custom", "rate.cutom.product_type":"Product type", "rate.custom.fee":"Fee", "rate.discount.weight":"Weight", "rate.discount.rate":"Discount rate", - "rate.custom_duty.title":"Custom Duties", - "rate.custom_duty":"Custom Duty", + "rate.custom_duty.title":"Custom Fee", + "rate.custom_duty":"Custom Fee", "rate.cargo.type":"Cargo Types", "rate.discount_by_weight":"Discounts by weight", "rate.discount_by_weight.edit.delete.confirm":"Delete this discount by weight?", @@ -428,6 +428,8 @@ "invoice.customer_name":"Customer name", "invoice.status":"Status", "invoice.amount":"Amount", + "invoice.weight":"Weight(lb)", + "invoice.rate":"Rate(USD)", "invoice.total":"Total amount", "invoice.balance":"Balance", "invoice.handling_fee":"Handling fee", @@ -435,15 +437,16 @@ "invoice.custom_fee_desc":"Custom fee description", "invoice.discount":"Discount code", "invoice.payment_method":"Payment method", - "invoice.delivery_fee":"Delivery fee : ", + "invoice.delivery_fee":"Delivery fee", "invoice.payment_attachment":"Payment attachment", - "invoice.box_info":"Carton information", + "invoice.box_info":"Cartons", "invoice.cargo_table":"Cargo table", "invoice.btn_create":"Create invoice", "invoice.btn_save":"Save invoice", "invoice.btn_payment_receipt":"Attachment payment receipt", "invoice.description": "Description", "invoice.box.cargo_type": "Cargo types", + "invoice.box.desc": "Description", "invoice.cargo_type":"Cargo types", "invoice.box.number":"Carton number", "invoice.box.length":"L", @@ -459,6 +462,12 @@ "invoice.popupmenu.paid":"Paid", "invoice.shipment.title":"Select FCS shipment", "invoice.customer.title":"Select Customer", + "invoice.add.custom.fee.menu":"Custom fee", + "invoice.add.handling.fee.menu":"Handling fee", + "invoice.add.discount.menu":"Discount", + "invoice.shipment.handling.fee.title":"Select shipment", + "invoice.shipment.number":"Shipment number", + "invoice.shipment.discount.title":"Select discount", "Invoices End ================================================================":"", "Discount Start ================================================================":"", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index 2e9273c..1228908 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -428,6 +428,8 @@ "invoice.customer_name":"ဝယ်ယူသူနာမည်", "invoice.status":"အခြေအနေ", "invoice.amount":"ပမာဏ", + "invoice.weight":"Weight(lb)", + "invoice.rate":"Rate(USD)", "invoice.total":"စုစုပေါင်းပမာဏ", "invoice.balance":"ပေးချေရန်ကျန်ရှိငွေ", "invoice.handling_fee":"ထိန်းသိမ်းခ", @@ -437,7 +439,7 @@ "invoice.payment_method":"ငွေပေးချေစနစ်", "invoice.delivery_fee":"ပို့ဆောင်ခ", "invoice.payment_attachment":"ပေးချေပြီးဖိုင်များ", - "invoice.box_info":"Box အချက်အလက်", + "invoice.box_info":"Cartons", "invoice.cargo_table":"ကုန်ပစ္စည်းဇယား", "invoice.btn_create":"ငွေတောင်းခံလွှာ ပြုလုပ်ရန်", "invoice.btn_save":"ငွေတောင်းခံလွှာ သိမ်းရန်", @@ -445,6 +447,7 @@ "invoice.box.cargo_type": "ကုန်ပစ္စည်းအမျိုးအစားများ", "invoice.description": "ငွေတောင်းခံလွှာအကြောင်းအရာ", "invoice.cargo_type":"Cargo Types", + "invoice.box.desc": "Description", "invoice.box.number":"Box နံပါတ်", "invoice.box.length":"L", "invoice.box.width":"W", @@ -459,6 +462,12 @@ "invoice.popupmenu.paid":"Paid", "invoice.shipment.title":"Select FCS shipment", "invoice.customer.title":"Select Customer", + "invoice.add.custom.fee.menu":"Custom fee", + "invoice.add.handling.fee.menu":"Handling fee", + "invoice.add.discount.menu":"Discount", + "invoice.shipment.number":"Shipment number", + "invoice.shipment.handling.fee.title":"Select shipment", + "invoice.shipment.discount.title":"Select discount", "Invoices End ================================================================":"", "Discount Start ================================================================":"", diff --git a/lib/domain/entities/cargo_type.dart b/lib/domain/entities/cargo_type.dart index d502d62..f6a4ea0 100644 --- a/lib/domain/entities/cargo_type.dart +++ b/lib/domain/entities/cargo_type.dart @@ -4,7 +4,6 @@ class CargoType { double rate; double weight; - int shipmentWeight; double amount; double calRate; @@ -16,9 +15,10 @@ class CargoType { name: map['name'], rate: map['rate']?.toDouble() ?? 0, weight: map['weight']?.toDouble() ?? 0, + calWeight: map['cal_weight']?.toDouble() ?? 0, ); } - CargoType({this.id, this.name, this.rate, this.weight}); + CargoType({this.id, this.name, this.rate, this.weight, this.calWeight}); Map toMap() { return { @@ -26,6 +26,7 @@ class CargoType { 'name': name, 'rate': rate, 'weight': weight, + 'cal_weight': calWeight, }; } diff --git a/lib/domain/entities/carton.dart b/lib/domain/entities/carton.dart index 609ed70..a11d949 100644 --- a/lib/domain/entities/carton.dart +++ b/lib/domain/entities/carton.dart @@ -1,6 +1,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fcs/domain/entities/discount_by_weight.dart'; import 'package:fcs/domain/entities/rate.dart'; +import 'package:fcs/domain/entities/shipment.dart'; import 'package:fcs/domain/vo/shipment_status.dart'; import 'package:fcs/domain/vo/delivery_address.dart'; @@ -9,6 +10,7 @@ import 'package.dart'; class Carton { String id; + String shipmentID; String shipmentNumber; String senderFCSID; String senderName; @@ -47,9 +49,9 @@ class Carton { List packageIDs; List packages; List cargoTypes; - List cartons; DeliveryAddress deliveryAddress; + Shipment shipment; int get amount => rate != null && weight != null ? rate * weight : 0; @@ -73,6 +75,23 @@ class Carton { return (length * width * height) / volumetricRatio; } + /// getCargoTypeForCalWeight returns carton with shipment weight + List getCargoTypeForCalWeight(double volumetricRatio) { + // get shipment weight + double volume = (length ?? 0) * (width ?? 0) * (height ?? 0); + double sw = volume / volumetricRatio ?? 0; + + // get actual weight + double aw = cargoTypes.fold(0.0, (p, c) => p + c.weight); + if (aw == 0 || sw == 0) return []; + + cargoTypes.forEach((e) { + double calWeight = aw > sw ? e.weight : e.weight / aw * sw; + e.calWeight = calWeight; + }); + return cargoTypes; + } + /// calAmount returns total amount double calAmount(Rate rate) { // get shipment weight @@ -101,6 +120,7 @@ class Carton { Carton( {this.id, + this.shipmentID, this.shipmentNumber, this.senderFCSID, this.senderName, @@ -133,7 +153,6 @@ class Carton { this.cartonNumber, this.fcsShipmentID, this.fcsShipmentNumber, - this.cartons, this.packageIDs, this.mixCartonID, this.mixCartonNumber, @@ -143,7 +162,6 @@ class Carton { Map toMap() { List _cargoTypes = cargoTypes.map((c) => c.toMap()).toList(); List _packages = packages?.map((c) => c.toJson())?.toList(); - List _cartons = cartons?.map((c) => c.toMap())?.toList() ?? []; return { "id": id, 'fcs_shipment_id': fcsShipmentID, @@ -155,8 +173,7 @@ class Carton { 'height': height, 'delivery_address': deliveryAddress.toMap(), 'carton_type': cartonType, - 'cartons': _cartons, - 'mix_carton_id': mixCartonID + 'mix_carton_id': mixCartonID, }; } @@ -171,6 +188,7 @@ class Carton { return Carton( id: docID, arrivedDate: _arrivedDate != null ? _arrivedDate.toDate() : null, + shipmentID: map['shipment_id'], shipmentNumber: map['shipment_number'], receiverNumber: map['receiver_number'], boxNumber: map['box_number'], diff --git a/lib/domain/entities/invoice.dart b/lib/domain/entities/invoice.dart index 8d63e51..5bc0eb9 100644 --- a/lib/domain/entities/invoice.dart +++ b/lib/domain/entities/invoice.dart @@ -3,63 +3,59 @@ import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/custom_duty.dart'; import 'package:fcs/domain/entities/discount.dart'; import 'package:fcs/domain/entities/discount_by_weight.dart'; +import 'package:fcs/domain/entities/payment_method.dart'; import 'package:fcs/domain/entities/rate.dart'; - -import 'package.dart'; -import 'receipt.dart'; +import 'package:fcs/domain/entities/shipment.dart'; class Invoice { String id; String invoiceNumber; DateTime invoiceDate; - String customerName; - String customerPhoneNumber; - double amount; + String fcsShipmentID; + String userID; + String userName; + String phoneNumber; String status; - String paymentAttachment; + double handlingFee; double deliveryFee; double paidAmount; + double amount; - List packages; - List receipts; - List receiptPhotos; List customDuties; List cartons; + List cargoTypes; + List shipments; Discount discount; + PaymentMethod paymentMethod; List getCargoTypes(Rate rate) { List cargoTypes = []; - double actualWeight = 0; - double shipmentWeight = 0; - cartons.forEach((c) { - c.cargoTypes.forEach((tc) { - if (cargoTypes.contains(tc)) { - CargoType existing = cargoTypes.firstWhere((wc) => wc.id == tc.id); - existing.weight += tc.weight; - } else { - cargoTypes.add(tc.clone()); - } - actualWeight += tc.weight; - }); - double volume = (c.length ?? 0) * (c.width ?? 0) * (c.height ?? 0); - double sw = volume / rate.volumetricRatio ?? 0; - shipmentWeight += sw; + double totalCalWeight = 0; + cartons.forEach((carton) { + if (carton.isChecked) { + var _cartonsTypes = + carton.getCargoTypeForCalWeight(rate.volumetricRatio); + _cartonsTypes.forEach((ct) { + if (cargoTypes.contains(ct)) { + CargoType existing = cargoTypes.firstWhere((wc) => wc.id == ct.id); + existing.calWeight += ct.calWeight; + } else { + cargoTypes.add(ct.clone()); + } + totalCalWeight += ct.calWeight; + }); + } }); - DiscountByWeight discountByWeight = rate.getDiscountByWeight( - shipmentWeight > actualWeight ? shipmentWeight : actualWeight); + DiscountByWeight discountByWeight = + rate.getDiscountByWeight(totalCalWeight); cargoTypes.forEach((e) { - print(actualWeight > shipmentWeight); - double cargoWeight = actualWeight > shipmentWeight - ? e.weight - : e.weight / actualWeight * shipmentWeight; double r = e.rate - (discountByWeight != null ? discountByWeight.discount : 0); - double amount = cargoWeight * r; + double amount = e.calWeight * r; e.calRate = r; - e.calWeight = cargoWeight; e.amount = amount; }); return cargoTypes; @@ -81,6 +77,12 @@ class Invoice { return total; } + double getHandlingFee() { + return shipments?.where((sh) => sh.isSelected)?.fold(0, (p, s) { + return p + (s?.handlingFee ?? 0) - (s?.paidHandlingFee ?? 0); + }); + } + double getTotalBalance(Rate rate) { return getNetAmount(rate) - (paidAmount ?? 0); } @@ -89,49 +91,57 @@ class Invoice { return customDuties == null ? 0 : customDuties.fold(0, (p, d) => p + d.fee); } - double getHandlingFee() { - return handlingFee == null ? 0 : handlingFee; - } - double getDeliveryFee() { return deliveryFee == null ? 0 : deliveryFee; } double getDiscount() => discount == null ? 0 : discount.amount; - Invoice( - {this.id, - this.invoiceNumber, - this.invoiceDate, - this.customerName, - this.customerPhoneNumber, - this.amount, - this.discount, - this.status, - this.paymentAttachment, - this.packages, - this.receiptPhotos, - this.customDuties, - this.cartons, - this.handlingFee, - this.receipts}); - - double get getAmount => packages.fold(0, (p, e) => (e.rate * e.weight) + p); + Invoice({ + this.id, + this.invoiceNumber, + this.invoiceDate, + this.userName, + this.phoneNumber, + this.amount, + this.discount, + this.status, + this.customDuties, + this.cartons, + this.cargoTypes, + this.handlingFee, + this.fcsShipmentID, + this.shipments, + }); factory Invoice.fromMap(Map map, String docID) { return Invoice( id: docID, invoiceNumber: map['invoice_number'], invoiceDate: map['invoice_date'], - customerName: map['customer_name'], - customerPhoneNumber: map['phone_number'], + userName: map['user_name'], + phoneNumber: map['phone_number'], amount: map['amount'], status: map['status'], discount: map['discount'], - paymentAttachment: map['payment_attachment'], - packages: map['packages'], - receiptPhotos: map['receiptPhotos'], - receipts: map['receipts'], ); } + + Map toMap() { + List _cargoTypes = cargoTypes.map((c) => c.toMap()).toList(); + List _customDuties = customDuties?.map((c) => c.toMap())?.toList(); + List _cartons = cartons?.map((c) => c.toMap())?.toList() ?? []; + return { + "id": id, + "invoice_date": invoiceDate, + "user_id": userID, + 'fcs_shipment_id': fcsShipmentID, + 'cargo_types': _cargoTypes, + 'custom_duties': _customDuties, + 'cartons': _cartons, + 'discount': discount?.toMap(), + 'handling_fee': handlingFee, + 'delivery_fee': deliveryFee, + }; + } } diff --git a/lib/domain/entities/receipt.dart b/lib/domain/entities/receipt.dart index 854b12d..6fffbbe 100644 --- a/lib/domain/entities/receipt.dart +++ b/lib/domain/entities/receipt.dart @@ -1,5 +1,3 @@ -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; - class Receipt { String id; int amount; diff --git a/lib/domain/entities/shipment.dart b/lib/domain/entities/shipment.dart index 3a71a72..e07c3ff 100644 --- a/lib/domain/entities/shipment.dart +++ b/lib/domain/entities/shipment.dart @@ -18,6 +18,7 @@ class Shipment { int numberOfPackage; int weight; double handlingFee; + double paidHandlingFee; String address; String status; bool isCourier; @@ -31,6 +32,7 @@ class Shipment { String fcsShipmentID; String fcsShipmentNumber; String shipmentLabelUrl; + bool isSelected; Shipment( {this.id, @@ -44,6 +46,7 @@ class Shipment { this.numberOfPackage, this.weight, this.handlingFee, + this.paidHandlingFee, this.address, this.status, this.pickupDate, @@ -89,6 +92,7 @@ class Shipment { pickupUserName: map['pickup_user_name'], pickupUserPhoneNumber: map['pickup_user_phone_number'], handlingFee: map['handling_fee'], + paidHandlingFee: map['paid_handling_fee'], fcsShipmentID: map['fcs_shipment_id'], fcsShipmentNumber: map['fcs_shipment_number'], shipmentLabelUrl: map['shipment_label_url'], @@ -116,6 +120,12 @@ class Shipment { }; } + @override + bool operator ==(Object other) => other is Shipment && other.id == id; + + @override + int get hashCode => id.hashCode; + @override String toString() { return 'PickUp{id:$id, userName:$userName,phoneNumber:$phoneNumber,numberOfPackage:$numberOfPackage,weight:$weight,status:$status}'; diff --git a/lib/pages/carton/carton_editor.dart b/lib/pages/carton/carton_editor.dart index 331016b..6577c30 100644 --- a/lib/pages/carton/carton_editor.dart +++ b/lib/pages/carton/carton_editor.dart @@ -89,14 +89,16 @@ class _CartonEditorState extends State { _user = User(fcsID: _carton.fcsID, name: _carton.userName); _loadPackages(); } else { - _carton = Carton(cargoTypes: [], packages: [], cartons: []); + _carton = Carton( + cargoTypes: [], + packages: [], + ); _lengthController.text = "12"; _widthController.text = "12"; _heightController.text = "12"; _isNew = true; _selectedCartonType = carton_from_packages; _loadFcsShipments(); - _loadMixCartons(); } } @@ -112,16 +114,6 @@ class _CartonEditorState extends State { }); } - _loadMixCartons() async { - if (_fcsShipment == null) return; - CartonModel cartonModel = Provider.of(context, listen: false); - var mixCartons = - await cartonModel.getMixCartonsByFcsShipment(_fcsShipment.id); - setState(() { - _mixCartons = mixCartons; - }); - } - _loadPackages() async { if (_user == null) return; PackageModel packageModel = @@ -159,19 +151,6 @@ class _CartonEditorState extends State { _populateDeliveryAddress(); } - _loadCartons() async { - if (_fcsShipment == null) return; - CartonModel cartonModel = Provider.of(context, listen: false); - List cartons = - await cartonModel.getCartonsByFcsShipment(_fcsShipment.id); - cartons.forEach((c) { - c.isChecked = true; - }); - setState(() { - _carton.cartons = cartons; - }); - } - _populateDeliveryAddress() { if (_carton.packages == null) return; var d = _carton.packages @@ -217,12 +196,6 @@ class _CartonEditorState extends State { setState(() { _fcsShipment = v; }); - if (_selectedCartonType == carton_mix_box) { - _loadCartons(); - } - if (_selectedCartonType == carton_small_bag) { - _loadMixCartons(); - } }, labelKey: "shipment.pack.fcs.shipment", iconData: Ionicons.ios_airplane, @@ -322,9 +295,6 @@ class _CartonEditorState extends State { setState(() { _selectedCartonType = v; }); - if (_selectedCartonType == carton_mix_box) { - _loadCartons(); - } }); final cargoTableTitleBox = LocalTitle( @@ -507,9 +477,6 @@ class _CartonEditorState extends State { carton.width = w; carton.height = h; carton.deliveryAddress = _deliveryAddress; - carton.cartons = _carton.cartons == null - ? [] - : _carton.cartons.where((c) => c.isChecked).toList(); setState(() { _isLoading = true; }); diff --git a/lib/pages/carton/model/carton_model.dart b/lib/pages/carton/model/carton_model.dart index c4f40fc..c503249 100644 --- a/lib/pages/carton/model/carton_model.dart +++ b/lib/pages/carton/model/carton_model.dart @@ -179,14 +179,15 @@ class CartonModel extends BaseModel { String path = "/$cartons_collection"; var querySnap = await Firestore.instance .collection(path) - // .where("fcs_shipment_id", isEqualTo: fcsShipmentID) + .where("fcs_shipment_id", isEqualTo: fcsShipmentID) .where("user_id", isEqualTo: userID) .where("is_deleted", isEqualTo: false) .where("is_invoiced", isEqualTo: false) .getDocuments(); - return querySnap.documents + List cartons = querySnap.documents .map((e) => Carton.fromMap(e.data, e.documentID)) .toList(); + return cartons; } Future> getMixCartonsByFcsShipment(String fcsShipmentID) async { diff --git a/lib/pages/discount/model/discount_model.dart b/lib/pages/discount/model/discount_model.dart index 1c425b6..5a25660 100644 --- a/lib/pages/discount/model/discount_model.dart +++ b/lib/pages/discount/model/discount_model.dart @@ -49,9 +49,9 @@ class DiscountModel extends BaseModel { .orderBy("code", descending: false) .snapshots() .listen((snaps) { - discounts.clear(); + _discounts.clear(); snaps.documents.forEach((d) { - discounts.add(Discount.fromMap(d.data, d.documentID)); + _discounts.add(Discount.fromMap(d.data, d.documentID)); }); notifyListeners(); }); @@ -73,6 +73,27 @@ class DiscountModel extends BaseModel { return paginator; } + Future> getDiscount(String userID) async { + String path = "/$discounts_collection"; + try { + var q = Firestore.instance + .collection("$path") + .where("customer_id", isEqualTo: userID) + .where("status", isEqualTo: "available"); + var snaps = await q.getDocuments(source: Source.server); + List discounts = snaps.documents.map((snap) { + if (snap.exists) { + var s = Discount.fromMap(snap.data, snap.documentID); + return s; + } + }).toList(); + return discounts; + } catch (e) { + log.warning("Error!! $e"); + } + return null; + } + Future loadMore() async { if (_used.ended || _selectedIndex == 1) return; isLoading = true; diff --git a/lib/pages/invoice/invoice_cargo_table.dart b/lib/pages/invoice/invoice_cargo_table.dart deleted file mode 100644 index 748ac2e..0000000 --- a/lib/pages/invoice/invoice_cargo_table.dart +++ /dev/null @@ -1,350 +0,0 @@ -import 'package:fcs/domain/entities/cargo_type.dart'; -import 'package:fcs/domain/entities/discount.dart'; -import 'package:fcs/domain/entities/invoice.dart'; -import 'package:fcs/domain/entities/rate.dart'; -import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/pages/discount/discount_list.dart'; -import 'package:fcs/pages/main/util.dart'; -import 'package:fcs/pages/widgets/local_text.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; - -typedef OnDiscountSelected(Discount discount); -typedef OnDeliveryFeeSelected(bool selected); -final formatter = new NumberFormat("#,###.00"); - -class InvoiceCargoTable extends StatelessWidget { - final Invoice invoice; - final Rate rate; - final OnDiscountSelected discountSelected; - final OnDeliveryFeeSelected deliveryFeeSelected; - - const InvoiceCargoTable( - {Key key, - this.invoice, - this.discountSelected, - this.deliveryFeeSelected, - this.rate}) - : super(key: key); - @override - Widget build(BuildContext context) { - return Column(children: getRows(context)); - } - - getRows(BuildContext context) { - List _cargoTypes = invoice.getCargoTypes(rate); - double total = 0; - List dataRow = _cargoTypes.map((cargo) { - var amount = cargo.calWeight * cargo.calRate; - total += amount; - return Container( - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), - child: Row( - children: [ - Expanded(flex: 2, child: Text('${cargo.name}')), - Expanded( - flex: 2, - child: Text( - '${cargo.calWeight.toStringAsFixed(2)} x ${cargo.calRate.toStringAsFixed(2)}', - textAlign: TextAlign.center)), - Expanded( - child: Text('\$ ${amount.toStringAsFixed(2)}', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - ))) - ], - ), - ); - }).toList(); - dataRow.insert( - 0, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - child: Row( - children: [ - Expanded( - flex: 2, - child: Text(getLocalString(context, 'invoice.box.cargo_type'), - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.grey))), - Expanded( - flex: 2, - child: Text( - getLocalString(context, 'cargo.weight') + - ' x ' + - getLocalString(context, 'cargo.rate'), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.grey))), - Expanded( - child: Text(getLocalString(context, 'invoice.amount'), - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.grey))) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - child: Row( - children: [ - Expanded( - flex: 1, - child: Container( - alignment: Alignment.centerRight, - child: LocalText( - context, - 'invoice.total', - color: Colors.black, - ), - ), - ), - SizedBox(width: 40), - Expanded( - child: Text( - '\$ ${total.toStringAsFixed(2)}', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - textAlign: TextAlign.end, - )) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only(left: 5.0, right: 5.0, top: 0), - child: Row( - children: [ - Expanded( - flex: 1, - child: Container( - alignment: Alignment.centerRight, - child: LocalText( - context, - 'invoice.discount_value', - color: Colors.black, - ), - ), - ), - new IconButton( - icon: Icon(Icons.search, color: primaryColor), - onPressed: () async { - Discount discount = await Navigator.of(context).push( - CupertinoPageRoute( - builder: (context) => - DiscountList(selectionMode: true))); - if (discountSelected != null) { - discountSelected(discount); - } - }), - Expanded( - child: - Text('\$ ( ${invoice.getDiscount().toStringAsFixed(2)} )', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - ))) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 0.0), - child: Row( - children: [ - Expanded( - flex: 1, - child: Container( - alignment: Alignment.centerRight, - child: LocalText( - context, - 'invoice.custom_fee', - color: Colors.black, - ), - ), - ), - SizedBox(width: 40), - Expanded( - child: Text('\$ ${invoice.getCustomFee().toStringAsFixed(2)}', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - )), - ), - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only(left: 5.0, right: 5.0, top: 20.0), - child: Row( - children: [ - Expanded( - flex: 1, - child: Container( - alignment: Alignment.centerRight, - child: LocalText( - context, - 'invoice.handling_fee', - color: Colors.black, - ), - ), - ), - SizedBox(width: 50), - Expanded( - child: Text( - '\$ ${invoice.getHandlingFee().toStringAsFixed(2) ?? ""}', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - ))) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - child: Row( - children: [ - Expanded( - flex: 1, - child: Container( - alignment: Alignment.centerRight, - child: LocalText( - context, - 'invoice.delivery_fee', - color: Colors.black, - ), - )), - Switch( - value: (invoice.deliveryFee ?? 0) > 0, - onChanged: (value) { - if (deliveryFeeSelected != null) { - deliveryFeeSelected(value); - } - }, - activeTrackColor: primaryColor.withOpacity(0.8), - activeColor: primaryColor, - ), - Expanded( - child: - Text('\$ ${invoice.getDeliveryFee().toStringAsFixed(2)}', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - ))) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - child: Row( - children: [ - Expanded(child: Text('')), - Expanded( - flex: 2, - child: Divider( - thickness: 3, - )), - ], - ))); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.net_amount', - color: Colors.black, - fontSize: 15, - fontWeight: FontWeight.bold, - ), - ), - ), - Expanded( - child: Text( - '\$ ${invoice.getNetAmount(rate).toStringAsFixed(2)}', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: primaryColor))) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.balance', - color: Colors.black, - fontSize: 15, - fontWeight: FontWeight.bold, - ), - ), - ), - Expanded( - child: Text( - '\$ ${invoice.getTotalBalance(rate).toStringAsFixed(2)}', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: primaryColor))) - ], - ), - )); - - return dataRow; - } -} diff --git a/lib/pages/invoice/invoice_carton_table.dart b/lib/pages/invoice/invoice_carton_table.dart index 4e94119..77f05ec 100644 --- a/lib/pages/invoice/invoice_carton_table.dart +++ b/lib/pages/invoice/invoice_carton_table.dart @@ -1,5 +1,4 @@ import 'package:fcs/domain/entities/carton.dart'; -import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/rate.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/widgets/local_text.dart'; @@ -20,7 +19,7 @@ class InvoiceCartonTable extends StatelessWidget { @override Widget build(BuildContext context) { final tableTitle = Container( - padding: EdgeInsets.only(right: 10.0, top: 20), + padding: EdgeInsets.only(right: 10.0, top: 5), child: Row( children: [ SizedBox( @@ -76,10 +75,6 @@ class InvoiceCartonTable extends StatelessWidget { p.value.cartonNumber, style: textStyle, ), - Text( - p.value.shipmentNumber ?? "", - style: textStyle, - ), ], )), Flexible( diff --git a/lib/pages/invoice/invoice_discount_table.dart b/lib/pages/invoice/invoice_discount_table.dart new file mode 100644 index 0000000..880eda3 --- /dev/null +++ b/lib/pages/invoice/invoice_discount_table.dart @@ -0,0 +1,88 @@ +import 'package:fcs/domain/entities/discount.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:fcs/pages/widgets/my_data_table.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class InvoiceDiscountTable extends StatelessWidget { + final List discounts; + + const InvoiceDiscountTable({ + Key key, + this.discounts, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(CupertinoIcons.back), + onPressed: () => Navigator.pop(context), + ), + backgroundColor: primaryColor, + title: LocalText( + context, + "invoice.shipment.discount.title", + fontSize: 20, + color: Colors.white, + ), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: table(context), + )); + } + + Widget table(BuildContext context) { + return MyDataTable( + headingRowHeight: 40, + columns: [ + MyDataColumn( + label: LocalText( + context, + "discount.code", + color: Colors.grey, + ), + ), + MyDataColumn( + label: LocalText( + context, + "discount.amount", + color: Colors.grey, + ), + ), + ], + rows: getRows(context), + ); + } + + List getRows(BuildContext context) { + if (discounts == null) { + return []; + } + var rows = discounts.map((c) { + return MyDataRow( + onSelectChanged: (value) => Navigator.pop(context, c), + cells: [ + MyDataCell(new Text( + c.code ?? "", + style: textStyle, + )), + MyDataCell( + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(c.amount?.toStringAsFixed(2) ?? "0", style: textStyle), + ], + ), + ), + ], + ); + }).toList(); + + return rows; + } +} diff --git a/lib/pages/invoice/invoice_editor.dart b/lib/pages/invoice/invoice_editor.dart index 480bc15..41deb04 100644 --- a/lib/pages/invoice/invoice_editor.dart +++ b/lib/pages/invoice/invoice_editor.dart @@ -1,28 +1,33 @@ -import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/custom_duty.dart'; import 'package:fcs/domain/entities/discount.dart'; import 'package:fcs/domain/entities/fcs_shipment.dart'; import 'package:fcs/domain/entities/invoice.dart'; import 'package:fcs/domain/entities/payment_method.dart'; +import 'package:fcs/domain/entities/shipment.dart'; import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/carton/model/carton_model.dart'; import 'package:fcs/pages/discount/discount_list.dart'; import 'package:fcs/pages/discount/model/discount_model.dart'; -import 'package:fcs/pages/invoice/invoice_cargo_table.dart'; +import 'package:fcs/pages/invoice/invoice_discount_table.dart'; +import 'package:fcs/pages/invoice/invoice_shipment_table.dart'; +import 'package:fcs/pages/invoice/invoice_table.dart'; import 'package:fcs/pages/invoice/invoice_carton_table.dart'; import 'package:fcs/pages/main/model/main_model.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/payment_methods/model/payment_method_model.dart'; import 'package:fcs/pages/rates/custom_list.dart'; import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; +import 'package:fcs/pages/shipment/model/shipment_model.dart'; import 'package:fcs/pages/widgets/display_text.dart'; +import 'package:fcs/pages/widgets/fcs_icons.dart'; import 'package:fcs/pages/widgets/fcs_id_icon.dart'; import 'package:fcs/pages/widgets/local_dropdown.dart'; +import 'package:fcs/pages/widgets/local_popup_menu_button.dart'; +import 'package:fcs/pages/widgets/local_popupmenu.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_title.dart'; -import 'package:fcs/pages/widgets/multi_img_controller.dart'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -44,99 +49,84 @@ class InvoiceEditor extends StatefulWidget { } class _InvoiceEditorState extends State { - User user; + User _user; var dateFormatter = new DateFormat('dd MMM yyyy'); - TextEditingController _invoiceNumberController = new TextEditingController(); - TextEditingController _dateController = new TextEditingController(); - TextEditingController _nameController = new TextEditingController(); - TextEditingController _phoneController = new TextEditingController(); - TextEditingController _discountController = new TextEditingController(); - TextEditingController _amountController = new TextEditingController(); - TextEditingController _statusController = new TextEditingController(); - TextEditingController _handlingFeeController = new TextEditingController(); - TextEditingController _customFeeController = new TextEditingController(); - MultiImgController multiImgController = MultiImgController(); - TextEditingController _descriptionController = new TextEditingController(); - TextEditingController _balanceController = new TextEditingController(); Invoice _invoice; bool _isLoading = false; - List _cartons = []; - bool isSwitched = false; - int deliveryfee = 0; - double customFee = 10.0; - double handlingFee = 10.0; // it will get from shipment - double total = 0; - Discount _discount; - bool _isNew = false; - Discount selectedDiscount; - int selectedDiscountAmt; - PaymentMethod _paymentMethod; - double volumetricRatio = 0; - - List selectedBoxes = []; - List customs = []; - - // List _cargoTypes = [ - // CargoType(name: 'General Cargo', weight: 33, rate: 6), - // CargoType(name: 'Medicine', weight: 33, rate: 7), - // CargoType(name: 'Dangerous Cargo', weight: 33, rate: 8) - // ]; + bool _isNew; + List isSelected = [false]; + bool _showCartons = false; @override void initState() { super.initState(); - - volumetricRatio = Provider.of(context, listen: false) - .rate - .volumetricRatio; + _isNew = widget.invoice == null; if (widget.invoice != null) { - _isNew = false; _invoice = widget.invoice; - _invoiceNumberController.text = _invoice.invoiceNumber; - _dateController.text = dateFormatter.format(_invoice.invoiceDate); - _nameController.text = _invoice.customerName; - _phoneController.text = _invoice.customerPhoneNumber; - // _amountController.text = _invoice.getAmount.toString(); - _amountController.text = _invoice.amount.toString(); - _statusController.text = _invoice.status.toString(); - _handlingFeeController.text = '0'; - _customFeeController.text = '0'; - // multiImgController.setImageUrls = _receipts; - _descriptionController.text = 'For Electronics goods'; - _balanceController.text = - (_invoice.amount - _invoice.receipts[0].amount).toString(); - // _boxes = _invoice.packages; } else { - _isNew = true; - _dateController.text = dateFormatter.format(DateTime.now()); - _amountController.text = '0'; - _handlingFeeController.text = '0'; - _customFeeController.text = '0'; - _descriptionController.text = ''; - _balanceController.text = '0'; - _invoice = Invoice(customDuties: [], cartons: []); + _invoice = Invoice( + customDuties: [], + cartons: [], + shipments: [], + invoiceDate: DateTime.now()); } + _user = widget.customer; + _loadAll(); + } - if (widget.customer != null && widget.invoice == null) { - user = widget.customer; + _loadAll() async { + setState(() { + _isLoading = true; + }); + try { + await _loadCartons(); + await _loadShipments(); + await _loadDiscount(); + } catch (e) {} finally { setState(() { - _isNew = true; + _isLoading = false; }); } - - _loadCartons(); } _loadCartons() async { CartonModel cartonModel = Provider.of(context, listen: false); List cartons = await cartonModel.getCartonsForInvoice( widget.fcsShipment.id, widget.customer.id); - setState(() { - _cartons = cartons; + cartons.forEach((c) { + c.isChecked = true; }); + setState(() { + _invoice.cartons = cartons; + }); + } + + _loadShipments() async { + ShipmentModel shipmentModel = + Provider.of(context, listen: false); + List shipments = await shipmentModel.getShipmentWithHandlingFee( + widget.fcsShipment.id, widget.customer.id); + shipments.forEach((s) { + s.isSelected = true; + }); + setState(() { + _invoice.shipments = shipments; + }); + } + + List discounts = []; + _loadDiscount() async { + DiscountModel discountModel = + Provider.of(context, listen: false); + discounts = await discountModel.getDiscount(widget.customer.id); + if (discounts != null && discounts.length > 0) { + setState(() { + _invoice.discount = discounts.first; + }); + } } @override @@ -147,38 +137,22 @@ class _InvoiceEditorState extends State { @override Widget build(BuildContext context) { var mainModel = Provider.of(context); - var discountModel = Provider.of(context); var paymentMethodModel = Provider.of(context); var rateModel = Provider.of(context); var rate = rateModel.rate; - final nameBox = DisplayText( - iconData: Feather.user, - labelTextKey: 'invoice.customer_name', - text: user != null ? user.name : 'Ko Nyi'); - final statusBox = DisplayText( - text: _statusController.text, + text: _invoice?.status ?? "", iconData: Icons.av_timer, labelTextKey: 'invoice.status'); - final fcsIDBox = DisplayText( - text: user != null ? user.fcsID : "FCS-KRUTUG", - labelTextKey: "box.fcs.id", - icon: FcsIDIcon(), - ); final cartonTable = InvoiceCartonTable( - cartons: _cartons, + cartons: _invoice.cartons, rate: rate, onSelect: (c, checked) { setState(() { c.isChecked = checked; }); - if (checked) { - _invoice.cartons.add(c); - } else { - _invoice.cartons.remove(c); - } }, ); final customTableHeaderBox = LocalTitle( @@ -196,19 +170,19 @@ class _InvoiceEditorState extends State { onAdd: (c) => _addCustom(c), onRemove: (c) => _removeCustom(c), ); - var paymentTypesBox = LocalDropdown( + final paymentTypesBox = LocalDropdown( callback: (v) { setState(() { - _paymentMethod = v; + _invoice.paymentMethod = v; }); }, labelKey: "invoice.payment_method", iconData: FontAwesome.money, display: (u) => u.name, - selectedValue: _paymentMethod, + selectedValue: _invoice.paymentMethod, values: paymentMethodModel.paymentMethods, ); - final cargoTypeTableBox = InvoiceCargoTable( + final invoiceTableBox = InvoiceTable( invoice: _invoice, rate: rate, deliveryFeeSelected: (selected) { @@ -225,6 +199,97 @@ class _InvoiceEditorState extends State { _invoice.discount = discount; }); }, + onRemove: (i) { + if (i.invoiceDataType == InvoiceDataType.CustomFeeDataType) { + _removeCustom(i.data); + } + if (i.invoiceDataType == InvoiceDataType.DiscountDataType) { + setState(() { + _invoice.discount = null; + }); + } + if (i.invoiceDataType == InvoiceDataType.DeliveryFeeType) { + setState(() { + _invoice.deliveryFee = 0; + }); + } + if (i.invoiceDataType == InvoiceDataType.HandlingFeeType) { + setState(() { + _removeShipment(i.data); + }); + } + }, + ); + final toggleButtonsBox = ToggleButtons( + color: Colors.black45, + selectedColor: Colors.black45, + disabledColor: Colors.grey, + selectedBorderColor: primaryColor, + borderColor: Colors.transparent, + fillColor: Colors.transparent, + highlightColor: Colors.black45, + children: [ + Icon(cartonIconData), + ], + onPressed: (int index) { + setState(() { + _showCartons = !_showCartons; + }); + }, + isSelected: [_showCartons], + ); + + final popupMenu = LocalPopupMenuButton( + buttonIcon: Icons.add_circle, + selectable: false, + buttonColor: Colors.black45, + popmenus: [ + LocalPopupMenu( + id: 1, + textKey: "invoice.add.custom.fee.menu", + ), + LocalPopupMenu( + id: 2, + textKey: "invoice.add.handling.fee.menu", + ), + LocalPopupMenu( + id: 3, + textKey: "invoice.add.discount.menu", + ), + LocalPopupMenu( + id: 4, + textKey: "invoice.delivery_fee", + ) + ], + popupMenuCallback: (p) async { + if (p.id == 1) { + CustomDuty customDuty = await Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => CustomList(selected: true))); + _addCustom(customDuty); + } else if (p.id == 2) { + Shipment shipment = await Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => + InvoiceShipmentTable(shipments: _invoice.shipments))); + _addShipment(shipment); + } else if (p.id == 3) { + Discount discount = + await Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => InvoiceDiscountTable( + discounts: discounts, + ))); + if (discount != null) { + setState(() { + _invoice.discount = discount; + }); + } + } else if (p.id == 4) { + setState(() { + _invoice.deliveryFee = rate.deliveryFee; + }); + } + }, ); return LocalProgress( @@ -245,31 +310,55 @@ class _InvoiceEditorState extends State { padding: const EdgeInsets.all(8.0), child: ListView( children: [ - LocalTitle(textKey: "invoice.customer_info"), - DisplayText( - labelTextKey: 'invoice.date', - iconData: Icons.date_range, - text: _dateController.text), - widget.invoice == null + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(dateFormatter.format(_invoice.invoiceDate)), + SizedBox( + height: 10, + ), + Text(_user?.name ?? ""), + Text( + _user?.fcsID ?? "", + style: TextStyle(fontSize: 12), + ) + ], + ), + Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + toggleButtonsBox, + popupMenu, + ], + ), + ], + ), + _isNew ? Container() : DisplayText( labelTextKey: 'invoice.number', iconData: FontAwesomeIcons.fileInvoice, - text: _invoiceNumberController.text), - fcsIDBox, - nameBox, + text: _invoice?.invoiceNumber ?? ""), _isNew ? Container() : statusBox, - SizedBox(height: 20), - customTableHeaderBox, - customTableBox, - SizedBox(height: 20), - cartonTable, - LocalTitle(textKey: "invoice.cargo_type"), - cargoTypeTableBox, - // Column(children: getCargoTableByBox(context)), - SizedBox(height: 20), + _showCartons ? cartonTable : Container(), + _showCartons + ? Divider( + color: primaryColor, + thickness: 2, + ) + : Container(), + invoiceTableBox, + SizedBox( + height: 10, + ), paymentTypesBox, - SizedBox(height: 20), + SizedBox( + height: 10, + ), _isNew ? Container() : LocalTitle( @@ -277,7 +366,7 @@ class _InvoiceEditorState extends State { trailing: IconButton( icon: Icon(Icons.add_circle, color: primaryColor), onPressed: () async {})), - widget.invoice == null + _isNew ? fcsButton( context, getLocalString(context, 'invoice.btn_create')) : mainModel.isCustomer() @@ -300,15 +389,6 @@ class _InvoiceEditorState extends State { ); } - getTotalBalance(total) { - double balance = 0; - double custom = customFee != 0 ? customFee.toDouble() : 0; - double discount = _discount != null ? _discount.amount.toDouble() : 0; - double deliveryFee = deliveryfee != 0 ? deliveryfee.toDouble() : 0; - balance = (total + custom + deliveryFee) - discount; - return balance; - } - _addCustom(CustomDuty customDuty) { if (customDuty == null) return; setState(() { @@ -317,6 +397,24 @@ class _InvoiceEditorState extends State { }); } + _addShipment(Shipment shipment) { + if (shipment == null) return; + shipment.isSelected = true; + setState(() { + _invoice.shipments.remove(shipment); + _invoice.shipments.add(shipment); + }); + } + + _removeShipment(Shipment shipment) { + if (shipment == null) return; + shipment.isSelected = false; + setState(() { + _invoice.shipments.remove(shipment); + _invoice.shipments.add(shipment); + }); + } + _removeCustom(CustomDuty customDuty) { setState(() { _invoice.customDuties.remove(customDuty); diff --git a/lib/pages/invoice/invoice_info.dart b/lib/pages/invoice/invoice_info.dart index 7a6a9a0..b86d82d 100644 --- a/lib/pages/invoice/invoice_info.dart +++ b/lib/pages/invoice/invoice_info.dart @@ -99,16 +99,14 @@ class _InvoiceInfoPageState extends State { _invoice = widget.invoice; _invoiceNumberController.text = _invoice.invoiceNumber; _dateController.text = dateFormatter.format(_invoice.invoiceDate); - _nameController.text = _invoice.customerName; - _phoneController.text = _invoice.customerPhoneNumber; + _nameController.text = _invoice.userName; + _phoneController.text = _invoice.phoneNumber; // _amountController.text = _invoice.getAmount.toString(); _amountController.text = _invoice.amount.toString(); _statusController.text = _invoice.status.toString(); _customFeeController.text = '0'; // multiImgController.setImageUrls = _receipts; _descriptionController.text = 'For Electronics goods'; - _balanceController.text = - (_invoice.amount - _invoice.receipts[0].amount).toString(); // _boxes = _invoice.packages; } else { _dateController.text = dateFormatter.format(DateTime.now()); diff --git a/lib/pages/invoice/invoice_shipment_table.dart b/lib/pages/invoice/invoice_shipment_table.dart new file mode 100644 index 0000000..abc0318 --- /dev/null +++ b/lib/pages/invoice/invoice_shipment_table.dart @@ -0,0 +1,105 @@ +import 'package:fcs/domain/entities/shipment.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:fcs/pages/widgets/my_data_table.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +typedef OnAdd(Shipment shipment); +typedef OnRemove(Shipment shipment); + +class InvoiceShipmentTable extends StatelessWidget { + final List shipments; + final OnAdd onAdd; + final OnRemove onRemove; + + const InvoiceShipmentTable( + {Key key, this.shipments, this.onAdd, this.onRemove}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(CupertinoIcons.back), + onPressed: () => Navigator.pop(context), + ), + backgroundColor: primaryColor, + title: LocalText( + context, + "invoice.shipment.handling.fee.title", + fontSize: 20, + color: Colors.white, + ), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: table(context), + )); + } + + Widget table(BuildContext context) { + return MyDataTable( + headingRowHeight: 40, + columns: [ + MyDataColumn( + label: LocalText( + context, + "invoice.shipment.number", + color: Colors.grey, + ), + ), + MyDataColumn( + label: LocalText( + context, + "invoice.add.handling.fee.menu", + color: Colors.grey, + ), + ), + ], + rows: getRows(context), + ); + } + + List getRows(BuildContext context) { + if (shipments == null) { + return []; + } + var rows = shipments.map((c) { + return MyDataRow( + onSelectChanged: (value) => Navigator.pop(context, c), + cells: [ + MyDataCell(new Text( + c.shipmentNumber ?? "", + style: textStyle, + )), + MyDataCell( + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(c.handlingFee?.toStringAsFixed(2) ?? "0", + style: textStyle), + onRemove == null + ? SizedBox( + width: 50, + ) + : IconButton( + icon: Icon( + Icons.remove_circle, + color: primaryColor, + ), + onPressed: () { + if (onRemove != null) onRemove(c); + }) + ], + ), + ), + ], + ); + }).toList(); + + return rows; + } +} diff --git a/lib/pages/invoice/invoice_table.dart b/lib/pages/invoice/invoice_table.dart new file mode 100644 index 0000000..1df1cfd --- /dev/null +++ b/lib/pages/invoice/invoice_table.dart @@ -0,0 +1,377 @@ +import 'package:fcs/domain/entities/cargo_type.dart'; +import 'package:fcs/domain/entities/discount.dart'; +import 'package:fcs/domain/entities/invoice.dart'; +import 'package:fcs/domain/entities/rate.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/discount/discount_list.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +typedef OnDiscountSelected(Discount discount); +typedef OnDeliveryFeeSelected(bool selected); +typedef OnRemove(InvoiceTableRow row); +final formatter = new NumberFormat("#,###.00"); + +enum InvoiceDataType { + CargoDataType, + DiscountDataType, + CustomFeeDataType, + HandlingFeeType, + DeliveryFeeType, +} + +class InvoiceTableRow { + final dynamic data; + final String id; + final InvoiceDataType invoiceDataType; + final String desc; + final String rate; + final String amount; + + InvoiceTableRow( + {this.id, + this.data, + this.invoiceDataType, + this.desc, + this.rate, + this.amount}); +} + +class InvoiceTable extends StatelessWidget { + final Invoice invoice; + final Rate rate; + final OnDiscountSelected discountSelected; + final OnDeliveryFeeSelected deliveryFeeSelected; + final OnRemove onRemove; + + const InvoiceTable( + {Key key, + this.invoice, + this.discountSelected, + this.deliveryFeeSelected, + this.onRemove, + this.rate}) + : super(key: key); + @override + Widget build(BuildContext context) { + return Column(children: getRows(context)); + } + + List getTableRows() { + List tableRows = []; + // add cargo types + List _cargoTypes = invoice.getCargoTypes(rate); + _cargoTypes.forEach((c) { + tableRows.add(InvoiceTableRow( + invoiceDataType: InvoiceDataType.CargoDataType, + desc: c.name, + rate: + "${c.calWeight.toStringAsFixed(2)} x ${c.calRate.toStringAsFixed(2)}", + amount: "${c.amount.toStringAsFixed(2)}")); + }); + invoice.shipments.where((ss) => (ss.isSelected ?? false)).forEach((s) { + tableRows.add(InvoiceTableRow( + data: s, + invoiceDataType: InvoiceDataType.HandlingFeeType, + desc: "Handling fee\n${s.shipmentNumber}", + rate: "", + amount: "${s.handlingFee.toStringAsFixed(2)}")); + }); + // add custom fee + invoice.customDuties.forEach((c) { + tableRows.add(InvoiceTableRow( + data: c, + invoiceDataType: InvoiceDataType.CustomFeeDataType, + desc: "${c.productType} custom fee", + rate: "", + amount: "${c.fee.toStringAsFixed(2)}")); + }); + // add delivery fee + tableRows.add(InvoiceTableRow( + data: invoice.deliveryFee == null || invoice.deliveryFee == 0 + ? null + : invoice.deliveryFee, + invoiceDataType: InvoiceDataType.DeliveryFeeType, + desc: "Delivery fee", + rate: "", + amount: "${invoice?.deliveryFee?.toStringAsFixed(2) ?? '0'}")); + + // add discounts + if (invoice.discount != null) { + tableRows.add(InvoiceTableRow( + data: invoice.discount, + invoiceDataType: InvoiceDataType.DiscountDataType, + desc: "Discount\n${invoice?.discount?.code ?? ""}", + rate: "", + amount: "(${invoice?.discount?.amount?.toStringAsFixed(2) ?? ''})")); + } + + return tableRows; + } + + getRows(BuildContext context) { + List tableRows = getTableRows(); + + List dataRow = tableRows.map((r) { + return Container( + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.grey))), + padding: const EdgeInsets.only( + left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), + child: Row( + children: [ + Expanded( + flex: 2, + child: Row( + children: [ + Flexible(child: Text('${r.desc}')), + SizedBox( + width: 5, + ), + r.data == null || onRemove == null + ? Container() + : InkWell( + onTap: () => onRemove(r), + child: Icon( + Icons.remove_circle, + color: Colors.black45, + ), + ) + ], + )), + Expanded( + flex: 1, + child: Text( + '${r.rate}', + textAlign: TextAlign.end, + style: TextStyle(fontSize: 12), + )), + Expanded( + flex: 1, + child: Text('\$ ${r.amount}', + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ))) + ], + ), + ); + }).toList(); + dataRow.insert( + 0, + Container( + padding: const EdgeInsets.only( + left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.grey))), + child: Row( + children: [ + Expanded( + flex: 2, + child: Text(getLocalString(context, 'invoice.box.desc'), + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.grey))), + Expanded( + flex: 2, + child: Text( + getLocalString(context, 'invoice.weight') + + ' x ' + + getLocalString(context, 'invoice.rate'), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.grey))), + Expanded( + child: Text(getLocalString(context, 'invoice.amount'), + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.grey))) + ], + ), + )); + + dataRow.insert( + dataRow.length, + Container( + padding: const EdgeInsets.only( + left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: Container( + alignment: Alignment.centerRight, + child: LocalText( + context, + 'invoice.total', + color: Colors.black, + ), + ), + ), + SizedBox(width: 20), + Text( + '\$ ${invoice.getNetAmount(rate).toStringAsFixed(2)}', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + textAlign: TextAlign.end, + ) + ], + ), + )); + + // dataRow.insert( + // dataRow.length, + // Container( + // padding: const EdgeInsets.only(left: 5.0, right: 5.0, top: 20.0), + // child: Row( + // children: [ + // Expanded( + // flex: 1, + // child: Container( + // alignment: Alignment.centerRight, + // child: LocalText( + // context, + // 'invoice.handling_fee', + // color: Colors.black, + // ), + // ), + // ), + // SizedBox(width: 50), + // Expanded( + // child: Text( + // '\$ ${invoice.getHandlingFee().toStringAsFixed(2) ?? ""}', + // textAlign: TextAlign.end, + // style: TextStyle( + // fontSize: 15, + // fontWeight: FontWeight.bold, + // ))) + // ], + // ), + // )); + + // dataRow.insert( + // dataRow.length, + // Container( + // padding: const EdgeInsets.only( + // left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), + // child: Row( + // children: [ + // Expanded( + // flex: 1, + // child: Container( + // alignment: Alignment.centerRight, + // child: LocalText( + // context, + // 'invoice.delivery_fee', + // color: Colors.black, + // ), + // )), + // Switch( + // value: (invoice.deliveryFee ?? 0) > 0, + // onChanged: (value) { + // if (deliveryFeeSelected != null) { + // deliveryFeeSelected(value); + // } + // }, + // activeTrackColor: primaryColor.withOpacity(0.8), + // activeColor: primaryColor, + // ), + // Expanded( + // child: + // Text('\$ ${invoice.getDeliveryFee().toStringAsFixed(2)}', + // textAlign: TextAlign.end, + // style: TextStyle( + // fontSize: 15, + // fontWeight: FontWeight.bold, + // ))) + // ], + // ), + // )); + + // dataRow.insert( + // dataRow.length, + // Container( + // child: Row( + // children: [ + // Expanded(child: Text('')), + // Expanded( + // flex: 2, + // child: Divider( + // thickness: 3, + // )), + // ], + // ))); + + // dataRow.insert( + // dataRow.length, + // Container( + // padding: const EdgeInsets.only( + // left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), + // child: Row( + // children: [ + // Expanded( + // flex: 2, + // child: Center( + // child: LocalText( + // context, + // 'invoice.net_amount', + // color: Colors.black, + // fontSize: 15, + // fontWeight: FontWeight.bold, + // ), + // ), + // ), + // Expanded( + // child: Text( + // '\$ ${invoice.getNetAmount(rate).toStringAsFixed(2)}', + // textAlign: TextAlign.end, + // style: TextStyle( + // fontSize: 18, + // fontWeight: FontWeight.bold, + // color: primaryColor))) + // ], + // ), + // )); + + // dataRow.insert( + // dataRow.length, + // Container( + // padding: const EdgeInsets.only( + // left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), + // child: Row( + // children: [ + // Expanded( + // flex: 2, + // child: Center( + // child: LocalText( + // context, + // 'invoice.balance', + // color: Colors.black, + // fontSize: 15, + // fontWeight: FontWeight.bold, + // ), + // ), + // ), + // Expanded( + // child: Text( + // '\$ ${invoice.getTotalBalance(rate).toStringAsFixed(2)}', + // textAlign: TextAlign.end, + // style: TextStyle( + // fontSize: 18, + // fontWeight: FontWeight.bold, + // color: primaryColor))) + // ], + // ), + // )); + + return dataRow; + } +} diff --git a/lib/pages/invoice/model/invoice_model.dart b/lib/pages/invoice/model/invoice_model.dart index fa74b18..a25678b 100644 --- a/lib/pages/invoice/model/invoice_model.dart +++ b/lib/pages/invoice/model/invoice_model.dart @@ -1,15 +1,12 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:fcs/data/services/services.dart'; +import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/invoice.dart'; -import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/receipt.dart'; -import 'package:fcs/domain/vo/message.dart'; import 'package:fcs/helpers/paginator.dart'; import 'package:fcs/pages/main/model/base_model.dart'; import 'package:logging/logging.dart'; -import 'package:fcs/domain/constants.dart'; class InvoiceModel extends BaseModel { final log = Logger('InvoiceModel'); @@ -18,37 +15,13 @@ class InvoiceModel extends BaseModel { List _invoices = [ Invoice( - invoiceNumber: 'A092(A)-33', - invoiceDate: DateTime(2020, 4, 6, 12, 15), - customerName: 'Ko Myo Min', - customerPhoneNumber: '+959 555555555', - amount: 300, - status: 'Pending', - receipts: [ - Receipt(amount: 200, date: '1 Jun 2020'), - ], - packages: [ - Package( - shipmentNumber: "A201", - receiverNumber: "1", - boxNumber: "1", - rate: 9, - packageType: "Dangerous", - weight: 25, - status: "Delivered", - arrivedDate: DateTime(2020, 5, 21), - receiverAddress: '3 Kambzwza St, Bahan Tsp, Yangon'), - Package( - shipmentNumber: "A201", - receiverNumber: "1", - boxNumber: "2", - rate: 7, - packageType: "General", - weight: 5, - status: "Delivered", - arrivedDate: DateTime(2020, 5, 21), - receiverAddress: '3 Kambzwza St, Bahan Tsp, Yangon'), - ]) + invoiceNumber: 'A092(A)-33', + invoiceDate: DateTime(2020, 4, 6, 12, 15), + userName: 'Ko Myo Min', + phoneNumber: '+959 555555555', + amount: 300, + status: 'Pending', + ) ]; List get invoices => diff --git a/lib/pages/invoice/payment_page.dart b/lib/pages/invoice/payment_page.dart index d05bd37..52ff087 100644 --- a/lib/pages/invoice/payment_page.dart +++ b/lib/pages/invoice/payment_page.dart @@ -121,7 +121,7 @@ class _PaymentPageState extends State { getCustomFeeRows(BuildContext context) { List dataRow = []; - dataRow = _invoice.receipts.asMap().entries.map((receipt) { + dataRow = [].asMap().entries.map((receipt) { var r = receipt.value; var k = receipt.key + 1; return Container( diff --git a/lib/pages/shipment/model/shipment_model.dart b/lib/pages/shipment/model/shipment_model.dart index 8e8cbe5..6c96da4 100644 --- a/lib/pages/shipment/model/shipment_model.dart +++ b/lib/pages/shipment/model/shipment_model.dart @@ -148,6 +148,30 @@ class ShipmentModel extends BaseModel { return null; } + Future> getShipmentWithHandlingFee( + String fcsShipmentID, String userID) async { + String path = "/$shipments_collection"; + try { + var q = Firestore.instance + .collection("$path") + .where("user_id", isEqualTo: userID) + .where("is_deleted", isEqualTo: false) + .where("handling_fee", isGreaterThan: 0) + .where("fcs_shipment_id", isEqualTo: fcsShipmentID); + var snaps = await q.getDocuments(source: Source.server); + List shipments = snaps.documents.map((snap) { + if (snap.exists) { + var s = Shipment.fromMap(snap.data, snap.documentID); + return s; + } + }).toList(); + return shipments; + } catch (e) { + log.warning("Error!! $e"); + } + return null; + } + void initUser(user) { super.initUser(user); } diff --git a/lib/pages/widgets/fcs_icons.dart b/lib/pages/widgets/fcs_icons.dart new file mode 100644 index 0000000..19c844e --- /dev/null +++ b/lib/pages/widgets/fcs_icons.dart @@ -0,0 +1,5 @@ +import 'package:flutter_icons/flutter_icons.dart'; + +const cartonIconData = MaterialCommunityIcons.package; +const customFeeIconData = MaterialCommunityIcons.security; +const shipmentIconData = SimpleLineIcons.direction; diff --git a/lib/pages/widgets/local_popup_menu_button.dart b/lib/pages/widgets/local_popup_menu_button.dart index 2a29681..53701e7 100644 --- a/lib/pages/widgets/local_popup_menu_button.dart +++ b/lib/pages/widgets/local_popup_menu_button.dart @@ -12,6 +12,7 @@ class LocalPopupMenuButton extends StatefulWidget { final bool multiSelect; final bool selectable; final IconData buttonIcon; + final Color buttonColor; const LocalPopupMenuButton( {Key key, @@ -19,7 +20,8 @@ class LocalPopupMenuButton extends StatefulWidget { this.popmenus, this.buttonIcon, this.selectable = true, - this.multiSelect = false}) + this.multiSelect = false, + this.buttonColor = primaryColor}) : super(key: key); @override @@ -76,7 +78,7 @@ class _LocalPopupMenuButtonState extends State { children: [ Icon( widget.buttonIcon ?? Icons.filter_list, - color: primaryColor, + color: widget.buttonColor ?? primaryColor, ), hightlight ? Positioned( From 2786acfd08847b8d594ec4bb19eda512832c3c49 Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Mon, 26 Oct 2020 04:41:24 +0630 Subject: [PATCH 03/10] add invoice pdf --- assets/local/localization_en.json | 7 +- assets/local/localization_mu.json | 8 +- lib/data/provider/invoice_data_provider.dart | 25 + lib/data/services/invoice_imp.dart | 34 + lib/data/services/invoice_service.dart | 8 + lib/data/services/services.dart | 7 + lib/domain/constants.dart | 6 + lib/domain/entities/cargo_type.dart | 12 +- lib/domain/entities/carton.dart | 2 +- lib/domain/entities/invoice.dart | 102 +- lib/domain/entities/shipment.dart | 2 +- lib/helpers/api_helper.dart | 92 ++ lib/helpers/cache_mgr.dart | 12 + lib/pages/carton/carton_editor.dart | 14 +- .../{ => editor}/invoice_carton_table.dart | 0 .../invoice_discount_list.dart} | 4 +- .../invoice/{ => editor}/invoice_editor.dart | 172 ++-- .../invoice_handling_fee_list.dart} | 4 +- lib/pages/invoice/invoice_custom_table.dart | 107 --- lib/pages/invoice/invoice_customer_list.dart | 9 +- lib/pages/invoice/invoice_info.dart | 874 +++--------------- .../{invoce_list.dart => invoice_list.dart} | 31 +- lib/pages/invoice/invoice_list_row.dart | 14 +- lib/pages/invoice/invoice_shipment_list.dart | 11 +- .../invoice/invoice_shipment_list_row.dart | 10 +- lib/pages/invoice/invoice_table.dart | 10 +- lib/pages/invoice/model/invoice_model.dart | 75 +- lib/pages/invoice/payment_pdf_screen.dart | 171 ++-- lib/pages/invoice/widgets.dart | 24 + lib/pages/main/home_page.dart | 6 +- lib/pages/shipment/shipment_list.dart | 4 +- pubspec.lock | 43 +- pubspec.yaml | 1 + 33 files changed, 787 insertions(+), 1114 deletions(-) create mode 100644 lib/data/provider/invoice_data_provider.dart create mode 100644 lib/data/services/invoice_imp.dart create mode 100644 lib/data/services/invoice_service.dart create mode 100644 lib/helpers/cache_mgr.dart rename lib/pages/invoice/{ => editor}/invoice_carton_table.dart (100%) rename lib/pages/invoice/{invoice_discount_table.dart => editor/invoice_discount_list.dart} (96%) rename lib/pages/invoice/{ => editor}/invoice_editor.dart (76%) rename lib/pages/invoice/{invoice_shipment_table.dart => editor/invoice_handling_fee_list.dart} (96%) delete mode 100644 lib/pages/invoice/invoice_custom_table.dart rename lib/pages/invoice/{invoce_list.dart => invoice_list.dart} (81%) create mode 100644 lib/pages/invoice/widgets.dart diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index 17d9baf..adcf3fe 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -441,7 +441,7 @@ "invoice.payment_attachment":"Payment attachment", "invoice.box_info":"Cartons", "invoice.cargo_table":"Cargo table", - "invoice.btn_create":"Create invoice", + "invoice.issue.btn":"Issue invoice", "invoice.btn_save":"Save invoice", "invoice.btn_payment_receipt":"Attachment payment receipt", "invoice.description": "Description", @@ -458,8 +458,9 @@ "invoice.pdf": "Invoice PDF", "invoice.total_custom_fee":"Total custom fee", "invoice.shipment_weight":"Shipment weight", - "invoice.popupmenu.pending":"Pending", + "invoice.popupmenu.issused":"Issused", "invoice.popupmenu.paid":"Paid", + "invoice.popupmenu.cancel":"Canceled", "invoice.shipment.title":"Select FCS shipment", "invoice.customer.title":"Select Customer", "invoice.add.custom.fee.menu":"Custom fee", @@ -468,6 +469,8 @@ "invoice.shipment.handling.fee.title":"Select shipment", "invoice.shipment.number":"Shipment number", "invoice.shipment.discount.title":"Select discount", + "invoice.cancel.btn":"Cancel", + "invoice.cancel.confirm":"Cancel invoice?", "Invoices End ================================================================":"", "Discount Start ================================================================":"", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index 1228908..8e064c6 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -441,7 +441,8 @@ "invoice.payment_attachment":"ပေးချေပြီးဖိုင်များ", "invoice.box_info":"Cartons", "invoice.cargo_table":"ကုန်ပစ္စည်းဇယား", - "invoice.btn_create":"ငွေတောင်းခံလွှာ ပြုလုပ်ရန်", + "invoice.issue.btn":"ငွေတောင်းခံလွှာ ထုတ်မည်", + "invoice.btn_save":"ငွေတောင်းခံလွှာ သိမ်းရန်", "invoice.btn_payment_receipt":"ငွေလက်ခံဖြတ်ပိုင်း ထည့်ရန်", "invoice.box.cargo_type": "ကုန်ပစ္စည်းအမျိုးအစားများ", @@ -458,8 +459,9 @@ "invoice.pdf": "Invoice PDF", "invoice.total_custom_fee":"အခွန်စုစုပေါင်း", "invoice.shipment_weight":"Shipment weight", - "invoice.popupmenu.pending":"Pending", + "invoice.popupmenu.issused":"Issused", "invoice.popupmenu.paid":"Paid", + "invoice.popupmenu.cancel":"Canceled", "invoice.shipment.title":"Select FCS shipment", "invoice.customer.title":"Select Customer", "invoice.add.custom.fee.menu":"Custom fee", @@ -468,6 +470,8 @@ "invoice.shipment.number":"Shipment number", "invoice.shipment.handling.fee.title":"Select shipment", "invoice.shipment.discount.title":"Select discount", + "invoice.cancel.btn":"Cancel", + "invoice.cancel.confirm":"Cancel invoice?", "Invoices End ================================================================":"", "Discount Start ================================================================":"", diff --git a/lib/data/provider/invoice_data_provider.dart b/lib/data/provider/invoice_data_provider.dart new file mode 100644 index 0000000..1afee73 --- /dev/null +++ b/lib/data/provider/invoice_data_provider.dart @@ -0,0 +1,25 @@ +import 'package:fcs/domain/entities/invoice.dart'; +import 'package:fcs/helpers/api_helper.dart'; +import 'package:fcs/helpers/firebase_helper.dart'; +import 'package:logging/logging.dart'; + +class InvoiceDataProvider { + final log = Logger('InvoiceDataProvider'); + static final InvoiceDataProvider instance = InvoiceDataProvider._(); + InvoiceDataProvider._(); + + Future createInvoice(Invoice invoice) async { + return await requestAPI("/invoices", "POST", + payload: invoice.toMap(), token: await getToken()); + } + + Future updateInvoice(Invoice invoice) async { + return await requestAPI("/invoices", "PUT", + payload: invoice.toMap(), token: await getToken()); + } + + Future cancelInvoice(Invoice invoice) async { + return await requestAPI("/invoices/cancel", "PUT", + payload: {"id": invoice.id}, token: await getToken()); + } +} diff --git a/lib/data/services/invoice_imp.dart b/lib/data/services/invoice_imp.dart new file mode 100644 index 0000000..3fe5df1 --- /dev/null +++ b/lib/data/services/invoice_imp.dart @@ -0,0 +1,34 @@ +import 'package:fcs/data/provider/invoice_data_provider.dart'; +import 'package:fcs/data/provider/shipment_data_provider.dart'; +import 'package:fcs/data/services/shipment_service.dart'; +import 'package:fcs/domain/entities/connectivity.dart'; +import 'package:fcs/domain/entities/invoice.dart'; +import 'package:fcs/domain/entities/shipment.dart'; +import 'package:flutter/material.dart'; + +import 'invoice_service.dart'; + +class InvoiceServiceImp implements InvoiceService { + InvoiceServiceImp({ + @required this.invoiceDataProvider, + @required this.connectivity, + }); + + final Connectivity connectivity; + final InvoiceDataProvider invoiceDataProvider; + + @override + Future cancelInvoice(Invoice invoice) { + return invoiceDataProvider.cancelInvoice(invoice); + } + + @override + Future createInvoice(Invoice invoice) { + return invoiceDataProvider.createInvoice(invoice); + } + + @override + Future updateInvoice(Invoice invoice) { + return invoiceDataProvider.updateInvoice(invoice); + } +} diff --git a/lib/data/services/invoice_service.dart b/lib/data/services/invoice_service.dart new file mode 100644 index 0000000..feed4e2 --- /dev/null +++ b/lib/data/services/invoice_service.dart @@ -0,0 +1,8 @@ +import 'package:fcs/domain/entities/invoice.dart'; +import 'package:fcs/domain/entities/shipment.dart'; + +abstract class InvoiceService { + Future createInvoice(Invoice invoice); + Future updateInvoice(Invoice invoice); + Future cancelInvoice(Invoice invoice); +} diff --git a/lib/data/services/services.dart b/lib/data/services/services.dart index 70471d8..aee169b 100644 --- a/lib/data/services/services.dart +++ b/lib/data/services/services.dart @@ -3,6 +3,7 @@ import 'package:fcs/data/provider/carton_data_provider.dart'; import 'package:fcs/data/provider/common_data_provider.dart'; import 'package:fcs/data/provider/delivery_address_data_provider.dart'; import 'package:fcs/data/provider/fcs_shipment_data_provider.dart'; +import 'package:fcs/data/provider/invoice_data_provider.dart'; import 'package:fcs/data/provider/package_data_provider.dart'; import 'package:fcs/data/provider/rate_data_provider.dart'; import 'package:fcs/data/provider/shipment_data_provider.dart'; @@ -13,6 +14,8 @@ import 'package:fcs/data/services/delivery_address_imp.dart'; import 'package:fcs/data/services/delivery_address_service.dart'; import 'package:fcs/data/services/fcs_shipment_imp.dart'; import 'package:fcs/data/services/fcs_shipment_service.dart'; +import 'package:fcs/data/services/invoice_imp.dart'; +import 'package:fcs/data/services/invoice_service.dart'; import 'package:fcs/data/services/rate_imp.dart'; import 'package:fcs/data/services/rate_service.dart'; import 'package:fcs/data/services/shipment_imp.dart'; @@ -42,6 +45,7 @@ class Services { RateService _rateService; ShipmentService _shipmentService; CartonService _cartonService; + InvoiceService _invoiceService; Services._() { _authService = AuthServiceImp( authFb: AuthFb.instance, @@ -65,6 +69,8 @@ class Services { connectivity: null); _cartonService = CartonServiceImp( cartonDataProvider: CartonDataProvider.instance, connectivity: null); + _invoiceService = InvoiceServiceImp( + invoiceDataProvider: InvoiceDataProvider.instance, connectivity: null); } AuthService get authService => _authService; @@ -77,4 +83,5 @@ class Services { RateService get rateService => _rateService; ShipmentService get shipmentService => _shipmentService; CartonService get cartonService => _cartonService; + InvoiceService get invoiceService => _invoiceService; } diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index fb283bf..7243933 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -91,3 +91,9 @@ const shipment_pickuped_status = "pickuped"; const shipment_packed_status = "packed"; const shipment_shipped_status = "shipped"; const shipment_delivered_status = "delivered"; + +// invoice status +const invoice_issued_status = "issued"; +const invoice_saved_status = "saved"; +const invoice_cancel_status = "canceled"; +const invoice_paid_status = "paid"; diff --git a/lib/domain/entities/cargo_type.dart b/lib/domain/entities/cargo_type.dart index f6a4ea0..7247ce0 100644 --- a/lib/domain/entities/cargo_type.dart +++ b/lib/domain/entities/cargo_type.dart @@ -4,7 +4,7 @@ class CargoType { double rate; double weight; - double amount; + double get calAmount => (calRate ?? 0) * (calWeight ?? 0); double calRate; double calWeight; @@ -16,9 +16,16 @@ class CargoType { rate: map['rate']?.toDouble() ?? 0, weight: map['weight']?.toDouble() ?? 0, calWeight: map['cal_weight']?.toDouble() ?? 0, + calRate: map['cal_rate']?.toDouble() ?? 0, ); } - CargoType({this.id, this.name, this.rate, this.weight, this.calWeight}); + CargoType( + {this.id, + this.name, + this.rate, + this.weight, + this.calWeight, + this.calRate}); Map toMap() { return { @@ -27,6 +34,7 @@ class CargoType { 'rate': rate, 'weight': weight, 'cal_weight': calWeight, + 'cal_rate': calRate, }; } diff --git a/lib/domain/entities/carton.dart b/lib/domain/entities/carton.dart index a11d949..cccac21 100644 --- a/lib/domain/entities/carton.dart +++ b/lib/domain/entities/carton.dart @@ -171,7 +171,7 @@ class Carton { 'length': length, 'width': width, 'height': height, - 'delivery_address': deliveryAddress.toMap(), + 'delivery_address': deliveryAddress?.toMap(), 'carton_type': cartonType, 'mix_carton_id': mixCartonID, }; diff --git a/lib/domain/entities/invoice.dart b/lib/domain/entities/invoice.dart index 5bc0eb9..b7c3460 100644 --- a/lib/domain/entities/invoice.dart +++ b/lib/domain/entities/invoice.dart @@ -1,3 +1,4 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/custom_duty.dart'; @@ -13,6 +14,7 @@ class Invoice { DateTime invoiceDate; String fcsShipmentID; String userID; + String fcsID; String userName; String phoneNumber; String status; @@ -28,20 +30,23 @@ class Invoice { List shipments; Discount discount; PaymentMethod paymentMethod; + String invoiceURL; List getCargoTypes(Rate rate) { - List cargoTypes = []; + if (cargoTypes != null) return cargoTypes; + + List _cargoTypes = []; double totalCalWeight = 0; cartons.forEach((carton) { if (carton.isChecked) { var _cartonsTypes = carton.getCargoTypeForCalWeight(rate.volumetricRatio); _cartonsTypes.forEach((ct) { - if (cargoTypes.contains(ct)) { - CargoType existing = cargoTypes.firstWhere((wc) => wc.id == ct.id); + if (_cargoTypes.contains(ct)) { + CargoType existing = _cargoTypes.firstWhere((wc) => wc.id == ct.id); existing.calWeight += ct.calWeight; } else { - cargoTypes.add(ct.clone()); + _cargoTypes.add(ct.clone()); } totalCalWeight += ct.calWeight; }); @@ -51,25 +56,23 @@ class Invoice { DiscountByWeight discountByWeight = rate.getDiscountByWeight(totalCalWeight); - cargoTypes.forEach((e) { + _cargoTypes.forEach((e) { double r = e.rate - (discountByWeight != null ? discountByWeight.discount : 0); - double amount = e.calWeight * r; e.calRate = r; - e.amount = amount; }); - return cargoTypes; + return _cargoTypes; } double getTotal(Rate rate) { List cargoTypes = getCargoTypes(rate); - var total = cargoTypes.fold(0.0, (p, c) => c.amount + p); + var total = cargoTypes.fold(0.0, (p, c) => c.calAmount + p); return total; } double getNetAmount(Rate rate) { List cargoTypes = getCargoTypes(rate); - var total = cargoTypes.fold(0.0, (p, c) => c.amount + p); + var total = cargoTypes.fold(0.0, (p, c) => c.calAmount + p); total += getCustomFee(); total += getDeliveryFee(); total += getHandlingFee(); @@ -78,7 +81,7 @@ class Invoice { } double getHandlingFee() { - return shipments?.where((sh) => sh.isSelected)?.fold(0, (p, s) { + return shipments?.where((sh) => sh.isSelected ?? false)?.fold(0, (p, s) { return p + (s?.handlingFee ?? 0) - (s?.paidHandlingFee ?? 0); }); } @@ -97,33 +100,65 @@ class Invoice { double getDiscount() => discount == null ? 0 : discount.amount; - Invoice({ - this.id, - this.invoiceNumber, - this.invoiceDate, - this.userName, - this.phoneNumber, - this.amount, - this.discount, - this.status, - this.customDuties, - this.cartons, - this.cargoTypes, - this.handlingFee, - this.fcsShipmentID, - this.shipments, - }); + Invoice( + {this.id, + this.invoiceNumber, + this.invoiceDate, + this.fcsID, + this.userName, + this.phoneNumber, + this.amount, + this.discount, + this.status, + this.customDuties, + this.cartons, + this.cargoTypes, + this.handlingFee, + this.deliveryFee, + this.fcsShipmentID, + this.shipments, + this.invoiceURL, + this.paymentMethod}); factory Invoice.fromMap(Map map, String docID) { + var invd = (map['invoice_date'] as Timestamp); + var cargoTypesMaps = + List>.from(map['cargo_types'] ?? []); + var cargoTypes = + cargoTypesMaps.map((e) => CargoType.fromMap(e, e["id"])).toList(); + var customDutiesMap = + List>.from(map['custom_duties'] ?? []); + var customDuties = + customDutiesMap.map((e) => CustomDuty.fromMap(e, e["id"])).toList(); + var handlingShipmentsMap = + List>.from(map['handling_fee_shipments'] ?? []); + var handingShipments = + handlingShipmentsMap.map((e) => Shipment.fromMap(e, e["id"])).toList(); + var cartonsMap = List>.from(map['cartons'] ?? []); + var cartons = cartonsMap.map((e) => Carton.fromMap(e, e["id"])).toList(); + var paymentMethodMap = map['payment_method']; + var paymentMethod = paymentMethodMap != null + ? PaymentMethod.fromMap(paymentMethodMap, paymentMethodMap['id']) + : null; + var discountMap = map['discount']; + var discount = Discount.fromMap(discountMap, discountMap['id']); return Invoice( id: docID, invoiceNumber: map['invoice_number'], - invoiceDate: map['invoice_date'], + invoiceDate: invd?.toDate(), userName: map['user_name'], + fcsID: map['fcs_id'], phoneNumber: map['phone_number'], amount: map['amount'], status: map['status'], - discount: map['discount'], + cartons: cartons, + cargoTypes: cargoTypes, + shipments: handingShipments, + customDuties: customDuties, + deliveryFee: map['delivery_fee'], + invoiceURL: map['invoice_url'], + paymentMethod: paymentMethod, + discount: discount, ); } @@ -131,17 +166,24 @@ class Invoice { List _cargoTypes = cargoTypes.map((c) => c.toMap()).toList(); List _customDuties = customDuties?.map((c) => c.toMap())?.toList(); List _cartons = cartons?.map((c) => c.toMap())?.toList() ?? []; + List _shipments = shipments?.map((s) => s.toMap())?.toList() ?? []; return { "id": id, - "invoice_date": invoiceDate, + "invoice_date": invoiceDate?.toUtc()?.toIso8601String(), "user_id": userID, + "user_name": userName, + "invoice_number": invoiceNumber, 'fcs_shipment_id': fcsShipmentID, 'cargo_types': _cargoTypes, 'custom_duties': _customDuties, + 'handling_fee_shipments': _shipments, 'cartons': _cartons, 'discount': discount?.toMap(), + 'amount': amount, 'handling_fee': handlingFee, 'delivery_fee': deliveryFee, + 'invoice_url': invoiceURL, + 'payment_method': paymentMethod?.toMap(), }; } } diff --git a/lib/domain/entities/shipment.dart b/lib/domain/entities/shipment.dart index e07c3ff..7b837df 100644 --- a/lib/domain/entities/shipment.dart +++ b/lib/domain/entities/shipment.dart @@ -100,7 +100,7 @@ class Shipment { } Map toMap() { - List _boxes = boxes.map((l) => l.toMap()).toList(); + List _boxes = boxes?.map((l) => l.toMap())?.toList() ?? []; return { "id": id, diff --git a/lib/helpers/api_helper.dart b/lib/helpers/api_helper.dart index 0be6fdd..38415ef 100644 --- a/lib/helpers/api_helper.dart +++ b/lib/helpers/api_helper.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -5,9 +6,11 @@ import 'package:device_info/device_info.dart'; import 'package:dio/dio.dart'; import 'package:fcs/domain/vo/status.dart'; import 'package:logging/logging.dart'; +import 'package:path_provider/path_provider.dart'; import '../config.dart'; import 'dev_info.dart'; +import 'firebase_helper.dart'; final log = Logger('requestAPI'); @@ -146,3 +149,92 @@ Future requestDownloadPDFAPI(String path, method, throw e; } } + +typedef OnDownloadDone(File file); +// request makes http request +// if token is null +Future requestDownload(String path, method, + {dynamic payload, + String token, + String url, + String filePath, + OnDownloadDone onDownloadDone}) async { + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + String deviceName = "${androidInfo.model}(${androidInfo.id})"; + log.info("device:${androidInfo.androidId},deviceName:$deviceName"); + + var bytes = utf8.encode(payload); + var base64Str = base64.encode(bytes); + String escapePayload = HtmlEscape().convert(base64Str); + + try { + String baseUrl = url == null ? Config.instance.apiURL : url; + log.info("Path:$baseUrl$path"); + HttpClient client = new HttpClient(); + // var _downloadData = StringBuffer(); + var request = await client.postUrl(Uri.parse("$baseUrl$path")); + request.headers.set("Project-ID", Config.instance.reportProjectID); + if (token != null) { + request.headers.set("Token", token); + } + if (androidInfo.androidId != null) { + request.headers.set("Device", androidInfo.androidId + ":" + deviceName); + } + request.headers + .set(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8'); + request.headers.set("payload", escapePayload); + request.write(payload); + // request.write(escapePayload); + var response = await request.close(); + print("headers:${response.headers}"); + var _downloadData = List(); + var cd = response.headers.value("content-disposition"); + String fileName = "download.csv"; + if (cd != null && cd.contains("filename=")) { + fileName = cd.substring(cd.indexOf("=") + 1); + } + + var file = filePath + "/" + fileName; + var fileSave = new File(filePath + "/" + fileName); + response.listen((d) => _downloadData.addAll(d), onDone: () async { + await fileSave.writeAsBytes(_downloadData); + if (onDownloadDone != null) { + onDownloadDone(fileSave); + } + + // final message = await OpenFile.open(file); + // log.info("Open file result:${message.message}"); + // if (message.message != "done") { + // throw Exception(message.message); + // } + // await Share.file(fileName, fileName, _downloadData, + // downloadType == DownloadType.CSV ? "text/csv" : "application/pdf"); + }); + } catch (e) { + e.toString(); + log.warning("path:$path, api:$e"); + throw e; + } +} + +Future downloadPDF( + String templateName, Map values) async { + final completer = Completer(); + + final directory = await getApplicationSupportDirectory(); + String path = ('${directory.path}'); + log.info("download file path:$path"); + + var data = {"template_name": templateName, "data": values}; + print("payload:$data"); + + await requestDownload("/api/pdf", "POST", + filePath: path, + url: Config.instance.reportURL, + token: await getToken(), + payload: jsonEncode(data), onDownloadDone: (f) async { + completer.complete(f); + }); + return completer.future; +} diff --git a/lib/helpers/cache_mgr.dart b/lib/helpers/cache_mgr.dart new file mode 100644 index 0000000..75c5167 --- /dev/null +++ b/lib/helpers/cache_mgr.dart @@ -0,0 +1,12 @@ +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; + +class PdfCacheMgr { + static const key = 'pdfs'; + static CacheManager pdfs = CacheManager( + Config( + key, + stalePeriod: const Duration(days: 7), + maxNrOfCacheObjects: 20, + ), + ); +} diff --git a/lib/pages/carton/carton_editor.dart b/lib/pages/carton/carton_editor.dart index 6577c30..a2746dc 100644 --- a/lib/pages/carton/carton_editor.dart +++ b/lib/pages/carton/carton_editor.dart @@ -162,6 +162,18 @@ class _CartonEditorState extends State { }); } + _loadMixCartons() async { + if (_fcsShipment == null || _fcsShipment.id == null) return; + if (_selectedCartonType != carton_small_bag) return; + + CartonModel cartonModel = Provider.of(context, listen: false); + List cartons = + await cartonModel.getMixCartonsByFcsShipment(_fcsShipment.id); + setState(() { + _mixCartons = cartons; + }); + } + _calShipmentWeight() { double l = double.parse(_lengthController.text, (s) => 0); double w = double.parse(_widthController.text, (s) => 0); @@ -196,6 +208,7 @@ class _CartonEditorState extends State { setState(() { _fcsShipment = v; }); + _loadMixCartons(); }, labelKey: "shipment.pack.fcs.shipment", iconData: Ionicons.ios_airplane, @@ -291,7 +304,6 @@ class _CartonEditorState extends State { values: boxModel.cartonTypes, selectedValue: _selectedCartonType, callback: (v) { - print(v); setState(() { _selectedCartonType = v; }); diff --git a/lib/pages/invoice/invoice_carton_table.dart b/lib/pages/invoice/editor/invoice_carton_table.dart similarity index 100% rename from lib/pages/invoice/invoice_carton_table.dart rename to lib/pages/invoice/editor/invoice_carton_table.dart diff --git a/lib/pages/invoice/invoice_discount_table.dart b/lib/pages/invoice/editor/invoice_discount_list.dart similarity index 96% rename from lib/pages/invoice/invoice_discount_table.dart rename to lib/pages/invoice/editor/invoice_discount_list.dart index 880eda3..13ad897 100644 --- a/lib/pages/invoice/invoice_discount_table.dart +++ b/lib/pages/invoice/editor/invoice_discount_list.dart @@ -5,10 +5,10 @@ import 'package:fcs/pages/widgets/my_data_table.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -class InvoiceDiscountTable extends StatelessWidget { +class InvoiceDiscountList extends StatelessWidget { final List discounts; - const InvoiceDiscountTable({ + const InvoiceDiscountList({ Key key, this.discounts, }) : super(key: key); diff --git a/lib/pages/invoice/invoice_editor.dart b/lib/pages/invoice/editor/invoice_editor.dart similarity index 76% rename from lib/pages/invoice/invoice_editor.dart rename to lib/pages/invoice/editor/invoice_editor.dart index 41deb04..a56c531 100644 --- a/lib/pages/invoice/invoice_editor.dart +++ b/lib/pages/invoice/editor/invoice_editor.dart @@ -1,3 +1,4 @@ +import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/custom_duty.dart'; import 'package:fcs/domain/entities/discount.dart'; @@ -8,12 +9,12 @@ import 'package:fcs/domain/entities/shipment.dart'; import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/carton/model/carton_model.dart'; -import 'package:fcs/pages/discount/discount_list.dart'; import 'package:fcs/pages/discount/model/discount_model.dart'; -import 'package:fcs/pages/invoice/invoice_discount_table.dart'; -import 'package:fcs/pages/invoice/invoice_shipment_table.dart'; +import 'package:fcs/pages/invoice/editor/invoice_carton_table.dart'; +import 'package:fcs/pages/invoice/editor/invoice_discount_list.dart'; +import 'package:fcs/pages/invoice/editor/invoice_handling_fee_list.dart'; import 'package:fcs/pages/invoice/invoice_table.dart'; -import 'package:fcs/pages/invoice/invoice_carton_table.dart'; +import 'package:fcs/pages/invoice/model/invoice_model.dart'; import 'package:fcs/pages/main/model/main_model.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/payment_methods/model/payment_method_model.dart'; @@ -22,12 +23,11 @@ import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; import 'package:fcs/pages/shipment/model/shipment_model.dart'; import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/fcs_icons.dart'; -import 'package:fcs/pages/widgets/fcs_id_icon.dart'; +import 'package:fcs/pages/widgets/local_button.dart'; import 'package:fcs/pages/widgets/local_dropdown.dart'; import 'package:fcs/pages/widgets/local_popup_menu_button.dart'; import 'package:fcs/pages/widgets/local_popupmenu.dart'; import 'package:fcs/pages/widgets/local_text.dart'; -import 'package:fcs/pages/widgets/local_title.dart'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -36,8 +36,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -import 'invoice_custom_table.dart'; - class InvoiceEditor extends StatefulWidget { final Invoice invoice; final User customer; @@ -49,15 +47,13 @@ class InvoiceEditor extends StatefulWidget { } class _InvoiceEditorState extends State { - User _user; - var dateFormatter = new DateFormat('dd MMM yyyy'); Invoice _invoice; bool _isLoading = false; bool _isNew; + User _user; - List isSelected = [false]; bool _showCartons = false; @override void initState() { @@ -141,11 +137,14 @@ class _InvoiceEditorState extends State { var rateModel = Provider.of(context); var rate = rateModel.rate; + final invoiceNumberBox = DisplayText( + labelTextKey: 'invoice.number', + iconData: FontAwesomeIcons.fileInvoice, + text: _invoice?.invoiceNumber ?? ""); final statusBox = DisplayText( text: _invoice?.status ?? "", iconData: Icons.av_timer, labelTextKey: 'invoice.status'); - final cartonTable = InvoiceCartonTable( cartons: _invoice.cartons, rate: rate, @@ -155,21 +154,6 @@ class _InvoiceEditorState extends State { }); }, ); - final customTableHeaderBox = LocalTitle( - textKey: "invoice.custom_fee", - trailing: IconButton( - icon: Icon(Icons.add_circle, color: primaryColor), - onPressed: () async { - CustomDuty customDuty = await Navigator.of(context).push( - CupertinoPageRoute( - builder: (context) => CustomList(selected: true))); - _addCustom(customDuty); - })); - final customTableBox = InvoiceCustomTable( - customDuties: _invoice.customDuties, - onAdd: (c) => _addCustom(c), - onRemove: (c) => _removeCustom(c), - ); final paymentTypesBox = LocalDropdown( callback: (v) { setState(() { @@ -238,7 +222,6 @@ class _InvoiceEditorState extends State { }, isSelected: [_showCartons], ); - final popupMenu = LocalPopupMenuButton( buttonIcon: Icons.add_circle, selectable: false, @@ -271,12 +254,12 @@ class _InvoiceEditorState extends State { Shipment shipment = await Navigator.of(context).push( CupertinoPageRoute( builder: (context) => - InvoiceShipmentTable(shipments: _invoice.shipments))); + InvoiceHandlingFeeList(shipments: _invoice.shipments))); _addShipment(shipment); } else if (p.id == 3) { Discount discount = await Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => InvoiceDiscountTable( + builder: (context) => InvoiceDiscountList( discounts: discounts, ))); if (discount != null) { @@ -292,6 +275,39 @@ class _InvoiceEditorState extends State { }, ); + final headerBox = Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(dateFormatter.format(_invoice.invoiceDate)), + SizedBox( + height: 10, + ), + Text(_user?.name ?? ""), + Text( + _user?.fcsID ?? "", + style: TextStyle(fontSize: 12), + ) + ], + ), + Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + toggleButtonsBox, + popupMenu, + ], + ), + ], + ); + + final createBtn = LocalButton( + textKey: "invoice.issue.btn", + callBack: _save, + ); + return LocalProgress( inAsyncCall: _isLoading, child: Scaffold( @@ -310,39 +326,8 @@ class _InvoiceEditorState extends State { padding: const EdgeInsets.all(8.0), child: ListView( children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(dateFormatter.format(_invoice.invoiceDate)), - SizedBox( - height: 10, - ), - Text(_user?.name ?? ""), - Text( - _user?.fcsID ?? "", - style: TextStyle(fontSize: 12), - ) - ], - ), - Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - toggleButtonsBox, - popupMenu, - ], - ), - ], - ), - _isNew - ? Container() - : DisplayText( - labelTextKey: 'invoice.number', - iconData: FontAwesomeIcons.fileInvoice, - text: _invoice?.invoiceNumber ?? ""), + headerBox, + _isNew ? Container() : invoiceNumberBox, _isNew ? Container() : statusBox, _showCartons ? cartonTable : Container(), _showCartons @@ -360,15 +345,7 @@ class _InvoiceEditorState extends State { height: 10, ), _isNew - ? Container() - : LocalTitle( - textKey: "invoice.payment_attachment", - trailing: IconButton( - icon: Icon(Icons.add_circle, color: primaryColor), - onPressed: () async {})), - _isNew - ? fcsButton( - context, getLocalString(context, 'invoice.btn_create')) + ? createBtn : mainModel.isCustomer() ? Container() : Container( @@ -421,5 +398,54 @@ class _InvoiceEditorState extends State { }); } - _save() {} + _save() async { + var rateModel = Provider.of(context, listen: false); + double amount = _invoice.getNetAmount(rateModel.rate); + if (_invoice.paymentMethod == null) { + showMsgDialog(context, "Error", "Payment method required"); + return; + } + List cargoTypes = _invoice.getCargoTypes(rateModel.rate); + if (cargoTypes == null || cargoTypes.length == 0) { + showMsgDialog(context, "Error", "Expected at least one cargo type"); + return; + } + if ((amount ?? 0) <= 0) { + showMsgDialog(context, "Error", "Expected positive amount"); + return; + } + + setState(() { + _isLoading = true; + }); + + try { + InvoiceModel invoiceModel = + Provider.of(context, listen: false); + + Invoice invoice = Invoice(); + invoice.cargoTypes = cargoTypes; + invoice.amount = amount; + invoice.handlingFee = _invoice.getHandlingFee(); + invoice.cartons = _invoice.cartons.where((c) => c.isChecked).toList(); + invoice.shipments = + _invoice.shipments.where((s) => s.isSelected).toList(); + invoice.discount = _invoice.discount; + invoice.deliveryFee = _invoice.deliveryFee; + + invoice.userID = widget.customer.id; + invoice.fcsShipmentID = widget.fcsShipment.id; + invoice.invoiceDate = _invoice.invoiceDate; + invoice.paymentMethod = _invoice.paymentMethod; + + await invoiceModel.createInvoice(invoice); + Navigator.pop(context, true); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } } diff --git a/lib/pages/invoice/invoice_shipment_table.dart b/lib/pages/invoice/editor/invoice_handling_fee_list.dart similarity index 96% rename from lib/pages/invoice/invoice_shipment_table.dart rename to lib/pages/invoice/editor/invoice_handling_fee_list.dart index abc0318..9e1af8b 100644 --- a/lib/pages/invoice/invoice_shipment_table.dart +++ b/lib/pages/invoice/editor/invoice_handling_fee_list.dart @@ -8,12 +8,12 @@ import 'package:flutter/material.dart'; typedef OnAdd(Shipment shipment); typedef OnRemove(Shipment shipment); -class InvoiceShipmentTable extends StatelessWidget { +class InvoiceHandlingFeeList extends StatelessWidget { final List shipments; final OnAdd onAdd; final OnRemove onRemove; - const InvoiceShipmentTable( + const InvoiceHandlingFeeList( {Key key, this.shipments, this.onAdd, this.onRemove}) : super(key: key); diff --git a/lib/pages/invoice/invoice_custom_table.dart b/lib/pages/invoice/invoice_custom_table.dart deleted file mode 100644 index cd80426..0000000 --- a/lib/pages/invoice/invoice_custom_table.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:fcs/domain/entities/custom_duty.dart'; -import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/pages/widgets/local_text.dart'; -import 'package:fcs/pages/widgets/my_data_table.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; - -typedef OnAdd(CustomDuty customDuty); -typedef OnRemove(CustomDuty customDuty); - -class InvoiceCustomTable extends StatelessWidget { - final List customDuties; - final OnAdd onAdd; - final OnRemove onRemove; - - const InvoiceCustomTable( - {Key key, this.customDuties, this.onAdd, this.onRemove}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return MyDataTable( - headingRowHeight: 40, - columns: [ - MyDataColumn( - label: LocalText( - context, - "rate.cutom.product_type", - color: Colors.grey, - ), - ), - MyDataColumn( - label: LocalText( - context, - "rate.custom.fee", - color: Colors.grey, - ), - ), - ], - rows: getRows(context), - ); - } - - List getRows(BuildContext context) { - if (customDuties == null) { - return []; - } - double total = 0; - var rows = customDuties.map((c) { - total += c.fee; - return MyDataRow( - cells: [ - MyDataCell(new Text( - c.productType == null ? "" : c.productType, - style: textStyle, - )), - MyDataCell( - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text(c.fee == null ? "0" : c.fee.toString(), style: textStyle), - onRemove == null - ? SizedBox( - width: 50, - ) - : IconButton( - icon: Icon( - Icons.remove_circle, - color: primaryColor, - ), - onPressed: () { - if (onRemove != null) onRemove(c); - }) - ], - ), - ), - ], - ); - }).toList(); - - var totalRow = MyDataRow( - onSelectChanged: (bool selected) {}, - cells: [ - MyDataCell(Align( - alignment: Alignment.centerRight, - child: LocalText( - context, - "invoice.total_custom_fee", - color: Colors.black87, - fontWeight: FontWeight.bold, - ), - )), - MyDataCell( - Padding( - padding: const EdgeInsets.only(right: 48.0), - child: Align( - alignment: Alignment.centerRight, - child: Text(total.toString(), - style: TextStyle(fontWeight: FontWeight.bold))), - ), - ), - ], - ); - rows.add(totalRow); - return rows; - } -} diff --git a/lib/pages/invoice/invoice_customer_list.dart b/lib/pages/invoice/invoice_customer_list.dart index 95a4361..dfa7a61 100644 --- a/lib/pages/invoice/invoice_customer_list.dart +++ b/lib/pages/invoice/invoice_customer_list.dart @@ -2,7 +2,7 @@ import 'package:fcs/domain/entities/fcs_shipment.dart'; import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/customer/model/customer_model.dart'; -import 'package:fcs/pages/invoice/invoice_editor.dart'; +import 'package:fcs/pages/invoice/editor/invoice_editor.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; @@ -82,12 +82,15 @@ class _InvoiceCustomerListState extends State { Widget _item(User customer) { return InkWell( - onTap: () { - Navigator.of(context).push(CupertinoPageRoute( + onTap: () async { + bool created = await Navigator.of(context).push(CupertinoPageRoute( builder: (context) => InvoiceEditor( customer: customer, fcsShipment: widget.fcsShipment, ))); + if (created ?? false) { + _load(); + } }, child: Padding( padding: const EdgeInsets.only(left: 12.0, right: 12), diff --git a/lib/pages/invoice/invoice_info.dart b/lib/pages/invoice/invoice_info.dart index b86d82d..66acd78 100644 --- a/lib/pages/invoice/invoice_info.dart +++ b/lib/pages/invoice/invoice_info.dart @@ -1,170 +1,60 @@ +import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/carton.dart'; -import 'package:fcs/domain/entities/cargo_type.dart'; -import 'package:fcs/domain/entities/custom_duty.dart'; -import 'package:fcs/domain/entities/discount.dart'; import 'package:fcs/domain/entities/invoice.dart'; -import 'package:fcs/domain/entities/payment_method.dart'; -import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/localization/app_translations.dart'; -import 'package:fcs/pages/discount/discount_list.dart'; -import 'package:fcs/pages/discount/model/discount_model.dart'; -import 'package:fcs/pages/invoice/invoice_editor.dart'; -import 'package:fcs/pages/main/model/language_model.dart'; -import 'package:fcs/pages/main/model/main_model.dart'; +import 'package:fcs/pages/carton/model/carton_model.dart'; +import 'package:fcs/pages/invoice/editor/invoice_carton_table.dart'; +import 'package:fcs/pages/invoice/invoice_table.dart'; +import 'package:fcs/pages/invoice/model/invoice_model.dart'; +import 'package:fcs/pages/invoice/widgets.dart'; import 'package:fcs/pages/main/util.dart'; -import 'package:fcs/pages/payment_methods/model/payment_method_model.dart'; -import 'package:fcs/pages/rates/custom_list.dart'; import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; -import 'package:fcs/pages/user_search/user_serach.dart'; -import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; -import 'package:fcs/pages/widgets/discount_dropdown.dart'; import 'package:fcs/pages/widgets/display_text.dart'; -import 'package:fcs/pages/widgets/fcs_id_icon.dart'; -import 'package:fcs/pages/widgets/input_text.dart'; -import 'package:fcs/pages/widgets/local_dropdown.dart'; +import 'package:fcs/pages/widgets/fcs_icons.dart'; +import 'package:fcs/pages/widgets/local_button.dart'; import 'package:fcs/pages/widgets/local_text.dart'; -import 'package:fcs/pages/widgets/local_title.dart'; -import 'package:fcs/pages/widgets/multi_img_controller.dart'; -import 'package:fcs/pages/widgets/multi_img_file.dart'; -import 'package:fcs/pages/widgets/my_data_table.dart'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_icons/flutter_icons.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -class InvoiceInfoPage extends StatefulWidget { +class InvoiceInfo extends StatefulWidget { final Invoice invoice; - final User customer; - InvoiceInfoPage({this.invoice, this.customer}); + InvoiceInfo({this.invoice}); @override - _InvoiceInfoPageState createState() => _InvoiceInfoPageState(); + _InvoiceInfoState createState() => _InvoiceInfoState(); } -class _InvoiceInfoPageState extends State { - User user; - +class _InvoiceInfoState extends State { var dateFormatter = new DateFormat('dd MMM yyyy'); - TextEditingController _invoiceNumberController = new TextEditingController(); - TextEditingController _dateController = new TextEditingController(); - TextEditingController _nameController = new TextEditingController(); - TextEditingController _phoneController = new TextEditingController(); - TextEditingController _discountController = new TextEditingController(); - TextEditingController _amountController = new TextEditingController(); - TextEditingController _statusController = new TextEditingController(); - TextEditingController _customFeeController = new TextEditingController(); - MultiImgController multiImgController = MultiImgController(); - TextEditingController _descriptionController = new TextEditingController(); - TextEditingController _balanceController = new TextEditingController(); Invoice _invoice; bool _isLoading = false; - List _boxes = []; - bool isSwitched = false; - int deliveryfee = 0; - double customFee = 0; - double total = 0; - Discount _discount; - bool isNew = false; - Discount selectedDiscount; - int selectedDiscountAmt; - PaymentMethod paymentMethod; - double volumetricRatio = 0; - - List selectedBoxes = []; - List customs = []; - - List _cargoTypes = [ - CargoType(id: "1", name: 'General Cargo', weight: 33, rate: 6), - CargoType(id: "2", name: 'Medicine', weight: 33, rate: 7), - CargoType(id: "3", name: 'Dangerous Cargo', weight: 33, rate: 8) - ]; - - List _receipts = [ - "assets/buying_online_with_first_last_name.png", - ]; + bool _showCartons = false; @override void initState() { super.initState(); - volumetricRatio = Provider.of(context, listen: false) - .rate - .volumetricRatio; + _invoice = widget.invoice; + _invoice.shipments?.forEach((s) { + s.isSelected = true; + }); + _loadCartons(); + } - if (widget.invoice != null) { - _invoice = widget.invoice; - _invoiceNumberController.text = _invoice.invoiceNumber; - _dateController.text = dateFormatter.format(_invoice.invoiceDate); - _nameController.text = _invoice.userName; - _phoneController.text = _invoice.phoneNumber; - // _amountController.text = _invoice.getAmount.toString(); - _amountController.text = _invoice.amount.toString(); - _statusController.text = _invoice.status.toString(); - _customFeeController.text = '0'; - // multiImgController.setImageUrls = _receipts; - _descriptionController.text = 'For Electronics goods'; - // _boxes = _invoice.packages; - } else { - _dateController.text = dateFormatter.format(DateTime.now()); - _amountController.text = '0'; - _customFeeController.text = '0'; - _descriptionController.text = ''; - _balanceController.text = '0'; - setState(() { - isNew = true; - }); + _loadCartons() async { + CartonModel cartonModel = Provider.of(context, listen: false); + List cartons = []; + for (var c in _invoice?.cartons ?? []) { + var _carton = await cartonModel.getCarton(c.id); + _carton.isChecked = true; + cartons.add(_carton); } - - _boxes = [ - Carton( - shipmentNumber: "A202", - receiverNumber: "3", - receiverName: "Ko Myo Min", - boxNumber: "1", - rate: 7, - packageType: "General", - weight: 75, - status: "Packed", - receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon', - cargoDesc: "Clothes", - arrivedDate: DateTime(2020, 6, 1), - width: 10, - height: 10, - length: 10, - // packages: packages, - // statusHistory: statusHistory, - cargoTypes: [ - CargoType(name: 'General Cargo', weight: 25), - CargoType(name: 'Medicine', weight: 20), - CargoType(name: 'Dangerous Cargo', weight: 30) - ]), - Carton( - shipmentNumber: "A202", - receiverNumber: "3", - receiverName: "Ko Myo Min", - boxNumber: "2", - rate: 7, - packageType: "General", - weight: 75, - status: "Packed", - cargoDesc: "Clothes", - arrivedDate: DateTime(2020, 6, 1), - width: 10, - height: 10, - length: 10, - // statusHistory: statusHistory, - // packages: packages, - receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon', - cargoTypes: [ - CargoType(name: 'General Cargo', weight: 25), - CargoType(name: 'Medicine', weight: 20), - CargoType(name: 'Dangerous Cargo', weight: 30) - ]) - ]; + setState(() { + _invoice.cartons = cartons; + }); } @override @@ -174,21 +64,91 @@ class _InvoiceInfoPageState extends State { @override Widget build(BuildContext context) { - final nameBox = DisplayText( - iconData: Feather.user, - labelTextKey: 'invoice.customer_name', - text: user != null ? user.name : 'Ko Nyi'); + bool isCanceled = _invoice.status == invoice_cancel_status; + var rateModel = Provider.of(context); + var rate = rateModel.rate; - final fcsIDBox = DisplayText( - text: user != null ? user.fcsID : "FCS-KRUTUG", - labelTextKey: "box.fcs.id", - icon: FcsIDIcon(), + final cartonTable = InvoiceCartonTable( + cartons: _invoice.cartons, + rate: rate, ); - final statusBox = DisplayText( - text: _statusController.text, - iconData: Icons.av_timer, - labelTextKey: 'invoice.status'); + final invoiceTableBox = InvoiceTable( + invoice: _invoice, + rate: rate, + deliveryFeeSelected: (selected) { + setState(() { + if (selected) { + _invoice.deliveryFee = rate.deliveryFee; + } else { + _invoice.deliveryFee = 0; + } + }); + }, + discountSelected: (discount) { + setState(() { + _invoice.discount = discount; + }); + }, + ); + final toggleButtonsBox = ToggleButtons( + color: Colors.black45, + selectedColor: Colors.black45, + disabledColor: Colors.grey, + selectedBorderColor: primaryColor, + borderColor: Colors.transparent, + fillColor: Colors.transparent, + highlightColor: Colors.black45, + children: [ + Icon(cartonIconData), + ], + onPressed: (int index) { + setState(() { + _showCartons = !_showCartons; + }); + }, + isSelected: [_showCartons], + ); + + final headerBox = Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(dateFormatter.format(_invoice.invoiceDate)), + SizedBox( + height: 5, + ), + Text(_invoice?.userName ?? ""), + Text( + _invoice?.fcsID ?? "", + style: TextStyle(fontSize: 12), + ) + ], + ), + Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + toggleButtonsBox, + ], + ), + ], + ); + final paymentMethodBox = DisplayText( + labelTextKey: "invoice.payment_method", + text: _invoice.paymentMethod.name, + ); + + final receiptsBtn = LocalButton( + textKey: "invoice.btn_payment_receipt", + callBack: _cancel, + ); + final cancelBtn = LocalButton( + textKey: "invoice.cancel.btn", + callBack: _cancel, + ); return LocalProgress( inAsyncCall: _isLoading, @@ -203,71 +163,30 @@ class _InvoiceInfoPageState extends State { shadowColor: Colors.transparent, title: LocalText(context, 'invoice.form.title', color: primaryColor, fontSize: 20), - actions: [ - new IconButton( - icon: Icon( - Icons.delete, - color: primaryColor, - ), - onPressed: () {}), - new IconButton( - icon: Icon( - Icons.edit, - color: primaryColor, - ), - onPressed: () { - Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => InvoiceEditor(invoice: _invoice))); - }) - ], ), body: Padding( padding: const EdgeInsets.all(8.0), child: ListView( children: [ - LocalTitle(textKey: "invoice.customer_info"), - DisplayText( - labelTextKey: 'invoice.date', - iconData: Icons.date_range, - text: _dateController.text), - widget.invoice == null - ? Container() - : DisplayText( - labelTextKey: 'invoice.number', - iconData: FontAwesomeIcons.fileInvoice, - text: _invoiceNumberController.text), - fcsIDBox, - nameBox, - statusBox, - LocalTitle(textKey: "invoice.box_info"), - Center(child: Column(children: getCartonRows(context))), - SizedBox(height: 20), - LocalTitle(textKey: "invoice.custom_fee"), - Column(children: getCustomFeeRows(context)), - SizedBox(height: 20), - LocalTitle(textKey: "invoice.cargo_type"), - Column(children: getCargoTableByBox(context)), - SizedBox(height: 20), - Container( - padding: EdgeInsets.only(top: 5, left: 18), - child: Row( - children: [ - Expanded( - child: LocalText(context, 'invoice.payment_method', - fontSize: 16, - color: Colors.grey, - fontWeight: FontWeight.bold), - ), - Text( - '${paymentMethod != null ? paymentMethod.accountName : "KBZ Bank"}', - style: TextStyle(fontSize: 16)), - ], - ), - ), - SizedBox(height: 30), - !isNew - ? LocalTitle(textKey: "invoice.payment_attachment") + getInvoiceStatus(context, _invoice), + headerBox, + _showCartons ? cartonTable : Container(), + _showCartons + ? Divider( + color: primaryColor, + thickness: 2, + ) : Container(), + invoiceTableBox, + SizedBox( + height: 10, + ), + paymentMethodBox, + SizedBox( + height: 10, + ), + isCanceled ? Container() : receiptsBtn, + isCanceled ? Container() : cancelBtn, ], ), ), @@ -275,528 +194,27 @@ class _InvoiceInfoPageState extends State { ); } - getCartonRows(BuildContext context) { - List dataRow = []; - - dataRow = _boxes.map((box) { - return Container( - height: 50, - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - padding: - const EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0, bottom: 5.0), - child: Row( - children: [ - Expanded( - flex: 1, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(box.packageNumber), - Text(box.shipmentNumber), - ], - )), - Expanded( - flex: 2, - child: Text( - box.length == null - ? "" - : box.length.toString() + - ' x ' + - box.length.toString() + - ' x ' + - box.height.toString(), - textAlign: TextAlign.center)), - Expanded( - flex: 2, - child: Center( - child: Text( - box.getShipmentWeight(volumetricRatio).toString()))), - ], - ), - ); - }).toList(); - - dataRow.insert( - 0, - Container( - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 5.0, bottom: 15.0), - child: Row( - children: [ - Expanded( - flex: 1, - child: Center( - child: LocalText( - context, - "invoice.box.number", - color: Colors.grey, - ), - )), - Expanded( - flex: 2, - child: Center( - child: Text('L x W x H', - style: TextStyle(color: Colors.grey)))), - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - "invoice.shipment_weight", - color: Colors.grey, - ), - )), - ], - ), - )); - - return dataRow; + _cancel() { + showConfirmDialog(context, "invoice.cancel.confirm", _cancelInvoice); } - getCustomFeeRows(BuildContext context) { - customFee = 0; - List dataRow = []; + _cancelInvoice() async { + setState(() { + _isLoading = true; + }); - dataRow = customs.map((custom) { - customFee += custom.fee; - return Container( - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - padding: - const EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0, bottom: 5.0), - child: Row( - children: [ - Expanded(flex: 2, child: Text('${custom.productType}')), - Expanded( - flex: 1, - child: Text('\$ ${custom.fee}', textAlign: TextAlign.center)), - Expanded(child: SizedBox(height: 40)) - ], - ), - ); - }).toList(); + try { + InvoiceModel invoiceModel = + Provider.of(context, listen: false); - dataRow.insert( - 0, - Container( - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Text('Product', style: TextStyle(color: Colors.grey))), - Expanded( - flex: 1, - child: Text('Fee', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey))), - Expanded(flex: 1, child: Container()) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.total_custom_fee', - color: Colors.black, - fontWeight: FontWeight.bold, - ), - ), - ), - Expanded( - flex: 1, - child: Center( - child: Text('\$ $customFee', - textAlign: TextAlign.center, - style: TextStyle(fontWeight: FontWeight.bold)))), - Expanded( - child: Container(), - ) - ], - ), - )); - return dataRow; - } - - getCargoTableByBox(BuildContext context) { - total = 0; - List dataRow = _cargoTypes.map((cargo) { - var amount = cargo.weight * cargo.rate; - total += amount; - return Container( - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), - child: Row( - children: [ - Expanded(flex: 2, child: Text('${cargo.rate}')), - Expanded( - flex: 2, - child: Text('${cargo.weight} x ${cargo.rate}', - textAlign: TextAlign.center)), - Expanded( - child: Text('\$ $amount', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - ))) - ], - ), - ); - }).toList(); - dataRow.insert( - 0, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - child: Row( - children: [ - Expanded( - flex: 2, - child: Text(getLocalString(context, 'invoice.box.cargo_type'), - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.grey))), - Expanded( - flex: 2, - child: Text( - getLocalString(context, 'cargo.weight') + - ' x ' + - getLocalString(context, 'cargo.rate'), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.grey))), - Expanded( - child: Text(getLocalString(context, 'invoice.amount'), - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.grey))) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.total', - color: Colors.black, - ), - ), - ), - Expanded( - child: Text( - '\$ $total', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - textAlign: TextAlign.end, - )) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only(left: 5.0, right: 5.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.discount_value', - color: Colors.black, - ), - ), - ), - Expanded( - child: Text( - '\$ ${_discount != null ? _discount.amount.toInt() : 0}', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - ))) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 0.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.custom_fee', - color: Colors.black, - ), - )), - Expanded( - child: Text('\$ $customFee', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - )), - ), - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 20.0, bottom: 5.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.handling_fee', - color: Colors.black, - ), - )), - Expanded( - child: Text('\$ 10.0', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - )), - ), - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.delivery_fee', - color: Colors.black, - ), - ), - ), - Expanded( - child: Text('\$ $deliveryfee', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - ))) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - child: Row( - children: [ - Expanded(child: Text('')), - Expanded( - flex: 2, - child: Divider( - thickness: 3, - )), - ], - ))); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.net_amount', - color: Colors.black, - fontSize: 15, - fontWeight: FontWeight.bold, - ), - ), - ), - Expanded( - child: Text('\$ ${getTotalBalance(total)}', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: primaryColor))) - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'invoice.balance', - color: Colors.black, - fontSize: 15, - fontWeight: FontWeight.bold, - ), - ), - ), - Expanded( - child: Text('\$ ${getTotalBalance(total)}', - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: primaryColor))) - ], - ), - )); - - return dataRow; - } - - getTotalBalance(total) { - double balance = 0; - double custom = customFee != 0 ? customFee.toDouble() : 0; - double deliveryFee = deliveryfee != 0 ? deliveryfee.toDouble() : 0; - double discount = _discount != null ? _discount.amount.toDouble() : 0; - balance = (total + custom + deliveryFee) - discount; - return balance; - } - - List getBoxRow(BuildContext context) { - return _boxes.map((p) { - p.cargoTypes.map((cargo) { - _cargoTypes.asMap().map((index, _cargo) { - if (_cargo.id == cargo.id) { - setState(() { - _cargoTypes[index].weight += cargo.weight; - }); - } - }); + await invoiceModel.cancelInvoice(_invoice); + Navigator.pop(context, true); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; }); - return MyDataRow( - onSelectChanged: (bool selected) {}, - cells: [ - MyDataCell(Checkbox( - value: true, - onChanged: (value) { - selectedBoxes.add(p); - }, - )), - MyDataCell(new Text( - p.boxNumber == null - ? "" - : '${p.shipmentNumber}-${p.receiverNumber} #${p.boxNumber}', - style: textStyle, - )), - MyDataCell(new Text( - p.length == null - ? "" - : p.length.toString() + - ' x ' + - p.length.toString() + - ' x ' + - p.height.toString(), - style: textStyle, - )), - ], - ); - }).toList(); - } - - List getCargoDataRow(BuildContext context) { - return _cargoTypes.asMap().entries.map((c) { - var cargo = c.value; - var amt = cargo.weight * cargo.rate; - return MyDataRow( - onSelectChanged: (bool selected) {}, - cells: [ - MyDataCell(new Text( - cargo.name, - style: textStyle, - )), - MyDataCell(new Text( - cargo.weight.toString() + ' x ' + cargo.rate.toString(), - style: textStyle, - )), - MyDataCell(new Text( - "\$$amt", - style: textStyle, - )), - ], - ); - }).toList() - // .insert(_cargoTypes.length,MyDataRow(cells: [ - // MyDataCell(new Text('')), - // MyDataCell(new Text('Total')), - // MyDataCell(new Text( - // "\$5000", - // style: textStyle, - // )), - // ]) - // ) - ; + } } } diff --git a/lib/pages/invoice/invoce_list.dart b/lib/pages/invoice/invoice_list.dart similarity index 81% rename from lib/pages/invoice/invoce_list.dart rename to lib/pages/invoice/invoice_list.dart index 2406a8d..14b1012 100644 --- a/lib/pages/invoice/invoce_list.dart +++ b/lib/pages/invoice/invoice_list.dart @@ -1,6 +1,11 @@ +import 'dart:io'; + +import 'package:fcs/helpers/api_helper.dart'; +import 'package:fcs/helpers/firebase_helper.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/invoice/invoice_shipment_list.dart'; import 'package:fcs/pages/invoice/model/invoice_model.dart'; +import 'package:fcs/pages/invoice/payment_pdf_screen.dart'; import 'package:fcs/pages/widgets/local_popup_menu_button.dart'; import 'package:fcs/pages/widgets/local_popupmenu.dart'; import 'package:fcs/pages/widgets/local_text.dart'; @@ -21,7 +26,6 @@ class InvoiceList extends StatefulWidget { class _InvoiceListState extends State { bool _isLoading = false; - bool _showPaid = false; var _controller = ScrollController(); @override @@ -34,7 +38,8 @@ class _InvoiceListState extends State { } }); - Provider.of(context, listen: false).initData(false); + Provider.of(context, listen: false) + .initData(widget.forCustomer, true, false); } @override @@ -51,15 +56,29 @@ class _InvoiceListState extends State { popmenus: [ LocalPopupMenu( id: 1, - textKey: "invoice.popupmenu.pending", + textKey: "invoice.popupmenu.issused", selected: invoiceModel.selectedIndex == 1), LocalPopupMenu( id: 2, textKey: "invoice.popupmenu.paid", - selected: invoiceModel.selectedIndex == 2) + selected: invoiceModel.selectedIndex == 2), + LocalPopupMenu( + id: 3, + textKey: "invoice.popupmenu.cancel", + selected: invoiceModel.selectedIndex == 3) ], popupMenuCallback: (p) => this.setState(() { - _showPaid = p.id == 2; + invoiceModel.selectedIndex = p.id; + if (p.id == 2) { + Provider.of(context, listen: false) + .initData(widget.forCustomer, false, true); + } else if (p.id == 3) { + Provider.of(context, listen: false) + .initData(widget.forCustomer, true, false); + } else { + Provider.of(context, listen: false) + .initData(widget.forCustomer, true, false); + } }), ); @@ -131,7 +150,7 @@ class _InvoiceListState extends State { ); } - _newInvoice() { + _newInvoice() async { Navigator.of(context) .push(CupertinoPageRoute(builder: (context) => InvoiceShipmentList())); } diff --git a/lib/pages/invoice/invoice_list_row.dart b/lib/pages/invoice/invoice_list_row.dart index c644ea0..384f3af 100644 --- a/lib/pages/invoice/invoice_list_row.dart +++ b/lib/pages/invoice/invoice_list_row.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'dart:io'; +import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/invoice.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/invoice/invoice_info.dart'; -import 'package:fcs/pages/widgets/bottom_up_page_route.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -12,7 +12,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; -import 'invoice_editor.dart'; import 'payment_page.dart'; import 'payment_pdf_screen.dart'; @@ -81,11 +80,11 @@ class _InvoiceListRowState extends State { owner ? Navigator.of(context).push(CupertinoPageRoute( builder: (context) => PaymentPDFScreen( - path: pdfPath, + url: _invoice.invoiceURL, ))) : Navigator.of(context).push(CupertinoPageRoute( builder: (context) => PaymentPDFScreen( - path: pdfPath, + url: _invoice.invoiceURL, ))); }, child: new Row( @@ -132,7 +131,7 @@ class _InvoiceListRowState extends State { // padding: const EdgeInsets.all(0), // child: getStatus(_invoice.status), // ), - _invoice.status == "Pending" + _invoice.status == invoice_issued_status ? Padding( padding: const EdgeInsets.only(left: 10.0), child: InkWell( @@ -191,10 +190,11 @@ class _InvoiceListRowState extends State { ), ], ), - onPressed: () { + onPressed: () async { //to go invoice info page + Navigator.pop(context); Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => InvoiceInfoPage(invoice: _invoice))); + builder: (context) => InvoiceInfo(invoice: _invoice))); }, ) ], diff --git a/lib/pages/invoice/invoice_shipment_list.dart b/lib/pages/invoice/invoice_shipment_list.dart index 9a5575a..44b9726 100644 --- a/lib/pages/invoice/invoice_shipment_list.dart +++ b/lib/pages/invoice/invoice_shipment_list.dart @@ -1,13 +1,13 @@ import 'package:fcs/domain/entities/fcs_shipment.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/fcs_shipment/model/fcs_shipment_model.dart'; -import 'package:fcs/pages/shipment/model/shipment_model.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'invoice_customer_list.dart'; import 'invoice_shipment_list_row.dart'; class InvoiceShipmentList extends StatefulWidget { @@ -65,7 +65,14 @@ class _InvoiceShipmentListState extends State { itemCount: _fcsShipments.length, itemBuilder: (BuildContext context, int index) { return InvoiceShipmentListRow( - fcsShipment: _fcsShipments[index]); + fcsShipment: _fcsShipments[index], + onSelect: (f) { + Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => InvoiceCustomerList( + fcsShipment: f, + ))); + }, + ); }), ), ), diff --git a/lib/pages/invoice/invoice_shipment_list_row.dart b/lib/pages/invoice/invoice_shipment_list_row.dart index c0f6df2..eb66ce6 100644 --- a/lib/pages/invoice/invoice_shipment_list_row.dart +++ b/lib/pages/invoice/invoice_shipment_list_row.dart @@ -8,9 +8,12 @@ import 'package:intl/intl.dart'; import '../main/util.dart'; import 'invoice_customer_list.dart'; +typedef OnSelect(FcsShipment fcsShipment); + class InvoiceShipmentListRow extends StatefulWidget { + final OnSelect onSelect; final FcsShipment fcsShipment; - const InvoiceShipmentListRow({this.fcsShipment}); + const InvoiceShipmentListRow({this.fcsShipment, this.onSelect}); @override _InvoiceShipmentListRowState createState() => _InvoiceShipmentListRowState(); @@ -35,10 +38,7 @@ class _InvoiceShipmentListRowState extends State { padding: EdgeInsets.only(left: 15, right: 15), child: InkWell( onTap: () { - Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => InvoiceCustomerList( - fcsShipment: _fcsShipment, - ))); + if (widget.onSelect != null) widget.onSelect(widget.fcsShipment); }, child: Row( children: [ diff --git a/lib/pages/invoice/invoice_table.dart b/lib/pages/invoice/invoice_table.dart index 1df1cfd..a1a5ee3 100644 --- a/lib/pages/invoice/invoice_table.dart +++ b/lib/pages/invoice/invoice_table.dart @@ -63,14 +63,14 @@ class InvoiceTable extends StatelessWidget { List getTableRows() { List tableRows = []; // add cargo types - List _cargoTypes = invoice.getCargoTypes(rate); + List _cargoTypes = invoice.getCargoTypes(rate) ?? []; _cargoTypes.forEach((c) { tableRows.add(InvoiceTableRow( invoiceDataType: InvoiceDataType.CargoDataType, desc: c.name, rate: "${c.calWeight.toStringAsFixed(2)} x ${c.calRate.toStringAsFixed(2)}", - amount: "${c.amount.toStringAsFixed(2)}")); + amount: "${c.calAmount.toStringAsFixed(2)}")); }); invoice.shipments.where((ss) => (ss.isSelected ?? false)).forEach((s) { tableRows.add(InvoiceTableRow( @@ -80,7 +80,7 @@ class InvoiceTable extends StatelessWidget { rate: "", amount: "${s.handlingFee.toStringAsFixed(2)}")); }); - // add custom fee + // // add custom fee invoice.customDuties.forEach((c) { tableRows.add(InvoiceTableRow( data: c, @@ -89,7 +89,7 @@ class InvoiceTable extends StatelessWidget { rate: "", amount: "${c.fee.toStringAsFixed(2)}")); }); - // add delivery fee + // // add delivery fee tableRows.add(InvoiceTableRow( data: invoice.deliveryFee == null || invoice.deliveryFee == 0 ? null @@ -99,7 +99,7 @@ class InvoiceTable extends StatelessWidget { rate: "", amount: "${invoice?.deliveryFee?.toStringAsFixed(2) ?? '0'}")); - // add discounts + // // add discounts if (invoice.discount != null) { tableRows.add(InvoiceTableRow( data: invoice.discount, diff --git a/lib/pages/invoice/model/invoice_model.dart b/lib/pages/invoice/model/invoice_model.dart index a25678b..2a6b007 100644 --- a/lib/pages/invoice/model/invoice_model.dart +++ b/lib/pages/invoice/model/invoice_model.dart @@ -1,33 +1,30 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:fcs/config.dart'; +import 'package:fcs/data/services/services.dart'; import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/invoice.dart'; -import 'package:fcs/domain/entities/receipt.dart'; +import 'package:fcs/helpers/api_helper.dart'; +import 'package:fcs/helpers/firebase_helper.dart'; import 'package:fcs/helpers/paginator.dart'; import 'package:fcs/pages/main/model/base_model.dart'; import 'package:logging/logging.dart'; +import 'package:path_provider/path_provider.dart'; class InvoiceModel extends BaseModel { final log = Logger('InvoiceModel'); StreamSubscription listener; - List _invoices = [ - Invoice( - invoiceNumber: 'A092(A)-33', - invoiceDate: DateTime(2020, 4, 6, 12, 15), - userName: 'Ko Myo Min', - phoneNumber: '+959 555555555', - amount: 300, - status: 'Pending', - ) - ]; + List _invoices = []; List get invoices => - _selectedIndex == 1 ? _invoices : List.from(_paid.values); + _selectedIndex == 1 ? _invoices : List.from(_paginator.values); - Paginator _paid; + Paginator _paginator; bool endOfPaidInvoices = false; bool isLoading = false; @@ -45,15 +42,17 @@ class InvoiceModel extends BaseModel { super.privilegeChanged(); } - initData(bool forCustomer) { - _selectedIndex = 1; - // _loadFcsInvoices(forCustomer); + initData(bool forCustomer, bool isCanceled, bool isPaid) { + _loadInvoices(forCustomer); - if (_paid != null) _paid.close(); - _paid = _getPaid(forCustomer); + if (_paginator != null) _paginator.close(); + _paginator = _getPaginator(forCustomer, isCanceled, isPaid); + _paginator.load(onFinished: () { + notifyListeners(); + }); } - Future _loadFcsInvoices(bool forCustomer) async { + Future _loadInvoices(bool forCustomer) async { if (user == null) return; if (!forCustomer && !user.hasInvoices()) return; String path = "/$invoices_collection"; @@ -63,7 +62,7 @@ class InvoiceModel extends BaseModel { try { var q = Firestore.instance .collection("$path") - .where("is_paid", isEqualTo: false) + .where("status", isEqualTo: invoice_issued_status) .where("is_deleted", isEqualTo: false); if (forCustomer) { @@ -84,18 +83,24 @@ class InvoiceModel extends BaseModel { } } - Paginator _getPaid(bool isCustomer) { + Paginator _getPaginator(bool isCustomer, bool isCanceled, bool isPaid) { if (!isCustomer) { if (user == null || !(user.hasInvoices())) throw "No privilege"; } var pageQuery = Firestore.instance - .collection("/$packages_collection") - .where("is_delivered", isEqualTo: true) + .collection("/$invoices_collection") .where("is_deleted", isEqualTo: false); if (isCustomer) { pageQuery = pageQuery.where("user_id", isEqualTo: user.id); } - pageQuery = pageQuery.orderBy("status_date", descending: true); + if (isCanceled) { + pageQuery = pageQuery.where("status", isEqualTo: invoice_cancel_status); + } + if (isPaid) { + pageQuery = pageQuery.where("status", isEqualTo: invoice_paid_status); + } + + pageQuery = pageQuery.orderBy("created_at", descending: true); var paginator = new Paginator(pageQuery, rowPerLoad: 20, toObj: (data, id) { return Invoice.fromMap(data, id); }); @@ -103,11 +108,11 @@ class InvoiceModel extends BaseModel { } Future loadMore({bool isCustomer}) async { - if (_paid.ended || _selectedIndex == 1) + if (_paginator.ended || _selectedIndex == 1) return; // when paid menu is not selected return isLoading = true; notifyListeners(); - await _paid.load(onFinished: () { + await _paginator.load(onFinished: () { isLoading = false; notifyListeners(); }); @@ -115,7 +120,7 @@ class InvoiceModel extends BaseModel { Future refresh({bool isCustomer}) async { if (_selectedIndex == 1) return; // when paid menu is not selected return - await _paid.refresh(onFinished: () { + await _paginator.refresh(onFinished: () { notifyListeners(); }); } @@ -125,16 +130,24 @@ class InvoiceModel extends BaseModel { } logout() async { - if (_paid != null) _paid.close(); + if (_paginator != null) _paginator.close(); if (listener != null) await listener.cancel(); _invoices = []; } - Future createInvoice(Invoice invoice) { - // return Services.instance.invoiceService.createInvoice(invoice); + Future createInvoice(Invoice invoice) async { + File file = await downloadPDF("invoice", invoice.toMap()); + String url = await uploadStorage("pdfs", file); + print("uploaded url: $url"); + invoice.invoiceURL = url; + return Services.instance.invoiceService.createInvoice(invoice); } Future updateInvoice(Invoice invoice) { - // return Services.instance.invoiceService.updateInvoice(invoice); + return Services.instance.invoiceService.updateInvoice(invoice); + } + + Future cancelInvoice(Invoice invoice) { + return Services.instance.invoiceService.cancelInvoice(invoice); } } diff --git a/lib/pages/invoice/payment_pdf_screen.dart b/lib/pages/invoice/payment_pdf_screen.dart index a2e1591..7f6ddf9 100644 --- a/lib/pages/invoice/payment_pdf_screen.dart +++ b/lib/pages/invoice/payment_pdf_screen.dart @@ -1,23 +1,20 @@ import 'dart:async'; import 'dart:io'; -import 'package:dio/dio.dart'; +import 'package:fcs/helpers/cache_mgr.dart'; import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/pages/main/model/main_model.dart'; +import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_pdfview/flutter_pdfview.dart'; -import 'package:http/http.dart' as http; -import 'package:path_provider/path_provider.dart'; -import 'package:provider/provider.dart'; import 'package:share/share.dart'; class PaymentPDFScreen extends StatefulWidget { - final String path; + final String url; - PaymentPDFScreen({Key key, this.path}) : super(key: key); + PaymentPDFScreen({Key key, this.url}) : super(key: key); _PaymentPDFScreenState createState() => _PaymentPDFScreenState(); } @@ -30,88 +27,100 @@ class _PaymentPDFScreenState extends State int currentPage = 0; bool isReady = false; String errorMessage = ''; + bool _isLoading = true; + + void initState() { + super.initState(); + download(); + } + + File file; + Future download() async { + try { + File f = await PdfCacheMgr.pdfs.getSingleFile(widget.url); + setState(() { + file = f; + }); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - backgroundColor: primaryColor, - title: LocalText(context, 'invoice.pdf', - color: Colors.white, fontSize: 20), - leading: new IconButton( - icon: new Icon(CupertinoIcons.back), - onPressed: () { - Navigator.of(context).pop(); - }), - actions: [ - IconButton( - icon: Icon(Icons.file_download), - onPressed: () async { - var file = await copyAsset(); - print('file=> $file'); - }, + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + backgroundColor: Colors.white, + shadowColor: Colors.transparent, + title: LocalText(context, 'invoice.pdf', + color: Colors.white, fontSize: 20), + leading: new IconButton( + icon: new Icon(CupertinoIcons.back, color: primaryColor), + onPressed: () => Navigator.of(context).pop(), ), - IconButton( - icon: Icon(Icons.share), - onPressed: () => _share(widget.path), - ), - ], - ), - body: Stack( - children: [ - PDFView( - filePath: widget.path, - enableSwipe: true, - swipeHorizontal: true, - autoSpacing: false, - pageFling: true, - pageSnap: true, - defaultPage: currentPage, - fitPolicy: FitPolicy.BOTH, - preventLinkNavigation: - false, // if set to true the link is handled in flutter - onRender: (_pages) { - print(('pages => $pages')); - setState(() { - pages = _pages; - isReady = true; - }); - }, - onViewCreated: (PDFViewController pdfViewController) { - _controller.complete(pdfViewController); - }, - onLinkHandler: (String uri) { - print('goto uri: $uri'); - }, - onPageChanged: (int page, int total) { - print('page change: $page/$total'); - setState(() { - currentPage = page; - }); - }, - ), - ], + actions: [ + IconButton( + icon: Icon( + Icons.share, + color: primaryColor, + ), + onPressed: _share, + ), + ], + ), + body: Stack( + children: [ + _isLoading + ? Container() + : PDFView( + filePath: file?.path ?? "", + enableSwipe: true, + swipeHorizontal: true, + autoSpacing: false, + pageFling: true, + pageSnap: true, + defaultPage: currentPage, + fitPolicy: FitPolicy.BOTH, + preventLinkNavigation: + false, // if set to true the link is handled in flutter + onRender: (_pages) { + print(('pages => $pages')); + setState(() { + pages = _pages; + isReady = true; + }); + }, + onViewCreated: (PDFViewController pdfViewController) { + _controller.complete(pdfViewController); + }, + onLinkHandler: (String uri) { + print('goto uri: $uri'); + }, + onPageChanged: (int page, int total) { + print('page change: $page/$total'); + setState(() { + currentPage = page; + }); + }, + ), + ], + ), ), ); } - _share(String url) async { - MainModel mainModel = Provider.of(context, listen: false); - String appUrl = mainModel.setting.appUrl; + _share() async { final RenderBox box = context.findRenderObject(); - await Share.share( - "Join us on FCS Logistics App. Here is the link:\n $appUrl\n" + url, - subject: "Invitation to FCS Logistics App", + await Share.shareFiles([file.path], + mimeTypes: ["application/pdf"], + subject: "Invoice", sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size); } - - Future copyAsset() async { - Directory tempDir = await getTemporaryDirectory(); - String tempPath = tempDir.path; - File tempFile = File('$tempPath/Invoice-A092(A)-32.pdf'); - ByteData bd = await rootBundle.load('assets/Invoice-A092(A)-32.pdf'); - await tempFile.writeAsBytes(bd.buffer.asUint8List(), flush: true); - return tempFile; - } } diff --git a/lib/pages/invoice/widgets.dart b/lib/pages/invoice/widgets.dart new file mode 100644 index 0000000..d631d46 --- /dev/null +++ b/lib/pages/invoice/widgets.dart @@ -0,0 +1,24 @@ +import 'package:fcs/domain/entities/invoice.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:flutter/material.dart'; + +Widget getInvoiceStatus(BuildContext context, Invoice invoice) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LocalText( + context, + '', + text: invoice.invoiceNumber ?? "", + color: primaryColor, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Chip(label: Text(invoice.status ?? "")), + ), + ], + ); +} diff --git a/lib/pages/main/home_page.dart b/lib/pages/main/home_page.dart index 95bc12b..13529e7 100644 --- a/lib/pages/main/home_page.dart +++ b/lib/pages/main/home_page.dart @@ -16,7 +16,7 @@ import 'package:fcs/pages/delivery/delivery_list.dart'; import 'package:fcs/pages/discount/discount_list.dart'; import 'package:fcs/pages/faq/faq_list_page.dart'; import 'package:fcs/pages/fcs_shipment/fcs_shipment_list.dart'; -import 'package:fcs/pages/invoice/invoce_list.dart'; +import 'package:fcs/pages/invoice/invoice_list.dart'; import 'package:fcs/pages/main/model/language_model.dart'; import 'package:fcs/pages/main/model/main_model.dart'; import 'package:fcs/pages/main/util.dart'; @@ -298,11 +298,11 @@ class _HomePageState extends State { final invoicesBtn = TaskButton("invoices.btn", icon: FontAwesomeIcons.fileInvoice, btnCallback: () => Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => InvoiceList(forCustomer: false)))); + builder: (context) => InvoiceList(forCustomer: true)))); final invoicesBtnFcs = TaskButton("invoices.btn", icon: FontAwesomeIcons.fileInvoice, btnCallback: () => Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => InvoiceList(forCustomer: true)))); + builder: (context) => InvoiceList(forCustomer: false)))); final discountBtn = TaskButton("discount.btn", icon: Entypo.price_ribbon, diff --git a/lib/pages/shipment/shipment_list.dart b/lib/pages/shipment/shipment_list.dart index 9932bf7..1424639 100644 --- a/lib/pages/shipment/shipment_list.dart +++ b/lib/pages/shipment/shipment_list.dart @@ -68,9 +68,9 @@ class _ShipmentListState extends State { if (p.id == 3) { Provider.of(context, listen: false) .initData(widget.forCustomer, myPickup: true); - } else if (p.id == 1) { + } else { Provider.of(context, listen: false) - .initData(widget.forCustomer); + .initData(widget.forCustomer, myPickup: false); } }), ); diff --git a/pubspec.lock b/pubspec.lock index fe1340b..cf0475c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -28,7 +28,7 @@ packages: name: cached_network_image url: "https://pub.dartlang.org" source: hosted - version: "2.3.2+1" + version: "2.3.3" camera: dependency: "direct main" description: @@ -176,6 +176,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" file: dependency: transitive description: @@ -246,13 +253,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.6" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.11" flutter: dependency: "direct main" description: flutter @@ -266,12 +266,12 @@ packages: source: hosted version: "0.5.0" flutter_cache_manager: - dependency: transitive + dependency: "direct main" description: name: flutter_cache_manager url: "https://pub.dartlang.org" source: hosted - version: "1.4.2" + version: "2.0.0" flutter_datetime_picker: dependency: "direct main" description: @@ -454,7 +454,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.14" + version: "1.6.22" path_provider_linux: dependency: transitive description: @@ -476,6 +476,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+1" pedantic: dependency: transitive description: @@ -546,13 +553,6 @@ packages: relative: true source: path version: "0.0.1" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" provider: dependency: "direct main" description: @@ -768,6 +768,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.3" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3e85b57..a4fcf54 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,6 +56,7 @@ dependencies: flutter_local_notifications: ^1.4.4+4 share: ^0.6.5 cached_network_image: ^2.3.2+1 + flutter_cache_manager: ^2.0.0 dev_dependencies: flutter_test: From d5c24075452ff98c529ae1ed0b89268d5659ba47 Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Wed, 28 Oct 2020 05:11:06 +0630 Subject: [PATCH 04/10] add payment --- assets/local/localization_en.json | 2 + assets/local/localization_mu.json | 4 +- .../provider/fcs_shipment_data_provider.dart | 6 + lib/data/provider/invoice_data_provider.dart | 11 + lib/data/services/fcs_shipment_imp.dart | 5 + lib/data/services/fcs_shipment_service.dart | 1 + lib/data/services/invoice_imp.dart | 11 + lib/data/services/invoice_service.dart | 4 +- lib/domain/constants.dart | 6 + lib/domain/entities/fcs_shipment.dart | 3 + lib/domain/entities/invoice.dart | 10 + lib/domain/entities/payment.dart | 50 +++ lib/helpers/api_helper.dart | 2 +- lib/pages/fcs_shipment/fcs_shipment_info.dart | 43 ++- .../model/fcs_shipment_model.dart | 4 + lib/pages/invoice/editor/invoice_editor.dart | 1 + lib/pages/invoice/invoice_list.dart | 21 +- lib/pages/invoice/invoice_list_row.dart | 160 +++------ lib/pages/invoice/invoice_table.dart | 147 -------- lib/pages/invoice/model/invoice_model.dart | 36 +- lib/pages/invoice/payment/payment_page.dart | 319 ++++++++++++++++++ .../{ => payment}/payment_page_edit.dart | 0 lib/pages/invoice/payment_page.dart | 301 ----------------- lib/pages/package/model/package_model.dart | 1 - lib/pages/receiving/receiving_editor.dart | 4 +- lib/pages/widgets/img_picker.dart | 162 +++++++++ .../pdf_screen.dart} | 17 +- lib/pages/widgets/show_img.dart | 10 +- 28 files changed, 740 insertions(+), 601 deletions(-) create mode 100644 lib/domain/entities/payment.dart create mode 100644 lib/pages/invoice/payment/payment_page.dart rename lib/pages/invoice/{ => payment}/payment_page_edit.dart (100%) delete mode 100644 lib/pages/invoice/payment_page.dart create mode 100644 lib/pages/widgets/img_picker.dart rename lib/pages/{invoice/payment_pdf_screen.dart => widgets/pdf_screen.dart} (88%) diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index adcf3fe..c60d571 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -471,6 +471,8 @@ "invoice.shipment.discount.title":"Select discount", "invoice.cancel.btn":"Cancel", "invoice.cancel.confirm":"Cancel invoice?", + "invoice.payment.confirm.confirm":"Confirm payment?", + "invoice.payment.cancel.confirm":"Cancel payment?", "Invoices End ================================================================":"", "Discount Start ================================================================":"", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index 8e064c6..141d669 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -8,7 +8,7 @@ "btn.approve":"အတည်ပြုရန်", "btn.delete":"ဖျက်ရန်", "btn.select":"ရွေးချယ်ပါ", - "btn.cancel":"ဖျက်သိမ်းမည်", + "btn.cancel":"မလုပ်နဲ့", "btn.ok": "အိုကေ", "feet":"ပေ", "inch":"လက်မ", @@ -472,6 +472,8 @@ "invoice.shipment.discount.title":"Select discount", "invoice.cancel.btn":"Cancel", "invoice.cancel.confirm":"Cancel invoice?", + "invoice.payment.confirm.confirm":"Confirm payment?", + "invoice.payment.cancel.confirm":"Cancel payment?", "Invoices End ================================================================":"", "Discount Start ================================================================":"", diff --git a/lib/data/provider/fcs_shipment_data_provider.dart b/lib/data/provider/fcs_shipment_data_provider.dart index 530a85d..9fbf375 100644 --- a/lib/data/provider/fcs_shipment_data_provider.dart +++ b/lib/data/provider/fcs_shipment_data_provider.dart @@ -25,4 +25,10 @@ class FcsShipmentDataProvider { return await requestAPI("/fcs_shipments/ship", "PUT", payload: fcsShipment.toMap(), token: await getToken()); } + + Future reportFcsShipment(FcsShipment fcsShipment) async { + dynamic data = await requestAPI("/fcs_shipments/report", "POST", + payload: fcsShipment.toMap(), token: await getToken()); + return data["url"]; + } } diff --git a/lib/data/provider/invoice_data_provider.dart b/lib/data/provider/invoice_data_provider.dart index 1afee73..380f9e4 100644 --- a/lib/data/provider/invoice_data_provider.dart +++ b/lib/data/provider/invoice_data_provider.dart @@ -1,4 +1,5 @@ import 'package:fcs/domain/entities/invoice.dart'; +import 'package:fcs/domain/entities/payment.dart'; import 'package:fcs/helpers/api_helper.dart'; import 'package:fcs/helpers/firebase_helper.dart'; import 'package:logging/logging.dart'; @@ -22,4 +23,14 @@ class InvoiceDataProvider { return await requestAPI("/invoices/cancel", "PUT", payload: {"id": invoice.id}, token: await getToken()); } + + Future pay(Payment payment) async { + return await requestAPI("/invoices/pay", "PUT", + payload: payment.toMap(), token: await getToken()); + } + + Future updatPaymentStatus(Payment payment) async { + return await requestAPI("/invoices/pay/status", "PUT", + payload: payment.toMap(), token: await getToken()); + } } diff --git a/lib/data/services/fcs_shipment_imp.dart b/lib/data/services/fcs_shipment_imp.dart index 4eb5e94..6ffaafa 100644 --- a/lib/data/services/fcs_shipment_imp.dart +++ b/lib/data/services/fcs_shipment_imp.dart @@ -33,4 +33,9 @@ class FcsShipmentServiceImp implements FcsShipmentService { Future ship(FcsShipment fcsShipment) { return shipmentDataProvider.ship(fcsShipment); } + + @override + Future report(FcsShipment fcsShipment) { + return shipmentDataProvider.reportFcsShipment(fcsShipment); + } } diff --git a/lib/data/services/fcs_shipment_service.dart b/lib/data/services/fcs_shipment_service.dart index 46e4ba8..2284122 100644 --- a/lib/data/services/fcs_shipment_service.dart +++ b/lib/data/services/fcs_shipment_service.dart @@ -5,4 +5,5 @@ abstract class FcsShipmentService { Future updateFcsShipment(FcsShipment fcsShipment); Future deleteFcsShipment(FcsShipment fcsShipment); Future ship(FcsShipment fcsShipment); + Future report(FcsShipment fcsShipment); } diff --git a/lib/data/services/invoice_imp.dart b/lib/data/services/invoice_imp.dart index 3fe5df1..c26dab4 100644 --- a/lib/data/services/invoice_imp.dart +++ b/lib/data/services/invoice_imp.dart @@ -3,6 +3,7 @@ import 'package:fcs/data/provider/shipment_data_provider.dart'; import 'package:fcs/data/services/shipment_service.dart'; import 'package:fcs/domain/entities/connectivity.dart'; import 'package:fcs/domain/entities/invoice.dart'; +import 'package:fcs/domain/entities/payment.dart'; import 'package:fcs/domain/entities/shipment.dart'; import 'package:flutter/material.dart'; @@ -31,4 +32,14 @@ class InvoiceServiceImp implements InvoiceService { Future updateInvoice(Invoice invoice) { return invoiceDataProvider.updateInvoice(invoice); } + + @override + Future pay(Payment payment) { + return invoiceDataProvider.pay(payment); + } + + @override + Future updatPaymentStatus(Payment payment) { + return invoiceDataProvider.updatPaymentStatus(payment); + } } diff --git a/lib/data/services/invoice_service.dart b/lib/data/services/invoice_service.dart index feed4e2..270fed7 100644 --- a/lib/data/services/invoice_service.dart +++ b/lib/data/services/invoice_service.dart @@ -1,8 +1,10 @@ import 'package:fcs/domain/entities/invoice.dart'; -import 'package:fcs/domain/entities/shipment.dart'; +import 'package:fcs/domain/entities/payment.dart'; abstract class InvoiceService { Future createInvoice(Invoice invoice); Future updateInvoice(Invoice invoice); Future cancelInvoice(Invoice invoice); + Future pay(Payment payment); + Future updatPaymentStatus(Payment payment); } diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index 7243933..fe1d6e4 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -27,6 +27,7 @@ const user_joined_status = "joined"; const pkg_files_path = "/packages"; const shipment_labels_files_path = "/shipment_labels"; +const receipt_labels_files_path = "/receipts"; // Link page const page_payment_methods = "payment_methods"; @@ -97,3 +98,8 @@ const invoice_issued_status = "issued"; const invoice_saved_status = "saved"; const invoice_cancel_status = "canceled"; const invoice_paid_status = "paid"; + +// payment status +const payment_pending_status = "pending"; +const payment_confirmed_status = "confirmed"; +const payment_canceled_status = "canceled"; diff --git a/lib/domain/entities/fcs_shipment.dart b/lib/domain/entities/fcs_shipment.dart index 5a6264a..adfc4f2 100644 --- a/lib/domain/entities/fcs_shipment.dart +++ b/lib/domain/entities/fcs_shipment.dart @@ -13,6 +13,7 @@ class FcsShipment { String port; String destination; String status; + String reportName; FcsShipment({ this.id, this.shipmentNumber, @@ -24,6 +25,7 @@ class FcsShipment { this.consignee, this.port, this.destination, + this.reportName, }); factory FcsShipment.fromMap(Map map, String docID) { @@ -57,6 +59,7 @@ class FcsShipment { 'port': port, 'destination': destination, 'status': status, + 'report_name': reportName, }; } diff --git a/lib/domain/entities/invoice.dart b/lib/domain/entities/invoice.dart index b7c3460..b614fcc 100644 --- a/lib/domain/entities/invoice.dart +++ b/lib/domain/entities/invoice.dart @@ -4,6 +4,7 @@ import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/custom_duty.dart'; import 'package:fcs/domain/entities/discount.dart'; import 'package:fcs/domain/entities/discount_by_weight.dart'; +import 'package:fcs/domain/entities/payment.dart'; import 'package:fcs/domain/entities/payment_method.dart'; import 'package:fcs/domain/entities/rate.dart'; import 'package:fcs/domain/entities/shipment.dart'; @@ -28,6 +29,7 @@ class Invoice { List cartons; List cargoTypes; List shipments; + List payments; Discount discount; PaymentMethod paymentMethod; String invoiceURL; @@ -70,6 +72,8 @@ class Invoice { return total; } + double get balance => (amount ?? 0) - (paidAmount ?? 0); + double getNetAmount(Rate rate) { List cargoTypes = getCargoTypes(rate); var total = cargoTypes.fold(0.0, (p, c) => c.calAmount + p); @@ -108,6 +112,7 @@ class Invoice { this.userName, this.phoneNumber, this.amount, + this.paidAmount, this.discount, this.status, this.customDuties, @@ -118,6 +123,7 @@ class Invoice { this.fcsShipmentID, this.shipments, this.invoiceURL, + this.payments, this.paymentMethod}); factory Invoice.fromMap(Map map, String docID) { @@ -142,6 +148,8 @@ class Invoice { : null; var discountMap = map['discount']; var discount = Discount.fromMap(discountMap, discountMap['id']); + var paymentMaps = List>.from(map['payments'] ?? []); + var payments = paymentMaps.map((e) => Payment.fromMap(e, e["id"])).toList(); return Invoice( id: docID, invoiceNumber: map['invoice_number'], @@ -150,6 +158,7 @@ class Invoice { fcsID: map['fcs_id'], phoneNumber: map['phone_number'], amount: map['amount'], + paidAmount: double.tryParse(map['paid_amount'].toString()) ?? 0, status: map['status'], cartons: cartons, cargoTypes: cargoTypes, @@ -159,6 +168,7 @@ class Invoice { invoiceURL: map['invoice_url'], paymentMethod: paymentMethod, discount: discount, + payments: payments, ); } diff --git a/lib/domain/entities/payment.dart b/lib/domain/entities/payment.dart new file mode 100644 index 0000000..d30eebc --- /dev/null +++ b/lib/domain/entities/payment.dart @@ -0,0 +1,50 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class Payment { + String id; + String invoiceID; + DateTime paymentDate; + String paymentReceiptURL; + String status; + double amount; + + Payment( + {this.id, + this.invoiceID, + this.paymentDate, + this.paymentReceiptURL, + this.status, + this.amount}); + + factory Payment.fromMap(Map map, String id) { + var _paymentDate = (map['payment_date'] as Timestamp); + return Payment( + id: id, + paymentDate: _paymentDate?.toDate(), + paymentReceiptURL: map['payment_receipt_url'], + status: map['status'], + amount: map['amount']?.toDouble() ?? 0, + ); + } + + Map toMap() { + return { + "id": id, + "invoice_id": invoiceID, + 'payment_date': paymentDate?.toUtc()?.toIso8601String(), + 'payment_receipt_url': paymentReceiptURL, + 'status': status, + 'amount': amount, + }; + } + + Payment clone() { + return Payment.fromMap(toMap(), this.id); + } + + @override + bool operator ==(Object other) => other is Payment && other.id == id; + + @override + int get hashCode => id.hashCode; +} diff --git a/lib/helpers/api_helper.dart b/lib/helpers/api_helper.dart index 38415ef..06fca69 100644 --- a/lib/helpers/api_helper.dart +++ b/lib/helpers/api_helper.dart @@ -41,7 +41,7 @@ Future requestAPI( method: method, baseUrl: url == null ? Config.instance.apiURL : url, connectTimeout: 10000, - receiveTimeout: 10000, + receiveTimeout: 60000, headers: headers, ); log.info("baseUrl:${options.baseUrl}, path:$path"); diff --git a/lib/pages/fcs_shipment/fcs_shipment_info.dart b/lib/pages/fcs_shipment/fcs_shipment_info.dart index 81c2fa8..819e7f6 100644 --- a/lib/pages/fcs_shipment/fcs_shipment_info.dart +++ b/lib/pages/fcs_shipment/fcs_shipment_info.dart @@ -6,6 +6,7 @@ import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/local_button.dart'; import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:fcs/pages/widgets/pdf_screen.dart'; import 'package:fcs/pages/widgets/popupmenu.dart'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; @@ -200,13 +201,8 @@ class _FcsShipmentInfoState extends State { return PopupMenuButton( elevation: 3.2, icon: Icon(Icons.more_vert, color: primaryColor), - tooltip: 'This is tooltip', onSelected: (choice) { - print(choice.id); - if (choice.id == 1) { - } else if (choice.id == 2) { - } else if (choice.id == 3) { - } else if (choice.id == 4) {} + _showPDF(choice.id); }, itemBuilder: (BuildContext context) { return menuPopup.map((PopupMenu choice) { @@ -252,4 +248,39 @@ class _FcsShipmentInfoState extends State { }); } } + + _showPDF(int id) async { + setState(() { + _isLoading = true; + }); + try { + var reportName = ""; + if (id == 1) { + reportName = "commercial_invoice"; + } else if (id == 2) { + reportName = "packing_list"; + } else if (id == 3) { + reportName = "dms"; + } else if (id == 4) { + reportName = "manifest"; + } + _fcsShipment.reportName = reportName; + + FcsShipmentModel fcsShipmentModel = + Provider.of(context, listen: false); + String url = await fcsShipmentModel.report(_fcsShipment); + Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => PDFScreen( + title: "", + url: url, + ))); + // Navigator.pop(context, true); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } } diff --git a/lib/pages/fcs_shipment/model/fcs_shipment_model.dart b/lib/pages/fcs_shipment/model/fcs_shipment_model.dart index 6d60365..aaf40a4 100644 --- a/lib/pages/fcs_shipment/model/fcs_shipment_model.dart +++ b/lib/pages/fcs_shipment/model/fcs_shipment_model.dart @@ -172,4 +172,8 @@ class FcsShipmentModel extends BaseModel { Future ship(FcsShipment fcsShipment) { return Services.instance.fcsShipmentService.ship(fcsShipment); } + + Future report(FcsShipment fcsShipment) { + return Services.instance.fcsShipmentService.report(fcsShipment); + } } diff --git a/lib/pages/invoice/editor/invoice_editor.dart b/lib/pages/invoice/editor/invoice_editor.dart index a56c531..783a4f2 100644 --- a/lib/pages/invoice/editor/invoice_editor.dart +++ b/lib/pages/invoice/editor/invoice_editor.dart @@ -437,6 +437,7 @@ class _InvoiceEditorState extends State { invoice.fcsShipmentID = widget.fcsShipment.id; invoice.invoiceDate = _invoice.invoiceDate; invoice.paymentMethod = _invoice.paymentMethod; + invoice.customDuties = _invoice.customDuties; await invoiceModel.createInvoice(invoice); Navigator.pop(context, true); diff --git a/lib/pages/invoice/invoice_list.dart b/lib/pages/invoice/invoice_list.dart index 14b1012..567c21d 100644 --- a/lib/pages/invoice/invoice_list.dart +++ b/lib/pages/invoice/invoice_list.dart @@ -1,11 +1,6 @@ -import 'dart:io'; - -import 'package:fcs/helpers/api_helper.dart'; -import 'package:fcs/helpers/firebase_helper.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/invoice/invoice_shipment_list.dart'; import 'package:fcs/pages/invoice/model/invoice_model.dart'; -import 'package:fcs/pages/invoice/payment_pdf_screen.dart'; import 'package:fcs/pages/widgets/local_popup_menu_button.dart'; import 'package:fcs/pages/widgets/local_popupmenu.dart'; import 'package:fcs/pages/widgets/local_text.dart'; @@ -38,8 +33,9 @@ class _InvoiceListState extends State { } }); - Provider.of(context, listen: false) - .initData(widget.forCustomer, true, false); + InvoiceModel invoiceModel = + Provider.of(context, listen: false); + invoiceModel.initData(widget.forCustomer, true, false); } @override @@ -49,7 +45,6 @@ class _InvoiceListState extends State { @override Widget build(BuildContext context) { - var owner = true; var invoiceModel = Provider.of(context); final popupMenu = LocalPopupMenuButton( @@ -98,8 +93,9 @@ class _InvoiceListState extends State { color: Colors.white, fontSize: 20), actions: [popupMenu], ), - floatingActionButton: owner - ? FloatingActionButton.extended( + floatingActionButton: widget.forCustomer + ? null + : FloatingActionButton.extended( onPressed: () { _newInvoice(); }, @@ -107,8 +103,7 @@ class _InvoiceListState extends State { label: LocalText(context, 'invoices.add', color: Colors.white), backgroundColor: primaryColor, - ) - : null, + ), body: Column( children: [ Expanded( @@ -118,9 +113,9 @@ class _InvoiceListState extends State { controller: _controller, separatorBuilder: (context, index) => Divider( color: Colors.black, + height: 1, ), scrollDirection: Axis.vertical, - padding: EdgeInsets.only(top: 15), shrinkWrap: true, itemCount: invoiceModel.invoices.length, itemBuilder: (BuildContext context, int index) { diff --git a/lib/pages/invoice/invoice_list_row.dart b/lib/pages/invoice/invoice_list_row.dart index 384f3af..a06d268 100644 --- a/lib/pages/invoice/invoice_list_row.dart +++ b/lib/pages/invoice/invoice_list_row.dart @@ -1,137 +1,75 @@ -import 'dart:async'; -import 'dart:io'; - import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/invoice.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/invoice/invoice_info.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; -import 'package:path_provider/path_provider.dart'; -import 'payment_page.dart'; -import 'payment_pdf_screen.dart'; +import 'payment/payment_page.dart'; +import '../widgets/pdf_screen.dart'; -class InvoiceListRow extends StatefulWidget { +class InvoiceListRow extends StatelessWidget { + final dateFormatter = new DateFormat('dd MMM yyyy'); final Invoice invoice; InvoiceListRow({Key key, this.invoice}) : super(key: key); - @override - _InvoiceListRowState createState() => _InvoiceListRowState(); -} - -class _InvoiceListRowState extends State { - var dateFormatter = new DateFormat('dd MMM yyyy'); - final double dotSize = 15.0; - Invoice _invoice = new Invoice(); - String pdfPath = ''; - - @override - void initState() { - super.initState(); - - if (widget.invoice != null) { - _invoice = widget.invoice; - } - - fromAsset('assets/Invoice-A092(A)-32.pdf', 'Invoice-A092(A)-32.pdf') - .then((f) { - setState(() { - pdfPath = f.path; - }); - }); - } - - Future fromAsset(String asset, String filename) async { - // To open from assets, you can copy them to the app storage folder, and the access them "locally" - Completer completer = Completer(); - print('asset => $asset'); - print('assest => ${await rootBundle.load(asset)}'); - try { - var dir = await getApplicationDocumentsDirectory(); - File file = File("${dir.path}/$filename"); - var data = await rootBundle.load(asset); - print('data => $data'); - var bytes = data.buffer.asUint8List(); - await file.writeAsBytes(bytes, flush: true); - completer.complete(file); - } catch (e) { - throw Exception('Error parsing asset file! ===> ' + e.toString()); - } - - return completer.future; - } - @override Widget build(BuildContext context) { - var owner = true; - return Container( - padding: EdgeInsets.only(left: 15, right: 15), + return InkWell( + onTap: () { + Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => PDFScreen( + title: invoice.invoiceNumber, + url: invoice.invoiceURL, + ))); + }, child: Row( children: [ Expanded( child: new Padding( padding: const EdgeInsets.symmetric(vertical: 10.0), - child: InkWell( - onTap: () { - owner - ? Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => PaymentPDFScreen( - url: _invoice.invoiceURL, - ))) - : Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => PaymentPDFScreen( - url: _invoice.invoiceURL, - ))); - }, - child: new Row( - children: [ - Container( - padding: EdgeInsets.only(left: 5, right: 10), - child: Icon( - FontAwesomeIcons.fileInvoice, - color: primaryColor, - size: 30, - ), + child: new Row( + children: [ + Container( + padding: EdgeInsets.only(left: 5, right: 10), + child: Icon( + FontAwesomeIcons.fileInvoice, + color: primaryColor, + size: 30, ), - new Expanded( + ), + new Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 0), child: new Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only(left: 8.0), - child: new Text( - _invoice.invoiceNumber == null - ? '' - : _invoice.invoiceNumber, - style: new TextStyle( - fontSize: 15.0, color: Colors.black), - ), + new Text( + invoice.invoiceNumber ?? "", + style: new TextStyle( + fontSize: 15.0, color: Colors.black), ), - Padding( - padding: const EdgeInsets.only(left: 10.0, top: 10), - child: new Text( - dateFormatter.format(_invoice.invoiceDate), - style: new TextStyle( - fontSize: 15.0, color: Colors.grey), - ), + new Text( + invoice.status ?? "", + style: new TextStyle( + fontSize: 13.0, color: primaryColor), + ), + new Text( + dateFormatter.format(invoice.invoiceDate), + style: new TextStyle( + fontSize: 15.0, color: Colors.grey), ) ], ), ), - ], - ), + ), + ], ), ), ), - // Padding( - // padding: const EdgeInsets.all(0), - // child: getStatus(_invoice.status), - // ), - _invoice.status == invoice_issued_status + invoice.status == invoice_issued_status ? Padding( padding: const EdgeInsets.only(left: 10.0), child: InkWell( @@ -143,27 +81,29 @@ class _InvoiceListRowState extends State { color: primaryColor, ), Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Text("Payment"), + padding: const EdgeInsets.only(left: 3.0), + child: Text( + "Payment", + style: TextStyle(fontSize: 12, color: Colors.black), + ), ) ], ), onPressed: () { Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => - PaymentPage(invoice: _invoice))); + builder: (context) => PaymentPage(invoice: invoice))); }, )), ) : Container(), Padding( padding: const EdgeInsets.only(left: 8.0), - child: InkWell( - child: Icon( + child: IconButton( + icon: Icon( Icons.more_vert, color: primaryColor, ), - onTap: () { + onPressed: () { var act = actionSheet(context); showCupertinoModalPopup( context: context, builder: (BuildContext context) => act); @@ -194,7 +134,7 @@ class _InvoiceListRowState extends State { //to go invoice info page Navigator.pop(context); Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => InvoiceInfo(invoice: _invoice))); + builder: (context) => InvoiceInfo(invoice: invoice))); }, ) ], diff --git a/lib/pages/invoice/invoice_table.dart b/lib/pages/invoice/invoice_table.dart index a1a5ee3..b25c222 100644 --- a/lib/pages/invoice/invoice_table.dart +++ b/lib/pages/invoice/invoice_table.dart @@ -2,8 +2,6 @@ import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/discount.dart'; import 'package:fcs/domain/entities/invoice.dart'; import 'package:fcs/domain/entities/rate.dart'; -import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/pages/discount/discount_list.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:flutter/cupertino.dart'; @@ -227,151 +225,6 @@ class InvoiceTable extends StatelessWidget { ), )); - // dataRow.insert( - // dataRow.length, - // Container( - // padding: const EdgeInsets.only(left: 5.0, right: 5.0, top: 20.0), - // child: Row( - // children: [ - // Expanded( - // flex: 1, - // child: Container( - // alignment: Alignment.centerRight, - // child: LocalText( - // context, - // 'invoice.handling_fee', - // color: Colors.black, - // ), - // ), - // ), - // SizedBox(width: 50), - // Expanded( - // child: Text( - // '\$ ${invoice.getHandlingFee().toStringAsFixed(2) ?? ""}', - // textAlign: TextAlign.end, - // style: TextStyle( - // fontSize: 15, - // fontWeight: FontWeight.bold, - // ))) - // ], - // ), - // )); - - // dataRow.insert( - // dataRow.length, - // Container( - // padding: const EdgeInsets.only( - // left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - // child: Row( - // children: [ - // Expanded( - // flex: 1, - // child: Container( - // alignment: Alignment.centerRight, - // child: LocalText( - // context, - // 'invoice.delivery_fee', - // color: Colors.black, - // ), - // )), - // Switch( - // value: (invoice.deliveryFee ?? 0) > 0, - // onChanged: (value) { - // if (deliveryFeeSelected != null) { - // deliveryFeeSelected(value); - // } - // }, - // activeTrackColor: primaryColor.withOpacity(0.8), - // activeColor: primaryColor, - // ), - // Expanded( - // child: - // Text('\$ ${invoice.getDeliveryFee().toStringAsFixed(2)}', - // textAlign: TextAlign.end, - // style: TextStyle( - // fontSize: 15, - // fontWeight: FontWeight.bold, - // ))) - // ], - // ), - // )); - - // dataRow.insert( - // dataRow.length, - // Container( - // child: Row( - // children: [ - // Expanded(child: Text('')), - // Expanded( - // flex: 2, - // child: Divider( - // thickness: 3, - // )), - // ], - // ))); - - // dataRow.insert( - // dataRow.length, - // Container( - // padding: const EdgeInsets.only( - // left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - // child: Row( - // children: [ - // Expanded( - // flex: 2, - // child: Center( - // child: LocalText( - // context, - // 'invoice.net_amount', - // color: Colors.black, - // fontSize: 15, - // fontWeight: FontWeight.bold, - // ), - // ), - // ), - // Expanded( - // child: Text( - // '\$ ${invoice.getNetAmount(rate).toStringAsFixed(2)}', - // textAlign: TextAlign.end, - // style: TextStyle( - // fontSize: 18, - // fontWeight: FontWeight.bold, - // color: primaryColor))) - // ], - // ), - // )); - - // dataRow.insert( - // dataRow.length, - // Container( - // padding: const EdgeInsets.only( - // left: 5.0, right: 5.0, top: 10.0, bottom: 10.0), - // child: Row( - // children: [ - // Expanded( - // flex: 2, - // child: Center( - // child: LocalText( - // context, - // 'invoice.balance', - // color: Colors.black, - // fontSize: 15, - // fontWeight: FontWeight.bold, - // ), - // ), - // ), - // Expanded( - // child: Text( - // '\$ ${invoice.getTotalBalance(rate).toStringAsFixed(2)}', - // textAlign: TextAlign.end, - // style: TextStyle( - // fontSize: 18, - // fontWeight: FontWeight.bold, - // color: primaryColor))) - // ], - // ), - // )); - return dataRow; } } diff --git a/lib/pages/invoice/model/invoice_model.dart b/lib/pages/invoice/model/invoice_model.dart index 2a6b007..f8f7758 100644 --- a/lib/pages/invoice/model/invoice_model.dart +++ b/lib/pages/invoice/model/invoice_model.dart @@ -1,18 +1,16 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:fcs/config.dart'; import 'package:fcs/data/services/services.dart'; import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/invoice.dart'; -import 'package:fcs/helpers/api_helper.dart'; +import 'package:fcs/domain/entities/payment.dart'; import 'package:fcs/helpers/firebase_helper.dart'; import 'package:fcs/helpers/paginator.dart'; import 'package:fcs/pages/main/model/base_model.dart'; import 'package:logging/logging.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as Path; class InvoiceModel extends BaseModel { final log = Logger('InvoiceModel'); @@ -135,11 +133,33 @@ class InvoiceModel extends BaseModel { _invoices = []; } + Future getInvoice(String id) async { + String path = "/$invoices_collection"; + try { + var ref = Firestore.instance.collection("$path").document(id); + var snap = await ref.get(source: Source.server); + if (snap.exists) { + var s = Invoice.fromMap(snap.data, snap.documentID); + return s; + } + } catch (e) { + log.warning("Error!! $e"); + } + return null; + } + + Future pay(Payment payment, File file) async { + String path = Path.join(receipt_labels_files_path, user.id); + String url = await uploadStorage(path, file); + payment.paymentReceiptURL = url; + return Services.instance.invoiceService.pay(payment); + } + + Future updatePaymentStatus(Payment payment) async { + return Services.instance.invoiceService.updatPaymentStatus(payment); + } + Future createInvoice(Invoice invoice) async { - File file = await downloadPDF("invoice", invoice.toMap()); - String url = await uploadStorage("pdfs", file); - print("uploaded url: $url"); - invoice.invoiceURL = url; return Services.instance.invoiceService.createInvoice(invoice); } diff --git a/lib/pages/invoice/payment/payment_page.dart b/lib/pages/invoice/payment/payment_page.dart new file mode 100644 index 0000000..4a85022 --- /dev/null +++ b/lib/pages/invoice/payment/payment_page.dart @@ -0,0 +1,319 @@ +import 'dart:io'; + +import 'package:fcs/domain/constants.dart'; +import 'package:fcs/domain/entities/invoice.dart'; +import 'package:fcs/domain/entities/payment.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/localization/app_translations.dart'; +import 'package:fcs/pages/invoice/model/invoice_model.dart'; +import 'package:fcs/pages/invoice/payment/payment_page_edit.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/widgets/image_file_picker.dart'; +import 'package:fcs/pages/widgets/img_picker.dart'; +import 'package:fcs/pages/widgets/input_text.dart'; +import 'package:fcs/pages/widgets/local_button.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:fcs/pages/widgets/local_title.dart'; +import 'package:fcs/pages/widgets/progress.dart'; +import 'package:fcs/pages/widgets/show_img.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class PaymentPage extends StatefulWidget { + final Invoice invoice; + PaymentPage({this.invoice}); + + @override + _PaymentPageState createState() => _PaymentPageState(); +} + +class _PaymentPageState extends State { + TextEditingController _amountController = new TextEditingController(); + var dateFormatter = new DateFormat('dd MMM yyyy'); + + Invoice _invoice = new Invoice(); + bool _isLoading = false; + + bool isNew; + File _file; + + @override + void initState() { + super.initState(); + _invoice = widget.invoice; + _loadInvoice(); + } + + _loadInvoice() async { + InvoiceModel invoiceModel = + Provider.of(context, listen: false); + Invoice i = await invoiceModel.getInvoice(_invoice.id); + setState(() { + _invoice = i; + }); + } + + @override + void dispose() { + super.dispose(); + } + + final DateFormat dateFormat = DateFormat("d MMM yyyy"); + + @override + Widget build(BuildContext context) { + final amountBox = InputText( + labelTextKey: 'pm.amount', + controller: _amountController, + iconData: FontAwesomeIcons.moneyBill); + + final receiptFileBox = Row(children: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Text("Attach receipt"), + ), + LocalImagePicker( + color: primaryColor, + title: "Receipt", + onFile: (f) => _file = f, + ) + ]); + + final payBtnBox = LocalButton( + callBack: _pay, + textKey: "pm.pay", + ); + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(CupertinoIcons.back), + onPressed: () => Navigator.of(context).pop(), + ), + backgroundColor: primaryColor, + title: Text(AppTranslations.of(context).text("pm_.title")), + ), + body: ListView( + padding: const EdgeInsets.all(10.0), + children: [ + amountBox, + SizedBox(height: 10), + Align( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: receiptFileBox, + ), + alignment: Alignment.centerLeft, + ), + payBtnBox, + SizedBox(height: 15), + LocalTitle(textKey: "pm.receipt"), + Column( + children: getCustomFeeRows(context), + ), + SizedBox(height: 25), + ], + ), + ), + ); + } + + getCustomFeeRows(BuildContext context) { + List dataRow = []; + + dataRow = _invoice?.payments?.map((p) { + return Container( + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.grey))), + padding: const EdgeInsets.only( + left: 5.0, right: 5.0, top: 5.0, bottom: 5.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + '${p.paymentDate != null ? dateFormatter.format(p.paymentDate) : ""}'), + ), + SizedBox( + height: 5, + ), + LocalImagePicker( + key: ValueKey(p.id), + enabled: false, + initialImgUrl: p.paymentReceiptURL, + title: "Receipt", + color: primaryColor, + ) + ], + )), + Expanded( + flex: 1, + child: Center( + child: Column( + children: [Text('\$ ${p.amount}'), Text('${p.status}')], + ))), + Expanded( + flex: 1, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: p.status == payment_pending_status + ? [ + InkWell( + onTap: () => _confirm(p), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.check, + color: primaryColor, + ), + ), + ), + InkWell( + onTap: () => _cancel(p), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon(Icons.close, color: primaryColor), + ), + ), + ] + : [], + )), + ], + ), + ); + })?.toList() ?? + []; + + dataRow.insert( + 0, + Container( + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.grey))), + padding: const EdgeInsets.only( + left: 5.0, right: 5.0, top: 10.0, bottom: 15.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: Text('Receipt', style: TextStyle(color: Colors.grey))), + Expanded( + flex: 1, + child: Text('Amount', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey))), + Expanded( + flex: 1, + child: Text('Actions', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey))), + ], + ), + )); + + dataRow.insert( + dataRow.length, + Container( + padding: const EdgeInsets.only( + left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: Center( + child: LocalText( + context, + 'pm.remaining_balance', + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + flex: 1, + child: Center( + child: Text( + '\$ ${widget.invoice.balance.toStringAsFixed(2)}', + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 16.0)))), + Expanded( + flex: 1, + child: Container(), + ), + ], + ), + )); + return dataRow; + } + + _confirm(Payment payment) { + payment.status = payment_confirmed_status; + showConfirmDialog(context, "invoice.payment.confirm.confirm", + () => _updatePayment(payment)); + } + + _cancel(Payment payment) { + payment.status = payment_canceled_status; + showConfirmDialog(context, "invoice.payment.cancel.confirm", + () => _updatePayment(payment)); + } + + _updatePayment(Payment payment) async { + payment.invoiceID = widget.invoice.id; + setState(() { + _isLoading = true; + }); + try { + InvoiceModel invoiceModel = + Provider.of(context, listen: false); + await invoiceModel.updatePaymentStatus(payment); + Navigator.pop(context, true); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } + + _pay() async { + if (_file == null) { + showMsgDialog(context, "Error", "Expect receipt attachment"); + return; + } + double amount = double.tryParse(_amountController.text) ?? 0; + if (amount <= 0) { + showMsgDialog(context, "Error", "Expect valid amount"); + return; + } + Payment payment = Payment(invoiceID: _invoice.id, amount: amount); + setState(() { + _isLoading = true; + }); + try { + InvoiceModel invoiceModel = + Provider.of(context, listen: false); + await invoiceModel.pay(payment, _file); + Navigator.pop(context, true); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/pages/invoice/payment_page_edit.dart b/lib/pages/invoice/payment/payment_page_edit.dart similarity index 100% rename from lib/pages/invoice/payment_page_edit.dart rename to lib/pages/invoice/payment/payment_page_edit.dart diff --git a/lib/pages/invoice/payment_page.dart b/lib/pages/invoice/payment_page.dart deleted file mode 100644 index 52ff087..0000000 --- a/lib/pages/invoice/payment_page.dart +++ /dev/null @@ -1,301 +0,0 @@ -import 'dart:io'; - -import 'package:fcs/domain/entities/invoice.dart'; -import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/localization/app_translations.dart'; -import 'package:fcs/pages/invoice/payment_page_edit.dart'; -import 'package:fcs/pages/widgets/image_file_picker.dart'; -import 'package:fcs/pages/widgets/input_text.dart'; -import 'package:fcs/pages/widgets/local_text.dart'; -import 'package:fcs/pages/widgets/local_title.dart'; -import 'package:fcs/pages/widgets/progress.dart'; -import 'package:fcs/pages/widgets/show_img.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:intl/intl.dart'; - -class PaymentPage extends StatefulWidget { - final Invoice invoice; - PaymentPage({this.invoice}); - - @override - _PaymentPageState createState() => _PaymentPageState(); -} - -class _PaymentPageState extends State { - TextEditingController _amountController = new TextEditingController(); - var dateFormatter = new DateFormat('dd MMM yyyy'); - - Invoice _invoice = new Invoice(); - bool _isLoading = false; - - bool isNew; - File _file; - - @override - void initState() { - if (widget.invoice != null) { - _invoice = widget.invoice; - } - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - - final DateFormat dateFormat = DateFormat("d MMM yyyy"); - - @override - Widget build(BuildContext context) { - final amountBox = InputText( - labelTextKey: 'pm.amount', - controller: _amountController, - iconData: FontAwesomeIcons.moneyBill); - - final receiptFileBox = Row(children: [ - LocalText(context, 'pm.attachment', fontSize: 16, color: Colors.grey), - _file != null - ? InkWell( - onTap: () async { - await _dialog(context); - }, - child: Chip( - label: Icon(Icons.image, color: primaryColor), - ), - ) - : IconButton( - icon: Icon(Icons.attachment, color: primaryColor), - onPressed: () async { - await _dialog(context); - }), - ]); - - final payBox = Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - height: 50, - width: 100, - alignment: Alignment.center, - child: Text(AppTranslations.of(context).text("pm.pay"), - textAlign: TextAlign.center, - style: TextStyle(color: Colors.white, fontSize: 14)), - color: primaryColor) - ]); - - return LocalProgress( - inAsyncCall: _isLoading, - child: Scaffold( - appBar: AppBar( - centerTitle: true, - leading: new IconButton( - icon: new Icon(CupertinoIcons.back), - onPressed: () => Navigator.of(context).pop(), - ), - backgroundColor: primaryColor, - title: Text(AppTranslations.of(context).text("pm_.title")), - ), - body: ListView( - padding: const EdgeInsets.all(10.0), - children: [ - amountBox, - SizedBox(height: 10), - receiptFileBox, - SizedBox(height: 10), - payBox, - Divider(), - SizedBox(height: 10), - LocalTitle(textKey: "pm.receipt"), - Column( - children: getCustomFeeRows(context), - ), - SizedBox(height: 25), - ], - ), - ), - ); - } - - getCustomFeeRows(BuildContext context) { - List dataRow = []; - - dataRow = [].asMap().entries.map((receipt) { - var r = receipt.value; - var k = receipt.key + 1; - return Container( - height: 50, - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - padding: - const EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0, bottom: 5.0), - child: InkWell( - onTap: () { - Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => PaymentPageEdit(receipt: r))); - }, - child: Row( - children: [ - Expanded(flex: 2, child: Text('${r.date}')), - Expanded( - flex: 2, - child: InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ShowImage( - // localImage: attachment.value.fileUrl, - localImage: 'assets/logo.jpg', - url: null, - fileName: 'image'))); - }, - child: Chip( - label: Icon(Icons.image, color: primaryColor), - ), - ), - ), - Expanded(flex: 1, child: Center(child: Text('pending'))), - Expanded(flex: 1, child: Center(child: Text('\$ ${r.amount}'))), - ], - ), - ), - ); - }).toList(); - - dataRow.insert( - 0, - Container( - decoration: BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.grey))), - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 10.0, bottom: 15.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Text('Date', style: TextStyle(color: Colors.grey))), - Expanded( - flex: 2, - child: Text('File', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey))), - Expanded( - flex: 1, - child: Text('Status', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey))), - Expanded( - flex: 1, - child: Text('Fee', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey))), - ], - ), - )); - - dataRow.insert( - dataRow.length, - Container( - padding: const EdgeInsets.only( - left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), - child: Row( - children: [ - Expanded( - flex: 2, - child: Center( - child: LocalText( - context, - 'pm.remaining_balance', - color: Colors.black, - fontWeight: FontWeight.bold, - ), - ), - ), - Expanded( - flex: 2, - child: Container(), - ), - Expanded( - flex: 1, - child: Center( - child: Text('\$ 300', - textAlign: TextAlign.center, - style: TextStyle( - fontWeight: FontWeight.bold, fontSize: 16.0)))), - ], - ), - )); - return dataRow; - } - - Future _dialog(BuildContext context) { - return showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - content: Container( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: Icon( - FontAwesomeIcons.camera, - size: 30, - color: primaryColor, - ), - onPressed: () async { - // Navigator.pop(context); - // cameraPress(); - var selectedFile = - await pickImage(ImageSource.camera); - setState(() { - _file = selectedFile; - }); - Navigator.pop(context); - }), - Text("Camera") - ], - ), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: Icon( - Icons.photo_library, - size: 30, - color: primaryColor, - ), - onPressed: () async { - // Navigator.pop(context); - // photoPress(); - var selectedFile = - await pickImage(ImageSource.gallery); - setState(() { - _file = selectedFile; - }); - Navigator.pop(context); - }), - Text("Gallery") - ], - ), - ], - ), - ), - ), - ); - }, - ); - } - - pickImage(ImageSource source) async { - var tempImage = await ImagePicker.pickImage(source: source); - return tempImage; - } -} diff --git a/lib/pages/package/model/package_model.dart b/lib/pages/package/model/package_model.dart index f1608d6..efa1134 100644 --- a/lib/pages/package/model/package_model.dart +++ b/lib/pages/package/model/package_model.dart @@ -7,7 +7,6 @@ import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/domain/vo/delivery_address.dart'; -import 'package:fcs/domain/vo/message.dart'; import 'package:fcs/helpers/firebase_helper.dart'; import 'package:fcs/helpers/paginator.dart'; import 'package:fcs/pages/main/model/base_model.dart'; diff --git a/lib/pages/receiving/receiving_editor.dart b/lib/pages/receiving/receiving_editor.dart index f8c59d3..e0dd8ad 100644 --- a/lib/pages/receiving/receiving_editor.dart +++ b/lib/pages/receiving/receiving_editor.dart @@ -1,4 +1,3 @@ -import 'package:barcode_scan/barcode_scan.dart'; import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/helpers/theme.dart'; @@ -137,7 +136,8 @@ class _ReceivingEditorState extends State { appBar: AppBar( centerTitle: true, leading: new IconButton( - icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), + icon: + new Icon(CupertinoIcons.back, color: primaryColor, size: 30), onPressed: () => Navigator.of(context).pop(), ), shadowColor: Colors.transparent, diff --git a/lib/pages/widgets/img_picker.dart b/lib/pages/widgets/img_picker.dart new file mode 100644 index 0000000..71fc9c2 --- /dev/null +++ b/lib/pages/widgets/img_picker.dart @@ -0,0 +1,162 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:image_picker/image_picker.dart'; + +import 'show_img.dart'; + +typedef OnFile = void Function(File); + +class LocalImagePicker extends StatefulWidget { + final Color color; + final String title; + final OnFile onFile; + final bool enabled; + final String initialImgUrl; + final ImageSource imageSource; + + const LocalImagePicker( + {Key key, + this.title, + this.onFile, + this.enabled = true, + this.initialImgUrl, + this.imageSource = ImageSource.gallery, + this.color}) + : super(key: key); + @override + _LocalImagePickerState createState() => _LocalImagePickerState(); +} + +class _LocalImagePickerState extends State { + String url; + File file; + + @override + void initState() { + super.initState(); + this.url = widget.initialImgUrl == null || widget.initialImgUrl == "" + ? null + : widget.initialImgUrl; + } + + @override + Widget build(BuildContext context) { + return Container( + height: 30, + child: this.file == null && this.url == null + ? IconButton( + padding: const EdgeInsets.all(3.0), + icon: Icon(Icons.attach_file, color: widget.color ?? Colors.blue), + onPressed: () async { + if (!widget.enabled) return; + bool camera = false, gallery = false; + await _dialog( + context, () => camera = true, () => gallery = true); + if (camera || gallery) { + var selectedFile = await ImagePicker.pickImage( + source: camera ? ImageSource.camera : ImageSource.gallery, + imageQuality: 80, + maxWidth: 1000); + if (selectedFile != null) { + setState(() { + this.file = selectedFile; + }); + if (widget.onFile != null) { + widget.onFile(selectedFile); + } + } + } + }, + ) + : InkWell( + onTap: () => { + Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => ShowImage( + imageFile: file, + url: file == null ? widget.initialImgUrl : null, + fileName: widget.title))), + }, + child: Padding( + padding: const EdgeInsets.only(left: 3.0), + child: Chip( + avatar: Icon( + Icons.image, + color: widget.color ?? Colors.blue, + ), + onDeleted: !widget.enabled + ? null + : () { + setState(() { + this.file = null; + this.url = null; + if (widget.onFile != null) { + widget.onFile(null); + } + }); + }, + deleteIcon: Icon( + Icons.close, + color: widget.color ?? Colors.blue, + ), + label: Text(widget.title), + ), + ), + ), + ); + } + + Future _dialog(BuildContext context, cameraPress(), photoPress()) { + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Container( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + FontAwesomeIcons.camera, + size: 30, + color: widget.color ?? Colors.blue, + ), + onPressed: () { + Navigator.pop(context); + cameraPress(); + }), + Text("Camera") + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + Icons.photo_library, + size: 30, + color: widget.color ?? Colors.blue, + ), + onPressed: () { + Navigator.pop(context); + photoPress(); + }), + Text("Gallery") + ], + ), + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/invoice/payment_pdf_screen.dart b/lib/pages/widgets/pdf_screen.dart similarity index 88% rename from lib/pages/invoice/payment_pdf_screen.dart rename to lib/pages/widgets/pdf_screen.dart index 7f6ddf9..e9a1397 100644 --- a/lib/pages/invoice/payment_pdf_screen.dart +++ b/lib/pages/widgets/pdf_screen.dart @@ -4,23 +4,22 @@ import 'dart:io'; import 'package:fcs/helpers/cache_mgr.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/main/util.dart'; -import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_pdfview/flutter_pdfview.dart'; import 'package:share/share.dart'; -class PaymentPDFScreen extends StatefulWidget { +class PDFScreen extends StatefulWidget { + final String title; final String url; - PaymentPDFScreen({Key key, this.url}) : super(key: key); + PDFScreen({Key key, this.url, this.title}) : super(key: key); - _PaymentPDFScreenState createState() => _PaymentPDFScreenState(); + _PDFScreenState createState() => _PDFScreenState(); } -class _PaymentPDFScreenState extends State - with WidgetsBindingObserver { +class _PDFScreenState extends State with WidgetsBindingObserver { final Completer _controller = Completer(); int pages = 0; @@ -59,8 +58,8 @@ class _PaymentPDFScreenState extends State centerTitle: true, backgroundColor: Colors.white, shadowColor: Colors.transparent, - title: LocalText(context, 'invoice.pdf', - color: Colors.white, fontSize: 20), + title: + Text(widget.title ?? "", style: TextStyle(color: primaryColor)), leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor), onPressed: () => Navigator.of(context).pop(), @@ -120,7 +119,7 @@ class _PaymentPDFScreenState extends State final RenderBox box = context.findRenderObject(); await Share.shareFiles([file.path], mimeTypes: ["application/pdf"], - subject: "Invoice", + subject: "File", sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size); } } diff --git a/lib/pages/widgets/show_img.dart b/lib/pages/widgets/show_img.dart index a107dd6..130cdad 100644 --- a/lib/pages/widgets/show_img.dart +++ b/lib/pages/widgets/show_img.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:fcs/helpers/theme.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; @@ -21,12 +22,19 @@ class _ShowImageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + leading: new IconButton( + icon: new Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), backgroundColor: primaryColor, - title: Text(widget.fileName, style: TextStyle(color: Colors.white)), + shadowColor: Colors.transparent, iconTheme: new IconThemeData(color: Colors.white), ), body: Center( child: PhotoView( + backgroundDecoration: const BoxDecoration( + color: primaryColor, + ), imageProvider: widget.url != null ? NetworkImage(widget.url) : widget.imageFile != null From c89d10db75298ca13c518c155e104eb2cb948534 Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Wed, 28 Oct 2020 06:01:36 +0630 Subject: [PATCH 05/10] fix rate cal --- lib/pages/rates/shipment_rates_calculate.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/rates/shipment_rates_calculate.dart b/lib/pages/rates/shipment_rates_calculate.dart index 3ad1f62..7b7b3e4 100644 --- a/lib/pages/rates/shipment_rates_calculate.dart +++ b/lib/pages/rates/shipment_rates_calculate.dart @@ -125,6 +125,7 @@ class _ShipmentRatesCalState extends State { callback: (v) { setState(() { _cargoType = v; + _calShipmentWeight(); }); }, labelKey: "cargo.type", From 9f58e7f74899eced9e64f5906915eddf603780c1 Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Wed, 28 Oct 2020 06:12:30 +0630 Subject: [PATCH 06/10] fix invoice cancel --- lib/pages/invoice/invoice_info.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/pages/invoice/invoice_info.dart b/lib/pages/invoice/invoice_info.dart index 66acd78..838a6aa 100644 --- a/lib/pages/invoice/invoice_info.dart +++ b/lib/pages/invoice/invoice_info.dart @@ -141,10 +141,6 @@ class _InvoiceInfoState extends State { text: _invoice.paymentMethod.name, ); - final receiptsBtn = LocalButton( - textKey: "invoice.btn_payment_receipt", - callBack: _cancel, - ); final cancelBtn = LocalButton( textKey: "invoice.cancel.btn", callBack: _cancel, @@ -185,7 +181,6 @@ class _InvoiceInfoState extends State { SizedBox( height: 10, ), - isCanceled ? Container() : receiptsBtn, isCanceled ? Container() : cancelBtn, ], ), From bd63e1e1618c4a9c70be232325615284d48205f3 Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Wed, 28 Oct 2020 06:23:04 +0630 Subject: [PATCH 07/10] fix invoice display --- lib/pages/invoice/invoice_list.dart | 6 +- lib/pages/invoice/invoice_list_row.dart | 8 +- lib/pages/invoice/payment/payment_page.dart | 82 +++++++++++---------- lib/pages/shipment/shipment_info.dart | 4 +- 4 files changed, 54 insertions(+), 46 deletions(-) diff --git a/lib/pages/invoice/invoice_list.dart b/lib/pages/invoice/invoice_list.dart index 567c21d..6222180 100644 --- a/lib/pages/invoice/invoice_list.dart +++ b/lib/pages/invoice/invoice_list.dart @@ -120,8 +120,10 @@ class _InvoiceListState extends State { itemCount: invoiceModel.invoices.length, itemBuilder: (BuildContext context, int index) { return InvoiceListRow( - key: ValueKey(invoiceModel.invoices[index].id), - invoice: invoiceModel.invoices[index]); + key: ValueKey(invoiceModel.invoices[index].id), + invoice: invoiceModel.invoices[index], + forCustomer: widget.forCustomer, + ); }), onRefresh: () => invoiceModel.refresh(), ), diff --git a/lib/pages/invoice/invoice_list_row.dart b/lib/pages/invoice/invoice_list_row.dart index a06d268..69d527b 100644 --- a/lib/pages/invoice/invoice_list_row.dart +++ b/lib/pages/invoice/invoice_list_row.dart @@ -13,7 +13,8 @@ import '../widgets/pdf_screen.dart'; class InvoiceListRow extends StatelessWidget { final dateFormatter = new DateFormat('dd MMM yyyy'); final Invoice invoice; - InvoiceListRow({Key key, this.invoice}) : super(key: key); + final bool forCustomer; + InvoiceListRow({Key key, this.invoice, this.forCustomer}) : super(key: key); @override Widget build(BuildContext context) { @@ -91,7 +92,10 @@ class InvoiceListRow extends StatelessWidget { ), onPressed: () { Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => PaymentPage(invoice: invoice))); + builder: (context) => PaymentPage( + invoice: invoice, + forCustomer: forCustomer, + ))); }, )), ) diff --git a/lib/pages/invoice/payment/payment_page.dart b/lib/pages/invoice/payment/payment_page.dart index 4a85022..a98e1ec 100644 --- a/lib/pages/invoice/payment/payment_page.dart +++ b/lib/pages/invoice/payment/payment_page.dart @@ -6,26 +6,23 @@ import 'package:fcs/domain/entities/payment.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/localization/app_translations.dart'; import 'package:fcs/pages/invoice/model/invoice_model.dart'; -import 'package:fcs/pages/invoice/payment/payment_page_edit.dart'; import 'package:fcs/pages/main/util.dart'; -import 'package:fcs/pages/widgets/image_file_picker.dart'; import 'package:fcs/pages/widgets/img_picker.dart'; import 'package:fcs/pages/widgets/input_text.dart'; import 'package:fcs/pages/widgets/local_button.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_title.dart'; import 'package:fcs/pages/widgets/progress.dart'; -import 'package:fcs/pages/widgets/show_img.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; class PaymentPage extends StatefulWidget { final Invoice invoice; - PaymentPage({this.invoice}); + final bool forCustomer; + PaymentPage({this.invoice, this.forCustomer}); @override _PaymentPageState createState() => _PaymentPageState(); @@ -164,32 +161,35 @@ class _PaymentPageState extends State { child: Column( children: [Text('\$ ${p.amount}'), Text('${p.status}')], ))), - Expanded( - flex: 1, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: p.status == payment_pending_status - ? [ - InkWell( - onTap: () => _confirm(p), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - Icons.check, - color: primaryColor, + widget.forCustomer + ? Container() + : Expanded( + flex: 1, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: p.status == payment_pending_status + ? [ + InkWell( + onTap: () => _confirm(p), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.check, + color: primaryColor, + ), + ), ), - ), - ), - InkWell( - onTap: () => _cancel(p), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Icon(Icons.close, color: primaryColor), - ), - ), - ] - : [], - )), + InkWell( + onTap: () => _cancel(p), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon(Icons.close, + color: primaryColor), + ), + ), + ] + : [], + )), ], ), ); @@ -213,11 +213,13 @@ class _PaymentPageState extends State { child: Text('Amount', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey))), - Expanded( - flex: 1, - child: Text('Actions', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey))), + widget.forCustomer + ? Container() + : Expanded( + flex: 1, + child: Text('Actions', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey))), ], ), )); @@ -248,10 +250,12 @@ class _PaymentPageState extends State { textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16.0)))), - Expanded( - flex: 1, - child: Container(), - ), + widget.forCustomer + ? Container() + : Expanded( + flex: 1, + child: Container(), + ), ], ), )); diff --git a/lib/pages/shipment/shipment_info.dart b/lib/pages/shipment/shipment_info.dart index d9075c3..0b011bc 100644 --- a/lib/pages/shipment/shipment_info.dart +++ b/lib/pages/shipment/shipment_info.dart @@ -311,9 +311,7 @@ class _ShipmentInfoState extends State { children: getBoxList(context, _shipment.boxes), ), !_isCustomer ? fcsShipmentNumberBox : Container(), - ...(_shipment.isAssigned || - _shipment.isConfirmed || - _shipment.isPending + ...(!_shipment.isPending ? [ handlingFeeBox, ] From fca5b2e8bf729974c1087c5a4379bc8a844cc34c Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Wed, 28 Oct 2020 06:35:47 +0630 Subject: [PATCH 08/10] disable customer cancel invoice --- lib/pages/invoice/invoice_info.dart | 5 +++-- lib/pages/invoice/invoice_list_row.dart | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pages/invoice/invoice_info.dart b/lib/pages/invoice/invoice_info.dart index 838a6aa..a495df5 100644 --- a/lib/pages/invoice/invoice_info.dart +++ b/lib/pages/invoice/invoice_info.dart @@ -21,7 +21,8 @@ import 'package:provider/provider.dart'; class InvoiceInfo extends StatefulWidget { final Invoice invoice; - InvoiceInfo({this.invoice}); + final bool forCustomer; + InvoiceInfo({this.invoice, this.forCustomer}); @override _InvoiceInfoState createState() => _InvoiceInfoState(); @@ -181,7 +182,7 @@ class _InvoiceInfoState extends State { SizedBox( height: 10, ), - isCanceled ? Container() : cancelBtn, + isCanceled || widget.forCustomer ? Container() : cancelBtn, ], ), ), diff --git a/lib/pages/invoice/invoice_list_row.dart b/lib/pages/invoice/invoice_list_row.dart index 69d527b..b369960 100644 --- a/lib/pages/invoice/invoice_list_row.dart +++ b/lib/pages/invoice/invoice_list_row.dart @@ -138,7 +138,8 @@ class InvoiceListRow extends StatelessWidget { //to go invoice info page Navigator.pop(context); Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => InvoiceInfo(invoice: invoice))); + builder: (context) => + InvoiceInfo(invoice: invoice, forCustomer: forCustomer))); }, ) ], From 703a633108b8b32a652eb72dc79cb715a10053fd Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Wed, 28 Oct 2020 06:58:45 +0630 Subject: [PATCH 09/10] disable cancel invoice in customer --- lib/pages/invoice/invoice_info.dart | 5 ++++- lib/pages/invoice/invoice_list_row.dart | 22 +++++++++++++++++++++ lib/pages/invoice/payment/payment_page.dart | 22 ++++++++++++--------- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/pages/invoice/invoice_info.dart b/lib/pages/invoice/invoice_info.dart index a495df5..8681798 100644 --- a/lib/pages/invoice/invoice_info.dart +++ b/lib/pages/invoice/invoice_info.dart @@ -66,6 +66,7 @@ class _InvoiceInfoState extends State { @override Widget build(BuildContext context) { bool isCanceled = _invoice.status == invoice_cancel_status; + bool isPaid = _invoice.status == invoice_paid_status; var rateModel = Provider.of(context); var rate = rateModel.rate; @@ -182,7 +183,9 @@ class _InvoiceInfoState extends State { SizedBox( height: 10, ), - isCanceled || widget.forCustomer ? Container() : cancelBtn, + isCanceled || isPaid || widget.forCustomer + ? Container() + : cancelBtn, ], ), ), diff --git a/lib/pages/invoice/invoice_list_row.dart b/lib/pages/invoice/invoice_list_row.dart index b369960..bc68360 100644 --- a/lib/pages/invoice/invoice_list_row.dart +++ b/lib/pages/invoice/invoice_list_row.dart @@ -141,6 +141,28 @@ class InvoiceListRow extends StatelessWidget { builder: (context) => InvoiceInfo(invoice: invoice, forCustomer: forCustomer))); }, + ), + CupertinoActionSheetAction( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + 'Payments', + style: TextStyle(fontSize: 16, color: Colors.black), + ), + ), + ], + ), + onPressed: () async { + //to go invoice info page + Navigator.pop(context); + Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => PaymentPage( + invoice: invoice, + forCustomer: forCustomer, + ))); + }, ) ], ); diff --git a/lib/pages/invoice/payment/payment_page.dart b/lib/pages/invoice/payment/payment_page.dart index a98e1ec..680bec1 100644 --- a/lib/pages/invoice/payment/payment_page.dart +++ b/lib/pages/invoice/payment/payment_page.dart @@ -37,11 +37,13 @@ class _PaymentPageState extends State { bool isNew; File _file; + bool _hasBalance; @override void initState() { super.initState(); _invoice = widget.invoice; + _hasBalance = widget.invoice.balance > 0; _loadInvoice(); } @@ -100,16 +102,18 @@ class _PaymentPageState extends State { body: ListView( padding: const EdgeInsets.all(10.0), children: [ - amountBox, + _hasBalance ? amountBox : Container(), SizedBox(height: 10), - Align( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: receiptFileBox, - ), - alignment: Alignment.centerLeft, - ), - payBtnBox, + _hasBalance + ? Align( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: receiptFileBox, + ), + alignment: Alignment.centerLeft, + ) + : Container(), + _hasBalance ? payBtnBox : Container(), SizedBox(height: 15), LocalTitle(textKey: "pm.receipt"), Column( From 84e8053040d327607912c456198ee9798008695d Mon Sep 17 00:00:00 2001 From: Sai Naw Wun Date: Wed, 28 Oct 2020 07:10:26 +0630 Subject: [PATCH 10/10] fix shipment list --- lib/pages/shipment/model/shipment_model.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pages/shipment/model/shipment_model.dart b/lib/pages/shipment/model/shipment_model.dart index 6c96da4..b78c4bb 100644 --- a/lib/pages/shipment/model/shipment_model.dart +++ b/lib/pages/shipment/model/shipment_model.dart @@ -32,10 +32,12 @@ class ShipmentModel extends BaseModel { initData(bool forCustomer, {bool myPickup = false}) { logout(); - _menuSelectedIndex = 1; _loadShipments(forCustomer, myPickup); _delivered = _getDelivered(forCustomer); - _delivered.load(); + _delivered.load(onFinished: () { + isLoading = false; + notifyListeners(); + }); } @override