diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index c3fa161..94d7a24 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -13,6 +13,7 @@ "btn.ok": "Ok", "feet":"Feet", "inch":"Inch", + "back.button_confirm":"Are you sure you want to continue without submitting changes?", "Buttons End ================================================================":"", "Offline Start ================================================================":"", @@ -292,6 +293,13 @@ "box.carton_size_remove.confirm":"Remove this carton size?", "box.carton_size.name":"Name", "box.cargo_total_title":"Edit Total Weight", + "box.new_carton_btn":"Create new carton", + "box.select.cargo.title":"Select cargos", + "box.complete.packaging":"Complete Packaging", + "box.mix_caton_title":"Mix cartons", + "box.min_caton.form.title":"Mix Carton", + "box.mix_carton_btn":"Create mix carton", + "box.mix_type":"Mix Box Types", "Boxes End ================================================================":"", "Delivery Start ================================================================":"", @@ -398,9 +406,12 @@ "rate.delivery_fee":"Delivery fees", "rate.total_estimated_amount":"Total estimated amount", "rate.volumetric_ratio":"Volumetric ratio", + "rate.diff_discount_weight":"Weight difference discount (lb)", + "rate.diff_weight_rate":"Weight difference rate", "rate.custom.form.title":"Custom", "rate.cutom.product_type":"Product type", "rate.custom.fee":"Fee", + "rate.custom.shipment_rate":"Shipment rate", "rate.discount.weight":"Weight", "rate.discount.rate":"Discount rate", "rate.custom_duty.title":"Custom Fee", @@ -416,6 +427,7 @@ "cargo.type":"Cargo type", "cargo.weight":"Weight", "cargo.rate":"Rate", + "cargo.qty":"Quantity", "cargo.amount":"Amount", "cargo.edit.delete.confirm":"Delete this cargo type?", "Cargo End ================================================================":"", @@ -548,7 +560,7 @@ "processing.create":"New Processing", "processing.update":"Update Processing", "processing.consignee.name":"Consignee name", - "processing.shipper.name":"Shipper name", + "processing.shipper.name":"Sender name", "processing.package.select.btn":"Select", "processing.package.create":"New Package", "processing.package.update":"Update Package", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index 91dd517..29ffefc 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -12,6 +12,7 @@ "btn.ok": "အိုကေ", "feet":"ပေ", "inch":"လက်မ", + "back.button_confirm":"Are you sure you want to continue without submitting changes?", "Buttons End ================================================================":"", "Offline Start ================================================================":"", @@ -292,6 +293,13 @@ "box.carton_size_remove.confirm":"Remove this carton size?", "box.carton_size.name":"နာမည်", "box.cargo_total_title":"စုစုပေါင်းအလေးချိန် ပြုပြင်မည်", + "box.new_carton_btn":"သေတ္တာ အသစ်ပြုလုပ်မည်", + "box.select.cargo.title":"Sကုန်ပစ္စည်းများ ရွေးခြင်း", + "box.complete.packaging":"ထုပ်ပိုးခြင်း ပြီးဆုံးမည်", + "box.mix_caton_title":"Mix cartons", + "box.min_caton.form.title":"Mix Carton", + "box.mix_carton_btn":"Create mix carton", + "box.mix_type":"Mix Box Types", "Boxes End ================================================================":"", "Delivery Start ================================================================":"", @@ -398,9 +406,12 @@ "rate.delivery_fee":"ပို့ဆောင်ခ", "rate.total_estimated_amount":"စုစုပေါင်းခန့်မှန်းပမာဏ", "rate.volumetric_ratio":"Volumetric Ratio", + "rate.diff_discount_weight":"Weight difference discount (lb)", + "rate.diff_weight_rate":"Weight difference rate", "rate.custom.form.title":"အကောက်ခွန်", "rate.cutom.product_type":"ကုန်ပစ္စည်းအမျိုးအစား", "rate.custom.fee":"အခကြေးငွေ", + "rate.custom.shipment_rate":"တင်ပို့နှုန်း", "rate.discount.weight":"အလေးချိန်", "rate.discount.rate":"လျှော့စျေးနှုန်း", "rate.custom_duty.title":"အကောက်ခွန်များ", @@ -416,6 +427,7 @@ "cargo.type":"ကုန်ပစ္စည်းအမျိုးအစား", "cargo.weight":"အလေးချိန်", "cargo.rate":"စျေးနှုန်း", + "cargo.qty":"အရေအတွက်", "cargo.amount":"ပမာဏ", "cargo.edit.delete.confirm":"ကုန်ပစ္စည်းကို ဖျက်မလား?", "Cargo End ================================================================":"", diff --git a/lib/config.dart b/lib/config.dart index ca00137..41fa63f 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -14,16 +14,18 @@ class Config { final String reportURL; final Level level; final String reportProjectID; + final String bucketName; factory Config( {@required Flavor flavor, @required String apiURL, @required String reportURL, @required String reportProjectID, + @required String bucketName, Color color: Colors.blue, Level level: Level.SEVERE}) { _instance ??= Config._internal(flavor, FlavorNames[flavor.index], color, - apiURL, reportURL, level, reportProjectID); + apiURL, reportURL, level, reportProjectID, bucketName); Logger.root.level = level; Logger.root.onRecord.listen((record) { @@ -35,7 +37,7 @@ class Config { } Config._internal(this.flavor, this.name, this.color, this.apiURL, - this.reportURL, this.level, this.reportProjectID); + this.reportURL, this.level, this.reportProjectID, this.bucketName); static Config get instance { return _instance; diff --git a/lib/data/provider/rate_data_provider.dart b/lib/data/provider/rate_data_provider.dart index 7d0529d..c01e863 100644 --- a/lib/data/provider/rate_data_provider.dart +++ b/lib/data/provider/rate_data_provider.dart @@ -92,6 +92,8 @@ class RateDataProvider { _rate.deliveryFee = rate.deliveryFee; _rate.freeDeliveryWeight = rate.freeDeliveryWeight; _rate.volumetricRatio = rate.volumetricRatio; + _rate.diffDiscountWeight = rate.diffDiscountWeight; + _rate.diffWeightRate = rate.diffWeightRate; controller.add(_rate); }); cargoListener = _cargoTypeStream().listen((cargoTypes) { diff --git a/lib/domain/constants.dart b/lib/domain/constants.dart index a5ba92b..69ab4fc 100644 --- a/lib/domain/constants.dart +++ b/lib/domain/constants.dart @@ -5,6 +5,8 @@ const user_collection = "users"; const invitations_collection = "invitations"; const privilege_collection = "privileges"; const markets_collection = "markets"; +const carton_sizes_collection = "carton_sizes"; + const packages_collection = "packages"; const messages_collection = "messages"; const fcs_shipment_collection = "fcs_shipments"; @@ -77,8 +79,13 @@ const shipment_courier_dropoff = "Courier drop off"; //Carton types const carton_from_packages = "From packages"; const carton_from_shipments = "From shipments"; -const carton_mix_box = "Mix carton"; +const carton_mix_carton = "Mix carton"; const carton_small_bag = "Small bag"; +const carton_mix_box = "Mix box"; + +//Mix types +const mix_delivery = "Mix Delivery"; +const mix_pickup = "Mix Pickup"; //Carton status const carton_packed_status = "packed"; diff --git a/lib/domain/entities/cargo_type.dart b/lib/domain/entities/cargo_type.dart index 6534d71..76ccfe2 100644 --- a/lib/domain/entities/cargo_type.dart +++ b/lib/domain/entities/cargo_type.dart @@ -4,6 +4,8 @@ class CargoType { double rate; double weight; bool isChecked; + int qty; + bool isCutomDuty = false; double get calAmount => (calRate ?? 0) * (calWeight ?? 0); @@ -20,15 +22,16 @@ class CargoType { calRate: map['cal_rate']?.toDouble() ?? 0, ); } - CargoType({ - this.id, - this.name, - this.rate, - this.weight, - this.calWeight, - this.calRate, - this.isChecked = false, - }); + CargoType( + {this.id, + this.name, + this.rate, + this.weight, + this.calWeight, + this.calRate, + this.isChecked = false, + this.qty = 0, + this.isCutomDuty = false}); Map toMap() { return { @@ -55,4 +58,8 @@ class CargoType { String toString() { return name; } + + bool isChangedForEdit(CargoType cargoType) { + return cargoType.name != this.name || cargoType.rate != this.rate; + } } diff --git a/lib/domain/entities/carton.dart b/lib/domain/entities/carton.dart index cccac21..7115129 100644 --- a/lib/domain/entities/carton.dart +++ b/lib/domain/entities/carton.dart @@ -36,6 +36,9 @@ class Carton { String fcsShipmentNumber; String mixCartonID; String mixCartonNumber; + String cartonSizeID; + String cartonSizeName; + String mixBoxType; int rate; int weight; @@ -62,7 +65,7 @@ class Carton { double get actualWeight => cargoTypes == null ? 0 : cargoTypes.fold(0, (p, e) => e.weight + p); - double getShipmentWeight(double volumetricRatio) { + int getShipmentWeight(double volumetricRatio) { if (length == null || length <= 0 || width == null || @@ -71,8 +74,8 @@ class Carton { height <= 0 || volumetricRatio == null || volumetricRatio <= 0) return 0; - - return (length * width * height) / volumetricRatio; + + return ((length * width * height) / volumetricRatio).round(); } /// getCargoTypeForCalWeight returns carton with shipment weight @@ -95,25 +98,26 @@ class Carton { /// calAmount returns total amount double calAmount(Rate rate) { // get shipment weight - double volume = (length ?? 0) * (width ?? 0) * (height ?? 0); - double sw = volume / rate.volumetricRatio ?? 0; + int sw = getShipmentWeight(rate.volumetricRatio); // get actual weight double aw = cargoTypes.fold(0.0, (p, c) => p + c.weight); if (aw == 0 || sw == 0) return 0; - DiscountByWeight discountByWeight = - rate.getDiscountByWeight(sw > aw ? sw : aw); + double wd = sw - aw; + wd = wd - rate.diffDiscountWeight; + double wdAmount = wd > 0 ? wd * rate.diffWeightRate : 0; + + DiscountByWeight discountByWeight = rate.getDiscountByWeight(aw); double total = 0; cargoTypes.forEach((e) { - double cargoWeight = aw > sw ? e.weight : e.weight / aw * sw; double r = e.rate - (discountByWeight != null ? discountByWeight.discount : 0); - double amount = cargoWeight * r; + double amount = e.weight * r; total += amount; }); - return total; + return total + wdAmount; } List shipmentHistory; @@ -157,7 +161,10 @@ class Carton { this.mixCartonID, this.mixCartonNumber, this.isShipmentCarton = false, - this.deliveryAddress}); + this.deliveryAddress, + this.cartonSizeID, + this.cartonSizeName, + this.mixBoxType}); Map toMap() { List _cargoTypes = cargoTypes.map((c) => c.toMap()).toList(); diff --git a/lib/domain/entities/carton_size.dart b/lib/domain/entities/carton_size.dart index 48c4ed9..2587bdf 100644 --- a/lib/domain/entities/carton_size.dart +++ b/lib/domain/entities/carton_size.dart @@ -5,4 +5,31 @@ class CartonSize { double width; double height; CartonSize({this.id, this.name, this.length, this.width, this.height}); + + Map toMap() { + return { + 'id': id, + 'name': name, + 'length': length, + 'width': width, + 'height': height, + }; + } + + factory CartonSize.fromMap(Map map, String id) { + return CartonSize( + id: id, + name: map['name'], + length: map['length'], + width: map['width'], + height: map['height'], + ); + } + + bool isChangedForEdit(CartonSize cartonSize) { + return cartonSize.name != this.name || + cartonSize.length != this.length || + cartonSize.width != this.width || + cartonSize.height != this.height; + } } diff --git a/lib/domain/entities/custom_duty.dart b/lib/domain/entities/custom_duty.dart index d5eba6a..fb2a3aa 100644 --- a/lib/domain/entities/custom_duty.dart +++ b/lib/domain/entities/custom_duty.dart @@ -3,7 +3,9 @@ class CustomDuty { String productType; String desc; double fee; - CustomDuty({this.id, this.productType, this.desc, this.fee}); + double shipmentRate; + CustomDuty( + {this.id, this.productType, this.desc, this.fee, this.shipmentRate}); factory CustomDuty.fromMap(Map map, String id) { return CustomDuty( @@ -28,4 +30,11 @@ class CustomDuty { @override int get hashCode => id.hashCode; + + bool isChangedForEdit(CustomDuty customDuty) { + return customDuty.productType != this.productType || + customDuty.fee != this.fee + // ||customDuty.shipmentRate != this.shipmentRate + ; + } } diff --git a/lib/domain/entities/discount.dart b/lib/domain/entities/discount.dart index 1815a72..cb2bda0 100644 --- a/lib/domain/entities/discount.dart +++ b/lib/domain/entities/discount.dart @@ -35,4 +35,10 @@ class Discount { status: map['status'], ); } + + bool isChangedForEdit(Discount discount) { + return discount.code != this.code || + discount.amount != this.amount || + discount.customerId != this.customerId; + } } diff --git a/lib/domain/entities/discount_by_weight.dart b/lib/domain/entities/discount_by_weight.dart index cc4b179..3ff9a1d 100644 --- a/lib/domain/entities/discount_by_weight.dart +++ b/lib/domain/entities/discount_by_weight.dart @@ -20,4 +20,9 @@ class DiscountByWeight { 'discount': discount, }; } + + bool isChangedForEdit(DiscountByWeight discountByWeight) { + return discountByWeight.weight != this.weight || + discountByWeight.discount != this.discount; + } } diff --git a/lib/domain/entities/fcs_shipment.dart b/lib/domain/entities/fcs_shipment.dart index adfc4f2..37e2c71 100644 --- a/lib/domain/entities/fcs_shipment.dart +++ b/lib/domain/entities/fcs_shipment.dart @@ -66,4 +66,14 @@ class FcsShipment { bool isConfirmed() { return status == fcs_shipment_confirmed_status; } + + bool isChangedForEdit(FcsShipment fcsShipment) { + return fcsShipment.shipmentNumber != this.shipmentNumber || + fcsShipment.cutoffDate != this.cutoffDate || + fcsShipment.arrivalDate != this.arrivalDate || + fcsShipment.shipType != this.shipType || + fcsShipment.consignee != this.consignee || + fcsShipment.port != this.port || + fcsShipment.destination != this.destination; + } } diff --git a/lib/domain/entities/package.dart b/lib/domain/entities/package.dart index e03c814..5365350 100644 --- a/lib/domain/entities/package.dart +++ b/lib/domain/entities/package.dart @@ -115,6 +115,7 @@ class Package { 'tracking_id': trackingID, 'market': market, 'fcs_id': fcsID, + 'sender_fcs_id': senderFCSID, "desc": desc, "remark": remark, "photo_urls": photoUrls @@ -131,6 +132,12 @@ class Package { currentStatusDate: DateTime.parse(json['status_date'])); } + bool isChangedForEdit(Package package) { + return package.trackingID != this.trackingID || + package.remark != this.remark || + package.fcsID != this.fcsID; + } + @override bool operator ==(Object other) => other is Package && other.id == id; diff --git a/lib/domain/entities/processing.dart b/lib/domain/entities/processing.dart index f801903..0006183 100644 --- a/lib/domain/entities/processing.dart +++ b/lib/domain/entities/processing.dart @@ -29,6 +29,12 @@ class Processing { @override int get hashCode => id.hashCode; + bool isChangedForEdit(Processing processing) { + return processing.userID != this.userID || + processing.fcsID != this.fcsID || + processing.packages != this.packages; + } + @override String toString() { return 'Processing{id: $id}'; diff --git a/lib/domain/entities/rate.dart b/lib/domain/entities/rate.dart index 94a1ec4..3dadb79 100644 --- a/lib/domain/entities/rate.dart +++ b/lib/domain/entities/rate.dart @@ -8,6 +8,9 @@ class Rate { double freeDeliveryWeight; double volumetricRatio; + double diffDiscountWeight; + double diffWeightRate; + List cargoTypes; List customDuties; List discountByWeights; @@ -22,17 +25,20 @@ class Rate { ? null : cargoTypes.firstWhere((e) => e.name == "General"); - Rate({ - this.deliveryFee, - this.freeDeliveryWeight, - this.volumetricRatio, - }); + Rate( + {this.deliveryFee, + this.freeDeliveryWeight, + this.volumetricRatio, + this.diffDiscountWeight, + this.diffWeightRate}); factory Rate.fromMap(Map map) { return Rate( deliveryFee: (map['delivery_fee'] ?? 0).toDouble(), freeDeliveryWeight: (map['free_delivery_weight'] ?? 0).toDouble(), volumetricRatio: (map['volumetric_ratio'] ?? 0).toDouble(), + diffDiscountWeight: (map['diff_discount_weight'] ?? 0).toDouble(), + diffWeightRate: (map['diff_weight_rate'] ?? 0).toDouble(), ); } @@ -41,11 +47,21 @@ class Rate { "delivery_fee": deliveryFee, 'free_delivery_weight': freeDeliveryWeight, 'volumetric_ratio': volumetricRatio, + 'diff_discount_weight': diffDiscountWeight, + 'diff_weight_rate': diffWeightRate, }; } + bool isChangedForEdit(Rate rate) { + return rate.freeDeliveryWeight != this.freeDeliveryWeight || + rate.deliveryFee != this.deliveryFee || + rate.volumetricRatio != this.volumetricRatio || + rate.diffDiscountWeight != this.diffDiscountWeight || + rate.diffWeightRate != this.diffWeightRate; + } + @override String toString() { - return 'Rate{deliveryFee:$deliveryFee,freeDeliveryWeight:$freeDeliveryWeight,volumetricRatio:$volumetricRatio}'; + return 'Rate{deliveryFee:$deliveryFee,freeDeliveryWeight:$freeDeliveryWeight,volumetricRatio:$volumetricRatio,diffDiscountWeight:$diffDiscountWeight,diffWeightRate:$diffWeightRate}'; } } diff --git a/lib/domain/vo/delivery_address.dart b/lib/domain/vo/delivery_address.dart index b7157bb..a0c507e 100644 --- a/lib/domain/vo/delivery_address.dart +++ b/lib/domain/vo/delivery_address.dart @@ -41,4 +41,13 @@ class DeliveryAddress { 'phone_number': phoneNumber, }; } + + bool isChangedForEdit(DeliveryAddress deliveryAddress) { + return deliveryAddress.fullName != this.fullName || + deliveryAddress.phoneNumber != this.phoneNumber || + deliveryAddress.addressLine1 != this.addressLine1 || + deliveryAddress.addressLine2 != this.addressLine2 || + deliveryAddress.state != this.state || + deliveryAddress.city != this.city; + } } diff --git a/lib/domain/vo/shipment_status.dart b/lib/domain/vo/shipment_status.dart index 7180de7..ed4549c 100644 --- a/lib/domain/vo/shipment_status.dart +++ b/lib/domain/vo/shipment_status.dart @@ -4,14 +4,18 @@ class ShipmentStatus { String status; DateTime date; bool done; - ShipmentStatus({this.status, this.date, this.done}); + String staffId; + String staffName; + ShipmentStatus( + {this.status, this.date, this.done, this.staffId, this.staffName}); factory ShipmentStatus.fromMap(Map map) { var _date = (map['date'] as Timestamp); return ShipmentStatus( - status: map['status'], - date: _date == null ? null : _date.toDate(), - done: map['done'], - ); + status: map['status'], + date: _date == null ? null : _date.toDate(), + done: map['done'], + staffId: map['staff_id'], + staffName: map['staff_name']); } } diff --git a/lib/helpers/firebase_helper.dart b/lib/helpers/firebase_helper.dart index f6d95c5..9716458 100644 --- a/lib/helpers/firebase_helper.dart +++ b/lib/helpers/firebase_helper.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:fcs/config.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:logging/logging.dart'; @@ -39,7 +40,9 @@ Future uploadStorage(String path, File file, {String fileName}) async { fileName = Uuid().v4(); } StorageReference storageReference = - FirebaseStorage.instance.ref().child('$path/$fileName'); + FirebaseStorage(storageBucket: Config.instance.bucketName) + .ref() + .child('$path/$fileName'); StorageUploadTask uploadTask = storageReference.putFile(file); await uploadTask.onComplete; String downloadUrl = await storageReference.getDownloadURL(); diff --git a/lib/main-dev.dart b/lib/main-dev.dart index fc234be..7ad6513 100644 --- a/lib/main-dev.dart +++ b/lib/main-dev.dart @@ -12,6 +12,7 @@ void main() { apiURL: "https://asia-northeast1-fcs-dev1.cloudfunctions.net/API", reportURL: "http://petrok.mokkon.com:8091", reportProjectID: "fcs-dev", + bucketName: "gs://fcs-dev1-us", level: Level.ALL); runApp(App()); } diff --git a/lib/main-local.dart b/lib/main-local.dart index 6f30bf4..a46ddde 100644 --- a/lib/main-local.dart +++ b/lib/main-local.dart @@ -9,9 +9,10 @@ void main() { Config( flavor: Flavor.DEV, color: Colors.blue, - reportURL: "http://192.168.100.11:8080", + reportURL: "http://petrok.mokkon.com:8091", reportProjectID: "fcs-dev", - apiURL: "http://192.168.100.11:7777", + apiURL: "http://192.168.1.155:7777", + bucketName: "gs://fcs-dev1-us", level: Level.ALL); runApp(App()); } diff --git a/lib/main-prod.dart b/lib/main-prod.dart index c0c431a..86c3693 100644 --- a/lib/main-prod.dart +++ b/lib/main-prod.dart @@ -12,6 +12,7 @@ void main() { apiURL: "https://asia-northeast1-fcs-prod1.cloudfunctions.net/API", reportURL: "http://petrok.mokkon.com:8092", reportProjectID: "fcs-prod", + bucketName: "gs://fcs-prod1", level: Level.ALL); runApp(App()); } diff --git a/lib/pages/carton/cargo_table.dart b/lib/pages/carton/cargo_table.dart new file mode 100644 index 0000000..1abdf9c --- /dev/null +++ b/lib/pages/carton/cargo_table.dart @@ -0,0 +1,118 @@ +import 'package:fcs/domain/entities/cargo_type.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 CargoTable extends StatefulWidget { + final List cargoTypes; + + const CargoTable({ + Key key, + this.cargoTypes, + }) : super(key: key); + + @override + _CargoTableState createState() => _CargoTableState(); +} + +class _CargoTableState extends State { + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: MyDataTable( + headingRowHeight: 40, + columnSpacing: 50, + columns: [ + MyDataColumn( + label: LocalText( + context, + "cargo.type", + color: Colors.grey, + ), + ), + MyDataColumn( + label: LocalText( + context, + "cargo.qty", + color: Colors.grey, + ), + ), + MyDataColumn( + label: LocalText( + context, + "cargo.weight", + color: Colors.grey, + ), + ), + ], + rows: getCargoRows(context), + ), + ); + } + + List getCargoRows(BuildContext context) { + if (widget.cargoTypes == null) { + return []; + } + double total = 0; + var rows = widget.cargoTypes.map((c) { + total += c.weight; + return MyDataRow( + onSelectChanged: (bool selected) async {}, + cells: [ + MyDataCell(new Text( + c.name == null ? "" : c.name, + style: textStyle, + )), + MyDataCell(c.qty == null || c.qty == 0 + ? Center( + child: Text( + "-", + style: textStyle, + ), + ) + : Center( + child: Text( + c.qty.toString(), + style: textStyle, + ), + )), + MyDataCell( + Text(c.weight == null ? "0" : c.weight.toStringAsFixed(2), + style: textStyle), + ), + ], + ); + }).toList(); + + var totalRow = MyDataRow( + onSelectChanged: (bool selected) {}, + cells: [ + MyDataCell(Align( + alignment: Alignment.centerRight, + child: LocalText( + context, + "shipment.cargo.total", + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + )), + MyDataCell(Text("")), + MyDataCell( + Padding( + padding: const EdgeInsets.only(right: 48.0), + child: Align( + alignment: Alignment.centerRight, + child: Text(total.toStringAsFixed(2), + style: TextStyle(fontWeight: FontWeight.bold))), + ), + ), + ], + ); + rows.add(totalRow); + return rows; + } +} diff --git a/lib/pages/carton/cargo_type_addtion.dart b/lib/pages/carton/cargo_type_addtion.dart index 64dff27..043c922 100644 --- a/lib/pages/carton/cargo_type_addtion.dart +++ b/lib/pages/carton/cargo_type_addtion.dart @@ -1,8 +1,11 @@ import 'package:fcs/domain/entities/cargo_type.dart'; +import 'package:fcs/domain/entities/custom_duty.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/rates/custom_list.dart'; import 'package:fcs/pages/rates/model/shipment_rate_model.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'; @@ -21,9 +24,15 @@ class _CargoTypeAdditionState extends State { @override void initState() { super.initState(); - cargos = - Provider.of(context, listen: false).rate.cargoTypes; - cargos.forEach((p) => p.isChecked = false); + var shipmentRateModel = + Provider.of(context, listen: false); + cargos = List.from(shipmentRateModel.rate.cargoTypes); + cargos.forEach((p) { + p.isChecked = false; + p.isCutomDuty = false; + p.weight = 0; + p.qty = null; + }); } @override @@ -31,48 +40,58 @@ class _CargoTypeAdditionState extends State { super.dispose(); } - List showcargoTypeList(BuildContext context) { - return this.cargos.map((c) { - return new ListTile( - contentPadding: EdgeInsets.all(0), - title: InkWell( - onTap: () { - setState(() { - c.isChecked = c.isChecked == null ? true : !c.isChecked; - }); - }, - child: new Row( - children: [ - new Checkbox( - value: c.isChecked == null ? false : c.isChecked, - activeColor: primaryColor, - onChanged: (bool value) { - setState(() { - c.isChecked = value; - }); - }), - new Text( - c.name, - style: TextStyle(fontSize: 15.0, color: primaryColor), - ), - ], - ), - )); - }).toList(); - } - @override Widget build(BuildContext context) { + List getCargoRowList() { + return cargos.map((c) { + return Container( + child: Container( + padding: + EdgeInsets.only(left: 10.0, right: 5.0, top: 3.0, bottom: 3.0), + child: Row( + children: [ + Checkbox( + value: c.isChecked, + activeColor: primaryColor, + onChanged: (bool check) { + setState(() { + c.isChecked = check; + }); + }), + new Text(c.name, style: textStyle), + c.isCutomDuty + ? Padding( + padding: const EdgeInsets.only(left: 15), + child: IconButton( + onPressed: () { + setState(() { + cargos.removeWhere((t) => t.name == c.name); + }); + }, + icon: Icon( + Icons.remove_circle, + color: Colors.black45, + ), + ), + ) + : Container(), + ], + ), + ), + ); + }).toList(); + } + final saveBtn = fcsButton( context, getLocalString(context, 'box.cargo.save.btn'), callack: () { - this.cargos.forEach((p) => p.weight = 0.00); List _cargos = this.cargos.where((c) => c.isChecked).toList(); Navigator.pop(context, _cargos); }, ); + return LocalProgress( inAsyncCall: _isLoading, child: Scaffold( @@ -92,12 +111,27 @@ class _CargoTypeAdditionState extends State { color: primaryColor, )), body: Container( - padding: EdgeInsets.all(18), + padding: EdgeInsets.all(8), child: ListView( shrinkWrap: true, children: [ + LocalTitle( + textKey: "box.select.cargo.title", + 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); + }), + ), Column( - children: showcargoTypeList(context), + children: getCargoRowList(), ), SizedBox(height: 30), saveBtn, @@ -109,8 +143,14 @@ class _CargoTypeAdditionState extends State { ); } - // List cargoTypeList() { - // return this.privileges.where((p) => p.isChecked).map((p) => p.id).toList(); - // } + _addCustom(CustomDuty customDuty) { + if (customDuty == null) return; + if (cargos.any((c) => c.name == customDuty.productType)) return; + + setState(() { + cargos.add(CargoType( + name: customDuty.productType, isCutomDuty: true, qty: 1, weight: 0)); + }); + } } diff --git a/lib/pages/carton/carton_cargo_table.dart b/lib/pages/carton/carton_cargo_table.dart index 8778547..ef0f495 100644 --- a/lib/pages/carton/carton_cargo_table.dart +++ b/lib/pages/carton/carton_cargo_table.dart @@ -1,22 +1,25 @@ import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/widgets/dialog_input.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'; import 'cargo_type_editor.dart'; -import 'total_weight_edit.dart'; typedef OnAdd(CargoType cargoType); typedef OnRemove(CargoType cargoType); class CargoTable extends StatefulWidget { final List cargoTypes; + final bool isNew; final OnAdd onAdd; final OnRemove onRemove; - const CargoTable({Key key, this.cargoTypes, this.onAdd, this.onRemove}) + const CargoTable( + {Key key, this.cargoTypes, this.isNew, this.onAdd, this.onRemove}) : super(key: key); @override @@ -25,27 +28,56 @@ class CargoTable extends StatefulWidget { class _CargoTableState extends State { double totalWeight = 0; + List _cargos = []; + double remainingWeight = 0; + List _list = []; + List _types = []; + + @override + void initState() { + if (!widget.isNew) { + totalWeight = widget.cargoTypes + .fold(0, (previous, current) => previous + current.weight); + } + super.initState(); + } + @override Widget build(BuildContext context) { - return MyDataTable( - headingRowHeight: 40, - columns: [ - MyDataColumn( - label: LocalText( - context, - "cargo.type", - color: Colors.grey, + remainingWeight = + widget.cargoTypes.length == 0 ? this.totalWeight : remainingWeight; + this._cargos = widget.cargoTypes.length == 0 ? [] : this._cargos; + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: MyDataTable( + headingRowHeight: 40, + columnSpacing: 40, + columns: [ + MyDataColumn( + label: LocalText( + context, + "cargo.type", + color: Colors.grey, + ), ), - ), - MyDataColumn( - label: LocalText( - context, - "cargo.weight", - color: Colors.grey, + MyDataColumn( + label: LocalText( + context, + "cargo.qty", + color: Colors.grey, + ), ), - ), - ], - rows: getCargoRows(context), + MyDataColumn( + label: LocalText( + context, + "cargo.weight", + color: Colors.grey, + ), + ), + ], + rows: getCargoRows(context), + ), ); } @@ -54,30 +86,118 @@ class _CargoTableState extends State { return []; } - double _total = 0; - var rows = widget.cargoTypes.map((c) { - _total += c.weight; + CargoType cargo; + var rows = widget.cargoTypes.map((c) { return MyDataRow( - onSelectChanged: (bool selected) async { - CargoType cargo = await Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => CargoTypeEditor( - cargo: c, - ))); - if (widget.onAdd != null) widget.onAdd(cargo); - }, + onSelectChanged: (bool selected) async {}, cells: [ - MyDataCell(new Text( - c.name == null ? "" : c.name, - style: textStyle, - )), + MyDataCell( + new Text( + c.name == null ? "" : c.name, + style: textStyle, + ), + ), + MyDataCell( + c.isCutomDuty + ? GestureDetector( + onTap: () async { + String _t = await showDialog( + context: context, + builder: (_) => DialogInput( + label: "cargo.qty", value: c.qty.toString())); + + if (_t == null) return; + setState(() { + c.qty = int.parse(_t); + }); + }, + child: Center( + child: Container( + width: 40, + padding: const EdgeInsets.all(7.0), + decoration: BoxDecoration( + border: Border.all(color: primaryColor), + borderRadius: BorderRadius.all(Radius.circular(5.0)), + ), + child: new Text( + c.qty == null ? "" : c.qty.toString(), + style: textStyle, + textAlign: TextAlign.center, + ), + ), + ), + ) + : Center( + child: new Text( + "-", + style: textStyle, + ), + ), + ), MyDataCell( Row( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(c.weight.toStringAsFixed(2), style: textStyle), + GestureDetector( + onTap: () async { + if (this.totalWeight <= 0) { + showMsgDialog( + context, "Error", "Please insert total weight"); + return; + } + + String _t = await showDialog( + context: context, + builder: (_) => DialogInput( + label: "cargo.weight", + value: c.weight.toStringAsFixed(2))); + + if (_t == null) return; + setState(() { + c.weight = double.parse(_t); + }); + + cargo = c; + this._cargos.add(cargo); + if (this.remainingWeight <= 0) return; + this.remainingWeight -= cargo.weight; + + this._cargos.forEach((c) { + _list.add(c.name); + }); + + widget.cargoTypes.forEach((c) { + _types.add(c.name); + }); + + if (this._cargos.length == widget.cargoTypes.length - 1) { + _types.forEach((t) { + if (!_list.contains(t)) { + widget.cargoTypes.forEach((c) { + if (c.name == t) { + c.weight = this.remainingWeight; + setState(() { + this._cargos = []; + }); + } + }); + } + }); + this.remainingWeight = this.totalWeight; + } + }, + child: Container( + padding: const EdgeInsets.all(7.0), + decoration: BoxDecoration( + border: Border.all(color: primaryColor), + borderRadius: BorderRadius.all(Radius.circular(5.0)), + ), + child: Text( + c.weight == null ? "0.00" : c.weight.toStringAsFixed(2), + style: textStyle), + ), + ), widget.onRemove == null ? SizedBox( width: 50, @@ -98,14 +218,7 @@ class _CargoTableState extends State { }).toList(); var totalRow = MyDataRow( - onSelectChanged: (bool selected) async { - double _t = await Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => TotalWeightEdit(totalWeight: totalWeight))); - if (_t == null) return; - setState(() { - totalWeight = _t; - }); - }, + onSelectChanged: (bool selected) {}, cells: [ MyDataCell(Align( alignment: Alignment.centerRight, @@ -116,13 +229,36 @@ class _CargoTableState extends State { fontWeight: FontWeight.bold, ), )), + MyDataCell(Text("")), MyDataCell( Padding( padding: const EdgeInsets.only(right: 48.0), child: Align( alignment: Alignment.centerRight, - child: Text(totalWeight.toStringAsFixed(2), - style: TextStyle(fontWeight: FontWeight.bold))), + child: InkWell( + onTap: () async { + String _t = await showDialog( + context: context, + builder: (_) => DialogInput( + label: "shipment.cargo.total", + value: totalWeight.toStringAsFixed(2))); + + if (_t == null) return; + setState(() { + totalWeight = double.parse(_t); + remainingWeight = totalWeight; + }); + }, + child: Container( + padding: const EdgeInsets.all(7.0), + decoration: BoxDecoration( + border: Border.all(color: primaryColor), + borderRadius: BorderRadius.all(Radius.circular(5.0)), + ), + child: Text(totalWeight.toStringAsFixed(2), + style: TextStyle(fontWeight: FontWeight.bold)), + ), + )), ), ), ], @@ -131,11 +267,6 @@ class _CargoTableState extends State { return rows; } - double getRemainBalance(double total) { - double _r = this.totalWeight < total ? 0 : this.totalWeight - total; - return _r; - } - List _getCargoRows(BuildContext context) { if (widget.cargoTypes == null) { return []; diff --git a/lib/pages/carton/carton_cargo_table_old.dart b/lib/pages/carton/carton_cargo_table_old.dart new file mode 100644 index 0000000..8e0d6f9 --- /dev/null +++ b/lib/pages/carton/carton_cargo_table_old.dart @@ -0,0 +1,184 @@ +import 'package:fcs/domain/entities/cargo_type.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/widgets/local_text.dart'; +import 'package:fcs/pages/widgets/my_data_table.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'cargo_type_editor.dart'; +import 'total_weight_edit.dart'; + +typedef OnAdd(CargoType cargoType); +typedef OnRemove(CargoType cargoType); + +class CargoTable extends StatefulWidget { + final List cargoTypes; + final OnAdd onAdd; + final OnRemove onRemove; + + const CargoTable({Key key, this.cargoTypes, this.onAdd, this.onRemove}) + : super(key: key); + + @override + _CargoTableState createState() => _CargoTableState(); +} + +class _CargoTableState extends State { + double totalWeight = 0; + List _cargos = []; + double remainingWeight = 0; + @override + Widget build(BuildContext context) { + return MyDataTable( + headingRowHeight: 40, + columns: [ + MyDataColumn( + label: LocalText( + context, + "cargo.type", + color: Colors.grey, + ), + ), + MyDataColumn( + label: Row( + children: [ + Container( + padding: EdgeInsets.only(left: 50), + child: LocalText( + context, + "cargo.weight", + color: Colors.grey, + ), + ), + ], + ), + ), + ], + rows: getCargoRows(context), + ); + } + + List getCargoRows(BuildContext context) { + if (widget.cargoTypes == null) { + return []; + } + List _list = []; + List _types = []; + double _total = 0; + + var rows = widget.cargoTypes.map((c) { + _total += c.weight; + return MyDataRow( + onSelectChanged: (bool selected) async {}, + cells: [ + MyDataCell(Row( + children: [ + new Text( + c.name == null ? "" : c.name, + style: textStyle, + ), + new Text( + c.qty == null || c.qty == 0 ? "" : " x ${c.qty.toString()}", + style: TextStyle(color: Colors.grey), + ), + ], + )), + MyDataCell( + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(c.weight.toStringAsFixed(2), style: textStyle), + widget.onRemove == null + ? SizedBox( + width: 50, + ) + : IconButton( + icon: Icon( + Icons.remove_circle, + color: primaryColor, + ), + onPressed: () { + if (widget.onRemove != null) widget.onRemove(c); + }) + ], + ), + ), + ], + ); + }).toList(); + + var totalRow = MyDataRow( + onSelectChanged: (bool selected) {}, + cells: [ + MyDataCell(Align( + alignment: Alignment.centerRight, + child: LocalText( + context, + "shipment.cargo.total", + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + )), + MyDataCell( + Padding( + padding: const EdgeInsets.only(right: 40.0), + child: Align( + alignment: Alignment.centerRight, + child: InkWell( + onTap: () async { + double _t = await Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => + TotalWeightEdit(totalWeight: totalWeight))); + if (_t == null) return; + setState(() { + totalWeight = _t; + this.remainingWeight = this.totalWeight - _total; + widget.cargoTypes.forEach((c) { + if (c.qty == null) { + this._cargos.add(c); + } + }); + this._cargos.forEach((c) { + _list.add(c.name); + }); + widget.cargoTypes.forEach((c) { + _types.add(c.name); + }); + if (this._cargos.length == widget.cargoTypes.length - 1) { + _types.forEach((t) { + if (!_list.contains(t)) { + widget.cargoTypes.forEach((c) { + if (c.name == t) { + c.weight = this.remainingWeight; + } + }); + } + }); + } + }); + }, + child: Container( + padding: const EdgeInsets.all(7.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.all(Radius.circular(5.0)), + ), + child: Text(totalWeight.toStringAsFixed(2), + style: TextStyle(fontWeight: FontWeight.bold)), + ), + )), + ), + ), + ], + ); + rows.add(totalRow); + return rows; + } + + double getRemainBalance(double total) { + double _r = this.totalWeight < total ? 0 : this.totalWeight - total; + return _r; + } +} diff --git a/lib/pages/carton/carton_editor.dart b/lib/pages/carton/carton_editor.dart index 878ad41..7d07175 100644 --- a/lib/pages/carton/carton_editor.dart +++ b/lib/pages/carton/carton_editor.dart @@ -7,20 +7,16 @@ 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/helpers/theme.dart'; -import 'package:fcs/pages/carton/carton_cargo_table.dart'; import 'package:fcs/pages/carton/carton_package_table.dart'; +import 'package:fcs/pages/carton_search/carton_search.dart'; import 'package:fcs/pages/carton_size/carton_size_list.dart'; -import 'package:fcs/pages/delivery_address/delivery_address_list.dart'; -import 'package:fcs/pages/delivery_address/delivery_address_row.dart'; import 'package:fcs/pages/fcs_shipment/model/fcs_shipment_model.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/package/model/package_model.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/defalut_delivery_address.dart'; import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/fcs_id_icon.dart'; -import 'package:fcs/pages/widgets/length_picker.dart'; import 'package:fcs/pages/widgets/local_button.dart'; import 'package:fcs/pages/widgets/local_dropdown.dart'; import 'package:fcs/pages/widgets/local_radio_buttons.dart'; @@ -31,10 +27,12 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_icons/flutter_icons.dart'; import 'package:provider/provider.dart'; -import 'cargo_type_addtion.dart'; -import 'cargo_type_editor.dart'; +import 'carton_list_row.dart'; +import 'carton_row.dart'; +import 'mix_carton_editor.dart'; import 'model/carton_model.dart'; import '../carton_size/model/carton_size_model.dart'; +import 'package_carton_editor.dart'; import 'widgets.dart'; class CartonEditor extends StatefulWidget { @@ -56,12 +54,14 @@ class _CartonEditorState extends State { DeliveryAddress _deliveryAddress = new DeliveryAddress(); User _user; String _selectedCartonType; + String _selectedMixType; double volumetricRatio = 0; double shipmentWeight = 0; FcsShipment _fcsShipment; List _fcsShipments; Carton _mixCarton; - List _mixCartons; + List _cartons = []; + List _mixCartons = []; @override void initState() { @@ -90,12 +90,19 @@ class _CartonEditorState extends State { cargoTypes: [], packages: [], ); - _lengthController.text = ""; - _widthController.text = ""; - _heightController.text = ""; + _lengthController.text = "0"; + _widthController.text = "0"; + _heightController.text = "0"; _isNew = true; _selectedCartonType = carton_from_packages; + _selectedMixType = mix_delivery; _loadFcsShipments(); + _cartons = [Carton(cartonNumber: "A100B-1#3", userName: "Seven 7")]; + + _mixCartons = [ + Carton(cartonNumber: "A100B-1#1", userName: "Seven 7"), + Carton(cartonNumber: "A100B-1#2", userName: "Seven 7"), + ]; } } @@ -159,18 +166,6 @@ 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); @@ -189,17 +184,13 @@ class _CartonEditorState extends State { Widget build(BuildContext context) { var boxModel = Provider.of(context); bool isMixBox = _selectedCartonType == carton_mix_box; - bool isSmallBag = _selectedCartonType == carton_small_bag; + final shipmentBox = DisplayText( text: _carton.fcsShipmentNumber, labelTextKey: "box.fcs_shipment_num", iconData: Ionicons.ios_airplane, ); - final mixCartonNumberBox = DisplayText( - text: _carton.mixCartonNumber, - labelTextKey: "box.mix.carton", - iconData: MaterialCommunityIcons.package, - ); + var fcsShipmentsBox = Container( padding: EdgeInsets.only(top: 10), child: LocalDropdown( @@ -207,7 +198,6 @@ class _CartonEditorState extends State { setState(() { _fcsShipment = v; }); - _loadMixCartons(); }, labelKey: "shipment.pack.fcs.shipment", iconData: Ionicons.ios_airplane, @@ -216,21 +206,6 @@ class _CartonEditorState extends State { values: _fcsShipments, )); - var mixCartonsBox = Container( - padding: EdgeInsets.only(top: 10), - child: LocalDropdown( - callback: (v) { - setState(() { - _mixCarton = v; - }); - }, - labelKey: "box.mix.carton", - iconData: MaterialCommunityIcons.package, - display: (u) => u.cartonNumber, - selectedValue: _mixCarton, - values: _mixCartons, - )); - final fcsIDBox = Container( padding: EdgeInsets.only(top: 10), child: Row( @@ -261,43 +236,8 @@ class _CartonEditorState extends State { iconData: Icons.person, ); - final lengthBox = LengthPicker( - controller: _lengthController, - lableKey: "box.length", - isReadOnly: true, - ); - final widthBox = LengthPicker( - controller: _widthController, - lableKey: "box.width", - isReadOnly: true, - ); - final heightBox = LengthPicker( - controller: _heightController, - lableKey: "box.height", - isReadOnly: true, - ); - - final dimBox = Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: Icon(FontAwesome.arrow_circle_right, color: primaryColor), - ), - SizedBox(child: lengthBox, width: 80), - SizedBox(child: widthBox, width: 80), - SizedBox(child: heightBox, width: 80), - ], - ); - - final shipmentWeightBox = DisplayText( - text: shipmentWeight != null ? shipmentWeight.toStringAsFixed(2) : "", - labelTextKey: "box.shipment_weight", - iconData: MaterialCommunityIcons.weight, - ); - final createBtn = LocalButton( - textKey: "box.create.btn", + textKey: "box.complete.packaging", callBack: _save, ); @@ -316,32 +256,118 @@ class _CartonEditorState extends State { }); }); - final cargoTableTitleBox = LocalTitle( - textKey: "box.cargo.type", - trailing: IconButton( + final cartonTitleBox = Container( + child: LocalTitle( + textKey: "boxes.title", + trailing: IconButton( + icon: Icon( + Icons.add_circle, + color: primaryColor, + ), + onPressed: () async { + bool isFromPackages = _selectedCartonType == carton_from_packages; + if (_user == null && isFromPackages) { + showMsgDialog(context, "Error", "Please select customer"); + return; + } + if (_fcsShipment == null && _isNew) { + showMsgDialog(context, "Error", "Please select FCS shipment"); + return; + } + + double l = double.parse(_lengthController.text, (s) => 0); + double w = double.parse(_widthController.text, (s) => 0); + double h = double.parse(_heightController.text, (s) => 0); + + Carton carton = Carton(); + carton.id = _carton.id; + carton.cartonType = _selectedCartonType; + carton.fcsShipmentID = + _isNew ? _fcsShipment.id : _carton.fcsShipmentID; + carton.userID = _user?.id; + carton.packages = + _carton.packages.where((e) => e.isChecked).toList(); + + carton.cargoTypes = _carton.cargoTypes; + carton.length = l; + carton.width = w; + carton.height = h; + + carton.deliveryAddress = _carton.deliveryAddress; + setState(() { + _isLoading = true; + }); + try { + Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => + PackageCartonEditor(carton: carton, isNew: _isNew)), + ); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + }), + ), + ); + + final mixTypeBox = Container( + padding: EdgeInsets.only(top: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 5), + child: LocalText( + context, + "box.mix_type", + color: primaryColor, + fontSize: 15, + fontWeight: FontWeight.bold, + )), + Row( + children: boxModel.mixTypes.map((e) { + return Row( + children: [ + Radio( + value: e, + groupValue: _selectedMixType, + activeColor: primaryColor, + onChanged: (v) { + setState(() { + _selectedMixType = v; + }); + }, + ), + Text(e), + ], + ); + }).toList(), + ), + ], + ), + ); + + final mixcartonTitleBox = Container( + child: LocalTitle( + textKey: "box.mix_caton_title", + trailing: IconButton( icon: Icon( Icons.add_circle, color: primaryColor, ), - onPressed: () async { - // CargoType cargo = await Navigator.push(context, - // CupertinoPageRoute(builder: (context) => CargoTypeEditor())); - // _addCargo(cargo); - List cargos = await Navigator.push>( - context, - CupertinoPageRoute(builder: (context) => CargoTypeAddition())); - if (cargos == null) return; - setState(() { - _carton.cargoTypes.clear(); - _carton.cargoTypes.addAll(cargos); - }); - }), - ); - - final cargoTableBox = CargoTable( - cargoTypes: _carton.cargoTypes, - onAdd: (c) => _addCargo(c), - onRemove: (c) => _removeCargo(c), + onPressed: () { + Navigator.push( + context, + CupertinoPageRoute(builder: (context) => MixCartonEditor()), + ); + }, + ), + ), ); return LocalProgress( @@ -351,23 +377,24 @@ class _CartonEditorState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), shadowColor: Colors.transparent, backgroundColor: Colors.white, - title: _isNew - ? LocalText( - context, - "boxes.create.title", - fontSize: 20, - color: primaryColor, - ) - : LocalText( - context, - "box.edit.title", - fontSize: 20, - color: primaryColor, - ), + title: LocalText( + context, + "box.info.title", + fontSize: 20, + color: primaryColor, + ), ), body: Padding( padding: const EdgeInsets.all(8.0), @@ -381,21 +408,15 @@ class _CartonEditorState extends State { cartonTypeBox, LocalTitle(textKey: "box.shipment_info"), _isNew ? fcsShipmentsBox : shipmentBox, - isSmallBag - ? _isNew - ? mixCartonsBox - : mixCartonNumberBox - : Container(), + isMixBox ? mixTypeBox : Container(), ...(isMixBox ? [ - // CartonMixTable( - // cartons: _carton.cartons, - // onSelect: (c, check) { - // setState(() { - // c.isChecked = check; - // }); - // }, - // ) + mixcartonTitleBox, + Column( + children: _getMixCartons( + context, + this._mixCartons, + )), ] : [ fcsIDBox, @@ -414,33 +435,17 @@ class _CartonEditorState extends State { _populateDeliveryAddress(); }, ), - cargoTableTitleBox, - cargoTableBox, - isSmallBag - ? Container() - : LocalTitle(textKey: "box.dimension"), - isSmallBag ? Container() : cartonSizeDropdown(), - isSmallBag ? Container() : dimBox, - isSmallBag ? Container() : shipmentWeightBox, - LocalTitle(textKey: "box.delivery_address"), - DefaultDeliveryAddress( - deliveryAddress: _deliveryAddress, - labelKey: "box.delivery_address", - onTap: () async { - DeliveryAddress _address = await Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => DeliveryAddressList( - isAdminCreation: true, - deliveryAddress: _deliveryAddress))); - if (_address == null) return; - setState(() { - _deliveryAddress = _address; - }); - }, - ), + cartonTitleBox, + Column( + children: _getCartons( + context, + this._cartons, + )), ]), - _isNew ? createBtn : saveBtn, + SizedBox( + height: 20, + ), + createBtn, SizedBox( height: 20, ), @@ -451,6 +456,36 @@ class _CartonEditorState extends State { ); } + _addCarton(Carton carton) { + if (carton == null) return; + this._cartons.add(carton); + setState(() {}); + } + + List _getCartons(BuildContext context, List cartons) { + return cartons.map((c) { + return InkWell( + onTap: () {}, + child: CartonRow( + key: ValueKey(c.id), + box: c, + ), + ); + }).toList(); + } + + List _getMixCartons(BuildContext context, List cartons) { + return cartons.map((c) { + return InkWell( + onTap: () {}, + child: CartonRow( + key: ValueKey(c.id), + box: c, + ), + ); + }).toList(); + } + CartonSize selectedCatonSize; Widget cartonSizeDropdown() { List _cartonSizes = @@ -530,92 +565,78 @@ class _CartonEditorState extends State { ); } - List getAddressList( - BuildContext context, List addresses) { - return addresses.asMap().entries.map((s) { - return InkWell( - onTap: () {}, - child: DeliveryAddressRow(deliveryAddress: s.value), - ); - }).toList(); - } - - _addCargo(CargoType cargo) { - if (cargo == null) return; - setState(() { - _carton.cargoTypes.remove(cargo); - _carton.cargoTypes.add(cargo); - }); - } - - _removeCargo(CargoType cargo) { - setState(() { - _carton.cargoTypes.remove(cargo); - }); - } - _save() async { - bool isFromShipment = _selectedCartonType == carton_from_shipments; - bool isSmallBag = _selectedCartonType == carton_small_bag; - if (_user == null && (isFromShipment || isSmallBag)) { - showMsgDialog(context, "Error", "Please select customer"); - return; - } - if (_fcsShipment == null && _isNew) { - showMsgDialog(context, "Error", "Please select FCS shipment"); - return; - } - if ((_carton.cargoTypes?.length ?? 0) == 0 && - (isFromShipment || isSmallBag)) { - showMsgDialog(context, "Error", "Expect at least one cargo type"); - return; - } - double l = double.parse(_lengthController.text, (s) => 0); - double w = double.parse(_widthController.text, (s) => 0); - double h = double.parse(_heightController.text, (s) => 0); - if ((l <= 0 || w <= 0 || h <= 0) && isFromShipment) { - showMsgDialog(context, "Error", "Invalid dimension"); - return; - } - if (_deliveryAddress == null && (isFromShipment || isSmallBag)) { - showMsgDialog(context, "Error", "Invalid delivery address"); - return; - } - if (isSmallBag && _mixCarton == null && _isNew) { - showMsgDialog(context, "Error", "Invalid mix carton"); - return; - } + // bool isFromShipment = _selectedCartonType == carton_from_shipments; + // bool isSmallBag = _selectedCartonType == carton_small_bag; + // if (_user == null && (isFromShipment || isSmallBag)) { + // showMsgDialog(context, "Error", "Please select customer"); + // return; + // } + // if (_fcsShipment == null && _isNew) { + // showMsgDialog(context, "Error", "Please select FCS shipment"); + // return; + // } + // if ((_carton.cargoTypes?.length ?? 0) == 0 && + // (isFromShipment || isSmallBag)) { + // showMsgDialog(context, "Error", "Expect at least one cargo type"); + // return; + // } + // double l = double.parse(_lengthController.text, (s) => 0); + // double w = double.parse(_widthController.text, (s) => 0); + // double h = double.parse(_heightController.text, (s) => 0); + // if ((l <= 0 || w <= 0 || h <= 0) && isFromShipment) { + // showMsgDialog(context, "Error", "Invalid dimension"); + // return; + // } + // if (_deliveryAddress == null && (isFromShipment || isSmallBag)) { + // showMsgDialog(context, "Error", "Invalid delivery address"); + // return; + // } + // if (isSmallBag && _mixCarton == null && _isNew) { + // showMsgDialog(context, "Error", "Invalid mix carton"); + // return; + // } - Carton carton = Carton(); - carton.id = _carton.id; - carton.cartonType = _selectedCartonType; - carton.fcsShipmentID = _isNew ? _fcsShipment.id : _carton.fcsShipmentID; - carton.userID = _user?.id; - carton.cargoTypes = _carton.cargoTypes; - carton.packages = _carton.packages.where((e) => e.isChecked).toList(); - carton.mixCartonID = _mixCarton?.id; - carton.length = l; - carton.width = w; - carton.height = h; - carton.deliveryAddress = _deliveryAddress; - setState(() { - _isLoading = true; - }); - try { - CartonModel cartonModel = - Provider.of(context, listen: false); - if (_isNew) { - await cartonModel.createCarton(carton); - } else { - await cartonModel.updateCarton(carton); - } - Navigator.pop(context, true); - } catch (e) { - showMsgDialog(context, "Error", e.toString()); - } finally { - setState(() { - _isLoading = false; - }); + // Carton carton = Carton(); + // carton.id = _carton.id; + // carton.cartonType = _selectedCartonType; + // carton.fcsShipmentID = _isNew ? _fcsShipment.id : _carton.fcsShipmentID; + // carton.userID = _user?.id; + // carton.cargoTypes = _carton.cargoTypes; + // carton.packages = _carton.packages.where((e) => e.isChecked).toList(); + // carton.mixCartonID = _mixCarton?.id; + // carton.length = l; + // carton.width = w; + // carton.height = h; + // carton.deliveryAddress = _deliveryAddress; + // setState(() { + // _isLoading = true; + // }); + // try { + // CartonModel cartonModel = + // Provider.of(context, listen: false); + // if (_isNew) { + // await cartonModel.createCarton(carton); + // } else { + // await cartonModel.updateCarton(carton); + // } + // Navigator.pop(context, true); + // } catch (e) { + // showMsgDialog(context, "Error", e.toString()); + // } finally { + // setState(() { + // _isLoading = false; + // }); + // } + Navigator.pop(context, true); + } + + isDataChanged() { + if (_isNew) { + return false; + // return _fcsShipment != null || _user != null; + } else { + return false; } } } diff --git a/lib/pages/carton/carton_info.dart b/lib/pages/carton/carton_info.dart index 0cea278..3ee835b 100644 --- a/lib/pages/carton/carton_info.dart +++ b/lib/pages/carton/carton_info.dart @@ -1,4 +1,5 @@ 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'; @@ -20,9 +21,10 @@ import 'package:flutter_icons/flutter_icons.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -import 'carton_cargo_table.dart'; +import 'cargo_table.dart'; import 'carton_editor.dart'; import 'carton_package_table.dart'; +import 'carton_row.dart'; import 'model/carton_model.dart'; import 'widgets.dart'; @@ -43,8 +45,11 @@ class _CartonInfoState extends State { TextEditingController _widthController = new TextEditingController(); TextEditingController _heightController = new TextEditingController(); TextEditingController _lengthController = new TextEditingController(); + TextEditingController _cartonSizeController = new TextEditingController(); double volumetricRatio = 0; double shipmentWeight = 0; + String selectMixBoxType; + List _cartons = []; bool isMixBox; bool isFromShipments; @@ -73,6 +78,7 @@ class _CartonInfoState extends State { _widthController.text = _box.width.toString(); _heightController.text = _box.height.toString(); _lengthController.text = _box.length.toString(); + _cartonSizeController.text = _box.cartonSizeName ?? ""; _deliveryAddress = _box.deliveryAddress; isMixBox = _box.cartonType == carton_mix_box; isFromShipments = _box.cartonType == carton_from_shipments; @@ -81,6 +87,17 @@ class _CartonInfoState extends State { isEdiable = !isMixBox && (isFromPackages || isSmallBag) && _box.status == carton_packed_status; + selectMixBoxType = _box.mixBoxType ?? "Mix Delivery"; + _cartons = [ + Carton( + cartonNumber: "A100B-1#1", + cargoTypes: [CargoType(name: "General", weight: 12)], + userName: "Seven 7"), + Carton( + cartonNumber: "A100B-1#2", + cargoTypes: [CargoType(name: "General", weight: 12)], + userName: "Seven 7"), + ]; } _loadPackages() async { @@ -176,10 +193,10 @@ class _CartonInfoState extends State { ], ); - final shipmentWeightBox = DisplayText( - text: shipmentWeight != null ? shipmentWeight.toStringAsFixed(2) : "", - labelTextKey: "box.shipment_weight", - iconData: MaterialCommunityIcons.weight, + final cartonSizeBox = DisplayText( + text: _cartonSizeController.text, + labelTextKey: "box.carton_size", + iconData: AntDesign.CodeSandbox, ); final cargoTableBox = CargoTable( cargoTypes: _box.cargoTypes, @@ -190,6 +207,36 @@ class _CartonInfoState extends State { iconData: MaterialCommunityIcons.package, ); + final mixTypeBox = Container( + padding: EdgeInsets.only(top: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.only(left: 5), + child: LocalText( + context, + "box.mix_type", + color: primaryColor, + fontSize: 15, + fontWeight: FontWeight.bold, + )), + Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.check, + color: primaryColor, + ), + ), + Text(selectMixBoxType) + ], + ) + ], + ), + ); + return LocalProgress( inAsyncCall: _isLoading, child: Scaffold( @@ -222,7 +269,7 @@ class _CartonInfoState extends State { ), body: Padding( padding: const EdgeInsets.all(10.0), - child: ListView(children: [ + child: ListView(shrinkWrap: true, children: [ Center(child: getCartonNumberStatus(context, _box)), LocalTitle(textKey: "box.type.title"), cartonTypeBox, @@ -231,6 +278,15 @@ class _CartonInfoState extends State { isSmallBag ? mixCartonNumberBox : Container(), isMixBox ? Container() : fcsIDBox, isMixBox ? Container() : customerNameBox, + isMixBox ? mixTypeBox : Container(), + isMixBox ? LocalTitle(textKey: "boxes.title") : Container(), + isMixBox + ? Column( + children: _getCartons( + context, + this._cartons, + )) + : Container(), isFromPackages || isSmallBag ? CartonPackageTable( packages: _box.packages, @@ -241,8 +297,8 @@ class _CartonInfoState extends State { ...(isFromPackages ? [ LocalTitle(textKey: "box.dimension"), + cartonSizeBox, dimBox, - shipmentWeightBox, ] : []), isMixBox @@ -263,6 +319,12 @@ class _CartonInfoState extends State { ); } + List _getCartons(BuildContext context, List cartons) { + return cartons.map((c) { + return CartonRow(box: c); + }).toList(); + } + _gotoEditor() async { bool updated = await Navigator.push( context, diff --git a/lib/pages/carton/carton_row.dart b/lib/pages/carton/carton_row.dart new file mode 100644 index 0000000..0c8eb21 --- /dev/null +++ b/lib/pages/carton/carton_row.dart @@ -0,0 +1,100 @@ +import 'package:fcs/domain/entities/carton.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_icons/flutter_icons.dart'; +import 'package:intl/intl.dart'; + +typedef OnRemove(Carton carton); + +class CartonRow extends StatelessWidget { + final Carton box; + final OnRemove onRemove; + CartonRow({Key key, this.box, this.onRemove}) : super(key: key); + + final double dotSize = 15.0; + final DateFormat dateFormat = new DateFormat("dd MMM yyyy"); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Colors.grey[300]), + ), + ), + child: Row( + children: [ + Expanded( + child: new Padding( + padding: const EdgeInsets.symmetric(vertical: 13.0), + child: new Row( + children: [ + Container( + padding: EdgeInsets.only(left: 5, right: 10), + child: Icon( + MaterialCommunityIcons.package, + color: primaryColor, + size: 30, + ), + ), + new Expanded( + child: new Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + box.cartonNumber ?? "", + style: new TextStyle( + fontSize: 15.0, color: Colors.black), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 10.0, top: 10), + child: new Text( + box.userName ?? "", + style: new TextStyle( + fontSize: 15.0, color: Colors.grey), + ), + ) + ], + ), + ), + ], + ), + ), + ), + Column( + children: [ + onRemove == null + ? Container() + : IconButton( + icon: Icon( + Icons.remove_circle, + color: primaryColor, + ), + onPressed: () { + if (onRemove != null) onRemove(box); + }), + box.actualWeight == 0 + ? Container() + : Padding( + padding: const EdgeInsets.only(left: 8.0, bottom: 5), + child: Row( + children: [ + new Text( + "${box.actualWeight?.toStringAsFixed(2) ?? ''} lb", + style: new TextStyle( + fontSize: 15.0, color: Colors.grey), + ), + ], + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/carton/input_text_border.dart b/lib/pages/carton/input_text_border.dart new file mode 100644 index 0000000..58f2314 --- /dev/null +++ b/lib/pages/carton/input_text_border.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +typedef OnAdd(String value); + +class InputTextBorder extends StatelessWidget { + final OnAdd onAdd; + final TextEditingController controller; + + const InputTextBorder({Key key, this.onAdd, this.controller}) + : super(key: key); + @override + Widget build(BuildContext context) { + return TextFormField( + textAlign: TextAlign.center, + controller: controller, + onChanged: (v) { + if (onAdd != null) onAdd(v); + }, + keyboardType: TextInputType.number, + decoration: new InputDecoration( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + ), + ), + ); + } +} diff --git a/lib/pages/carton/mix_carton_editor.dart b/lib/pages/carton/mix_carton_editor.dart new file mode 100644 index 0000000..7092d75 --- /dev/null +++ b/lib/pages/carton/mix_carton_editor.dart @@ -0,0 +1,128 @@ +import 'package:fcs/domain/entities/carton.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/carton_search/carton_search.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 'carton_row.dart'; + +class MixCartonEditor extends StatefulWidget { + final Carton box; + MixCartonEditor({this.box}); + + @override + _MixCartonEditorState createState() => _MixCartonEditorState(); +} + +class _MixCartonEditorState extends State { + Carton _box; + bool _isLoading = false; + bool _isNew; + List _cartons = []; + + @override + void initState() { + super.initState(); + + if (widget.box != null) { + _box = widget.box; + _isNew = false; + } else { + _isNew = true; + _box = Carton(cargoTypes: []); + } + } + + @override + Widget build(BuildContext context) { + final createBtn = LocalButton( + textKey: "box.mix_carton_btn", + callBack: _creatCarton, + ); + + final cargoTableTitleBox = LocalTitle( + textKey: "boxes.title", + trailing: IconButton( + icon: Icon( + Icons.add_circle, + color: primaryColor, + ), + onPressed: () => searchCarton(context, callbackCartonSelect: (c) { + _addMixCarton(c); + }), + ), + ); + + 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(), + ), + shadowColor: Colors.transparent, + backgroundColor: Colors.white, + title: LocalText( + context, + "box.min_caton.form.title", + fontSize: 20, + color: primaryColor, + ), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + children: [ + cargoTableTitleBox, + Column( + children: _getCartons( + context, + this._cartons, + )), + SizedBox( + height: 20, + ), + createBtn + ], + ), + ), + ), + ); + } + + List _getCartons(BuildContext context, List cartons) { + return cartons.map((c) { + return CartonRow( + key: ValueKey(c.id), + box: c, + onRemove: (carton) { + _removeMixCarton(carton); + }, + ); + }).toList(); + } + + _addMixCarton(Carton carton) { + if (carton == null) return; + if (this._cartons.any((c) => c.cartonNumber == carton.cartonNumber)) return; + setState(() { + this._cartons.add(carton); + }); + } + + _removeMixCarton(Carton carton) { + setState(() { + this._cartons.remove(carton); + }); + } + + _creatCarton() {} +} diff --git a/lib/pages/carton/model/carton_model.dart b/lib/pages/carton/model/carton_model.dart index c503249..4d07acf 100644 --- a/lib/pages/carton/model/carton_model.dart +++ b/lib/pages/carton/model/carton_model.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fcs/data/services/services.dart'; import 'package:fcs/domain/constants.dart'; +import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/vo/message.dart'; import 'package:fcs/domain/vo/shipment_status.dart'; @@ -55,11 +56,8 @@ class CartonModel extends BaseModel { }); } - List cartonTypes = [ - carton_from_packages, - carton_mix_box, - carton_small_bag - ]; + List cartonTypes = [carton_from_packages, carton_mix_box]; + List mixTypes = [mix_delivery, mix_pickup]; List cartonTypesInfo = [ carton_from_packages, carton_mix_box, @@ -220,4 +218,40 @@ class CartonModel extends BaseModel { Future deleteCarton(Carton carton) { return Services.instance.cartonService.deleteCarton(carton); } + + Future> searchCarton(String term) async { + if (term == null || term == '') return List(); + + List _cartons = []; + + try { + _cartons = [ + Carton( + cartonNumber: "A100B-1#1", + cargoTypes: [CargoType(name: "General", weight: 12)], + userName: "Seven 7"), + Carton( + cartonNumber: "A100B-1#2", + cargoTypes: [CargoType(name: "General", weight: 12)], + userName: "Seven 7"), + Carton( + cartonNumber: "A100B-1#3", + cargoTypes: [CargoType(name: "General", weight: 12)], + userName: "Seven 7"), + Carton( + cartonNumber: "A100B-1#4", + cargoTypes: [CargoType(name: "General", weight: 12)], + userName: "Seven 7"), + Carton( + cartonNumber: "A100B-1#5", + cargoTypes: [CargoType(name: "General", weight: 12)], + userName: "Seven 7"), + ]; + } catch (e) { + // permission error + log.warning("user error:" + e.toString()); + return null; + } + return _cartons; + } } diff --git a/lib/pages/carton/package_carton_editor.dart b/lib/pages/carton/package_carton_editor.dart new file mode 100644 index 0000000..3e2726a --- /dev/null +++ b/lib/pages/carton/package_carton_editor.dart @@ -0,0 +1,324 @@ +import 'package:fcs/domain/entities/carton.dart'; +import 'package:fcs/domain/entities/cargo_type.dart'; +import 'package:fcs/domain/entities/carton_size.dart'; +import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/vo/delivery_address.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/carton_size/carton_size_list.dart'; +import 'package:fcs/pages/carton_size/model/carton_size_model.dart'; +import 'package:fcs/pages/delivery_address/model/delivery_address_model.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; +import 'package:fcs/pages/widgets/defalut_delivery_address.dart'; +import 'package:fcs/pages/widgets/delivery_address_selection.dart'; +import 'package:fcs/pages/widgets/display_text.dart'; +import 'package:fcs/pages/widgets/length_picker.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:flutter_icons/flutter_icons.dart'; +import 'package:provider/provider.dart'; + +import 'cargo_type_addtion.dart'; +import 'carton_cargo_table.dart'; +import 'model/carton_model.dart'; + +class PackageCartonEditor extends StatefulWidget { + final Carton carton; + final bool isNew; + PackageCartonEditor({this.carton, this.isNew}); + + @override + _PackageCartonEditorState createState() => _PackageCartonEditorState(); +} + +class _PackageCartonEditorState extends State { + TextEditingController _lengthCtl = new TextEditingController(); + TextEditingController _widthCtl = new TextEditingController(); + TextEditingController _heightCtl = new TextEditingController(); + + Carton _carton; + bool _isLoading = false; + DeliveryAddress _deliveryAddress = new DeliveryAddress(); + + List _cargoTypes = []; + + @override + void initState() { + super.initState(); + if (widget.isNew) { + _carton = widget.carton; + _lengthCtl.text = "0"; + _widthCtl.text = "0"; + _heightCtl.text = "0"; + } else { + _carton = widget.carton; + _cargoTypes = List.from(widget.carton.cargoTypes); + _lengthCtl.text = _carton.length.toString(); + _widthCtl.text = _carton.width.toString(); + _heightCtl.text = _carton.height.toString(); + _deliveryAddress = _carton.deliveryAddress; + } + } + + @override + Widget build(BuildContext context) { + final lengthBox = LengthPicker( + controller: _lengthCtl, + lableKey: "box.length", + isReadOnly: true, + ); + final widthBox = LengthPicker( + controller: _widthCtl, + lableKey: "box.width", + isReadOnly: true, + ); + final heightBox = LengthPicker( + controller: _heightCtl, + lableKey: "box.height", + isReadOnly: true, + ); + final dimBox = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Icon(FontAwesome.arrow_circle_right, color: primaryColor), + ), + SizedBox(child: lengthBox, width: 80), + SizedBox(child: widthBox, width: 80), + SizedBox(child: heightBox, width: 80), + ], + ); + final createBtn = LocalButton( + textKey: "box.new_carton_btn", + callBack: _creatCarton, + ); + + final cargoTableTitleBox = LocalTitle( + textKey: "box.cargo.type", + trailing: IconButton( + icon: Icon( + Icons.add_circle, + color: primaryColor, + ), + onPressed: () async { + List cargos = await Navigator.push>( + context, + CupertinoPageRoute(builder: (context) => CargoTypeAddition())); + if (cargos == null) return; + setState(() { + _cargoTypes.clear(); + _cargoTypes.addAll(cargos); + }); + }), + ); + + final cargoTableBox = CargoTable( + isNew: widget.isNew, + cargoTypes: _cargoTypes, + onAdd: (c) => _addCargo(c), + onRemove: (c) => _removeCargo(c), + ); + + 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(), + ), + shadowColor: Colors.transparent, + backgroundColor: Colors.white, + title: LocalText( + context, + widget.isNew ? "boxes.create.title" : "box.edit.title", + fontSize: 20, + color: primaryColor, + ), + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + children: [ + cargoTableTitleBox, + cargoTableBox, + LocalTitle(textKey: "box.dimension"), + cartonSizeDropdown(), + dimBox, + LocalTitle(textKey: "box.delivery_address"), + DefaultDeliveryAddress( + deliveryAddress: _deliveryAddress, + labelKey: "box.delivery_address", + onTap: () async { + DeliveryAddress d = await Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => DeliveryAddressSelection( + deliveryAddress: _deliveryAddress, + )), + ); + if (d == null) return; + setState(() { + _deliveryAddress = d; + }); + }), + SizedBox( + height: 20, + ), + createBtn + ], + ), + ), + ), + ); + } + + CartonSize selectedCatonSize; + Widget cartonSizeDropdown() { + List _cartonSizes = + Provider.of(context).getCartonSizes; + + return Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 0, right: 10), + child: Icon(AntDesign.CodeSandbox, color: primaryColor), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(right: 18.0), + child: LocalText( + context, + "box.carton_size", + color: Colors.black54, + fontSize: 16, + ), + ), + DropdownButton( + isDense: true, + value: selectedCatonSize, + style: TextStyle(color: Colors.black, fontSize: 14), + underline: Container( + height: 1, + color: Colors.grey, + ), + onChanged: (CartonSize newValue) { + setState(() { + if (newValue.name == MANAGE_CARTONSIZE) { + selectedCatonSize = null; + _manageCartonSize(); + return; + } + selectedCatonSize = newValue; + _widthCtl.text = selectedCatonSize.width.toString(); + _heightCtl.text = selectedCatonSize.height.toString(); + _lengthCtl.text = selectedCatonSize.length.toString(); + }); + }, + isExpanded: true, + items: _cartonSizes + .map>((CartonSize value) { + return DropdownMenuItem( + value: value, + child: Text(value.name ?? "", + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: value.name == MANAGE_CARTONSIZE + ? secondaryColor + : primaryColor)), + ); + }).toList(), + ), + ], + ), + ), + ], + ), + ); + } + + _manageCartonSize() { + Navigator.push( + context, + CupertinoPageRoute(builder: (context) => CartonSizeList()), + ); + } + + _addCargo(CargoType cargo) { + if (cargo == null) return; + setState(() { + _cargoTypes.remove(cargo); + _cargoTypes.add(cargo); + }); + } + + _removeCargo(CargoType cargo) { + setState(() { + _cargoTypes.remove(cargo); + }); + } + + _creatCarton() async { + if ((_cargoTypes?.length ?? 0) == 0) { + showMsgDialog(context, "Error", "Expect at least one cargo type"); + return; + } + double l = double.parse(_lengthCtl.text, (s) => 0); + double w = double.parse(_widthCtl.text, (s) => 0); + double h = double.parse(_heightCtl.text, (s) => 0); + if ((l <= 0 || w <= 0 || h <= 0)) { + showMsgDialog(context, "Error", "Invalid dimension"); + return; + } + if (_deliveryAddress == null) { + showMsgDialog(context, "Error", "Invalid delivery address"); + return; + } + + Carton carton = Carton(); + carton.id = _carton.id; + carton.cartonType = _carton.cartonType; + carton.fcsShipmentID = _carton.fcsShipmentID; + carton.userID = _carton.userID; + carton.cargoTypes = _cargoTypes; + carton.packages = _carton.packages.where((e) => e.isChecked).toList(); + // carton.cartonSizeID = selectedCatonSize?.id; + carton.length = l; + carton.width = w; + carton.height = h; + carton.deliveryAddress = _deliveryAddress; + setState(() { + _isLoading = true; + }); + try { + CartonModel cartonModel = + Provider.of(context, listen: false); + if (widget.isNew) { + await cartonModel.createCarton(carton); + } else { + await cartonModel.updateCarton(carton); + } + Navigator.pop(context, true); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/pages/carton_search/carton_list_row.dart b/lib/pages/carton_search/carton_list_row.dart new file mode 100644 index 0000000..facdb06 --- /dev/null +++ b/lib/pages/carton_search/carton_list_row.dart @@ -0,0 +1,101 @@ +import 'package:fcs/domain/entities/carton.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_icons/flutter_icons.dart'; + +import 'carton_search.dart'; + +class CartonListRow extends StatefulWidget { + final CallbackCartonSelect callbackCartonSelect; + final Carton carton; + const CartonListRow({this.carton, this.callbackCartonSelect}); + + @override + _CartonListRowState createState() => _CartonListRowState(); +} + +class _CartonListRowState extends State { + final double dotSize = 15.0; + Carton _carton; + @override + void initState() { + super.initState(); + this._carton = widget.carton; + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Colors.grey[300]), + ), + ), + child: InkWell( + onTap: () { + Navigator.pop(context); + if (widget.callbackCartonSelect != null) + widget.callbackCartonSelect(widget.carton); + }, + child: Row( + children: [ + Expanded( + child: new Padding( + padding: const EdgeInsets.symmetric(vertical: 13.0), + child: new Row( + children: [ + new Padding( + padding: new EdgeInsets.symmetric( + horizontal: 25.0 - dotSize / 2), + child: Icon( + MaterialCommunityIcons.package, + color: primaryColor, + size: 30, + ), + ), + new Expanded( + child: new Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: new Text( + _carton.cartonNumber ?? "", + style: new TextStyle( + fontSize: 15.0, color: Colors.black), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 10.0, top: 10), + child: new Text( + _carton.userName ?? "", + style: new TextStyle( + fontSize: 15.0, color: Colors.grey), + ), + ) + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 8.0, bottom: 5, right: 10), + child: Row( + children: [ + new Text( + "${_carton.actualWeight?.toStringAsFixed(2) ?? ''} lb", + style: new TextStyle( + fontSize: 15.0, color: Colors.grey), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/carton_search/carton_search.dart b/lib/pages/carton_search/carton_search.dart new file mode 100644 index 0000000..741fc83 --- /dev/null +++ b/lib/pages/carton_search/carton_search.dart @@ -0,0 +1,122 @@ +import 'package:fcs/domain/entities/carton.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/carton/model/carton_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_icons/flutter_icons.dart'; +import 'package:provider/provider.dart'; + +import 'carton_list_row.dart'; + +typedef CallbackCartonSelect(Carton carton); + +Future searchCarton(BuildContext context, + {CallbackCartonSelect callbackCartonSelect}) async => + await showSearch( + context: context, + delegate: PartSearchDelegate(callbackCartonSelect: callbackCartonSelect), + ); + +class PartSearchDelegate extends SearchDelegate { + final CallbackCartonSelect callbackCartonSelect; + PartSearchDelegate({this.callbackCartonSelect}); + + @override + String get searchFieldLabel => 'Search by Carton Number/Customer Name'; + + @override + ThemeData appBarTheme(BuildContext context) { + final ThemeData theme = Theme.of(context); + return theme.copyWith( + inputDecorationTheme: InputDecorationTheme( + hintStyle: TextStyle( + color: theme.primaryTextTheme.caption.color, fontSize: 14)), + textTheme: theme.textTheme.copyWith( + title: theme.textTheme.title.copyWith( + color: theme.primaryTextTheme.title.color, fontSize: 16)), + primaryColor: primaryColor, + ); + } + + @override + List buildActions(BuildContext context) { + return [ + IconButton( + icon: Icon(Icons.clear), + onPressed: () => query = '', + ), + ]; + } + + @override + Widget buildLeading(BuildContext context) { + return IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () => close(context, null), + ); + } + + @override + Widget buildResults(BuildContext context) { + final cartonModel = Provider.of(context); + return FutureBuilder( + future: cartonModel.searchCarton(query), + builder: (context, AsyncSnapshot> snapshot) { + if (snapshot.hasData) { + if (snapshot.data.length == 0) { + return Container( + child: Center( + child: Text( + "No result found", + textAlign: TextAlign.center, + ), + ), + ); + } + return Container( + padding: EdgeInsets.only(top: 15), + child: ListView( + children: snapshot.data + .map((u) => CartonListRow( + carton: u, + callbackCartonSelect: callbackCartonSelect, + )) + .toList(), + ), + ); + } else if (snapshot.hasError) { + return Container( + child: Center( + child: Text( + '${snapshot.error}', + textAlign: TextAlign.center, + ), + ), + ); + } else { + return Container( + child: Center( + child: CircularProgressIndicator( + valueColor: + new AlwaysStoppedAnimation(primaryColor)), + ), + ); + } + }); + } + + @override + Widget buildSuggestions(BuildContext context) { + return Container( + child: Center( + child: Opacity( + opacity: 0.2, + child: Icon( + MaterialCommunityIcons.package, + color: primaryColor, + size: 200, + ), + ), + ), + ); + } +} diff --git a/lib/pages/carton_size/carton_size_editor.dart b/lib/pages/carton_size/carton_size_editor.dart index 6c4d51d..0b3662b 100644 --- a/lib/pages/carton_size/carton_size_editor.dart +++ b/lib/pages/carton_size/carton_size_editor.dart @@ -1,11 +1,8 @@ -import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/carton_size.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/main/util.dart'; -import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; import 'package:fcs/pages/widgets/input_text.dart'; import 'package:fcs/pages/widgets/length_picker.dart'; -import 'package:fcs/pages/widgets/local_dropdown.dart'; import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; @@ -139,7 +136,15 @@ class _CartonSizeEditorState extends State { leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), shadowColor: Colors.transparent, backgroundColor: Colors.white, @@ -165,4 +170,21 @@ class _CartonSizeEditorState extends State { ), ); } + + isDataChanged() { + if (_isNew) { + return _nameController.text != "" || + _lengthController.text != "12" || + _widthController.text != "12" || + _heightController.text != "12"; + } else { + double l = double.parse(_lengthController.text, (s) => 0); + double w = double.parse(_widthController.text, (s) => 0); + double h = double.parse(_heightController.text, (s) => 0); + CartonSize _cartonSize = CartonSize( + name: _nameController.text, length: l, width: w, height: h); + + return widget.cartonSize.isChangedForEdit(_cartonSize); + } + } } diff --git a/lib/pages/carton_size/carton_size_list.dart b/lib/pages/carton_size/carton_size_list.dart index 98a2687..2341edb 100644 --- a/lib/pages/carton_size/carton_size_list.dart +++ b/lib/pages/carton_size/carton_size_list.dart @@ -54,7 +54,7 @@ class _CartonSizeListState extends State { ], ), ), - Spacer(), + //Spacer(), IconButton( icon: Icon(Icons.remove, color: primaryColor), onPressed: () => _remove(p), diff --git a/lib/pages/carton_size/model/carton_size_model.dart b/lib/pages/carton_size/model/carton_size_model.dart index a42d0e3..d9e9c1b 100644 --- a/lib/pages/carton_size/model/carton_size_model.dart +++ b/lib/pages/carton_size/model/carton_size_model.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/carton_size.dart'; +import 'package:fcs/helpers/firebase_helper.dart'; import 'package:fcs/pages/main/model/base_model.dart'; import 'package:logging/logging.dart'; @@ -20,26 +22,49 @@ class CartonSizeModel extends BaseModel { void initUser(user) { super.initUser(user); - cartonSizes = [ - CartonSize( - id: "1", name: "FCS Small Box", length: 16, width: 12, height: 12), - CartonSize( - id: "2", name: "FCS Medium Box", length: 22, width: 16, height: 15), - CartonSize( - id: "3", name: "FCS Large Box", length: 28, width: 15, height: 16) - ]; + _loadCartonSizes(); + } + + Future _loadCartonSizes() async { + try { + if (listener != null) listener.cancel(); + + listener = Firestore.instance + .collection( + "/$config_collection/$setting_doc_id/$carton_sizes_collection") + .snapshots() + .listen((QuerySnapshot snapshot) { + cartonSizes.clear(); + cartonSizes = snapshot.documents.map((documentSnapshot) { + var c = CartonSize.fromMap( + documentSnapshot.data, documentSnapshot.documentID); + return c; + }).toList(); + notifyListeners(); + }); + } catch (e) { + log.warning("Error!! $e"); + } } @override logout() async { if (listener != null) await listener.cancel(); - cartonSizes = []; } - Future addCartonSize(CartonSize cartonSize) async {} + Future addCartonSize(CartonSize cartonSize) async { + await request("/carton_sizes", "POST", + payload: cartonSize.toMap(), token: await getToken()); + } - Future updateCartonSize(CartonSize cartonSize) async {} + Future updateCartonSize(CartonSize cartonSize) async { + await request("/carton_sizes", "PUT", + payload: cartonSize.toMap(), token: await getToken()); + } - Future deleteCartonSize(String id) async {} + Future deleteCartonSize(String id) async { + await request("/carton_sizes", "DELETE", + payload: {"id": id}, token: await getToken()); + } } diff --git a/lib/pages/contact/contact_editor.dart b/lib/pages/contact/contact_editor.dart index e91085a..5a7cf50 100644 --- a/lib/pages/contact/contact_editor.dart +++ b/lib/pages/contact/contact_editor.dart @@ -29,17 +29,21 @@ class _ContactEditorState extends State { TextEditingController _facebook = new TextEditingController(); bool _isLoading = false; + bool isNew; @override void initState() { super.initState(); if (widget.contact != null) { + isNew = false; _usaPhone.text = widget.contact.usaContactNumber; _mmPhone.text = widget.contact.mmContactNumber; _usaAddress.text = widget.contact.usaAddress; _mmAddress.text = widget.contact.mmAddress; _email.text = widget.contact.emailAddress; _facebook.text = widget.contact.facebookLink; + } else { + isNew = true; } } @@ -81,9 +85,10 @@ class _ContactEditorState extends State { appBar: AppBar( centerTitle: true, leading: new IconButton( - icon: new Icon(CupertinoIcons.back, color: primaryColor), - onPressed: () => Navigator.of(context).pop(), - ), + icon: new Icon(CupertinoIcons.back, color: primaryColor), + onPressed: () { + Navigator.of(context).pop(); + }), shadowColor: Colors.transparent, backgroundColor: Colors.white, title: LocalText( diff --git a/lib/pages/customer/invitation_create.dart b/lib/pages/customer/invitation_create.dart index 1d3c1fc..51fd10c 100644 --- a/lib/pages/customer/invitation_create.dart +++ b/lib/pages/customer/invitation_create.dart @@ -51,7 +51,15 @@ class _InvitationCreateState extends State { CupertinoIcons.back, color: primaryColor, ), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), title: LocalText( context, @@ -162,4 +170,10 @@ class _InvitationCreateState extends State { }); } } + + isDataChanged() { + String userName = _nameController.text; + String phoneNumber = _phoneController.text; + return userName != "" || phoneNumber != ""; + } } diff --git a/lib/pages/delivery/delivery_info.dart b/lib/pages/delivery/delivery_info.dart index dc0a5c4..f9fecd2 100644 --- a/lib/pages/delivery/delivery_info.dart +++ b/lib/pages/delivery/delivery_info.dart @@ -237,7 +237,9 @@ class _DeliveryInfoState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + Navigator.of(context).pop(); + }, ), shadowColor: Colors.transparent, backgroundColor: Colors.white, diff --git a/lib/pages/delivery_address/delivery_address_editor.dart b/lib/pages/delivery_address/delivery_address_editor.dart index f25fce8..26bbccd 100644 --- a/lib/pages/delivery_address/delivery_address_editor.dart +++ b/lib/pages/delivery_address/delivery_address_editor.dart @@ -106,7 +106,15 @@ class _DeliveryAddressEditorState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), backgroundColor: Colors.white, shadowColor: Colors.transparent, @@ -248,4 +256,18 @@ class _DeliveryAddressEditorState extends State { }); } } + + isDataChanged() { + if (_isNew) { + return _nameController.text != "" || + _phoneController.text != "" || + _address1Controller.text != "" || + _address2Controller.text != "" || + _cityController.text != "Yangon" || + _stateController.text != "Yangon"; + } else { + DeliveryAddress deliveryAddress = _getPayload(); + return widget.deliveryAddress.isChangedForEdit(deliveryAddress); + } + } } diff --git a/lib/pages/discount/discount_editor.dart b/lib/pages/discount/discount_editor.dart index e8d0a15..d28856f 100644 --- a/lib/pages/discount/discount_editor.dart +++ b/lib/pages/discount/discount_editor.dart @@ -40,7 +40,7 @@ class _DiscountEditorState extends State { if (widget.discount != null) { _discount = widget.discount; _codeController.text = _discount.code; - _amountController.text = _discount.amount.toString(); + _amountController.text = _discount.amount.toStringAsFixed(2); _statusController.text = _discount.status; customerName = widget.discount.customerName; customerId = widget.discount.customerId; @@ -97,7 +97,15 @@ class _DiscountEditorState extends State { ), leading: new IconButton( icon: new Icon(CupertinoIcons.back), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), backgroundColor: primaryColor, actions: [ @@ -188,4 +196,19 @@ class _DiscountEditorState extends State { }); } } + + isDataChanged() { + if (_isNew) { + return _codeController.text != "" || + _amountController.text != "" || + customerName != ""; + } else { + Discount _discount = Discount( + code: _codeController.text, + customerName: customerName, + customerId: customerId, + amount: double.parse(_amountController.text)); + return widget.discount.isChangedForEdit(_discount); + } + } } diff --git a/lib/pages/discount/discount_list_row.dart b/lib/pages/discount/discount_list_row.dart index 9047f3b..868905d 100644 --- a/lib/pages/discount/discount_list_row.dart +++ b/lib/pages/discount/discount_list_row.dart @@ -80,7 +80,7 @@ class DiscountListRow extends StatelessWidget { child: Row( children: [ new Text( - "${discount.amount ?? ''}", + "${discount.amount.toStringAsFixed(2) ?? ''}", style: new TextStyle(fontSize: 15.0, color: Colors.grey), ), diff --git a/lib/pages/faq/faq_edit_page.dart b/lib/pages/faq/faq_edit_page.dart index 69eb9be..73045be 100644 --- a/lib/pages/faq/faq_edit_page.dart +++ b/lib/pages/faq/faq_edit_page.dart @@ -126,7 +126,15 @@ class _FAQEditorState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), backgroundColor: Colors.white, shadowColor: Colors.transparent, @@ -236,4 +244,15 @@ class _FAQEditorState extends State { }); } } + + isDataChanged() { + return _sn.text != "" || + _engQ.text != "" || + _engA.text != "" || + _mmQ.text != "" || + _mmA.text != "" || + _pageLabelEng.text != "" || + _pageLabelMm.text != "" || + _pageLink != info; + } } diff --git a/lib/pages/fcs_shipment/fcs_shipment_editor.dart b/lib/pages/fcs_shipment/fcs_shipment_editor.dart index 43643af..82feb2e 100644 --- a/lib/pages/fcs_shipment/fcs_shipment_editor.dart +++ b/lib/pages/fcs_shipment/fcs_shipment_editor.dart @@ -59,9 +59,9 @@ class _FcsShipmentEditorState extends State { _consigneeController.text = _shipment.consignee; _portController.text = _shipment.port; _destinationController.text = _shipment.destination; - }else{ - var mainModel = Provider.of(context,listen: false); - _currentShipmentType =mainModel.setting.shipmentTypes[0]; + } else { + var mainModel = Provider.of(context, listen: false); + _currentShipmentType = mainModel.setting.shipmentTypes[0]; } } @@ -93,7 +93,15 @@ class _FcsShipmentEditorState extends State { shadowColor: Colors.transparent, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), backgroundColor: Colors.white, title: LocalText( @@ -267,4 +275,20 @@ class _FcsShipmentEditorState extends State { }); } } + + isDataChanged() { + if (_isNew) { + var mainModel = Provider.of(context, listen: false); + return _shipmentNumberController.text != "" || + _cutoffDateController.text != "" || + _arrivalDateController.text != "" || + _consigneeController.text != "" || + _portController.text != "" || + _destinationController.text != "" || + _currentShipmentType != mainModel.setting.shipmentTypes[0]; + } else { + FcsShipment fcsShipment = _getPayload(); + return widget.shipment.isChangedForEdit(fcsShipment); + } + } } diff --git a/lib/pages/invoice/editor/invoice_editor.dart b/lib/pages/invoice/editor/invoice_editor.dart index 783a4f2..16f3420 100644 --- a/lib/pages/invoice/editor/invoice_editor.dart +++ b/lib/pages/invoice/editor/invoice_editor.dart @@ -315,7 +315,9 @@ class _InvoiceEditorState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + Navigator.of(context).pop(); + }, ), backgroundColor: Colors.white, shadowColor: Colors.transparent, diff --git a/lib/pages/package/package_editor.dart b/lib/pages/package/package_editor.dart index 7216f63..0f488f3 100644 --- a/lib/pages/package/package_editor.dart +++ b/lib/pages/package/package_editor.dart @@ -91,7 +91,11 @@ class _PackageEditorPageState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + }, ), shadowColor: Colors.transparent, backgroundColor: Colors.white, diff --git a/lib/pages/package/package_info.dart b/lib/pages/package/package_info.dart index 7380cd8..5d18dda 100644 --- a/lib/pages/package/package_info.dart +++ b/lib/pages/package/package_info.dart @@ -48,7 +48,10 @@ class _PackageInfoState extends State { initPackage(widget.package); } - initPackage(Package package) { + initPackage(Package pkg) async { + PackageModel packageModel = + Provider.of(context, listen: false); + Package package = await packageModel.getPackageByTrackingID(pkg.trackingID); setState(() { _package = package; multiImgController.setImageUrls = package.photoUrls; @@ -63,38 +66,38 @@ class _PackageInfoState extends State { @override Widget build(BuildContext context) { String id = Provider.of(context).user.id; - bool owner = _package.userID == id; + bool owner = _package?.userID == id; bool canChangeDeliveryAddress = - _package.status == package_received_status || - _package.status == package_processed_status; + _package?.status == package_received_status || + _package?.status == package_processed_status; final trackingIdBox = DisplayText( - text: _package.trackingID, + text: _package?.trackingID??"", labelTextKey: "package.tracking.id", iconData: MaterialCommunityIcons.barcode_scan, ); var fcsIDBox = DisplayText( - text: _package.fcsID, + text: _package?.fcsID??"", labelTextKey: "processing.fcs.id", icon: FcsIDIcon(), ); final customerNameBox = DisplayText( - text: _package.userName, + text: _package?.userName??"", labelTextKey: "package.create.name", iconData: Icons.perm_identity, ); final marketBox = DisplayText( - text: _package.market ?? "-", + text: _package?.market ?? "-", labelTextKey: "package.create.market", iconData: Icons.store, ); final descBox = DisplayText( - text: _package.desc ?? "-", + text: _package?.desc ?? "-", labelTextKey: "package.edit.desc", iconData: MaterialCommunityIcons.message_text_outline, ); final remarkBox = DisplayText( - text: _package.remark ?? "-", + text: _package?.remark ?? "-", labelTextKey: "package.edit.remark", iconData: Entypo.new_message, ); @@ -109,7 +112,7 @@ class _PackageInfoState extends State { callBack: _return, ); final deliveryAddressBox = DefaultDeliveryAddress( - deliveryAddress: _package.deliveryAddress, + deliveryAddress: _package?.deliveryAddress, labelKey: "package.delivery.address", onTap: owner && canChangeDeliveryAddress ? () async { @@ -155,10 +158,10 @@ class _PackageInfoState extends State { widget.isSearchResult ? Container() : fcsIDBox, widget.isSearchResult ? Container() : customerNameBox, widget.isSearchResult ? Container() : marketBox, - _package.photoUrls.length == 0 ? Container() : img, + _package==null || _package.photoUrls.length == 0 ? Container() : img, widget.isSearchResult ? Container() : descBox, remarkBox, - _package.status == package_received_status && + _package?.status == package_received_status && widget.isCustomer ? returnButton : Container(), @@ -166,8 +169,8 @@ class _PackageInfoState extends State { widget.isSearchResult ? Container() : StatusTree( - shipmentHistory: _package.shipmentHistory, - currentStatus: _package.status), + shipmentHistory: _package?.shipmentHistory, + currentStatus: _package?.status), SizedBox( height: 20, ) diff --git a/lib/pages/payment_methods/payment_method_editor.dart b/lib/pages/payment_methods/payment_method_editor.dart index ec9be49..9118cdf 100644 --- a/lib/pages/payment_methods/payment_method_editor.dart +++ b/lib/pages/payment_methods/payment_method_editor.dart @@ -60,7 +60,15 @@ class _PaymentMethodEditorState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), title: LocalText( context, @@ -210,4 +218,13 @@ class _PaymentMethodEditorState extends State { }); } } + + isDataChanged() { + return _nameController.text != "" || + _accountNameController.text != "" || + _accountNumberController.text != "" || + _phoneController.text != "" || + _mailController.text != "" || + _linkController.text != ""; + } } diff --git a/lib/pages/processing/package_editor.dart b/lib/pages/processing/package_editor.dart index e997eca..4c3d49a 100644 --- a/lib/pages/processing/package_editor.dart +++ b/lib/pages/processing/package_editor.dart @@ -1,8 +1,10 @@ import 'package:fcs/domain/entities/market.dart'; import 'package:fcs/domain/entities/package.dart'; +import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/market/market_editor.dart'; import 'package:fcs/pages/market/model/market_model.dart'; +import 'package:fcs/pages/package/model/package_model.dart'; import 'package:fcs/pages/package/tracking_id_page.dart'; import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/widgets/barcode_scanner.dart'; @@ -19,7 +21,9 @@ import 'package:provider/provider.dart'; class PackageEditor extends StatefulWidget { final Package package; - PackageEditor({this.package}); + final User consignee; + final User sender; + PackageEditor({this.package, this.consignee, this.sender}); @override _PackageEditorState createState() => _PackageEditorState(); @@ -260,17 +264,24 @@ class _PackageEditorState extends State { setState(() { _isLoading = true; }); - + PackageModel packageModel = + Provider.of(context, listen: false); try { - this._package.trackingID = _trackingIDCtl.text; - this._package.market = selectedMarket; - this._package.desc = _descCtl.text; - this._package.remark = _remarkCtl.text; - this._package.photoFiles = _isNew + Package package=await packageModel.getPackageByTrackingID(_trackingIDCtl.text); + package.trackingID = _trackingIDCtl.text; + package.market = selectedMarket; + package.desc = _descCtl.text; + package.remark = _remarkCtl.text; + package.photoFiles = _isNew ? multiImgController.getAddedFile : multiImgController.getUpdatedFile; + package.fcsID=widget.consignee.fcsID; + package.senderFCSID=widget.sender?.fcsID; - Navigator.pop(context, this._package); + await packageModel.updateProcessing(package, + multiImgController.getAddedFile, multiImgController.getDeletedUrl); + + Navigator.pop(context, package); } catch (e) { showMsgDialog(context, "Error", e.toString()); } finally { diff --git a/lib/pages/processing/processing_edit_editor.dart b/lib/pages/processing/processing_edit_editor.dart index 839d6a1..908dbd4 100644 --- a/lib/pages/processing/processing_edit_editor.dart +++ b/lib/pages/processing/processing_edit_editor.dart @@ -117,7 +117,15 @@ class _ProcessingEditEditorState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), shadowColor: Colors.transparent, backgroundColor: Colors.white, @@ -257,4 +265,12 @@ class _ProcessingEditEditorState extends State { }); } } + + isDataChanged() { + return _user.fcsID != "" || + selectedMarket != "" || + _descCtl.text != "" || + _remarkCtl.text != "" || + multiImgController.getAddedFile.isNotEmpty; + } } diff --git a/lib/pages/processing/processing_editor.dart b/lib/pages/processing/processing_editor.dart index 6021019..dab0ad2 100644 --- a/lib/pages/processing/processing_editor.dart +++ b/lib/pages/processing/processing_editor.dart @@ -3,6 +3,7 @@ import 'package:fcs/domain/entities/processing.dart'; import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/package/package_info.dart'; import 'package:fcs/pages/user_search/user_serach.dart'; import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/fcs_id_icon.dart'; @@ -27,8 +28,8 @@ class _ProcesingEditorState extends State { Processing processing = Processing(); bool _isLoading = false; bool _isNew; - User user; - User shipper; + User consignee; + User sender; List packages = []; @override @@ -37,11 +38,11 @@ class _ProcesingEditorState extends State { _isNew = widget.processing == null; if (!_isNew) { processing = widget.processing; - user = User( + consignee = User( fcsID: processing.userID, name: processing.userName, phoneNumber: processing.userPhoneNumber); - shipper = User( + sender = User( fcsID: processing.fcsID, name: processing.shipperName, phoneNumber: processing.shipperPhoneNumber); @@ -55,7 +56,7 @@ class _ProcesingEditorState extends State { children: [ Expanded( child: DisplayText( - text: user != null ? user.fcsID : "", + text: consignee != null ? consignee.fcsID : "", labelTextKey: "processing.fcs.id", icon: FcsIDIcon(), )), @@ -63,21 +64,21 @@ class _ProcesingEditorState extends State { icon: Icon(Icons.search, color: primaryColor), onPressed: () => searchUser(context, callbackUserSelect: (u) { setState(() { - this.user = u; + this.consignee = u; }); })), ], ); final phoneNumberBox = DisplayText( - text: user != null ? user.phoneNumber : "", + text: consignee != null ? consignee.phoneNumber : "", labelTextKey: "processing.phone", maxLines: 2, iconData: Icons.phone, ); final namebox = DisplayText( - text: user != null ? user.name : "", + text: consignee != null ? consignee.name : "", labelTextKey: "processing.consignee.name", maxLines: 2, iconData: Icons.person, @@ -97,7 +98,7 @@ class _ProcesingEditorState extends State { children: [ Expanded( child: DisplayText( - text: shipper != null ? shipper.fcsID : "", + text: sender != null ? sender.fcsID : "", labelTextKey: "processing.fcs.id", icon: FcsIDIcon(), )), @@ -105,21 +106,21 @@ class _ProcesingEditorState extends State { icon: Icon(Icons.search, color: primaryColor), onPressed: () => searchUser(context, callbackUserSelect: (u) { setState(() { - this.shipper = u; + this.sender = u; }); })), ], ); final shipperPhoneNumberBox = DisplayText( - text: shipper != null ? shipper.phoneNumber : "", + text: sender != null ? sender.phoneNumber : "", labelTextKey: "processing.phone", maxLines: 2, iconData: Icons.phone, ); final shipperNamebox = DisplayText( - text: shipper != null ? shipper.name : "", + text: sender != null ? sender.name : "", labelTextKey: "processing.shipper.name", maxLines: 2, iconData: Icons.person, @@ -146,9 +147,17 @@ class _ProcesingEditorState extends State { color: primaryColor, ), onPressed: () async { + if (this.consignee == null) { + showMsgDialog(context, "Warning", "Please select 'Consignee'"); + return; + } Package _package = await Navigator.push( context, - CupertinoPageRoute(builder: (context) => PackageEditor()), + CupertinoPageRoute( + builder: (context) => PackageEditor( + sender: this.sender, + consignee: this.consignee, + )), ); _addPackage(_package); // _savePackage(_package); @@ -171,7 +180,15 @@ class _ProcesingEditorState extends State { leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), shadowColor: Colors.transparent, backgroundColor: Colors.white, @@ -216,64 +233,17 @@ class _ProcesingEditorState extends State { List _getPackages(BuildContext context, List packages) { return packages.map((p) { - return Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: Colors.grey[300]), - ), - ), - child: InkWell( - onTap: () async { - Package _package = await Navigator.of(context).push( - CupertinoPageRoute( - builder: (context) => PackageEditor(package: p))); - // setState(() { - // _savePackage(_package); - // }); - _savePackage(_package); - }, - child: Row( - children: [ - Expanded( - child: new Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: new Row( - children: [ - new Padding( - padding: new EdgeInsets.symmetric( - horizontal: 30.0 - 15.0 / 2), - child: Stack( - alignment: AlignmentDirectional.bottomEnd, - children: [ - Icon( - Octicons.package, - color: primaryColor, - size: 30, - ), - ], - ), - ), - new Expanded( - child: new Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - new Text( - p.trackingID, - style: new TextStyle(fontSize: 15.0), - ), - ], - ), - ), - IconButton( - icon: Icon(Icons.remove, color: primaryColor), - onPressed: () => _removePackage(p), - ) - ], - ), - ), - ), - ], - ), + return InkWell( + onTap: () async { + Package _package = await Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => PackageInfo(package: p))); + _savePackage(_package); + }, + child: DisplayText( + labelTextKey: "processing.tracking.id", + iconData: MaterialCommunityIcons.barcode_scan, + text: p.trackingID, ), ); }).toList(); @@ -320,4 +290,16 @@ class _ProcesingEditorState extends State { }); } } + + isDataChanged() { + if (_isNew) { + return this.packages.isNotEmpty || consignee != null || sender != null; + } else { + Processing _processing = Processing( + userID: consignee.fcsID, + fcsID: sender.fcsID, + packages: this.packages); + return widget.processing.isChangedForEdit(_processing); + } + } } diff --git a/lib/pages/profile/profile_currency_edit.dart b/lib/pages/profile/profile_currency_edit.dart index 6d91664..92fe1c8 100644 --- a/lib/pages/profile/profile_currency_edit.dart +++ b/lib/pages/profile/profile_currency_edit.dart @@ -59,7 +59,9 @@ class _ProfileCurrencyEditState extends State { size: 35, color: primaryColor, ), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + Navigator.of(context).pop(); + }, ), ), body: Column( diff --git a/lib/pages/profile/profile_edit.dart b/lib/pages/profile/profile_edit.dart index 23f4a37..a4ab671 100644 --- a/lib/pages/profile/profile_edit.dart +++ b/lib/pages/profile/profile_edit.dart @@ -74,7 +74,9 @@ class _ProfileEditState extends State { size: 35, color: primaryColor, ), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + Navigator.of(context).pop(); + }, ), ), body: Column( diff --git a/lib/pages/rates/cargo_editor.dart b/lib/pages/rates/cargo_editor.dart index a570b1d..d084754 100644 --- a/lib/pages/rates/cargo_editor.dart +++ b/lib/pages/rates/cargo_editor.dart @@ -32,7 +32,7 @@ class _CargoEditorState extends State { if (widget.cargo != null) { _cargo = widget.cargo; _descController.text = _cargo.name; - _rateController.text = _cargo.rate.toString(); + _rateController.text = _cargo.rate.toStringAsFixed(2); } else { _isNew = true; } @@ -60,7 +60,15 @@ class _CargoEditorState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), backgroundColor: primaryColor, title: Text(AppTranslations.of(context).text("cargo.form.title")), @@ -140,4 +148,14 @@ class _CargoEditorState extends State { }); } } + + isDataChanged() { + if (_isNew) { + return _descController.text != "" || _rateController.text != ""; + } else { + CargoType _cargo = CargoType( + name: _descController.text, rate: double.parse(_rateController.text)); + return widget.cargo.isChangedForEdit(_cargo); + } + } } diff --git a/lib/pages/rates/cargo_type_list.dart b/lib/pages/rates/cargo_type_list.dart index 4b4aa14..1e766c3 100644 --- a/lib/pages/rates/cargo_type_list.dart +++ b/lib/pages/rates/cargo_type_list.dart @@ -75,8 +75,8 @@ class _CargoTypeListState extends State { ))); }, child: Container( - child: _row(cargo.name, "\$ " + cargo.rate.toString(), - 'per pound'), + child: _row(cargo.name, + "\$ " + cargo.rate.toStringAsFixed(2), 'per pound'), ), ); }), diff --git a/lib/pages/rates/custom_editor.dart b/lib/pages/rates/custom_editor.dart index 747716d..9633fb2 100644 --- a/lib/pages/rates/custom_editor.dart +++ b/lib/pages/rates/custom_editor.dart @@ -22,6 +22,7 @@ class CustomEditor extends StatefulWidget { class _CustomEditorState extends State { TextEditingController _productController = new TextEditingController(); TextEditingController _feeController = new TextEditingController(); + TextEditingController _shipmentRateController = new TextEditingController(); bool _isLoading = false; CustomDuty _custom = new CustomDuty(); @@ -33,7 +34,10 @@ class _CustomEditorState extends State { if (widget.custom != null) { _custom = widget.custom; _productController.text = _custom.productType; - _feeController.text = _custom.fee.toString(); + _feeController.text = _custom.fee.toStringAsFixed(2); + _shipmentRateController.text = _custom.shipmentRate == null + ? "" + : _custom.shipmentRate.toStringAsFixed(2); } else { _isNew = true; } @@ -54,6 +58,11 @@ class _CustomEditorState extends State { labelTextKey: 'rate.custom.fee', iconData: Icons.attach_money, controller: _feeController); + + final shipmentRateBox = InputText( + labelTextKey: 'rate.custom.shipment_rate', + iconData: Icons.attach_money, + controller: _shipmentRateController); return LocalProgress( inAsyncCall: _isLoading, child: Scaffold( @@ -63,7 +72,15 @@ class _CustomEditorState extends State { icon: new Icon( CupertinoIcons.back, ), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), backgroundColor: primaryColor, title: @@ -84,6 +101,7 @@ class _CustomEditorState extends State { children: [ productBox, feeBox, + shipmentRateBox, SizedBox(height: 30), ], ), @@ -107,7 +125,8 @@ class _CustomEditorState extends State { Provider.of(context, listen: false); CustomDuty _customduty = CustomDuty( productType: _productController.text, - fee: double.parse(_feeController.text)); + fee: double.parse(_feeController.text), + shipmentRate: double.parse(_shipmentRateController.text)); print('_customduty => $_customduty'); if (_isNew) { await shipmentRateModel.addCustomDuty(_customduty); @@ -147,4 +166,19 @@ class _CustomEditorState extends State { }); } } + + isDataChanged() { + if (_isNew) { + return _productController.text != "" || + _feeController.text != "" || + _shipmentRateController.text != ""; + } else { + CustomDuty _customduty = CustomDuty( + productType: _productController.text, + fee: double.parse(_feeController.text), + // shipmentRate: double.parse(_shipmentRateController.text) + ); + return widget.custom.isChangedForEdit(_customduty); + } + } } diff --git a/lib/pages/rates/custom_list.dart b/lib/pages/rates/custom_list.dart index 41946e0..dc65fed 100644 --- a/lib/pages/rates/custom_list.dart +++ b/lib/pages/rates/custom_list.dart @@ -10,8 +10,6 @@ import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; - -import 'custom_row.dart'; import 'model/shipment_rate_model.dart'; class CustomList extends StatefulWidget { @@ -88,7 +86,11 @@ class _CustomListState extends State { }, child: Container( child: _row( - custom.productType, "\$ " + custom.fee.toString()), + custom.productType, + "\$ " + custom.fee.toStringAsFixed(2), + custom.shipmentRate == null + ? "" + : "\$ " + custom.shipmentRate.toStringAsFixed(2)), ), ); }), @@ -96,7 +98,7 @@ class _CustomListState extends State { ); } - _row(String desc, String price) { + _row(String desc, String fee, String shipmentRate) { return Container( padding: EdgeInsets.only(left: 25, top: 5, bottom: 5), child: Row( @@ -109,10 +111,19 @@ class _CustomListState extends State { Padding( padding: const EdgeInsets.only(bottom: 3.0), child: Text( - '$price', + '$fee', style: TextStyle(color: primaryColor, fontSize: 14), ), ), + shipmentRate == "" + ? Container() + : Padding( + padding: const EdgeInsets.only(top: 3.0), + child: Text( + "\$ " + "$shipmentRate", + style: TextStyle(color: Colors.grey, fontSize: 14), + ), + ) ], ), SizedBox( diff --git a/lib/pages/rates/discount_by_weight_editor.dart b/lib/pages/rates/discount_by_weight_editor.dart index 018c942..973d156 100644 --- a/lib/pages/rates/discount_by_weight_editor.dart +++ b/lib/pages/rates/discount_by_weight_editor.dart @@ -62,7 +62,15 @@ class _DiscountByWeightEditorState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), backgroundColor: primaryColor, title: Text(AppTranslations.of(context).text("discount.new")), @@ -145,4 +153,15 @@ class _DiscountByWeightEditorState extends State { }); } } + + isDataChanged() { + if (_isNew) { + return _weightController.text != "" || _discountController.text != ""; + } else { + DiscountByWeight _discount = DiscountByWeight( + weight: double.parse(_weightController.text), + discount: double.parse(_discountController.text)); + return widget.discountByWeight.isChangedForEdit(_discount); + } + } } diff --git a/lib/pages/rates/shipment_rates.dart b/lib/pages/rates/shipment_rates.dart index aa351a6..0da9be3 100644 --- a/lib/pages/rates/shipment_rates.dart +++ b/lib/pages/rates/shipment_rates.dart @@ -138,11 +138,21 @@ class _ShipmentRatesState extends State { color: Colors.grey, ), _row("Free delivery within Yangon \nfor shipments over", - "${rate.freeDeliveryWeight}", "pounds"), - _row("Delivery fees", "\$ ${rate.deliveryFee}", - "below ${rate.freeDeliveryWeight} pounds"), - _row("Volumetric Ratio", "${rate.volumetricRatio}", + "${rate.freeDeliveryWeight.toStringAsFixed(2)}", "pounds"), + _row("Delivery fees", "\$ ${rate.deliveryFee.toStringAsFixed(2)}", + "below ${rate.freeDeliveryWeight.toStringAsFixed(2)} pounds"), + _row( + "Volumetric Ratio", + "${rate.volumetricRatio.toStringAsFixed(2)}", "in3 per pound"), + _row( + "Weight difference discount", + "${rate.diffDiscountWeight.toStringAsFixed(2)}", + "pounds"), + _row( + "Weight difference rate", + "\$ ${rate.diffWeightRate.toStringAsFixed(2)}", + ""), Divider( color: Colors.grey, ), @@ -179,7 +189,8 @@ class _ShipmentRatesState extends State { List getCargoWidget(List cargos) { return cargos.map((cargo) { return Container( - child: _row(cargo.name, "\$ " + cargo.rate.toString(), 'per pound'), + child: _row( + cargo.name, "\$ " + cargo.rate.toStringAsFixed(2), 'per pound'), ); }).toList(); } @@ -187,7 +198,7 @@ class _ShipmentRatesState extends State { List getCustonWidget(List customs) { return customs.map((c) { return Container( - child: _row(c.productType, "\$ " + c.fee.toString(), ''), + child: _row(c.productType, "\$ " + c.fee.toStringAsFixed(2), ''), ); }).toList(); } @@ -197,7 +208,7 @@ class _ShipmentRatesState extends State { return discounts.map((d) { return Container( child: _row("${d.weight.toStringAsFixed(2)} lb", - "\$ " + d.discount.toString(), ''), + "\$ " + d.discount.toStringAsFixed(2), ''), ); }).toList(); } diff --git a/lib/pages/rates/shipment_rates_calculate.dart b/lib/pages/rates/shipment_rates_calculate.dart index 7b7b3e4..5406fc2 100644 --- a/lib/pages/rates/shipment_rates_calculate.dart +++ b/lib/pages/rates/shipment_rates_calculate.dart @@ -2,6 +2,7 @@ import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/rate.dart'; import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/input_text.dart'; @@ -46,7 +47,7 @@ class _ShipmentRatesCalState extends State { _lengthController.text = '12'; _widthController.text = '12'; _heightController.text = '12'; - _actualWeightCtl.text = "10"; + _actualWeightCtl.text = "10.00"; _calShipmentWeight(); } @@ -68,7 +69,7 @@ class _ShipmentRatesCalState extends State { _deliveryFee = effectiveWeight > rate.freeDeliveryWeight ? 0 : rate.deliveryFee; _amount = amount == null ? 0 : amount + _deliveryFee; - _shipmentWeight = shipmentWeight; + _shipmentWeight = shipmentWeight.toDouble(); }); } @@ -141,7 +142,9 @@ class _ShipmentRatesCalState extends State { centerTitle: true, leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + Navigator.of(context).pop(); + }, ), backgroundColor: Colors.white, shadowColor: Colors.transparent, @@ -163,7 +166,7 @@ class _ShipmentRatesCalState extends State { LocalText(context, "rate.delivery_fee", color: primaryColor, fontSize: 16), Text( - ':\$ $_deliveryFee', + ':\$ ${_deliveryFee.toStringAsFixed(2)}', style: TextStyle( color: primaryColor, fontSize: 16, @@ -192,41 +195,4 @@ class _ShipmentRatesCalState extends State { ), ); } - - _row(String desc, String price, String unit, String value, {bool input}) { - return Container( - padding: EdgeInsets.only(left: 25, top: 5, bottom: 5), - child: Row( - children: [ - Text('$desc ', style: TextStyle(fontSize: 15)), - Spacer(), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 3.0), - child: Text( - '$price', - style: TextStyle(color: primaryColor, fontSize: 14), - ), - ), - Text( - '$unit', - style: TextStyle(color: Colors.grey, fontSize: 14), - ), - // TextFormField(), - ], - ), - SizedBox( - width: 50, - ), - Container( - width: 50, - child: TextFormField( - initialValue: value, - textAlign: TextAlign.end, - )), - ], - )); - } } diff --git a/lib/pages/rates/shipment_rates_edit.dart b/lib/pages/rates/shipment_rates_edit.dart index 6fa144d..7d0cdb0 100644 --- a/lib/pages/rates/shipment_rates_edit.dart +++ b/lib/pages/rates/shipment_rates_edit.dart @@ -29,17 +29,23 @@ class _ShipmentRatesEditState extends State { TextEditingController _minWeight = new TextEditingController(); TextEditingController _deliveryFee = new TextEditingController(); TextEditingController _volumetricRatio = new TextEditingController(); + TextEditingController _diffDiscountWeight = new TextEditingController(); + TextEditingController _diffWeightRate = new TextEditingController(); + + Rate rate; @override void initState() { super.initState(); var shipmentRateModel = Provider.of(context, listen: false); - Rate rate = shipmentRateModel.rate; + rate = shipmentRateModel.rate; _minWeight.text = rate.freeDeliveryWeight?.toStringAsFixed(2) ?? ""; - _deliveryFee.text = rate.deliveryFee?.toString() ?? ""; - _volumetricRatio.text = rate.volumetricRatio?.toString() ?? ""; + _deliveryFee.text = rate.deliveryFee?.toStringAsFixed(2) ?? ""; + _volumetricRatio.text = rate.volumetricRatio?.toStringAsFixed(2) ?? ""; + _diffDiscountWeight.text = rate.diffDiscountWeight?.toStringAsFixed(2) ?? ""; + _diffWeightRate.text = rate.diffWeightRate?.toStringAsFixed(2) ?? ""; } @override @@ -61,8 +67,16 @@ class _ShipmentRatesEditState extends State { controller: _deliveryFee); final ratioBox = InputText( labelTextKey: 'rate.volumetric_ratio', - iconData: Icons.attach_money, + iconData: FontAwesomeIcons.weightHanging, controller: _volumetricRatio); + final diffDiscountWeightBox = InputText( + labelTextKey: 'rate.diff_discount_weight', + iconData: FontAwesomeIcons.weightHanging, + controller: _diffDiscountWeight); + final diffWeightRateBox = InputText( + labelTextKey: 'rate.diff_weight_rate', + iconData: Icons.attach_money, + controller: _diffWeightRate); return LocalProgress( inAsyncCall: _isLoading, @@ -73,7 +87,15 @@ class _ShipmentRatesEditState extends State { icon: new Icon( CupertinoIcons.back, ), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), backgroundColor: primaryColor, title: Text(AppTranslations.of(context).text("rate.edit.title")), @@ -88,6 +110,8 @@ class _ShipmentRatesEditState extends State { minWigBox, feeBox, ratioBox, + diffDiscountWeightBox, + diffWeightRateBox, SizedBox(height: 10), ], ), @@ -112,7 +136,9 @@ class _ShipmentRatesEditState extends State { Rate _rate = new Rate( deliveryFee: double.parse(_deliveryFee.text), freeDeliveryWeight: double.parse(_minWeight.text), - volumetricRatio: double.parse(_volumetricRatio.text)); + volumetricRatio: double.parse(_volumetricRatio.text), + diffDiscountWeight: double.parse(_diffDiscountWeight.text), + diffWeightRate: double.parse(_diffWeightRate.text)); Rate r = new Rate(); print('_rate =>$r'); await shipmentRateModel.updateRate(_rate); @@ -126,6 +152,17 @@ class _ShipmentRatesEditState extends State { } } + isDataChanged() { + Rate _rate = new Rate( + deliveryFee: double.parse(_deliveryFee.text), + freeDeliveryWeight: double.parse(_minWeight.text), + volumetricRatio: double.parse(_volumetricRatio.text), + diffDiscountWeight: double.parse(_diffDiscountWeight.text), + diffWeightRate: double.parse(_diffWeightRate.text), + ); + return rate.isChangedForEdit(_rate); + } + List getCargoRows(List cargos) { return cargos.map((r) { return MyDataRow( diff --git a/lib/pages/receiving/receiving_editor.dart b/lib/pages/receiving/receiving_editor.dart index 305ab6a..f9c670f 100644 --- a/lib/pages/receiving/receiving_editor.dart +++ b/lib/pages/receiving/receiving_editor.dart @@ -149,7 +149,15 @@ class _ReceivingEditorState extends State { leading: new IconButton( icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), shadowColor: Colors.transparent, backgroundColor: Colors.white, @@ -249,4 +257,22 @@ class _ReceivingEditorState extends State { }); } } + + isDataChanged() { + if (_isNew) { + return _trackingIDCtl.text != "" || + _remarkCtl.text != "" || + user != null || + _multiImgController.getAddedFile.isNotEmpty; + } else { + var _package = Package( + trackingID: _trackingIDCtl.text, + remark: _remarkCtl.text, + fcsID: user.fcsID, + photoUrls: widget.package.photoUrls); + return widget.package.isChangedForEdit(_package) || + _multiImgController.getAddedFile.isNotEmpty || + _multiImgController.getDeletedUrl.isNotEmpty; + } + } } diff --git a/lib/pages/shipment/shipment_editor.dart b/lib/pages/shipment/shipment_editor.dart index 5094612..0dbc141 100644 --- a/lib/pages/shipment/shipment_editor.dart +++ b/lib/pages/shipment/shipment_editor.dart @@ -165,7 +165,15 @@ class _ShipmentEditorState extends State { CupertinoIcons.back, color: primaryColor, ), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (isDataChanged()) { + showConfirmDialog(context, "back.button_confirm", () { + Navigator.of(context).pop(); + }); + } else { + Navigator.of(context).pop(); + } + }, ), shadowColor: Colors.transparent, backgroundColor: Colors.white, @@ -312,4 +320,8 @@ class _ShipmentEditorState extends State { MainModel mainModel = Provider.of(context, listen: false); launch("${mainModel.setting.courierWebsite}"); } + + isDataChanged() { + return _shipment.boxes.isNotEmpty; + } } diff --git a/lib/pages/staff/staff_editor.dart b/lib/pages/staff/staff_editor.dart index c21bd1d..4812c32 100644 --- a/lib/pages/staff/staff_editor.dart +++ b/lib/pages/staff/staff_editor.dart @@ -171,10 +171,11 @@ class _StaffEditorState extends State { appBar: AppBar( centerTitle: true, leading: new IconButton( - icon: - new Icon(CupertinoIcons.back, color: primaryColor, size: 30), - onPressed: () => Navigator.of(context).pop(), - ), + icon: new Icon(CupertinoIcons.back, + color: primaryColor, size: 30), + onPressed: () { + Navigator.of(context).pop(); + }), shadowColor: Colors.transparent, backgroundColor: Colors.white, title: LocalText( diff --git a/lib/pages/term/term_edit.dart b/lib/pages/term/term_edit.dart index 5a4732a..2a5c188 100644 --- a/lib/pages/term/term_edit.dart +++ b/lib/pages/term/term_edit.dart @@ -76,7 +76,9 @@ class _TermEditState extends State { color: primaryColor, size: 30, ), - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + Navigator.of(context).pop(); + }, ), shadowColor: Colors.transparent, backgroundColor: Colors.white, diff --git a/lib/pages/widgets/dialog_input.dart b/lib/pages/widgets/dialog_input.dart new file mode 100644 index 0000000..0edb5b5 --- /dev/null +++ b/lib/pages/widgets/dialog_input.dart @@ -0,0 +1,92 @@ +import 'package:fcs/helpers/theme.dart'; +import 'package:fcs/pages/main/util.dart'; +import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/material.dart'; +import 'local_text.dart'; + +class DialogInput extends StatefulWidget { + final String value; + final String label; + const DialogInput({Key key, this.label, this.value}) : super(key: key); + @override + _DialogInputState createState() => _DialogInputState(); +} + +class _DialogInputState extends State { + final _formKey = GlobalKey(); + TextEditingController _controller = new TextEditingController(); + + bool _isLoading = false; + + @override + void initState() { + super.initState(); + if (widget.value != null) { + _controller.text = widget.value; + } + } + + @override + Widget build(BuildContext context) { + return LocalProgress( + inAsyncCall: _isLoading, + child: AlertDialog( + title: LocalText( + context, + widget.label, + fontSize: 20, + color: primaryColor, + ), + content: Form( + key: _formKey, + child: TextField( + controller: _controller, + keyboardType: TextInputType.number, + decoration: new InputDecoration( + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: primaryColor, width: 1.0))), + ), + ), + actions: [ + FlatButton( + child: LocalText( + context, + 'btn.cancel', + color: labelColor, + ), + onPressed: () { + _controller.clear(); + Navigator.of(context).pop(); + }), + FlatButton( + color: primaryColor, + child: LocalText( + context, + 'btn.ok', + color: Colors.white, + fontWeight: FontWeight.bold, + ), + onPressed: () async { + if (!_formKey.currentState.validate()) return; + _save(); + }) + ], + ), + ); + } + + _save() { + setState(() { + _isLoading = true; + }); + try { + Navigator.pop(context, _controller.text); + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/pages/widgets/status_tree.dart b/lib/pages/widgets/status_tree.dart index 234cba3..27e2e49 100644 --- a/lib/pages/widgets/status_tree.dart +++ b/lib/pages/widgets/status_tree.dart @@ -54,6 +54,7 @@ class StatusTree extends StatelessWidget { e.done || isPacked ? Text(dateFormatter.format(e.date)) : Container(), + e.staffName == null ? Container() : Text(e.staffName) ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index b530941..088449f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: fcs description: FCS Logistics publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.5+7 +version: 1.0.6+8 environment: sdk: ">=2.7.0 <3.0.0" @@ -57,6 +57,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + test: ^1.15.7 flutter: uses-material-design: true diff --git a/test/rate_test.dart b/test/rate_test.dart new file mode 100644 index 0000000..a373e2c --- /dev/null +++ b/test/rate_test.dart @@ -0,0 +1,33 @@ +import 'package:fcs/domain/entities/cargo_type.dart'; +import 'package:fcs/domain/entities/carton.dart'; +import 'package:fcs/domain/entities/discount_by_weight.dart'; +import 'package:fcs/domain/entities/rate.dart'; +import 'package:test/test.dart'; + +@TestOn('vm') +void main() { + var rate = Rate(); + rate.discountByWeights = [ + DiscountByWeight(weight: 25, discount: 0.25), + DiscountByWeight(weight: 50, discount: 0.5), + ]; + rate.volumetricRatio = 166.36; + rate.diffDiscountWeight = 5; + rate.diffWeightRate = 1.5; + + test('Calculate carton price 1', () { + var ct = CargoType(name: "General", weight: 10, rate: 6); + var carton = Carton(cargoTypes: [ct], length: 15, width: 15, height: 15); + expect(carton.calAmount(rate), equals(67.50)); + }); + test('Calculate carton price 2', () { + var ct = CargoType(name: "General", weight: 10, rate: 6); + var carton = Carton(cargoTypes: [ct], length: 10, width: 10, height: 10); + expect(carton.calAmount(rate), equals(60)); + }); + test('Calculate carton price 3', () { + var ct = CargoType(name: "General", weight: 10, rate: 6); + var carton = Carton(cargoTypes: [ct], length: 15, width: 15, height: 10); + expect(carton.calAmount(rate), equals(60)); + }); +}