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: