diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index 0ae209d..c60d571 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.issue.btn":"Issue 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", @@ -455,10 +458,21 @@ "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", + "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", + "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 2e9273c..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":"လက်မ", @@ -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,14 +439,16 @@ "invoice.payment_method":"ငွေပေးချေစနစ်", "invoice.delivery_fee":"ပို့ဆောင်ခ", "invoice.payment_attachment":"ပေးချေပြီးဖိုင်များ", - "invoice.box_info":"Box အချက်အလက်", + "invoice.box_info":"Cartons", "invoice.cargo_table":"ကုန်ပစ္စည်းဇယား", - "invoice.btn_create":"ငွေတောင်းခံလွှာ ပြုလုပ်ရန်", + "invoice.issue.btn":"ငွေတောင်းခံလွှာ ထုတ်မည်", + "invoice.btn_save":"ငွေတောင်းခံလွှာ သိမ်းရန်", "invoice.btn_payment_receipt":"ငွေလက်ခံဖြတ်ပိုင်း ထည့်ရန်", "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", @@ -455,10 +459,21 @@ "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", + "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", + "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 new file mode 100644 index 0000000..380f9e4 --- /dev/null +++ b/lib/data/provider/invoice_data_provider.dart @@ -0,0 +1,36 @@ +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'; + +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()); + } + + 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 new file mode 100644 index 0000000..c26dab4 --- /dev/null +++ b/lib/data/services/invoice_imp.dart @@ -0,0 +1,45 @@ +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/payment.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); + } + + @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 new file mode 100644 index 0000000..270fed7 --- /dev/null +++ b/lib/data/services/invoice_service.dart @@ -0,0 +1,10 @@ +import 'package:fcs/domain/entities/invoice.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/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..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"; @@ -91,3 +92,14 @@ 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"; + +// payment status +const payment_pending_status = "pending"; +const payment_confirmed_status = "confirmed"; +const payment_canceled_status = "canceled"; diff --git a/lib/domain/entities/cargo_type.dart b/lib/domain/entities/cargo_type.dart index d502d62..7247ce0 100644 --- a/lib/domain/entities/cargo_type.dart +++ b/lib/domain/entities/cargo_type.dart @@ -4,8 +4,7 @@ class CargoType { double rate; double weight; - int shipmentWeight; - double amount; + double get calAmount => (calRate ?? 0) * (calWeight ?? 0); double calRate; double calWeight; @@ -16,9 +15,17 @@ class CargoType { name: map['name'], 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}); + CargoType( + {this.id, + this.name, + this.rate, + this.weight, + this.calWeight, + this.calRate}); Map toMap() { return { @@ -26,6 +33,8 @@ class CargoType { 'name': name, '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 609ed70..cccac21 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, @@ -153,10 +171,9 @@ class Carton { 'length': length, 'width': width, 'height': height, - 'delivery_address': deliveryAddress.toMap(), + '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/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 8d63e51..b614fcc 100644 --- a/lib/domain/entities/invoice.dart +++ b/lib/domain/entities/invoice.dart @@ -1,79 +1,82 @@ +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'; 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.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 fcsID; + 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; + List payments; Discount discount; + PaymentMethod paymentMethod; + String invoiceURL; 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; + 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); + 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; + _cargoTypes.forEach((e) { double r = e.rate - (discountByWeight != null ? discountByWeight.discount : 0); - double amount = cargoWeight * r; e.calRate = r; - e.calWeight = cargoWeight; - 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 get balance => (amount ?? 0) - (paidAmount ?? 0); + 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(); @@ -81,6 +84,12 @@ class Invoice { return total; } + double getHandlingFee() { + return shipments?.where((sh) => sh.isSelected ?? false)?.fold(0, (p, s) { + return p + (s?.handlingFee ?? 0) - (s?.paidHandlingFee ?? 0); + }); + } + double getTotalBalance(Rate rate) { return getNetAmount(rate) - (paidAmount ?? 0); } @@ -89,10 +98,6 @@ 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; } @@ -103,35 +108,92 @@ class Invoice { {this.id, this.invoiceNumber, this.invoiceDate, - this.customerName, - this.customerPhoneNumber, + this.fcsID, + this.userName, + this.phoneNumber, this.amount, + this.paidAmount, this.discount, this.status, - this.paymentAttachment, - this.packages, - this.receiptPhotos, this.customDuties, this.cartons, + this.cargoTypes, this.handlingFee, - this.receipts}); - - double get getAmount => packages.fold(0, (p, e) => (e.rate * e.weight) + p); + this.deliveryFee, + this.fcsShipmentID, + this.shipments, + this.invoiceURL, + this.payments, + 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']); + 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'], - invoiceDate: map['invoice_date'], - customerName: map['customer_name'], - customerPhoneNumber: map['phone_number'], + invoiceDate: invd?.toDate(), + userName: map['user_name'], + fcsID: map['fcs_id'], + phoneNumber: map['phone_number'], amount: map['amount'], + paidAmount: double.tryParse(map['paid_amount'].toString()) ?? 0, status: map['status'], - discount: map['discount'], - paymentAttachment: map['payment_attachment'], - packages: map['packages'], - receiptPhotos: map['receiptPhotos'], - receipts: map['receipts'], + cartons: cartons, + cargoTypes: cargoTypes, + shipments: handingShipments, + customDuties: customDuties, + deliveryFee: map['delivery_fee'], + invoiceURL: map['invoice_url'], + paymentMethod: paymentMethod, + discount: discount, + payments: payments, ); } + + 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() ?? []; + List _shipments = shipments?.map((s) => s.toMap())?.toList() ?? []; + return { + "id": id, + "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/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/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..7b837df 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'], @@ -96,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, @@ -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/helpers/api_helper.dart b/lib/helpers/api_helper.dart index 0be6fdd..06fca69 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'); @@ -38,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"); @@ -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 331016b..a2746dc 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 @@ -183,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); @@ -217,12 +208,7 @@ class _CartonEditorState extends State { setState(() { _fcsShipment = v; }); - if (_selectedCartonType == carton_mix_box) { - _loadCartons(); - } - if (_selectedCartonType == carton_small_bag) { - _loadMixCartons(); - } + _loadMixCartons(); }, labelKey: "shipment.pack.fcs.shipment", iconData: Ionicons.ios_airplane, @@ -318,13 +304,9 @@ class _CartonEditorState extends State { values: boxModel.cartonTypes, selectedValue: _selectedCartonType, callback: (v) { - print(v); setState(() { _selectedCartonType = v; }); - if (_selectedCartonType == carton_mix_box) { - _loadCartons(); - } }); final cargoTableTitleBox = LocalTitle( @@ -507,9 +489,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/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/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/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/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/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/invoice_carton_table.dart b/lib/pages/invoice/editor/invoice_carton_table.dart similarity index 93% rename from lib/pages/invoice/invoice_carton_table.dart rename to lib/pages/invoice/editor/invoice_carton_table.dart index 4e94119..77f05ec 100644 --- a/lib/pages/invoice/invoice_carton_table.dart +++ b/lib/pages/invoice/editor/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/editor/invoice_discount_list.dart b/lib/pages/invoice/editor/invoice_discount_list.dart new file mode 100644 index 0000000..13ad897 --- /dev/null +++ b/lib/pages/invoice/editor/invoice_discount_list.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 InvoiceDiscountList extends StatelessWidget { + final List discounts; + + const InvoiceDiscountList({ + 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/editor/invoice_editor.dart b/lib/pages/invoice/editor/invoice_editor.dart new file mode 100644 index 0000000..783a4f2 --- /dev/null +++ b/lib/pages/invoice/editor/invoice_editor.dart @@ -0,0 +1,452 @@ +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/model/discount_model.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/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'; +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/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/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 InvoiceEditor extends StatefulWidget { + final Invoice invoice; + final User customer; + final FcsShipment fcsShipment; + InvoiceEditor({this.invoice, this.customer, this.fcsShipment}); + + @override + _InvoiceEditorState createState() => _InvoiceEditorState(); +} + +class _InvoiceEditorState extends State { + var dateFormatter = new DateFormat('dd MMM yyyy'); + + Invoice _invoice; + bool _isLoading = false; + bool _isNew; + User _user; + + bool _showCartons = false; + @override + void initState() { + super.initState(); + _isNew = widget.invoice == null; + + if (widget.invoice != null) { + _invoice = widget.invoice; + } else { + _invoice = Invoice( + customDuties: [], + cartons: [], + shipments: [], + invoiceDate: DateTime.now()); + } + _user = widget.customer; + _loadAll(); + } + + _loadAll() async { + setState(() { + _isLoading = true; + }); + try { + await _loadCartons(); + await _loadShipments(); + await _loadDiscount(); + } catch (e) {} finally { + setState(() { + _isLoading = false; + }); + } + } + + _loadCartons() async { + CartonModel cartonModel = Provider.of(context, listen: false); + List cartons = await cartonModel.getCartonsForInvoice( + widget.fcsShipment.id, widget.customer.id); + 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 + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var mainModel = Provider.of(context); + var paymentMethodModel = Provider.of(context); + 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, + onSelect: (c, checked) { + setState(() { + c.isChecked = checked; + }); + }, + ); + final paymentTypesBox = LocalDropdown( + callback: (v) { + setState(() { + _invoice.paymentMethod = v; + }); + }, + labelKey: "invoice.payment_method", + iconData: FontAwesome.money, + display: (u) => u.name, + selectedValue: _invoice.paymentMethod, + values: paymentMethodModel.paymentMethods, + ); + 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; + }); + }, + 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) => + InvoiceHandlingFeeList(shipments: _invoice.shipments))); + _addShipment(shipment); + } else if (p.id == 3) { + Discount discount = + await Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => InvoiceDiscountList( + discounts: discounts, + ))); + if (discount != null) { + setState(() { + _invoice.discount = discount; + }); + } + } else if (p.id == 4) { + setState(() { + _invoice.deliveryFee = rate.deliveryFee; + }); + } + }, + ); + + 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( + appBar: AppBar( + centerTitle: true, + leading: new IconButton( + icon: new Icon(CupertinoIcons.back, color: primaryColor), + onPressed: () => Navigator.of(context).pop(), + ), + backgroundColor: Colors.white, + shadowColor: Colors.transparent, + title: LocalText(context, 'invoice.form.title', + color: primaryColor, fontSize: 20), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + children: [ + headerBox, + _isNew ? Container() : invoiceNumberBox, + _isNew ? Container() : statusBox, + _showCartons ? cartonTable : Container(), + _showCartons + ? Divider( + color: primaryColor, + thickness: 2, + ) + : Container(), + invoiceTableBox, + SizedBox( + height: 10, + ), + paymentTypesBox, + SizedBox( + height: 10, + ), + _isNew + ? createBtn + : mainModel.isCustomer() + ? Container() + : Container( + child: Column( + children: [ + fcsButton(context, + getLocalString(context, 'invoice.btn_save')) + ], + )), + _isNew + ? Container() + : fcsButton(context, + getLocalString(context, 'invoice.btn_payment_receipt')) + ], + ), + ), + ), + ); + } + + _addCustom(CustomDuty customDuty) { + if (customDuty == null) return; + setState(() { + _invoice.customDuties.remove(customDuty); + _invoice.customDuties.add(customDuty); + }); + } + + _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); + }); + } + + _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; + invoice.customDuties = _invoice.customDuties; + + 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_custom_table.dart b/lib/pages/invoice/editor/invoice_handling_fee_list.dart similarity index 56% rename from lib/pages/invoice/invoice_custom_table.dart rename to lib/pages/invoice/editor/invoice_handling_fee_list.dart index cd80426..9e1af8b 100644 --- a/lib/pages/invoice/invoice_custom_table.dart +++ b/lib/pages/invoice/editor/invoice_handling_fee_list.dart @@ -1,38 +1,60 @@ -import 'package:fcs/domain/entities/custom_duty.dart'; +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(CustomDuty customDuty); -typedef OnRemove(CustomDuty customDuty); +typedef OnAdd(Shipment shipment); +typedef OnRemove(Shipment shipment); -class InvoiceCustomTable extends StatelessWidget { - final List customDuties; +class InvoiceHandlingFeeList extends StatelessWidget { + final List shipments; final OnAdd onAdd; final OnRemove onRemove; - const InvoiceCustomTable( - {Key key, this.customDuties, this.onAdd, this.onRemove}) + const InvoiceHandlingFeeList( + {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, - "rate.cutom.product_type", + "invoice.shipment.number", color: Colors.grey, ), ), MyDataColumn( label: LocalText( context, - "rate.custom.fee", + "invoice.add.handling.fee.menu", color: Colors.grey, ), ), @@ -42,23 +64,23 @@ class InvoiceCustomTable extends StatelessWidget { } List getRows(BuildContext context) { - if (customDuties == null) { + if (shipments == null) { return []; } - double total = 0; - var rows = customDuties.map((c) { - total += c.fee; + var rows = shipments.map((c) { return MyDataRow( + onSelectChanged: (value) => Navigator.pop(context, c), cells: [ MyDataCell(new Text( - c.productType == null ? "" : c.productType, + c.shipmentNumber ?? "", style: textStyle, )), MyDataCell( Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - Text(c.fee == null ? "0" : c.fee.toString(), style: textStyle), + Text(c.handlingFee?.toStringAsFixed(2) ?? "0", + style: textStyle), onRemove == null ? SizedBox( width: 50, @@ -78,30 +100,6 @@ class InvoiceCustomTable extends StatelessWidget { ); }).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_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_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_editor.dart b/lib/pages/invoice/invoice_editor.dart deleted file mode 100644 index 480bc15..0000000 --- a/lib/pages/invoice/invoice_editor.dart +++ /dev/null @@ -1,327 +0,0 @@ -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/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_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/widgets/display_text.dart'; -import 'package:fcs/pages/widgets/fcs_id_icon.dart'; -import 'package:fcs/pages/widgets/local_dropdown.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'; -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'; - -import 'invoice_custom_table.dart'; - -class InvoiceEditor extends StatefulWidget { - final Invoice invoice; - final User customer; - final FcsShipment fcsShipment; - InvoiceEditor({this.invoice, this.customer, this.fcsShipment}); - - @override - _InvoiceEditorState createState() => _InvoiceEditorState(); -} - -class _InvoiceEditorState extends State { - 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) - // ]; - - @override - void initState() { - super.initState(); - - volumetricRatio = Provider.of(context, listen: false) - .rate - .volumetricRatio; - - 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: []); - } - - if (widget.customer != null && widget.invoice == null) { - user = widget.customer; - setState(() { - _isNew = true; - }); - } - - _loadCartons(); - } - - _loadCartons() async { - CartonModel cartonModel = Provider.of(context, listen: false); - List cartons = await cartonModel.getCartonsForInvoice( - widget.fcsShipment.id, widget.customer.id); - setState(() { - _cartons = cartons; - }); - } - - @override - void dispose() { - super.dispose(); - } - - @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, - 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, - rate: rate, - onSelect: (c, checked) { - setState(() { - c.isChecked = checked; - }); - if (checked) { - _invoice.cartons.add(c); - } else { - _invoice.cartons.remove(c); - } - }, - ); - 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), - ); - var paymentTypesBox = LocalDropdown( - callback: (v) { - setState(() { - _paymentMethod = v; - }); - }, - labelKey: "invoice.payment_method", - iconData: FontAwesome.money, - display: (u) => u.name, - selectedValue: _paymentMethod, - values: paymentMethodModel.paymentMethods, - ); - final cargoTypeTableBox = InvoiceCargoTable( - invoice: _invoice, - rate: rate, - deliveryFeeSelected: (selected) { - setState(() { - if (selected) { - _invoice.deliveryFee = rate.deliveryFee; - } else { - _invoice.deliveryFee = 0; - } - }); - }, - discountSelected: (discount) { - setState(() { - _invoice.discount = discount; - }); - }, - ); - - return LocalProgress( - inAsyncCall: _isLoading, - child: Scaffold( - appBar: AppBar( - centerTitle: true, - leading: new IconButton( - icon: new Icon(CupertinoIcons.back, color: primaryColor), - onPressed: () => Navigator.of(context).pop(), - ), - backgroundColor: Colors.white, - shadowColor: Colors.transparent, - title: LocalText(context, 'invoice.form.title', - color: primaryColor, fontSize: 20), - ), - 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, - _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), - paymentTypesBox, - SizedBox(height: 20), - _isNew - ? Container() - : LocalTitle( - textKey: "invoice.payment_attachment", - trailing: IconButton( - icon: Icon(Icons.add_circle, color: primaryColor), - onPressed: () async {})), - widget.invoice == null - ? fcsButton( - context, getLocalString(context, 'invoice.btn_create')) - : mainModel.isCustomer() - ? Container() - : Container( - child: Column( - children: [ - fcsButton(context, - getLocalString(context, 'invoice.btn_save')) - ], - )), - _isNew - ? Container() - : fcsButton(context, - getLocalString(context, 'invoice.btn_payment_receipt')) - ], - ), - ), - ), - ); - } - - 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(() { - _invoice.customDuties.remove(customDuty); - _invoice.customDuties.add(customDuty); - }); - } - - _removeCustom(CustomDuty customDuty) { - setState(() { - _invoice.customDuties.remove(customDuty); - }); - } - - _save() {} -} diff --git a/lib/pages/invoice/invoice_info.dart b/lib/pages/invoice/invoice_info.dart index 7a6a9a0..8681798 100644 --- a/lib/pages/invoice/invoice_info.dart +++ b/lib/pages/invoice/invoice_info.dart @@ -1,172 +1,61 @@ +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}); + final bool forCustomer; + InvoiceInfo({this.invoice, this.forCustomer}); @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.customerName; - _phoneController.text = _invoice.customerPhoneNumber; - // _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()); - _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 @@ -176,21 +65,88 @@ 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; + bool isPaid = _invoice.status == invoice_paid_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 cancelBtn = LocalButton( + textKey: "invoice.cancel.btn", + callBack: _cancel, + ); return LocalProgress( inAsyncCall: _isLoading, @@ -205,71 +161,31 @@ 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 || isPaid || widget.forCustomer + ? Container() + : cancelBtn, ], ), ), @@ -277,528 +193,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 75% rename from lib/pages/invoice/invoce_list.dart rename to lib/pages/invoice/invoice_list.dart index 2406a8d..6222180 100644 --- a/lib/pages/invoice/invoce_list.dart +++ b/lib/pages/invoice/invoice_list.dart @@ -21,7 +21,6 @@ class InvoiceList extends StatefulWidget { class _InvoiceListState extends State { bool _isLoading = false; - bool _showPaid = false; var _controller = ScrollController(); @override @@ -34,7 +33,9 @@ class _InvoiceListState extends State { } }); - Provider.of(context, listen: false).initData(false); + InvoiceModel invoiceModel = + Provider.of(context, listen: false); + invoiceModel.initData(widget.forCustomer, true, false); } @override @@ -44,22 +45,35 @@ class _InvoiceListState extends State { @override Widget build(BuildContext context) { - var owner = true; var invoiceModel = Provider.of(context); final popupMenu = LocalPopupMenuButton( 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); + } }), ); @@ -79,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(); }, @@ -88,8 +103,7 @@ class _InvoiceListState extends State { label: LocalText(context, 'invoices.add', color: Colors.white), backgroundColor: primaryColor, - ) - : null, + ), body: Column( children: [ Expanded( @@ -99,15 +113,17 @@ 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) { 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(), ), @@ -131,7 +147,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..bc68360 100644 --- a/lib/pages/invoice/invoice_list_row.dart +++ b/lib/pages/invoice/invoice_list_row.dart @@ -1,138 +1,76 @@ -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'; 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'; +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; - } + final bool forCustomer; + InvoiceListRow({Key key, this.invoice, this.forCustomer}) : super(key: key); @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( - path: pdfPath, - ))) - : Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => PaymentPDFScreen( - path: pdfPath, - ))); - }, - 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 == "Pending" + invoice.status == invoice_issued_status ? Padding( padding: const EdgeInsets.only(left: 10.0), child: InkWell( @@ -144,27 +82,32 @@ 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, + forCustomer: forCustomer, + ))); }, )), ) : 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); @@ -191,10 +134,34 @@ 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, 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/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 new file mode 100644 index 0000000..b25c222 --- /dev/null +++ b/lib/pages/invoice/invoice_table.dart @@ -0,0 +1,230 @@ +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/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.calAmount.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, + ) + ], + ), + )); + + return dataRow; + } +} diff --git a/lib/pages/invoice/model/invoice_model.dart b/lib/pages/invoice/model/invoice_model.dart index fa74b18..f8f7758 100644 --- a/lib/pages/invoice/model/invoice_model.dart +++ b/lib/pages/invoice/model/invoice_model.dart @@ -1,60 +1,28 @@ import 'dart:async'; +import 'dart:io'; 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/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:fcs/domain/constants.dart'; +import 'package:path/path.dart' as Path; class InvoiceModel extends BaseModel { final log = Logger('InvoiceModel'); StreamSubscription listener; - 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'), - ]) - ]; + 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; @@ -72,15 +40,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"; @@ -90,7 +60,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) { @@ -111,18 +81,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); }); @@ -130,11 +106,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(); }); @@ -142,7 +118,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(); }); } @@ -152,16 +128,46 @@ 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 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 { + 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/payment_page.dart b/lib/pages/invoice/payment/payment_page.dart new file mode 100644 index 0000000..680bec1 --- /dev/null +++ b/lib/pages/invoice/payment/payment_page.dart @@ -0,0 +1,327 @@ +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/main/util.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:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class PaymentPage extends StatefulWidget { + final Invoice invoice; + final bool forCustomer; + PaymentPage({this.invoice, this.forCustomer}); + + @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; + bool _hasBalance; + + @override + void initState() { + super.initState(); + _invoice = widget.invoice; + _hasBalance = widget.invoice.balance > 0; + _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: [ + _hasBalance ? amountBox : Container(), + SizedBox(height: 10), + _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( + 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}')], + ))), + 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), + ), + ), + ] + : [], + )), + ], + ), + ); + })?.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))), + widget.forCustomer + ? Container() + : 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)))), + widget.forCustomer + ? Container() + : 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 d05bd37..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 = _invoice.receipts.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/invoice/payment_pdf_screen.dart b/lib/pages/invoice/payment_pdf_screen.dart deleted file mode 100644 index a2e1591..0000000 --- a/lib/pages/invoice/payment_pdf_screen.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:dio/dio.dart'; -import 'package:fcs/helpers/theme.dart'; -import 'package:fcs/pages/main/model/main_model.dart'; -import 'package:fcs/pages/widgets/local_text.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; - - PaymentPDFScreen({Key key, this.path}) : super(key: key); - - _PaymentPDFScreenState createState() => _PaymentPDFScreenState(); -} - -class _PaymentPDFScreenState extends State - with WidgetsBindingObserver { - final Completer _controller = - Completer(); - int pages = 0; - int currentPage = 0; - bool isReady = false; - String errorMessage = ''; - - @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'); - }, - ), - 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; - }); - }, - ), - ], - ), - ); - } - - _share(String url) async { - MainModel mainModel = Provider.of(context, listen: false); - String appUrl = mainModel.setting.appUrl; - 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", - 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/package/model/package_model.dart b/lib/pages/package/model/package_model.dart index 9593ace..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'; @@ -186,7 +185,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/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", 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/shipment/model/shipment_model.dart b/lib/pages/shipment/model/shipment_model.dart index 8e33777..b78c4bb 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'; @@ -36,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 @@ -132,6 +130,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 { @@ -147,6 +150,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/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, ] 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/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/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/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( diff --git a/lib/pages/widgets/pdf_screen.dart b/lib/pages/widgets/pdf_screen.dart new file mode 100644 index 0000000..e9a1397 --- /dev/null +++ b/lib/pages/widgets/pdf_screen.dart @@ -0,0 +1,125 @@ +import 'dart:async'; +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/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 PDFScreen extends StatefulWidget { + final String title; + final String url; + + PDFScreen({Key key, this.url, this.title}) : super(key: key); + + _PDFScreenState createState() => _PDFScreenState(); +} + +class _PDFScreenState extends State with WidgetsBindingObserver { + final Completer _controller = + Completer(); + int pages = 0; + 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 LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + backgroundColor: Colors.white, + shadowColor: Colors.transparent, + title: + Text(widget.title ?? "", style: TextStyle(color: primaryColor)), + leading: new IconButton( + icon: new Icon(CupertinoIcons.back, color: primaryColor), + onPressed: () => Navigator.of(context).pop(), + ), + 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() async { + final RenderBox box = context.findRenderObject(); + await Share.shareFiles([file.path], + mimeTypes: ["application/pdf"], + 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 diff --git a/pubspec.lock b/pubspec.lock index bebd866..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: @@ -259,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: @@ -447,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: @@ -469,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: @@ -754,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: