Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Thinzar Win
2020-11-05 14:22:53 +06:30
60 changed files with 2449 additions and 2290 deletions

View File

@@ -393,13 +393,13 @@
"rate.delivery_fee":"Delivery fees", "rate.delivery_fee":"Delivery fees",
"rate.total_estimated_amount":"Total estimated amount", "rate.total_estimated_amount":"Total estimated amount",
"rate.volumetric_ratio":"Volumetric ratio", "rate.volumetric_ratio":"Volumetric ratio",
"rate.custom.form.title":"CUSTOM", "rate.custom.form.title":"Custom",
"rate.cutom.product_type":"Product type", "rate.cutom.product_type":"Product type",
"rate.custom.fee":"Fee", "rate.custom.fee":"Fee",
"rate.discount.weight":"Weight", "rate.discount.weight":"Weight",
"rate.discount.rate":"Discount rate", "rate.discount.rate":"Discount rate",
"rate.custom_duty.title":"Custom Duties", "rate.custom_duty.title":"Custom Fee",
"rate.custom_duty":"Custom Duty", "rate.custom_duty":"Custom Fee",
"rate.cargo.type":"Cargo Types", "rate.cargo.type":"Cargo Types",
"rate.discount_by_weight":"Discounts by weight", "rate.discount_by_weight":"Discounts by weight",
"rate.discount_by_weight.edit.delete.confirm":"Delete this discount by weight?", "rate.discount_by_weight.edit.delete.confirm":"Delete this discount by weight?",
@@ -428,6 +428,8 @@
"invoice.customer_name":"Customer name", "invoice.customer_name":"Customer name",
"invoice.status":"Status", "invoice.status":"Status",
"invoice.amount":"Amount", "invoice.amount":"Amount",
"invoice.weight":"Weight(lb)",
"invoice.rate":"Rate(USD)",
"invoice.total":"Total amount", "invoice.total":"Total amount",
"invoice.balance":"Balance", "invoice.balance":"Balance",
"invoice.handling_fee":"Handling fee", "invoice.handling_fee":"Handling fee",
@@ -435,15 +437,16 @@
"invoice.custom_fee_desc":"Custom fee description", "invoice.custom_fee_desc":"Custom fee description",
"invoice.discount":"Discount code", "invoice.discount":"Discount code",
"invoice.payment_method":"Payment method", "invoice.payment_method":"Payment method",
"invoice.delivery_fee":"Delivery fee : ", "invoice.delivery_fee":"Delivery fee",
"invoice.payment_attachment":"Payment attachment", "invoice.payment_attachment":"Payment attachment",
"invoice.box_info":"Carton information", "invoice.box_info":"Cartons",
"invoice.cargo_table":"Cargo table", "invoice.cargo_table":"Cargo table",
"invoice.btn_create":"Create invoice", "invoice.issue.btn":"Issue invoice",
"invoice.btn_save":"Save invoice", "invoice.btn_save":"Save invoice",
"invoice.btn_payment_receipt":"Attachment payment receipt", "invoice.btn_payment_receipt":"Attachment payment receipt",
"invoice.description": "Description", "invoice.description": "Description",
"invoice.box.cargo_type": "Cargo types", "invoice.box.cargo_type": "Cargo types",
"invoice.box.desc": "Description",
"invoice.cargo_type":"Cargo types", "invoice.cargo_type":"Cargo types",
"invoice.box.number":"Carton number", "invoice.box.number":"Carton number",
"invoice.box.length":"L", "invoice.box.length":"L",
@@ -455,10 +458,21 @@
"invoice.pdf": "Invoice PDF", "invoice.pdf": "Invoice PDF",
"invoice.total_custom_fee":"Total custom fee", "invoice.total_custom_fee":"Total custom fee",
"invoice.shipment_weight":"Shipment weight", "invoice.shipment_weight":"Shipment weight",
"invoice.popupmenu.pending":"Pending", "invoice.popupmenu.issused":"Issused",
"invoice.popupmenu.paid":"Paid", "invoice.popupmenu.paid":"Paid",
"invoice.popupmenu.cancel":"Canceled",
"invoice.shipment.title":"Select FCS shipment", "invoice.shipment.title":"Select FCS shipment",
"invoice.customer.title":"Select Customer", "invoice.customer.title":"Select Customer",
"invoice.add.custom.fee.menu":"Custom fee",
"invoice.add.handling.fee.menu":"Handling fee",
"invoice.add.discount.menu":"Discount",
"invoice.shipment.handling.fee.title":"Select shipment",
"invoice.shipment.number":"Shipment number",
"invoice.shipment.discount.title":"Select discount",
"invoice.cancel.btn":"Cancel",
"invoice.cancel.confirm":"Cancel invoice?",
"invoice.payment.confirm.confirm":"Confirm payment?",
"invoice.payment.cancel.confirm":"Cancel payment?",
"Invoices End ================================================================":"", "Invoices End ================================================================":"",
"Discount Start ================================================================":"", "Discount Start ================================================================":"",

View File

@@ -8,7 +8,7 @@
"btn.approve":"အတည်ပြုရန်", "btn.approve":"အတည်ပြုရန်",
"btn.delete":"ဖျက်ရန်", "btn.delete":"ဖျက်ရန်",
"btn.select":"ရွေးချယ်ပါ", "btn.select":"ရွေးချယ်ပါ",
"btn.cancel":"ဖျက်သိမ်းမည်", "btn.cancel":"မလုပ်နဲ့",
"btn.ok": "အိုကေ", "btn.ok": "အိုကေ",
"feet":"ပေ", "feet":"ပေ",
"inch":"လက်မ", "inch":"လက်မ",
@@ -428,6 +428,8 @@
"invoice.customer_name":"ဝယ်ယူသူနာမည်", "invoice.customer_name":"ဝယ်ယူသူနာမည်",
"invoice.status":"အခြေအနေ", "invoice.status":"အခြေအနေ",
"invoice.amount":"ပမာဏ", "invoice.amount":"ပမာဏ",
"invoice.weight":"Weight(lb)",
"invoice.rate":"Rate(USD)",
"invoice.total":"စုစုပေါင်းပမာဏ", "invoice.total":"စုစုပေါင်းပမာဏ",
"invoice.balance":"ပေးချေရန်ကျန်ရှိငွေ", "invoice.balance":"ပေးချေရန်ကျန်ရှိငွေ",
"invoice.handling_fee":"ထိန်းသိမ်းခ", "invoice.handling_fee":"ထိန်းသိမ်းခ",
@@ -437,14 +439,16 @@
"invoice.payment_method":"ငွေပေးချေစနစ်", "invoice.payment_method":"ငွေပေးချေစနစ်",
"invoice.delivery_fee":"ပို့ဆောင်ခ", "invoice.delivery_fee":"ပို့ဆောင်ခ",
"invoice.payment_attachment":"ပေးချေပြီးဖိုင်များ", "invoice.payment_attachment":"ပေးချေပြီးဖိုင်များ",
"invoice.box_info":"Box အချက်အလက်", "invoice.box_info":"Cartons",
"invoice.cargo_table":"ကုန်ပစ္စည်းဇယား", "invoice.cargo_table":"ကုန်ပစ္စည်းဇယား",
"invoice.btn_create":"ငွေတောင်းခံလွှာ ပြုလုပ်ရန်", "invoice.issue.btn":"ငွေတောင်းခံလွှာ ထုတ်မည်",
"invoice.btn_save":"ငွေတောင်းခံလွှာ သိမ်းရန်", "invoice.btn_save":"ငွေတောင်းခံလွှာ သိမ်းရန်",
"invoice.btn_payment_receipt":"ငွေလက်ခံဖြတ်ပိုင်း ထည့်ရန်", "invoice.btn_payment_receipt":"ငွေလက်ခံဖြတ်ပိုင်း ထည့်ရန်",
"invoice.box.cargo_type": "ကုန်ပစ္စည်းအမျိုးအစားများ", "invoice.box.cargo_type": "ကုန်ပစ္စည်းအမျိုးအစားများ",
"invoice.description": "ငွေတောင်းခံလွှာအကြောင်းအရာ", "invoice.description": "ငွေတောင်းခံလွှာအကြောင်းအရာ",
"invoice.cargo_type":"Cargo Types", "invoice.cargo_type":"Cargo Types",
"invoice.box.desc": "Description",
"invoice.box.number":"Box နံပါတ်", "invoice.box.number":"Box နံပါတ်",
"invoice.box.length":"L", "invoice.box.length":"L",
"invoice.box.width":"W", "invoice.box.width":"W",
@@ -455,10 +459,21 @@
"invoice.pdf": "Invoice PDF", "invoice.pdf": "Invoice PDF",
"invoice.total_custom_fee":"အခွန်စုစုပေါင်း", "invoice.total_custom_fee":"အခွန်စုစုပေါင်း",
"invoice.shipment_weight":"Shipment weight", "invoice.shipment_weight":"Shipment weight",
"invoice.popupmenu.pending":"Pending", "invoice.popupmenu.issused":"Issused",
"invoice.popupmenu.paid":"Paid", "invoice.popupmenu.paid":"Paid",
"invoice.popupmenu.cancel":"Canceled",
"invoice.shipment.title":"Select FCS shipment", "invoice.shipment.title":"Select FCS shipment",
"invoice.customer.title":"Select Customer", "invoice.customer.title":"Select Customer",
"invoice.add.custom.fee.menu":"Custom fee",
"invoice.add.handling.fee.menu":"Handling fee",
"invoice.add.discount.menu":"Discount",
"invoice.shipment.number":"Shipment number",
"invoice.shipment.handling.fee.title":"Select shipment",
"invoice.shipment.discount.title":"Select discount",
"invoice.cancel.btn":"Cancel",
"invoice.cancel.confirm":"Cancel invoice?",
"invoice.payment.confirm.confirm":"Confirm payment?",
"invoice.payment.cancel.confirm":"Cancel payment?",
"Invoices End ================================================================":"", "Invoices End ================================================================":"",
"Discount Start ================================================================":"", "Discount Start ================================================================":"",

View File

@@ -25,4 +25,10 @@ class FcsShipmentDataProvider {
return await requestAPI("/fcs_shipments/ship", "PUT", return await requestAPI("/fcs_shipments/ship", "PUT",
payload: fcsShipment.toMap(), token: await getToken()); payload: fcsShipment.toMap(), token: await getToken());
} }
Future<String> reportFcsShipment(FcsShipment fcsShipment) async {
dynamic data = await requestAPI("/fcs_shipments/report", "POST",
payload: fcsShipment.toMap(), token: await getToken());
return data["url"];
}
} }

View File

@@ -0,0 +1,36 @@
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/payment.dart';
import 'package:fcs/helpers/api_helper.dart';
import 'package:fcs/helpers/firebase_helper.dart';
import 'package:logging/logging.dart';
class InvoiceDataProvider {
final log = Logger('InvoiceDataProvider');
static final InvoiceDataProvider instance = InvoiceDataProvider._();
InvoiceDataProvider._();
Future<void> createInvoice(Invoice invoice) async {
return await requestAPI("/invoices", "POST",
payload: invoice.toMap(), token: await getToken());
}
Future<void> updateInvoice(Invoice invoice) async {
return await requestAPI("/invoices", "PUT",
payload: invoice.toMap(), token: await getToken());
}
Future<void> cancelInvoice(Invoice invoice) async {
return await requestAPI("/invoices/cancel", "PUT",
payload: {"id": invoice.id}, token: await getToken());
}
Future<void> pay(Payment payment) async {
return await requestAPI("/invoices/pay", "PUT",
payload: payment.toMap(), token: await getToken());
}
Future<void> updatPaymentStatus(Payment payment) async {
return await requestAPI("/invoices/pay/status", "PUT",
payload: payment.toMap(), token: await getToken());
}
}

View File

@@ -33,4 +33,9 @@ class FcsShipmentServiceImp implements FcsShipmentService {
Future<void> ship(FcsShipment fcsShipment) { Future<void> ship(FcsShipment fcsShipment) {
return shipmentDataProvider.ship(fcsShipment); return shipmentDataProvider.ship(fcsShipment);
} }
@override
Future<String> report(FcsShipment fcsShipment) {
return shipmentDataProvider.reportFcsShipment(fcsShipment);
}
} }

View File

@@ -5,4 +5,5 @@ abstract class FcsShipmentService {
Future<void> updateFcsShipment(FcsShipment fcsShipment); Future<void> updateFcsShipment(FcsShipment fcsShipment);
Future<void> deleteFcsShipment(FcsShipment fcsShipment); Future<void> deleteFcsShipment(FcsShipment fcsShipment);
Future<void> ship(FcsShipment fcsShipment); Future<void> ship(FcsShipment fcsShipment);
Future<String> report(FcsShipment fcsShipment);
} }

View File

@@ -0,0 +1,45 @@
import 'package:fcs/data/provider/invoice_data_provider.dart';
import 'package:fcs/data/provider/shipment_data_provider.dart';
import 'package:fcs/data/services/shipment_service.dart';
import 'package:fcs/domain/entities/connectivity.dart';
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/payment.dart';
import 'package:fcs/domain/entities/shipment.dart';
import 'package:flutter/material.dart';
import 'invoice_service.dart';
class InvoiceServiceImp implements InvoiceService {
InvoiceServiceImp({
@required this.invoiceDataProvider,
@required this.connectivity,
});
final Connectivity connectivity;
final InvoiceDataProvider invoiceDataProvider;
@override
Future<void> cancelInvoice(Invoice invoice) {
return invoiceDataProvider.cancelInvoice(invoice);
}
@override
Future<void> createInvoice(Invoice invoice) {
return invoiceDataProvider.createInvoice(invoice);
}
@override
Future<void> updateInvoice(Invoice invoice) {
return invoiceDataProvider.updateInvoice(invoice);
}
@override
Future<void> pay(Payment payment) {
return invoiceDataProvider.pay(payment);
}
@override
Future<void> updatPaymentStatus(Payment payment) {
return invoiceDataProvider.updatPaymentStatus(payment);
}
}

View File

@@ -0,0 +1,10 @@
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/payment.dart';
abstract class InvoiceService {
Future<void> createInvoice(Invoice invoice);
Future<void> updateInvoice(Invoice invoice);
Future<void> cancelInvoice(Invoice invoice);
Future<void> pay(Payment payment);
Future<void> updatPaymentStatus(Payment payment);
}

View File

@@ -3,6 +3,7 @@ import 'package:fcs/data/provider/carton_data_provider.dart';
import 'package:fcs/data/provider/common_data_provider.dart'; import 'package:fcs/data/provider/common_data_provider.dart';
import 'package:fcs/data/provider/delivery_address_data_provider.dart'; import 'package:fcs/data/provider/delivery_address_data_provider.dart';
import 'package:fcs/data/provider/fcs_shipment_data_provider.dart'; import 'package:fcs/data/provider/fcs_shipment_data_provider.dart';
import 'package:fcs/data/provider/invoice_data_provider.dart';
import 'package:fcs/data/provider/package_data_provider.dart'; import 'package:fcs/data/provider/package_data_provider.dart';
import 'package:fcs/data/provider/rate_data_provider.dart'; import 'package:fcs/data/provider/rate_data_provider.dart';
import 'package:fcs/data/provider/shipment_data_provider.dart'; import 'package:fcs/data/provider/shipment_data_provider.dart';
@@ -13,6 +14,8 @@ import 'package:fcs/data/services/delivery_address_imp.dart';
import 'package:fcs/data/services/delivery_address_service.dart'; import 'package:fcs/data/services/delivery_address_service.dart';
import 'package:fcs/data/services/fcs_shipment_imp.dart'; import 'package:fcs/data/services/fcs_shipment_imp.dart';
import 'package:fcs/data/services/fcs_shipment_service.dart'; import 'package:fcs/data/services/fcs_shipment_service.dart';
import 'package:fcs/data/services/invoice_imp.dart';
import 'package:fcs/data/services/invoice_service.dart';
import 'package:fcs/data/services/rate_imp.dart'; import 'package:fcs/data/services/rate_imp.dart';
import 'package:fcs/data/services/rate_service.dart'; import 'package:fcs/data/services/rate_service.dart';
import 'package:fcs/data/services/shipment_imp.dart'; import 'package:fcs/data/services/shipment_imp.dart';
@@ -42,6 +45,7 @@ class Services {
RateService _rateService; RateService _rateService;
ShipmentService _shipmentService; ShipmentService _shipmentService;
CartonService _cartonService; CartonService _cartonService;
InvoiceService _invoiceService;
Services._() { Services._() {
_authService = AuthServiceImp( _authService = AuthServiceImp(
authFb: AuthFb.instance, authFb: AuthFb.instance,
@@ -65,6 +69,8 @@ class Services {
connectivity: null); connectivity: null);
_cartonService = CartonServiceImp( _cartonService = CartonServiceImp(
cartonDataProvider: CartonDataProvider.instance, connectivity: null); cartonDataProvider: CartonDataProvider.instance, connectivity: null);
_invoiceService = InvoiceServiceImp(
invoiceDataProvider: InvoiceDataProvider.instance, connectivity: null);
} }
AuthService get authService => _authService; AuthService get authService => _authService;
@@ -77,4 +83,5 @@ class Services {
RateService get rateService => _rateService; RateService get rateService => _rateService;
ShipmentService get shipmentService => _shipmentService; ShipmentService get shipmentService => _shipmentService;
CartonService get cartonService => _cartonService; CartonService get cartonService => _cartonService;
InvoiceService get invoiceService => _invoiceService;
} }

View File

@@ -27,6 +27,7 @@ const user_joined_status = "joined";
const pkg_files_path = "/packages"; const pkg_files_path = "/packages";
const shipment_labels_files_path = "/shipment_labels"; const shipment_labels_files_path = "/shipment_labels";
const receipt_labels_files_path = "/receipts";
// Link page // Link page
const page_payment_methods = "payment_methods"; const page_payment_methods = "payment_methods";
@@ -91,3 +92,14 @@ const shipment_pickuped_status = "pickuped";
const shipment_packed_status = "packed"; const shipment_packed_status = "packed";
const shipment_shipped_status = "shipped"; const shipment_shipped_status = "shipped";
const shipment_delivered_status = "delivered"; const shipment_delivered_status = "delivered";
// invoice status
const invoice_issued_status = "issued";
const invoice_saved_status = "saved";
const invoice_cancel_status = "canceled";
const invoice_paid_status = "paid";
// payment status
const payment_pending_status = "pending";
const payment_confirmed_status = "confirmed";
const payment_canceled_status = "canceled";

View File

@@ -4,8 +4,7 @@ class CargoType {
double rate; double rate;
double weight; double weight;
int shipmentWeight; double get calAmount => (calRate ?? 0) * (calWeight ?? 0);
double amount;
double calRate; double calRate;
double calWeight; double calWeight;
@@ -16,9 +15,17 @@ class CargoType {
name: map['name'], name: map['name'],
rate: map['rate']?.toDouble() ?? 0, rate: map['rate']?.toDouble() ?? 0,
weight: map['weight']?.toDouble() ?? 0, weight: map['weight']?.toDouble() ?? 0,
calWeight: map['cal_weight']?.toDouble() ?? 0,
calRate: map['cal_rate']?.toDouble() ?? 0,
); );
} }
CargoType({this.id, this.name, this.rate, this.weight}); CargoType(
{this.id,
this.name,
this.rate,
this.weight,
this.calWeight,
this.calRate});
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
@@ -26,6 +33,8 @@ class CargoType {
'name': name, 'name': name,
'rate': rate, 'rate': rate,
'weight': weight, 'weight': weight,
'cal_weight': calWeight,
'cal_rate': calRate,
}; };
} }

View File

@@ -1,6 +1,7 @@
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fcs/domain/entities/discount_by_weight.dart'; import 'package:fcs/domain/entities/discount_by_weight.dart';
import 'package:fcs/domain/entities/rate.dart'; import 'package:fcs/domain/entities/rate.dart';
import 'package:fcs/domain/entities/shipment.dart';
import 'package:fcs/domain/vo/shipment_status.dart'; import 'package:fcs/domain/vo/shipment_status.dart';
import 'package:fcs/domain/vo/delivery_address.dart'; import 'package:fcs/domain/vo/delivery_address.dart';
@@ -9,6 +10,7 @@ import 'package.dart';
class Carton { class Carton {
String id; String id;
String shipmentID;
String shipmentNumber; String shipmentNumber;
String senderFCSID; String senderFCSID;
String senderName; String senderName;
@@ -47,9 +49,9 @@ class Carton {
List<String> packageIDs; List<String> packageIDs;
List<Package> packages; List<Package> packages;
List<CargoType> cargoTypes; List<CargoType> cargoTypes;
List<Carton> cartons;
DeliveryAddress deliveryAddress; DeliveryAddress deliveryAddress;
Shipment shipment;
int get amount => rate != null && weight != null ? rate * weight : 0; int get amount => rate != null && weight != null ? rate * weight : 0;
@@ -73,6 +75,23 @@ class Carton {
return (length * width * height) / volumetricRatio; return (length * width * height) / volumetricRatio;
} }
/// getCargoTypeForCalWeight returns carton with shipment weight
List<CargoType> getCargoTypeForCalWeight(double volumetricRatio) {
// get shipment weight
double volume = (length ?? 0) * (width ?? 0) * (height ?? 0);
double sw = volume / volumetricRatio ?? 0;
// get actual weight
double aw = cargoTypes.fold(0.0, (p, c) => p + c.weight);
if (aw == 0 || sw == 0) return [];
cargoTypes.forEach((e) {
double calWeight = aw > sw ? e.weight : e.weight / aw * sw;
e.calWeight = calWeight;
});
return cargoTypes;
}
/// calAmount returns total amount /// calAmount returns total amount
double calAmount(Rate rate) { double calAmount(Rate rate) {
// get shipment weight // get shipment weight
@@ -101,6 +120,7 @@ class Carton {
Carton( Carton(
{this.id, {this.id,
this.shipmentID,
this.shipmentNumber, this.shipmentNumber,
this.senderFCSID, this.senderFCSID,
this.senderName, this.senderName,
@@ -133,7 +153,6 @@ class Carton {
this.cartonNumber, this.cartonNumber,
this.fcsShipmentID, this.fcsShipmentID,
this.fcsShipmentNumber, this.fcsShipmentNumber,
this.cartons,
this.packageIDs, this.packageIDs,
this.mixCartonID, this.mixCartonID,
this.mixCartonNumber, this.mixCartonNumber,
@@ -143,7 +162,6 @@ class Carton {
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
List _cargoTypes = cargoTypes.map((c) => c.toMap()).toList(); List _cargoTypes = cargoTypes.map((c) => c.toMap()).toList();
List _packages = packages?.map((c) => c.toJson())?.toList(); List _packages = packages?.map((c) => c.toJson())?.toList();
List _cartons = cartons?.map((c) => c.toMap())?.toList() ?? [];
return { return {
"id": id, "id": id,
'fcs_shipment_id': fcsShipmentID, 'fcs_shipment_id': fcsShipmentID,
@@ -153,10 +171,9 @@ class Carton {
'length': length, 'length': length,
'width': width, 'width': width,
'height': height, 'height': height,
'delivery_address': deliveryAddress.toMap(), 'delivery_address': deliveryAddress?.toMap(),
'carton_type': cartonType, 'carton_type': cartonType,
'cartons': _cartons, 'mix_carton_id': mixCartonID,
'mix_carton_id': mixCartonID
}; };
} }
@@ -171,6 +188,7 @@ class Carton {
return Carton( return Carton(
id: docID, id: docID,
arrivedDate: _arrivedDate != null ? _arrivedDate.toDate() : null, arrivedDate: _arrivedDate != null ? _arrivedDate.toDate() : null,
shipmentID: map['shipment_id'],
shipmentNumber: map['shipment_number'], shipmentNumber: map['shipment_number'],
receiverNumber: map['receiver_number'], receiverNumber: map['receiver_number'],
boxNumber: map['box_number'], boxNumber: map['box_number'],

View File

@@ -13,6 +13,7 @@ class FcsShipment {
String port; String port;
String destination; String destination;
String status; String status;
String reportName;
FcsShipment({ FcsShipment({
this.id, this.id,
this.shipmentNumber, this.shipmentNumber,
@@ -24,6 +25,7 @@ class FcsShipment {
this.consignee, this.consignee,
this.port, this.port,
this.destination, this.destination,
this.reportName,
}); });
factory FcsShipment.fromMap(Map<String, dynamic> map, String docID) { factory FcsShipment.fromMap(Map<String, dynamic> map, String docID) {
@@ -57,6 +59,7 @@ class FcsShipment {
'port': port, 'port': port,
'destination': destination, 'destination': destination,
'status': status, 'status': status,
'report_name': reportName,
}; };
} }

View File

@@ -1,79 +1,82 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fcs/domain/entities/cargo_type.dart'; import 'package:fcs/domain/entities/cargo_type.dart';
import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/carton.dart';
import 'package:fcs/domain/entities/custom_duty.dart'; import 'package:fcs/domain/entities/custom_duty.dart';
import 'package:fcs/domain/entities/discount.dart'; import 'package:fcs/domain/entities/discount.dart';
import 'package:fcs/domain/entities/discount_by_weight.dart'; import 'package:fcs/domain/entities/discount_by_weight.dart';
import 'package:fcs/domain/entities/payment.dart';
import 'package:fcs/domain/entities/payment_method.dart';
import 'package:fcs/domain/entities/rate.dart'; import 'package:fcs/domain/entities/rate.dart';
import 'package:fcs/domain/entities/shipment.dart';
import 'package.dart';
import 'receipt.dart';
class Invoice { class Invoice {
String id; String id;
String invoiceNumber; String invoiceNumber;
DateTime invoiceDate; DateTime invoiceDate;
String customerName; String fcsShipmentID;
String customerPhoneNumber; String userID;
double amount; String fcsID;
String userName;
String phoneNumber;
String status; String status;
String paymentAttachment;
double handlingFee; double handlingFee;
double deliveryFee; double deliveryFee;
double paidAmount; double paidAmount;
double amount;
List<Package> packages;
List<Receipt> receipts;
List<String> receiptPhotos;
List<CustomDuty> customDuties; List<CustomDuty> customDuties;
List<Carton> cartons; List<Carton> cartons;
List<CargoType> cargoTypes;
List<Shipment> shipments;
List<Payment> payments;
Discount discount; Discount discount;
PaymentMethod paymentMethod;
String invoiceURL;
List<CargoType> getCargoTypes(Rate rate) { List<CargoType> getCargoTypes(Rate rate) {
List<CargoType> cargoTypes = []; if (cargoTypes != null) return cargoTypes;
double actualWeight = 0;
double shipmentWeight = 0; List<CargoType> _cargoTypes = [];
cartons.forEach((c) { double totalCalWeight = 0;
c.cargoTypes.forEach((tc) { cartons.forEach((carton) {
if (cargoTypes.contains(tc)) { if (carton.isChecked) {
CargoType existing = cargoTypes.firstWhere((wc) => wc.id == tc.id); var _cartonsTypes =
existing.weight += tc.weight; carton.getCargoTypeForCalWeight(rate.volumetricRatio);
} else { _cartonsTypes.forEach((ct) {
cargoTypes.add(tc.clone()); if (_cargoTypes.contains(ct)) {
} CargoType existing = _cargoTypes.firstWhere((wc) => wc.id == ct.id);
actualWeight += tc.weight; existing.calWeight += ct.calWeight;
}); } else {
double volume = (c.length ?? 0) * (c.width ?? 0) * (c.height ?? 0); _cargoTypes.add(ct.clone());
double sw = volume / rate.volumetricRatio ?? 0; }
shipmentWeight += sw; totalCalWeight += ct.calWeight;
});
}
}); });
DiscountByWeight discountByWeight = rate.getDiscountByWeight( DiscountByWeight discountByWeight =
shipmentWeight > actualWeight ? shipmentWeight : actualWeight); rate.getDiscountByWeight(totalCalWeight);
cargoTypes.forEach((e) { _cargoTypes.forEach((e) {
print(actualWeight > shipmentWeight);
double cargoWeight = actualWeight > shipmentWeight
? e.weight
: e.weight / actualWeight * shipmentWeight;
double r = double r =
e.rate - (discountByWeight != null ? discountByWeight.discount : 0); e.rate - (discountByWeight != null ? discountByWeight.discount : 0);
double amount = cargoWeight * r;
e.calRate = r; e.calRate = r;
e.calWeight = cargoWeight;
e.amount = amount;
}); });
return cargoTypes; return _cargoTypes;
} }
double getTotal(Rate rate) { double getTotal(Rate rate) {
List<CargoType> cargoTypes = getCargoTypes(rate); List<CargoType> cargoTypes = getCargoTypes(rate);
var total = cargoTypes.fold(0.0, (p, c) => c.amount + p); var total = cargoTypes.fold(0.0, (p, c) => c.calAmount + p);
return total; return total;
} }
double get balance => (amount ?? 0) - (paidAmount ?? 0);
double getNetAmount(Rate rate) { double getNetAmount(Rate rate) {
List<CargoType> cargoTypes = getCargoTypes(rate); List<CargoType> cargoTypes = getCargoTypes(rate);
var total = cargoTypes.fold(0.0, (p, c) => c.amount + p); var total = cargoTypes.fold(0.0, (p, c) => c.calAmount + p);
total += getCustomFee(); total += getCustomFee();
total += getDeliveryFee(); total += getDeliveryFee();
total += getHandlingFee(); total += getHandlingFee();
@@ -81,6 +84,12 @@ class Invoice {
return total; return total;
} }
double getHandlingFee() {
return shipments?.where((sh) => sh.isSelected ?? false)?.fold(0, (p, s) {
return p + (s?.handlingFee ?? 0) - (s?.paidHandlingFee ?? 0);
});
}
double getTotalBalance(Rate rate) { double getTotalBalance(Rate rate) {
return getNetAmount(rate) - (paidAmount ?? 0); return getNetAmount(rate) - (paidAmount ?? 0);
} }
@@ -89,10 +98,6 @@ class Invoice {
return customDuties == null ? 0 : customDuties.fold(0, (p, d) => p + d.fee); return customDuties == null ? 0 : customDuties.fold(0, (p, d) => p + d.fee);
} }
double getHandlingFee() {
return handlingFee == null ? 0 : handlingFee;
}
double getDeliveryFee() { double getDeliveryFee() {
return deliveryFee == null ? 0 : deliveryFee; return deliveryFee == null ? 0 : deliveryFee;
} }
@@ -103,35 +108,92 @@ class Invoice {
{this.id, {this.id,
this.invoiceNumber, this.invoiceNumber,
this.invoiceDate, this.invoiceDate,
this.customerName, this.fcsID,
this.customerPhoneNumber, this.userName,
this.phoneNumber,
this.amount, this.amount,
this.paidAmount,
this.discount, this.discount,
this.status, this.status,
this.paymentAttachment,
this.packages,
this.receiptPhotos,
this.customDuties, this.customDuties,
this.cartons, this.cartons,
this.cargoTypes,
this.handlingFee, this.handlingFee,
this.receipts}); this.deliveryFee,
this.fcsShipmentID,
double get getAmount => packages.fold(0, (p, e) => (e.rate * e.weight) + p); this.shipments,
this.invoiceURL,
this.payments,
this.paymentMethod});
factory Invoice.fromMap(Map<String, dynamic> map, String docID) { factory Invoice.fromMap(Map<String, dynamic> map, String docID) {
var invd = (map['invoice_date'] as Timestamp);
var cargoTypesMaps =
List<Map<String, dynamic>>.from(map['cargo_types'] ?? []);
var cargoTypes =
cargoTypesMaps.map((e) => CargoType.fromMap(e, e["id"])).toList();
var customDutiesMap =
List<Map<String, dynamic>>.from(map['custom_duties'] ?? []);
var customDuties =
customDutiesMap.map((e) => CustomDuty.fromMap(e, e["id"])).toList();
var handlingShipmentsMap =
List<Map<String, dynamic>>.from(map['handling_fee_shipments'] ?? []);
var handingShipments =
handlingShipmentsMap.map((e) => Shipment.fromMap(e, e["id"])).toList();
var cartonsMap = List<Map<String, dynamic>>.from(map['cartons'] ?? []);
var cartons = cartonsMap.map((e) => Carton.fromMap(e, e["id"])).toList();
var paymentMethodMap = map['payment_method'];
var paymentMethod = paymentMethodMap != null
? PaymentMethod.fromMap(paymentMethodMap, paymentMethodMap['id'])
: null;
var discountMap = map['discount'];
var discount = Discount.fromMap(discountMap, discountMap['id']);
var paymentMaps = List<Map<String, dynamic>>.from(map['payments'] ?? []);
var payments = paymentMaps.map((e) => Payment.fromMap(e, e["id"])).toList();
return Invoice( return Invoice(
id: docID, id: docID,
invoiceNumber: map['invoice_number'], invoiceNumber: map['invoice_number'],
invoiceDate: map['invoice_date'], invoiceDate: invd?.toDate(),
customerName: map['customer_name'], userName: map['user_name'],
customerPhoneNumber: map['phone_number'], fcsID: map['fcs_id'],
phoneNumber: map['phone_number'],
amount: map['amount'], amount: map['amount'],
paidAmount: double.tryParse(map['paid_amount'].toString()) ?? 0,
status: map['status'], status: map['status'],
discount: map['discount'], cartons: cartons,
paymentAttachment: map['payment_attachment'], cargoTypes: cargoTypes,
packages: map['packages'], shipments: handingShipments,
receiptPhotos: map['receiptPhotos'], customDuties: customDuties,
receipts: map['receipts'], deliveryFee: map['delivery_fee'],
invoiceURL: map['invoice_url'],
paymentMethod: paymentMethod,
discount: discount,
payments: payments,
); );
} }
Map<String, dynamic> toMap() {
List _cargoTypes = cargoTypes.map((c) => c.toMap()).toList();
List _customDuties = customDuties?.map((c) => c.toMap())?.toList();
List _cartons = cartons?.map((c) => c.toMap())?.toList() ?? [];
List _shipments = shipments?.map((s) => s.toMap())?.toList() ?? [];
return {
"id": id,
"invoice_date": invoiceDate?.toUtc()?.toIso8601String(),
"user_id": userID,
"user_name": userName,
"invoice_number": invoiceNumber,
'fcs_shipment_id': fcsShipmentID,
'cargo_types': _cargoTypes,
'custom_duties': _customDuties,
'handling_fee_shipments': _shipments,
'cartons': _cartons,
'discount': discount?.toMap(),
'amount': amount,
'handling_fee': handlingFee,
'delivery_fee': deliveryFee,
'invoice_url': invoiceURL,
'payment_method': paymentMethod?.toMap(),
};
}
} }

View File

@@ -0,0 +1,50 @@
import 'package:cloud_firestore/cloud_firestore.dart';
class Payment {
String id;
String invoiceID;
DateTime paymentDate;
String paymentReceiptURL;
String status;
double amount;
Payment(
{this.id,
this.invoiceID,
this.paymentDate,
this.paymentReceiptURL,
this.status,
this.amount});
factory Payment.fromMap(Map<String, dynamic> map, String id) {
var _paymentDate = (map['payment_date'] as Timestamp);
return Payment(
id: id,
paymentDate: _paymentDate?.toDate(),
paymentReceiptURL: map['payment_receipt_url'],
status: map['status'],
amount: map['amount']?.toDouble() ?? 0,
);
}
Map<String, dynamic> toMap() {
return {
"id": id,
"invoice_id": invoiceID,
'payment_date': paymentDate?.toUtc()?.toIso8601String(),
'payment_receipt_url': paymentReceiptURL,
'status': status,
'amount': amount,
};
}
Payment clone() {
return Payment.fromMap(toMap(), this.id);
}
@override
bool operator ==(Object other) => other is Payment && other.id == id;
@override
int get hashCode => id.hashCode;
}

View File

@@ -1,5 +1,3 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class Receipt { class Receipt {
String id; String id;
int amount; int amount;

View File

@@ -18,6 +18,7 @@ class Shipment {
int numberOfPackage; int numberOfPackage;
int weight; int weight;
double handlingFee; double handlingFee;
double paidHandlingFee;
String address; String address;
String status; String status;
bool isCourier; bool isCourier;
@@ -31,6 +32,7 @@ class Shipment {
String fcsShipmentID; String fcsShipmentID;
String fcsShipmentNumber; String fcsShipmentNumber;
String shipmentLabelUrl; String shipmentLabelUrl;
bool isSelected;
Shipment( Shipment(
{this.id, {this.id,
@@ -44,6 +46,7 @@ class Shipment {
this.numberOfPackage, this.numberOfPackage,
this.weight, this.weight,
this.handlingFee, this.handlingFee,
this.paidHandlingFee,
this.address, this.address,
this.status, this.status,
this.pickupDate, this.pickupDate,
@@ -89,6 +92,7 @@ class Shipment {
pickupUserName: map['pickup_user_name'], pickupUserName: map['pickup_user_name'],
pickupUserPhoneNumber: map['pickup_user_phone_number'], pickupUserPhoneNumber: map['pickup_user_phone_number'],
handlingFee: map['handling_fee'], handlingFee: map['handling_fee'],
paidHandlingFee: map['paid_handling_fee'],
fcsShipmentID: map['fcs_shipment_id'], fcsShipmentID: map['fcs_shipment_id'],
fcsShipmentNumber: map['fcs_shipment_number'], fcsShipmentNumber: map['fcs_shipment_number'],
shipmentLabelUrl: map['shipment_label_url'], shipmentLabelUrl: map['shipment_label_url'],
@@ -96,7 +100,7 @@ class Shipment {
} }
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
List _boxes = boxes.map((l) => l.toMap()).toList(); List _boxes = boxes?.map((l) => l.toMap())?.toList() ?? [];
return { return {
"id": id, "id": id,
@@ -116,6 +120,12 @@ class Shipment {
}; };
} }
@override
bool operator ==(Object other) => other is Shipment && other.id == id;
@override
int get hashCode => id.hashCode;
@override @override
String toString() { String toString() {
return 'PickUp{id:$id, userName:$userName,phoneNumber:$phoneNumber,numberOfPackage:$numberOfPackage,weight:$weight,status:$status}'; return 'PickUp{id:$id, userName:$userName,phoneNumber:$phoneNumber,numberOfPackage:$numberOfPackage,weight:$weight,status:$status}';

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
@@ -5,9 +6,11 @@ import 'package:device_info/device_info.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:fcs/domain/vo/status.dart'; import 'package:fcs/domain/vo/status.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import '../config.dart'; import '../config.dart';
import 'dev_info.dart'; import 'dev_info.dart';
import 'firebase_helper.dart';
final log = Logger('requestAPI'); final log = Logger('requestAPI');
@@ -38,7 +41,7 @@ Future<dynamic> requestAPI(
method: method, method: method,
baseUrl: url == null ? Config.instance.apiURL : url, baseUrl: url == null ? Config.instance.apiURL : url,
connectTimeout: 10000, connectTimeout: 10000,
receiveTimeout: 10000, receiveTimeout: 60000,
headers: headers, headers: headers,
); );
log.info("baseUrl:${options.baseUrl}, path:$path"); log.info("baseUrl:${options.baseUrl}, path:$path");
@@ -146,3 +149,92 @@ Future<dynamic> requestDownloadPDFAPI(String path, method,
throw e; throw e;
} }
} }
typedef OnDownloadDone(File file);
// request makes http request
// if token is null
Future<dynamic> requestDownload(String path, method,
{dynamic payload,
String token,
String url,
String filePath,
OnDownloadDone onDownloadDone}) async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
String deviceName = "${androidInfo.model}(${androidInfo.id})";
log.info("device:${androidInfo.androidId},deviceName:$deviceName");
var bytes = utf8.encode(payload);
var base64Str = base64.encode(bytes);
String escapePayload = HtmlEscape().convert(base64Str);
try {
String baseUrl = url == null ? Config.instance.apiURL : url;
log.info("Path:$baseUrl$path");
HttpClient client = new HttpClient();
// var _downloadData = StringBuffer();
var request = await client.postUrl(Uri.parse("$baseUrl$path"));
request.headers.set("Project-ID", Config.instance.reportProjectID);
if (token != null) {
request.headers.set("Token", token);
}
if (androidInfo.androidId != null) {
request.headers.set("Device", androidInfo.androidId + ":" + deviceName);
}
request.headers
.set(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8');
request.headers.set("payload", escapePayload);
request.write(payload);
// request.write(escapePayload);
var response = await request.close();
print("headers:${response.headers}");
var _downloadData = List<int>();
var cd = response.headers.value("content-disposition");
String fileName = "download.csv";
if (cd != null && cd.contains("filename=")) {
fileName = cd.substring(cd.indexOf("=") + 1);
}
var file = filePath + "/" + fileName;
var fileSave = new File(filePath + "/" + fileName);
response.listen((d) => _downloadData.addAll(d), onDone: () async {
await fileSave.writeAsBytes(_downloadData);
if (onDownloadDone != null) {
onDownloadDone(fileSave);
}
// final message = await OpenFile.open(file);
// log.info("Open file result:${message.message}");
// if (message.message != "done") {
// throw Exception(message.message);
// }
// await Share.file(fileName, fileName, _downloadData,
// downloadType == DownloadType.CSV ? "text/csv" : "application/pdf");
});
} catch (e) {
e.toString();
log.warning("path:$path, api:$e");
throw e;
}
}
Future<File> downloadPDF(
String templateName, Map<String, dynamic> values) async {
final completer = Completer<File>();
final directory = await getApplicationSupportDirectory();
String path = ('${directory.path}');
log.info("download file path:$path");
var data = {"template_name": templateName, "data": values};
print("payload:$data");
await requestDownload("/api/pdf", "POST",
filePath: path,
url: Config.instance.reportURL,
token: await getToken(),
payload: jsonEncode(data), onDownloadDone: (f) async {
completer.complete(f);
});
return completer.future;
}

View File

@@ -0,0 +1,12 @@
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class PdfCacheMgr {
static const key = 'pdfs';
static CacheManager pdfs = CacheManager(
Config(
key,
stalePeriod: const Duration(days: 7),
maxNrOfCacheObjects: 20,
),
);
}

View File

@@ -89,14 +89,16 @@ class _CartonEditorState extends State<CartonEditor> {
_user = User(fcsID: _carton.fcsID, name: _carton.userName); _user = User(fcsID: _carton.fcsID, name: _carton.userName);
_loadPackages(); _loadPackages();
} else { } else {
_carton = Carton(cargoTypes: [], packages: [], cartons: []); _carton = Carton(
cargoTypes: [],
packages: [],
);
_lengthController.text = "12"; _lengthController.text = "12";
_widthController.text = "12"; _widthController.text = "12";
_heightController.text = "12"; _heightController.text = "12";
_isNew = true; _isNew = true;
_selectedCartonType = carton_from_packages; _selectedCartonType = carton_from_packages;
_loadFcsShipments(); _loadFcsShipments();
_loadMixCartons();
} }
} }
@@ -112,16 +114,6 @@ class _CartonEditorState extends State<CartonEditor> {
}); });
} }
_loadMixCartons() async {
if (_fcsShipment == null) return;
CartonModel cartonModel = Provider.of<CartonModel>(context, listen: false);
var mixCartons =
await cartonModel.getMixCartonsByFcsShipment(_fcsShipment.id);
setState(() {
_mixCartons = mixCartons;
});
}
_loadPackages() async { _loadPackages() async {
if (_user == null) return; if (_user == null) return;
PackageModel packageModel = PackageModel packageModel =
@@ -159,19 +151,6 @@ class _CartonEditorState extends State<CartonEditor> {
_populateDeliveryAddress(); _populateDeliveryAddress();
} }
_loadCartons() async {
if (_fcsShipment == null) return;
CartonModel cartonModel = Provider.of<CartonModel>(context, listen: false);
List<Carton> cartons =
await cartonModel.getCartonsByFcsShipment(_fcsShipment.id);
cartons.forEach((c) {
c.isChecked = true;
});
setState(() {
_carton.cartons = cartons;
});
}
_populateDeliveryAddress() { _populateDeliveryAddress() {
if (_carton.packages == null) return; if (_carton.packages == null) return;
var d = _carton.packages var d = _carton.packages
@@ -183,6 +162,18 @@ class _CartonEditorState extends State<CartonEditor> {
}); });
} }
_loadMixCartons() async {
if (_fcsShipment == null || _fcsShipment.id == null) return;
if (_selectedCartonType != carton_small_bag) return;
CartonModel cartonModel = Provider.of<CartonModel>(context, listen: false);
List<Carton> cartons =
await cartonModel.getMixCartonsByFcsShipment(_fcsShipment.id);
setState(() {
_mixCartons = cartons;
});
}
_calShipmentWeight() { _calShipmentWeight() {
double l = double.parse(_lengthController.text, (s) => 0); double l = double.parse(_lengthController.text, (s) => 0);
double w = double.parse(_widthController.text, (s) => 0); double w = double.parse(_widthController.text, (s) => 0);
@@ -217,12 +208,7 @@ class _CartonEditorState extends State<CartonEditor> {
setState(() { setState(() {
_fcsShipment = v; _fcsShipment = v;
}); });
if (_selectedCartonType == carton_mix_box) { _loadMixCartons();
_loadCartons();
}
if (_selectedCartonType == carton_small_bag) {
_loadMixCartons();
}
}, },
labelKey: "shipment.pack.fcs.shipment", labelKey: "shipment.pack.fcs.shipment",
iconData: Ionicons.ios_airplane, iconData: Ionicons.ios_airplane,
@@ -318,13 +304,9 @@ class _CartonEditorState extends State<CartonEditor> {
values: boxModel.cartonTypes, values: boxModel.cartonTypes,
selectedValue: _selectedCartonType, selectedValue: _selectedCartonType,
callback: (v) { callback: (v) {
print(v);
setState(() { setState(() {
_selectedCartonType = v; _selectedCartonType = v;
}); });
if (_selectedCartonType == carton_mix_box) {
_loadCartons();
}
}); });
final cargoTableTitleBox = LocalTitle( final cargoTableTitleBox = LocalTitle(
@@ -507,9 +489,6 @@ class _CartonEditorState extends State<CartonEditor> {
carton.width = w; carton.width = w;
carton.height = h; carton.height = h;
carton.deliveryAddress = _deliveryAddress; carton.deliveryAddress = _deliveryAddress;
carton.cartons = _carton.cartons == null
? []
: _carton.cartons.where((c) => c.isChecked).toList();
setState(() { setState(() {
_isLoading = true; _isLoading = true;
}); });

View File

@@ -1,5 +1,4 @@
import 'package:fcs/domain/constants.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/entities/carton.dart';
import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/package.dart';
import 'package:fcs/domain/vo/delivery_address.dart'; import 'package:fcs/domain/vo/delivery_address.dart';
@@ -23,7 +22,6 @@ import 'package:provider/provider.dart';
import 'carton_cargo_table.dart'; import 'carton_cargo_table.dart';
import 'carton_editor.dart'; import 'carton_editor.dart';
import 'carton_mix_table.dart';
import 'carton_package_table.dart'; import 'carton_package_table.dart';
import 'model/carton_model.dart'; import 'model/carton_model.dart';
import 'widgets.dart'; import 'widgets.dart';
@@ -41,11 +39,6 @@ class CartonInfo extends StatefulWidget {
class _CartonInfoState extends State<CartonInfo> { class _CartonInfoState extends State<CartonInfo> {
bool _isLoading = false; bool _isLoading = false;
Carton _box; Carton _box;
String _selectedCartonType;
List<Package> _packages = [];
List<Carton> _mixBoxes = [];
Carton _selectedShipmentBox = new Carton();
List<CargoType> _cargoTypes = [];
DeliveryAddress _deliveryAddress = new DeliveryAddress(); DeliveryAddress _deliveryAddress = new DeliveryAddress();
TextEditingController _widthController = new TextEditingController(); TextEditingController _widthController = new TextEditingController();
TextEditingController _heightController = new TextEditingController(); TextEditingController _heightController = new TextEditingController();
@@ -63,7 +56,6 @@ class _CartonInfoState extends State<CartonInfo> {
void initState() { void initState() {
super.initState(); super.initState();
_box = widget.box; _box = widget.box;
_selectedCartonType = _box.cartonType;
//for shipment weight //for shipment weight
volumetricRatio = Provider.of<ShipmentRateModel>(context, listen: false) volumetricRatio = Provider.of<ShipmentRateModel>(context, listen: false)
@@ -81,7 +73,6 @@ class _CartonInfoState extends State<CartonInfo> {
_widthController.text = _box.width.toString(); _widthController.text = _box.width.toString();
_heightController.text = _box.height.toString(); _heightController.text = _box.height.toString();
_lengthController.text = _box.length.toString(); _lengthController.text = _box.length.toString();
_cargoTypes = _box.cargoTypes;
_deliveryAddress = _box.deliveryAddress; _deliveryAddress = _box.deliveryAddress;
isMixBox = _box.cartonType == carton_mix_box; isMixBox = _box.cartonType == carton_mix_box;
isFromShipments = _box.cartonType == carton_from_shipments; isFromShipments = _box.cartonType == carton_from_shipments;
@@ -101,7 +92,8 @@ class _CartonInfoState extends State<CartonInfo> {
List<Package> packages = await packageModel.getPackages(_box.userID, [ List<Package> packages = await packageModel.getPackages(_box.userID, [
package_processed_status, package_processed_status,
package_packed_status, package_packed_status,
package_shipped_status package_shipped_status,
package_delivered_status
]); ]);
packages = packages.where((p) => _box.packageIDs.contains(p.id)).toList(); packages = packages.where((p) => _box.packageIDs.contains(p.id)).toList();
packages.forEach((p) { packages.forEach((p) {
@@ -155,38 +147,6 @@ class _CartonInfoState extends State<CartonInfo> {
iconData: Icons.person, iconData: Icons.person,
); );
final shipmentBoxTitle = Container(
padding: EdgeInsets.only(left: 15, right: 10.0, top: 20),
child: Row(
children: <Widget>[
Expanded(
child:
LocalText(context, 'box.shipment_number', color: Colors.grey),
),
LocalText(context, 'box.shipment.desc', color: Colors.grey),
],
),
);
final shipmentBoxRow = Container(
padding: EdgeInsets.only(left: 15.0, right: 10.0, top: 5.0, bottom: 5.0),
child: Row(
children: <Widget>[
Expanded(
child: new Text(
_selectedShipmentBox.shipmentNumber == null
? ""
: _selectedShipmentBox.shipmentNumber,
style: textStyle,
)),
new Text(
_selectedShipmentBox.desc == null ? "" : _selectedShipmentBox.desc,
style: textStyle,
),
],
),
);
final lengthBox = LengthPicker( final lengthBox = LengthPicker(
controller: _lengthController, controller: _lengthController,
lableKey: "box.length", lableKey: "box.length",
@@ -276,30 +236,6 @@ class _CartonInfoState extends State<CartonInfo> {
packages: _box.packages, packages: _box.packages,
) )
: Container(), : Container(),
isFromPackages
? Container()
: isFromShipments
? Column(
children: [
LocalTitle(textKey: "box.shipment.boxes"),
shipmentBoxTitle,
Divider(
color: Colors.grey[400],
),
shipmentBoxRow
],
)
: Container(),
// : _selectedCartonType == "Mix carton"
// ? CartonMixTable(
// cartons: _box.cartons,
// onSelect: (c, check) {
// setState(() {
// c.isChecked = check;
// });
// },
// )
// : Container(),
isMixBox ? Container() : LocalTitle(textKey: "box.cargo.type"), isMixBox ? Container() : LocalTitle(textKey: "box.cargo.type"),
isMixBox ? Container() : cargoTableBox, isMixBox ? Container() : cargoTableBox,
...(isFromPackages ...(isFromPackages

View File

@@ -179,14 +179,15 @@ class CartonModel extends BaseModel {
String path = "/$cartons_collection"; String path = "/$cartons_collection";
var querySnap = await Firestore.instance var querySnap = await Firestore.instance
.collection(path) .collection(path)
// .where("fcs_shipment_id", isEqualTo: fcsShipmentID) .where("fcs_shipment_id", isEqualTo: fcsShipmentID)
.where("user_id", isEqualTo: userID) .where("user_id", isEqualTo: userID)
.where("is_deleted", isEqualTo: false) .where("is_deleted", isEqualTo: false)
.where("is_invoiced", isEqualTo: false) .where("is_invoiced", isEqualTo: false)
.getDocuments(); .getDocuments();
return querySnap.documents List<Carton> cartons = querySnap.documents
.map((e) => Carton.fromMap(e.data, e.documentID)) .map((e) => Carton.fromMap(e.data, e.documentID))
.toList(); .toList();
return cartons;
} }
Future<List<Carton>> getMixCartonsByFcsShipment(String fcsShipmentID) async { Future<List<Carton>> getMixCartonsByFcsShipment(String fcsShipmentID) async {

View File

@@ -99,7 +99,8 @@ class _DeliveryInfoState extends State<DeliveryInfo> {
List<Package> packages = await packageModel.getPackages(_box.userID, [ List<Package> packages = await packageModel.getPackages(_box.userID, [
package_processed_status, package_processed_status,
package_packed_status, package_packed_status,
package_shipped_status package_shipped_status,
package_delivered_status
]); ]);
packages = packages.where((p) => _box.packageIDs.contains(p.id)).toList(); packages = packages.where((p) => _box.packageIDs.contains(p.id)).toList();
packages.forEach((p) { packages.forEach((p) {

View File

@@ -16,7 +16,7 @@ import 'discount_editor.dart';
class DiscountList extends StatefulWidget { class DiscountList extends StatefulWidget {
final bool selectionMode; final bool selectionMode;
const DiscountList({Key key, this.selectionMode}) : super(key: key); const DiscountList({Key key, this.selectionMode = false}) : super(key: key);
@override @override
_DiscountListState createState() => _DiscountListState(); _DiscountListState createState() => _DiscountListState();
} }

View File

@@ -49,9 +49,9 @@ class DiscountModel extends BaseModel {
.orderBy("code", descending: false) .orderBy("code", descending: false)
.snapshots() .snapshots()
.listen((snaps) { .listen((snaps) {
discounts.clear(); _discounts.clear();
snaps.documents.forEach((d) { snaps.documents.forEach((d) {
discounts.add(Discount.fromMap(d.data, d.documentID)); _discounts.add(Discount.fromMap(d.data, d.documentID));
}); });
notifyListeners(); notifyListeners();
}); });
@@ -73,6 +73,27 @@ class DiscountModel extends BaseModel {
return paginator; return paginator;
} }
Future<List<Discount>> getDiscount(String userID) async {
String path = "/$discounts_collection";
try {
var q = Firestore.instance
.collection("$path")
.where("customer_id", isEqualTo: userID)
.where("status", isEqualTo: "available");
var snaps = await q.getDocuments(source: Source.server);
List<Discount> discounts = snaps.documents.map((snap) {
if (snap.exists) {
var s = Discount.fromMap(snap.data, snap.documentID);
return s;
}
}).toList();
return discounts;
} catch (e) {
log.warning("Error!! $e");
}
return null;
}
Future<void> loadMore() async { Future<void> loadMore() async {
if (_used.ended || _selectedIndex == 1) return; if (_used.ended || _selectedIndex == 1) return;
isLoading = true; isLoading = true;

View File

@@ -6,6 +6,7 @@ import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/display_text.dart';
import 'package:fcs/pages/widgets/local_button.dart'; import 'package:fcs/pages/widgets/local_button.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/pdf_screen.dart';
import 'package:fcs/pages/widgets/popupmenu.dart'; import 'package:fcs/pages/widgets/popupmenu.dart';
import 'package:fcs/pages/widgets/progress.dart'; import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@@ -200,13 +201,8 @@ class _FcsShipmentInfoState extends State<FcsShipmentInfo> {
return PopupMenuButton<PopupMenu>( return PopupMenuButton<PopupMenu>(
elevation: 3.2, elevation: 3.2,
icon: Icon(Icons.more_vert, color: primaryColor), icon: Icon(Icons.more_vert, color: primaryColor),
tooltip: 'This is tooltip',
onSelected: (choice) { onSelected: (choice) {
print(choice.id); _showPDF(choice.id);
if (choice.id == 1) {
} else if (choice.id == 2) {
} else if (choice.id == 3) {
} else if (choice.id == 4) {}
}, },
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
return menuPopup.map((PopupMenu choice) { return menuPopup.map((PopupMenu choice) {
@@ -252,4 +248,39 @@ class _FcsShipmentInfoState extends State<FcsShipmentInfo> {
}); });
} }
} }
_showPDF(int id) async {
setState(() {
_isLoading = true;
});
try {
var reportName = "";
if (id == 1) {
reportName = "commercial_invoice";
} else if (id == 2) {
reportName = "packing_list";
} else if (id == 3) {
reportName = "dms";
} else if (id == 4) {
reportName = "manifest";
}
_fcsShipment.reportName = reportName;
FcsShipmentModel fcsShipmentModel =
Provider.of<FcsShipmentModel>(context, listen: false);
String url = await fcsShipmentModel.report(_fcsShipment);
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => PDFScreen(
title: "",
url: url,
)));
// Navigator.pop(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
} }

View File

@@ -172,4 +172,8 @@ class FcsShipmentModel extends BaseModel {
Future<void> ship(FcsShipment fcsShipment) { Future<void> ship(FcsShipment fcsShipment) {
return Services.instance.fcsShipmentService.ship(fcsShipment); return Services.instance.fcsShipmentService.ship(fcsShipment);
} }
Future<String> report(FcsShipment fcsShipment) {
return Services.instance.fcsShipmentService.report(fcsShipment);
}
} }

View File

@@ -1,5 +1,4 @@
import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/carton.dart';
import 'package:fcs/domain/entities/package.dart';
import 'package:fcs/domain/entities/rate.dart'; import 'package:fcs/domain/entities/rate.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
@@ -20,7 +19,7 @@ class InvoiceCartonTable extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final tableTitle = Container( final tableTitle = Container(
padding: EdgeInsets.only(right: 10.0, top: 20), padding: EdgeInsets.only(right: 10.0, top: 5),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
SizedBox( SizedBox(
@@ -76,10 +75,6 @@ class InvoiceCartonTable extends StatelessWidget {
p.value.cartonNumber, p.value.cartonNumber,
style: textStyle, style: textStyle,
), ),
Text(
p.value.shipmentNumber ?? "",
style: textStyle,
),
], ],
)), )),
Flexible( Flexible(

View File

@@ -0,0 +1,88 @@
import 'package:fcs/domain/entities/discount.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/my_data_table.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class InvoiceDiscountList extends StatelessWidget {
final List<Discount> discounts;
const InvoiceDiscountList({
Key key,
this.discounts,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
leading: new IconButton(
icon: new Icon(CupertinoIcons.back),
onPressed: () => Navigator.pop(context),
),
backgroundColor: primaryColor,
title: LocalText(
context,
"invoice.shipment.discount.title",
fontSize: 20,
color: Colors.white,
),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: table(context),
));
}
Widget table(BuildContext context) {
return MyDataTable(
headingRowHeight: 40,
columns: [
MyDataColumn(
label: LocalText(
context,
"discount.code",
color: Colors.grey,
),
),
MyDataColumn(
label: LocalText(
context,
"discount.amount",
color: Colors.grey,
),
),
],
rows: getRows(context),
);
}
List<MyDataRow> getRows(BuildContext context) {
if (discounts == null) {
return [];
}
var rows = discounts.map((c) {
return MyDataRow(
onSelectChanged: (value) => Navigator.pop(context, c),
cells: [
MyDataCell(new Text(
c.code ?? "",
style: textStyle,
)),
MyDataCell(
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(c.amount?.toStringAsFixed(2) ?? "0", style: textStyle),
],
),
),
],
);
}).toList();
return rows;
}
}

View File

@@ -0,0 +1,452 @@
import 'package:fcs/domain/entities/cargo_type.dart';
import 'package:fcs/domain/entities/carton.dart';
import 'package:fcs/domain/entities/custom_duty.dart';
import 'package:fcs/domain/entities/discount.dart';
import 'package:fcs/domain/entities/fcs_shipment.dart';
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/payment_method.dart';
import 'package:fcs/domain/entities/shipment.dart';
import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/carton/model/carton_model.dart';
import 'package:fcs/pages/discount/model/discount_model.dart';
import 'package:fcs/pages/invoice/editor/invoice_carton_table.dart';
import 'package:fcs/pages/invoice/editor/invoice_discount_list.dart';
import 'package:fcs/pages/invoice/editor/invoice_handling_fee_list.dart';
import 'package:fcs/pages/invoice/invoice_table.dart';
import 'package:fcs/pages/invoice/model/invoice_model.dart';
import 'package:fcs/pages/main/model/main_model.dart';
import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/payment_methods/model/payment_method_model.dart';
import 'package:fcs/pages/rates/custom_list.dart';
import 'package:fcs/pages/rates/model/shipment_rate_model.dart';
import 'package:fcs/pages/shipment/model/shipment_model.dart';
import 'package:fcs/pages/widgets/display_text.dart';
import 'package:fcs/pages/widgets/fcs_icons.dart';
import 'package:fcs/pages/widgets/local_button.dart';
import 'package:fcs/pages/widgets/local_dropdown.dart';
import 'package:fcs/pages/widgets/local_popup_menu_button.dart';
import 'package:fcs/pages/widgets/local_popupmenu.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_icons/flutter_icons.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class InvoiceEditor extends StatefulWidget {
final Invoice invoice;
final User customer;
final FcsShipment fcsShipment;
InvoiceEditor({this.invoice, this.customer, this.fcsShipment});
@override
_InvoiceEditorState createState() => _InvoiceEditorState();
}
class _InvoiceEditorState extends State<InvoiceEditor> {
var dateFormatter = new DateFormat('dd MMM yyyy');
Invoice _invoice;
bool _isLoading = false;
bool _isNew;
User _user;
bool _showCartons = false;
@override
void initState() {
super.initState();
_isNew = widget.invoice == null;
if (widget.invoice != null) {
_invoice = widget.invoice;
} else {
_invoice = Invoice(
customDuties: [],
cartons: [],
shipments: [],
invoiceDate: DateTime.now());
}
_user = widget.customer;
_loadAll();
}
_loadAll() async {
setState(() {
_isLoading = true;
});
try {
await _loadCartons();
await _loadShipments();
await _loadDiscount();
} catch (e) {} finally {
setState(() {
_isLoading = false;
});
}
}
_loadCartons() async {
CartonModel cartonModel = Provider.of<CartonModel>(context, listen: false);
List<Carton> cartons = await cartonModel.getCartonsForInvoice(
widget.fcsShipment.id, widget.customer.id);
cartons.forEach((c) {
c.isChecked = true;
});
setState(() {
_invoice.cartons = cartons;
});
}
_loadShipments() async {
ShipmentModel shipmentModel =
Provider.of<ShipmentModel>(context, listen: false);
List<Shipment> shipments = await shipmentModel.getShipmentWithHandlingFee(
widget.fcsShipment.id, widget.customer.id);
shipments.forEach((s) {
s.isSelected = true;
});
setState(() {
_invoice.shipments = shipments;
});
}
List<Discount> discounts = [];
_loadDiscount() async {
DiscountModel discountModel =
Provider.of<DiscountModel>(context, listen: false);
discounts = await discountModel.getDiscount(widget.customer.id);
if (discounts != null && discounts.length > 0) {
setState(() {
_invoice.discount = discounts.first;
});
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
var mainModel = Provider.of<MainModel>(context);
var paymentMethodModel = Provider.of<PaymentMethodModel>(context);
var rateModel = Provider.of<ShipmentRateModel>(context);
var rate = rateModel.rate;
final invoiceNumberBox = DisplayText(
labelTextKey: 'invoice.number',
iconData: FontAwesomeIcons.fileInvoice,
text: _invoice?.invoiceNumber ?? "");
final statusBox = DisplayText(
text: _invoice?.status ?? "",
iconData: Icons.av_timer,
labelTextKey: 'invoice.status');
final cartonTable = InvoiceCartonTable(
cartons: _invoice.cartons,
rate: rate,
onSelect: (c, checked) {
setState(() {
c.isChecked = checked;
});
},
);
final paymentTypesBox = LocalDropdown<PaymentMethod>(
callback: (v) {
setState(() {
_invoice.paymentMethod = v;
});
},
labelKey: "invoice.payment_method",
iconData: FontAwesome.money,
display: (u) => u.name,
selectedValue: _invoice.paymentMethod,
values: paymentMethodModel.paymentMethods,
);
final invoiceTableBox = InvoiceTable(
invoice: _invoice,
rate: rate,
deliveryFeeSelected: (selected) {
setState(() {
if (selected) {
_invoice.deliveryFee = rate.deliveryFee;
} else {
_invoice.deliveryFee = 0;
}
});
},
discountSelected: (discount) {
setState(() {
_invoice.discount = discount;
});
},
onRemove: (i) {
if (i.invoiceDataType == InvoiceDataType.CustomFeeDataType) {
_removeCustom(i.data);
}
if (i.invoiceDataType == InvoiceDataType.DiscountDataType) {
setState(() {
_invoice.discount = null;
});
}
if (i.invoiceDataType == InvoiceDataType.DeliveryFeeType) {
setState(() {
_invoice.deliveryFee = 0;
});
}
if (i.invoiceDataType == InvoiceDataType.HandlingFeeType) {
setState(() {
_removeShipment(i.data);
});
}
},
);
final toggleButtonsBox = ToggleButtons(
color: Colors.black45,
selectedColor: Colors.black45,
disabledColor: Colors.grey,
selectedBorderColor: primaryColor,
borderColor: Colors.transparent,
fillColor: Colors.transparent,
highlightColor: Colors.black45,
children: <Widget>[
Icon(cartonIconData),
],
onPressed: (int index) {
setState(() {
_showCartons = !_showCartons;
});
},
isSelected: [_showCartons],
);
final popupMenu = LocalPopupMenuButton(
buttonIcon: Icons.add_circle,
selectable: false,
buttonColor: Colors.black45,
popmenus: [
LocalPopupMenu(
id: 1,
textKey: "invoice.add.custom.fee.menu",
),
LocalPopupMenu(
id: 2,
textKey: "invoice.add.handling.fee.menu",
),
LocalPopupMenu(
id: 3,
textKey: "invoice.add.discount.menu",
),
LocalPopupMenu(
id: 4,
textKey: "invoice.delivery_fee",
)
],
popupMenuCallback: (p) async {
if (p.id == 1) {
CustomDuty customDuty = await Navigator.of(context).push(
CupertinoPageRoute(
builder: (context) => CustomList(selected: true)));
_addCustom(customDuty);
} else if (p.id == 2) {
Shipment shipment = await Navigator.of(context).push(
CupertinoPageRoute(
builder: (context) =>
InvoiceHandlingFeeList(shipments: _invoice.shipments)));
_addShipment(shipment);
} else if (p.id == 3) {
Discount discount =
await Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => InvoiceDiscountList(
discounts: discounts,
)));
if (discount != null) {
setState(() {
_invoice.discount = discount;
});
}
} else if (p.id == 4) {
setState(() {
_invoice.deliveryFee = rate.deliveryFee;
});
}
},
);
final headerBox = Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(dateFormatter.format(_invoice.invoiceDate)),
SizedBox(
height: 10,
),
Text(_user?.name ?? ""),
Text(
_user?.fcsID ?? "",
style: TextStyle(fontSize: 12),
)
],
),
Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
toggleButtonsBox,
popupMenu,
],
),
],
);
final createBtn = LocalButton(
textKey: "invoice.issue.btn",
callBack: _save,
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
leading: new IconButton(
icon: new Icon(CupertinoIcons.back, color: primaryColor),
onPressed: () => Navigator.of(context).pop(),
),
backgroundColor: Colors.white,
shadowColor: Colors.transparent,
title: LocalText(context, 'invoice.form.title',
color: primaryColor, fontSize: 20),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: <Widget>[
headerBox,
_isNew ? Container() : invoiceNumberBox,
_isNew ? Container() : statusBox,
_showCartons ? cartonTable : Container(),
_showCartons
? Divider(
color: primaryColor,
thickness: 2,
)
: Container(),
invoiceTableBox,
SizedBox(
height: 10,
),
paymentTypesBox,
SizedBox(
height: 10,
),
_isNew
? createBtn
: mainModel.isCustomer()
? Container()
: Container(
child: Column(
children: <Widget>[
fcsButton(context,
getLocalString(context, 'invoice.btn_save'))
],
)),
_isNew
? Container()
: fcsButton(context,
getLocalString(context, 'invoice.btn_payment_receipt'))
],
),
),
),
);
}
_addCustom(CustomDuty customDuty) {
if (customDuty == null) return;
setState(() {
_invoice.customDuties.remove(customDuty);
_invoice.customDuties.add(customDuty);
});
}
_addShipment(Shipment shipment) {
if (shipment == null) return;
shipment.isSelected = true;
setState(() {
_invoice.shipments.remove(shipment);
_invoice.shipments.add(shipment);
});
}
_removeShipment(Shipment shipment) {
if (shipment == null) return;
shipment.isSelected = false;
setState(() {
_invoice.shipments.remove(shipment);
_invoice.shipments.add(shipment);
});
}
_removeCustom(CustomDuty customDuty) {
setState(() {
_invoice.customDuties.remove(customDuty);
});
}
_save() async {
var rateModel = Provider.of<ShipmentRateModel>(context, listen: false);
double amount = _invoice.getNetAmount(rateModel.rate);
if (_invoice.paymentMethod == null) {
showMsgDialog(context, "Error", "Payment method required");
return;
}
List<CargoType> cargoTypes = _invoice.getCargoTypes(rateModel.rate);
if (cargoTypes == null || cargoTypes.length == 0) {
showMsgDialog(context, "Error", "Expected at least one cargo type");
return;
}
if ((amount ?? 0) <= 0) {
showMsgDialog(context, "Error", "Expected positive amount");
return;
}
setState(() {
_isLoading = true;
});
try {
InvoiceModel invoiceModel =
Provider.of<InvoiceModel>(context, listen: false);
Invoice invoice = Invoice();
invoice.cargoTypes = cargoTypes;
invoice.amount = amount;
invoice.handlingFee = _invoice.getHandlingFee();
invoice.cartons = _invoice.cartons.where((c) => c.isChecked).toList();
invoice.shipments =
_invoice.shipments.where((s) => s.isSelected).toList();
invoice.discount = _invoice.discount;
invoice.deliveryFee = _invoice.deliveryFee;
invoice.userID = widget.customer.id;
invoice.fcsShipmentID = widget.fcsShipment.id;
invoice.invoiceDate = _invoice.invoiceDate;
invoice.paymentMethod = _invoice.paymentMethod;
invoice.customDuties = _invoice.customDuties;
await invoiceModel.createInvoice(invoice);
Navigator.pop(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -1,38 +1,60 @@
import 'package:fcs/domain/entities/custom_duty.dart'; import 'package:fcs/domain/entities/shipment.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/my_data_table.dart'; import 'package:fcs/pages/widgets/my_data_table.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
typedef OnAdd(CustomDuty customDuty); typedef OnAdd(Shipment shipment);
typedef OnRemove(CustomDuty customDuty); typedef OnRemove(Shipment shipment);
class InvoiceCustomTable extends StatelessWidget { class InvoiceHandlingFeeList extends StatelessWidget {
final List<CustomDuty> customDuties; final List<Shipment> shipments;
final OnAdd onAdd; final OnAdd onAdd;
final OnRemove onRemove; final OnRemove onRemove;
const InvoiceCustomTable( const InvoiceHandlingFeeList(
{Key key, this.customDuties, this.onAdd, this.onRemove}) {Key key, this.shipments, this.onAdd, this.onRemove})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
leading: new IconButton(
icon: new Icon(CupertinoIcons.back),
onPressed: () => Navigator.pop(context),
),
backgroundColor: primaryColor,
title: LocalText(
context,
"invoice.shipment.handling.fee.title",
fontSize: 20,
color: Colors.white,
),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: table(context),
));
}
Widget table(BuildContext context) {
return MyDataTable( return MyDataTable(
headingRowHeight: 40, headingRowHeight: 40,
columns: [ columns: [
MyDataColumn( MyDataColumn(
label: LocalText( label: LocalText(
context, context,
"rate.cutom.product_type", "invoice.shipment.number",
color: Colors.grey, color: Colors.grey,
), ),
), ),
MyDataColumn( MyDataColumn(
label: LocalText( label: LocalText(
context, context,
"rate.custom.fee", "invoice.add.handling.fee.menu",
color: Colors.grey, color: Colors.grey,
), ),
), ),
@@ -42,23 +64,23 @@ class InvoiceCustomTable extends StatelessWidget {
} }
List<MyDataRow> getRows(BuildContext context) { List<MyDataRow> getRows(BuildContext context) {
if (customDuties == null) { if (shipments == null) {
return []; return [];
} }
double total = 0; var rows = shipments.map((c) {
var rows = customDuties.map((c) {
total += c.fee;
return MyDataRow( return MyDataRow(
onSelectChanged: (value) => Navigator.pop(context, c),
cells: [ cells: [
MyDataCell(new Text( MyDataCell(new Text(
c.productType == null ? "" : c.productType, c.shipmentNumber ?? "",
style: textStyle, style: textStyle,
)), )),
MyDataCell( MyDataCell(
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Text(c.fee == null ? "0" : c.fee.toString(), style: textStyle), Text(c.handlingFee?.toStringAsFixed(2) ?? "0",
style: textStyle),
onRemove == null onRemove == null
? SizedBox( ? SizedBox(
width: 50, width: 50,
@@ -78,30 +100,6 @@ class InvoiceCustomTable extends StatelessWidget {
); );
}).toList(); }).toList();
var totalRow = MyDataRow(
onSelectChanged: (bool selected) {},
cells: [
MyDataCell(Align(
alignment: Alignment.centerRight,
child: LocalText(
context,
"invoice.total_custom_fee",
color: Colors.black87,
fontWeight: FontWeight.bold,
),
)),
MyDataCell(
Padding(
padding: const EdgeInsets.only(right: 48.0),
child: Align(
alignment: Alignment.centerRight,
child: Text(total.toString(),
style: TextStyle(fontWeight: FontWeight.bold))),
),
),
],
);
rows.add(totalRow);
return rows; return rows;
} }
} }

View File

@@ -1,350 +0,0 @@
import 'package:fcs/domain/entities/cargo_type.dart';
import 'package:fcs/domain/entities/discount.dart';
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/rate.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/discount/discount_list.dart';
import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
typedef OnDiscountSelected(Discount discount);
typedef OnDeliveryFeeSelected(bool selected);
final formatter = new NumberFormat("#,###.00");
class InvoiceCargoTable extends StatelessWidget {
final Invoice invoice;
final Rate rate;
final OnDiscountSelected discountSelected;
final OnDeliveryFeeSelected deliveryFeeSelected;
const InvoiceCargoTable(
{Key key,
this.invoice,
this.discountSelected,
this.deliveryFeeSelected,
this.rate})
: super(key: key);
@override
Widget build(BuildContext context) {
return Column(children: getRows(context));
}
getRows(BuildContext context) {
List<CargoType> _cargoTypes = invoice.getCargoTypes(rate);
double total = 0;
List<Widget> dataRow = _cargoTypes.map((cargo) {
var amount = cargo.calWeight * cargo.calRate;
total += amount;
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 15.0, bottom: 15.0),
child: Row(
children: [
Expanded(flex: 2, child: Text('${cargo.name}')),
Expanded(
flex: 2,
child: Text(
'${cargo.calWeight.toStringAsFixed(2)} x ${cargo.calRate.toStringAsFixed(2)}',
textAlign: TextAlign.center)),
Expanded(
child: Text('\$ ${amount.toStringAsFixed(2)}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)))
],
),
);
}).toList();
dataRow.insert(
0,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 15.0, bottom: 15.0),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(getLocalString(context, 'invoice.box.cargo_type'),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey))),
Expanded(
flex: 2,
child: Text(
getLocalString(context, 'cargo.weight') +
' x ' +
getLocalString(context, 'cargo.rate'),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey))),
Expanded(
child: Text(getLocalString(context, 'invoice.amount'),
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerRight,
child: LocalText(
context,
'invoice.total',
color: Colors.black,
),
),
),
SizedBox(width: 40),
Expanded(
child: Text(
'\$ ${total.toStringAsFixed(2)}',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
textAlign: TextAlign.end,
))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(left: 5.0, right: 5.0, top: 0),
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerRight,
child: LocalText(
context,
'invoice.discount_value',
color: Colors.black,
),
),
),
new IconButton(
icon: Icon(Icons.search, color: primaryColor),
onPressed: () async {
Discount discount = await Navigator.of(context).push(
CupertinoPageRoute(
builder: (context) =>
DiscountList(selectionMode: true)));
if (discountSelected != null) {
discountSelected(discount);
}
}),
Expanded(
child:
Text('\$ ( ${invoice.getDiscount().toStringAsFixed(2)} )',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 0.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerRight,
child: LocalText(
context,
'invoice.custom_fee',
color: Colors.black,
),
),
),
SizedBox(width: 40),
Expanded(
child: Text('\$ ${invoice.getCustomFee().toStringAsFixed(2)}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)),
),
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(left: 5.0, right: 5.0, top: 20.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerRight,
child: LocalText(
context,
'invoice.handling_fee',
color: Colors.black,
),
),
),
SizedBox(width: 50),
Expanded(
child: Text(
'\$ ${invoice.getHandlingFee().toStringAsFixed(2) ?? ""}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerRight,
child: LocalText(
context,
'invoice.delivery_fee',
color: Colors.black,
),
)),
Switch(
value: (invoice.deliveryFee ?? 0) > 0,
onChanged: (value) {
if (deliveryFeeSelected != null) {
deliveryFeeSelected(value);
}
},
activeTrackColor: primaryColor.withOpacity(0.8),
activeColor: primaryColor,
),
Expanded(
child:
Text('\$ ${invoice.getDeliveryFee().toStringAsFixed(2)}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
child: Row(
children: [
Expanded(child: Text('')),
Expanded(
flex: 2,
child: Divider(
thickness: 3,
)),
],
)));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.net_amount',
color: Colors.black,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Text(
'\$ ${invoice.getNetAmount(rate).toStringAsFixed(2)}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryColor)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.balance',
color: Colors.black,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Text(
'\$ ${invoice.getTotalBalance(rate).toStringAsFixed(2)}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryColor)))
],
),
));
return dataRow;
}
}

View File

@@ -2,7 +2,7 @@ import 'package:fcs/domain/entities/fcs_shipment.dart';
import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/customer/model/customer_model.dart'; import 'package:fcs/pages/customer/model/customer_model.dart';
import 'package:fcs/pages/invoice/invoice_editor.dart'; import 'package:fcs/pages/invoice/editor/invoice_editor.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/progress.dart'; import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@@ -82,12 +82,15 @@ class _InvoiceCustomerListState extends State<InvoiceCustomerList> {
Widget _item(User customer) { Widget _item(User customer) {
return InkWell( return InkWell(
onTap: () { onTap: () async {
Navigator.of(context).push(CupertinoPageRoute( bool created = await Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => InvoiceEditor( builder: (context) => InvoiceEditor(
customer: customer, customer: customer,
fcsShipment: widget.fcsShipment, fcsShipment: widget.fcsShipment,
))); )));
if (created ?? false) {
_load();
}
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 12.0, right: 12), padding: const EdgeInsets.only(left: 12.0, right: 12),

View File

@@ -1,327 +0,0 @@
import 'package:fcs/domain/entities/cargo_type.dart';
import 'package:fcs/domain/entities/carton.dart';
import 'package:fcs/domain/entities/custom_duty.dart';
import 'package:fcs/domain/entities/discount.dart';
import 'package:fcs/domain/entities/fcs_shipment.dart';
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/payment_method.dart';
import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/carton/model/carton_model.dart';
import 'package:fcs/pages/discount/discount_list.dart';
import 'package:fcs/pages/discount/model/discount_model.dart';
import 'package:fcs/pages/invoice/invoice_cargo_table.dart';
import 'package:fcs/pages/invoice/invoice_carton_table.dart';
import 'package:fcs/pages/main/model/main_model.dart';
import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/payment_methods/model/payment_method_model.dart';
import 'package:fcs/pages/rates/custom_list.dart';
import 'package:fcs/pages/rates/model/shipment_rate_model.dart';
import 'package:fcs/pages/widgets/display_text.dart';
import 'package:fcs/pages/widgets/fcs_id_icon.dart';
import 'package:fcs/pages/widgets/local_dropdown.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/local_title.dart';
import 'package:fcs/pages/widgets/multi_img_controller.dart';
import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_icons/flutter_icons.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'invoice_custom_table.dart';
class InvoiceEditor extends StatefulWidget {
final Invoice invoice;
final User customer;
final FcsShipment fcsShipment;
InvoiceEditor({this.invoice, this.customer, this.fcsShipment});
@override
_InvoiceEditorState createState() => _InvoiceEditorState();
}
class _InvoiceEditorState extends State<InvoiceEditor> {
User user;
var dateFormatter = new DateFormat('dd MMM yyyy');
TextEditingController _invoiceNumberController = new TextEditingController();
TextEditingController _dateController = new TextEditingController();
TextEditingController _nameController = new TextEditingController();
TextEditingController _phoneController = new TextEditingController();
TextEditingController _discountController = new TextEditingController();
TextEditingController _amountController = new TextEditingController();
TextEditingController _statusController = new TextEditingController();
TextEditingController _handlingFeeController = new TextEditingController();
TextEditingController _customFeeController = new TextEditingController();
MultiImgController multiImgController = MultiImgController();
TextEditingController _descriptionController = new TextEditingController();
TextEditingController _balanceController = new TextEditingController();
Invoice _invoice;
bool _isLoading = false;
List<Carton> _cartons = [];
bool isSwitched = false;
int deliveryfee = 0;
double customFee = 10.0;
double handlingFee = 10.0; // it will get from shipment
double total = 0;
Discount _discount;
bool _isNew = false;
Discount selectedDiscount;
int selectedDiscountAmt;
PaymentMethod _paymentMethod;
double volumetricRatio = 0;
List<Carton> selectedBoxes = [];
List<CustomDuty> customs = [];
// List<CargoType> _cargoTypes = [
// CargoType(name: 'General Cargo', weight: 33, rate: 6),
// CargoType(name: 'Medicine', weight: 33, rate: 7),
// CargoType(name: 'Dangerous Cargo', weight: 33, rate: 8)
// ];
@override
void initState() {
super.initState();
volumetricRatio = Provider.of<ShipmentRateModel>(context, listen: false)
.rate
.volumetricRatio;
if (widget.invoice != null) {
_isNew = false;
_invoice = widget.invoice;
_invoiceNumberController.text = _invoice.invoiceNumber;
_dateController.text = dateFormatter.format(_invoice.invoiceDate);
_nameController.text = _invoice.customerName;
_phoneController.text = _invoice.customerPhoneNumber;
// _amountController.text = _invoice.getAmount.toString();
_amountController.text = _invoice.amount.toString();
_statusController.text = _invoice.status.toString();
_handlingFeeController.text = '0';
_customFeeController.text = '0';
// multiImgController.setImageUrls = _receipts;
_descriptionController.text = 'For Electronics goods';
_balanceController.text =
(_invoice.amount - _invoice.receipts[0].amount).toString();
// _boxes = _invoice.packages;
} else {
_isNew = true;
_dateController.text = dateFormatter.format(DateTime.now());
_amountController.text = '0';
_handlingFeeController.text = '0';
_customFeeController.text = '0';
_descriptionController.text = '';
_balanceController.text = '0';
_invoice = Invoice(customDuties: [], cartons: []);
}
if (widget.customer != null && widget.invoice == null) {
user = widget.customer;
setState(() {
_isNew = true;
});
}
_loadCartons();
}
_loadCartons() async {
CartonModel cartonModel = Provider.of<CartonModel>(context, listen: false);
List<Carton> cartons = await cartonModel.getCartonsForInvoice(
widget.fcsShipment.id, widget.customer.id);
setState(() {
_cartons = cartons;
});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
var mainModel = Provider.of<MainModel>(context);
var discountModel = Provider.of<DiscountModel>(context);
var paymentMethodModel = Provider.of<PaymentMethodModel>(context);
var rateModel = Provider.of<ShipmentRateModel>(context);
var rate = rateModel.rate;
final nameBox = DisplayText(
iconData: Feather.user,
labelTextKey: 'invoice.customer_name',
text: user != null ? user.name : 'Ko Nyi');
final statusBox = DisplayText(
text: _statusController.text,
iconData: Icons.av_timer,
labelTextKey: 'invoice.status');
final fcsIDBox = DisplayText(
text: user != null ? user.fcsID : "FCS-KRUTUG",
labelTextKey: "box.fcs.id",
icon: FcsIDIcon(),
);
final cartonTable = InvoiceCartonTable(
cartons: _cartons,
rate: rate,
onSelect: (c, checked) {
setState(() {
c.isChecked = checked;
});
if (checked) {
_invoice.cartons.add(c);
} else {
_invoice.cartons.remove(c);
}
},
);
final customTableHeaderBox = LocalTitle(
textKey: "invoice.custom_fee",
trailing: IconButton(
icon: Icon(Icons.add_circle, color: primaryColor),
onPressed: () async {
CustomDuty customDuty = await Navigator.of(context).push(
CupertinoPageRoute(
builder: (context) => CustomList(selected: true)));
_addCustom(customDuty);
}));
final customTableBox = InvoiceCustomTable(
customDuties: _invoice.customDuties,
onAdd: (c) => _addCustom(c),
onRemove: (c) => _removeCustom(c),
);
var paymentTypesBox = LocalDropdown<PaymentMethod>(
callback: (v) {
setState(() {
_paymentMethod = v;
});
},
labelKey: "invoice.payment_method",
iconData: FontAwesome.money,
display: (u) => u.name,
selectedValue: _paymentMethod,
values: paymentMethodModel.paymentMethods,
);
final cargoTypeTableBox = InvoiceCargoTable(
invoice: _invoice,
rate: rate,
deliveryFeeSelected: (selected) {
setState(() {
if (selected) {
_invoice.deliveryFee = rate.deliveryFee;
} else {
_invoice.deliveryFee = 0;
}
});
},
discountSelected: (discount) {
setState(() {
_invoice.discount = discount;
});
},
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
leading: new IconButton(
icon: new Icon(CupertinoIcons.back, color: primaryColor),
onPressed: () => Navigator.of(context).pop(),
),
backgroundColor: Colors.white,
shadowColor: Colors.transparent,
title: LocalText(context, 'invoice.form.title',
color: primaryColor, fontSize: 20),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: <Widget>[
LocalTitle(textKey: "invoice.customer_info"),
DisplayText(
labelTextKey: 'invoice.date',
iconData: Icons.date_range,
text: _dateController.text),
widget.invoice == null
? Container()
: DisplayText(
labelTextKey: 'invoice.number',
iconData: FontAwesomeIcons.fileInvoice,
text: _invoiceNumberController.text),
fcsIDBox,
nameBox,
_isNew ? Container() : statusBox,
SizedBox(height: 20),
customTableHeaderBox,
customTableBox,
SizedBox(height: 20),
cartonTable,
LocalTitle(textKey: "invoice.cargo_type"),
cargoTypeTableBox,
// Column(children: getCargoTableByBox(context)),
SizedBox(height: 20),
paymentTypesBox,
SizedBox(height: 20),
_isNew
? Container()
: LocalTitle(
textKey: "invoice.payment_attachment",
trailing: IconButton(
icon: Icon(Icons.add_circle, color: primaryColor),
onPressed: () async {})),
widget.invoice == null
? fcsButton(
context, getLocalString(context, 'invoice.btn_create'))
: mainModel.isCustomer()
? Container()
: Container(
child: Column(
children: <Widget>[
fcsButton(context,
getLocalString(context, 'invoice.btn_save'))
],
)),
_isNew
? Container()
: fcsButton(context,
getLocalString(context, 'invoice.btn_payment_receipt'))
],
),
),
),
);
}
getTotalBalance(total) {
double balance = 0;
double custom = customFee != 0 ? customFee.toDouble() : 0;
double discount = _discount != null ? _discount.amount.toDouble() : 0;
double deliveryFee = deliveryfee != 0 ? deliveryfee.toDouble() : 0;
balance = (total + custom + deliveryFee) - discount;
return balance;
}
_addCustom(CustomDuty customDuty) {
if (customDuty == null) return;
setState(() {
_invoice.customDuties.remove(customDuty);
_invoice.customDuties.add(customDuty);
});
}
_removeCustom(CustomDuty customDuty) {
setState(() {
_invoice.customDuties.remove(customDuty);
});
}
_save() {}
}

View File

@@ -1,172 +1,61 @@
import 'package:fcs/domain/constants.dart';
import 'package:fcs/domain/entities/carton.dart'; import 'package:fcs/domain/entities/carton.dart';
import 'package:fcs/domain/entities/cargo_type.dart';
import 'package:fcs/domain/entities/custom_duty.dart';
import 'package:fcs/domain/entities/discount.dart';
import 'package:fcs/domain/entities/invoice.dart'; import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/payment_method.dart';
import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/localization/app_translations.dart'; import 'package:fcs/pages/carton/model/carton_model.dart';
import 'package:fcs/pages/discount/discount_list.dart'; import 'package:fcs/pages/invoice/editor/invoice_carton_table.dart';
import 'package:fcs/pages/discount/model/discount_model.dart'; import 'package:fcs/pages/invoice/invoice_table.dart';
import 'package:fcs/pages/invoice/invoice_editor.dart'; import 'package:fcs/pages/invoice/model/invoice_model.dart';
import 'package:fcs/pages/main/model/language_model.dart'; import 'package:fcs/pages/invoice/widgets.dart';
import 'package:fcs/pages/main/model/main_model.dart';
import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/payment_methods/model/payment_method_model.dart';
import 'package:fcs/pages/rates/custom_list.dart';
import 'package:fcs/pages/rates/model/shipment_rate_model.dart'; import 'package:fcs/pages/rates/model/shipment_rate_model.dart';
import 'package:fcs/pages/user_search/user_serach.dart';
import 'package:fcs/pages/widgets/bottom_up_page_route.dart';
import 'package:fcs/pages/widgets/discount_dropdown.dart';
import 'package:fcs/pages/widgets/display_text.dart'; import 'package:fcs/pages/widgets/display_text.dart';
import 'package:fcs/pages/widgets/fcs_id_icon.dart'; import 'package:fcs/pages/widgets/fcs_icons.dart';
import 'package:fcs/pages/widgets/input_text.dart'; import 'package:fcs/pages/widgets/local_button.dart';
import 'package:fcs/pages/widgets/local_dropdown.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/local_title.dart';
import 'package:fcs/pages/widgets/multi_img_controller.dart';
import 'package:fcs/pages/widgets/multi_img_file.dart';
import 'package:fcs/pages/widgets/my_data_table.dart';
import 'package:fcs/pages/widgets/progress.dart'; import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_icons/flutter_icons.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class InvoiceInfoPage extends StatefulWidget { class InvoiceInfo extends StatefulWidget {
final Invoice invoice; final Invoice invoice;
final User customer; final bool forCustomer;
InvoiceInfoPage({this.invoice, this.customer}); InvoiceInfo({this.invoice, this.forCustomer});
@override @override
_InvoiceInfoPageState createState() => _InvoiceInfoPageState(); _InvoiceInfoState createState() => _InvoiceInfoState();
} }
class _InvoiceInfoPageState extends State<InvoiceInfoPage> { class _InvoiceInfoState extends State<InvoiceInfo> {
User user;
var dateFormatter = new DateFormat('dd MMM yyyy'); var dateFormatter = new DateFormat('dd MMM yyyy');
TextEditingController _invoiceNumberController = new TextEditingController();
TextEditingController _dateController = new TextEditingController();
TextEditingController _nameController = new TextEditingController();
TextEditingController _phoneController = new TextEditingController();
TextEditingController _discountController = new TextEditingController();
TextEditingController _amountController = new TextEditingController();
TextEditingController _statusController = new TextEditingController();
TextEditingController _customFeeController = new TextEditingController();
MultiImgController multiImgController = MultiImgController();
TextEditingController _descriptionController = new TextEditingController();
TextEditingController _balanceController = new TextEditingController();
Invoice _invoice; Invoice _invoice;
bool _isLoading = false; bool _isLoading = false;
List<Carton> _boxes = [];
bool isSwitched = false;
int deliveryfee = 0;
double customFee = 0;
double total = 0;
Discount _discount;
bool isNew = false;
Discount selectedDiscount;
int selectedDiscountAmt;
PaymentMethod paymentMethod;
double volumetricRatio = 0;
List<Carton> selectedBoxes = [];
List<CustomDuty> customs = [];
List<CargoType> _cargoTypes = [
CargoType(id: "1", name: 'General Cargo', weight: 33, rate: 6),
CargoType(id: "2", name: 'Medicine', weight: 33, rate: 7),
CargoType(id: "3", name: 'Dangerous Cargo', weight: 33, rate: 8)
];
List<String> _receipts = [
"assets/buying_online_with_first_last_name.png",
];
bool _showCartons = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
volumetricRatio = Provider.of<ShipmentRateModel>(context, listen: false) _invoice = widget.invoice;
.rate _invoice.shipments?.forEach((s) {
.volumetricRatio; s.isSelected = true;
});
_loadCartons();
}
if (widget.invoice != null) { _loadCartons() async {
_invoice = widget.invoice; CartonModel cartonModel = Provider.of<CartonModel>(context, listen: false);
_invoiceNumberController.text = _invoice.invoiceNumber; List<Carton> cartons = [];
_dateController.text = dateFormatter.format(_invoice.invoiceDate); for (var c in _invoice?.cartons ?? []) {
_nameController.text = _invoice.customerName; var _carton = await cartonModel.getCarton(c.id);
_phoneController.text = _invoice.customerPhoneNumber; _carton.isChecked = true;
// _amountController.text = _invoice.getAmount.toString(); cartons.add(_carton);
_amountController.text = _invoice.amount.toString();
_statusController.text = _invoice.status.toString();
_customFeeController.text = '0';
// multiImgController.setImageUrls = _receipts;
_descriptionController.text = 'For Electronics goods';
_balanceController.text =
(_invoice.amount - _invoice.receipts[0].amount).toString();
// _boxes = _invoice.packages;
} else {
_dateController.text = dateFormatter.format(DateTime.now());
_amountController.text = '0';
_customFeeController.text = '0';
_descriptionController.text = '';
_balanceController.text = '0';
setState(() {
isNew = true;
});
} }
setState(() {
_boxes = [ _invoice.cartons = cartons;
Carton( });
shipmentNumber: "A202",
receiverNumber: "3",
receiverName: "Ko Myo Min",
boxNumber: "1",
rate: 7,
packageType: "General",
weight: 75,
status: "Packed",
receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon',
cargoDesc: "Clothes",
arrivedDate: DateTime(2020, 6, 1),
width: 10,
height: 10,
length: 10,
// packages: packages,
// statusHistory: statusHistory,
cargoTypes: [
CargoType(name: 'General Cargo', weight: 25),
CargoType(name: 'Medicine', weight: 20),
CargoType(name: 'Dangerous Cargo', weight: 30)
]),
Carton(
shipmentNumber: "A202",
receiverNumber: "3",
receiverName: "Ko Myo Min",
boxNumber: "2",
rate: 7,
packageType: "General",
weight: 75,
status: "Packed",
cargoDesc: "Clothes",
arrivedDate: DateTime(2020, 6, 1),
width: 10,
height: 10,
length: 10,
// statusHistory: statusHistory,
// packages: packages,
receiverAddress: '1 Bo Yar Nyunt St.\nDagon Tsp, Yangon',
cargoTypes: [
CargoType(name: 'General Cargo', weight: 25),
CargoType(name: 'Medicine', weight: 20),
CargoType(name: 'Dangerous Cargo', weight: 30)
])
];
} }
@override @override
@@ -176,21 +65,88 @@ class _InvoiceInfoPageState extends State<InvoiceInfoPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final nameBox = DisplayText( bool isCanceled = _invoice.status == invoice_cancel_status;
iconData: Feather.user, bool isPaid = _invoice.status == invoice_paid_status;
labelTextKey: 'invoice.customer_name', var rateModel = Provider.of<ShipmentRateModel>(context);
text: user != null ? user.name : 'Ko Nyi'); var rate = rateModel.rate;
final fcsIDBox = DisplayText( final cartonTable = InvoiceCartonTable(
text: user != null ? user.fcsID : "FCS-KRUTUG", cartons: _invoice.cartons,
labelTextKey: "box.fcs.id", rate: rate,
icon: FcsIDIcon(),
); );
final statusBox = DisplayText( final invoiceTableBox = InvoiceTable(
text: _statusController.text, invoice: _invoice,
iconData: Icons.av_timer, rate: rate,
labelTextKey: 'invoice.status'); deliveryFeeSelected: (selected) {
setState(() {
if (selected) {
_invoice.deliveryFee = rate.deliveryFee;
} else {
_invoice.deliveryFee = 0;
}
});
},
discountSelected: (discount) {
setState(() {
_invoice.discount = discount;
});
},
);
final toggleButtonsBox = ToggleButtons(
color: Colors.black45,
selectedColor: Colors.black45,
disabledColor: Colors.grey,
selectedBorderColor: primaryColor,
borderColor: Colors.transparent,
fillColor: Colors.transparent,
highlightColor: Colors.black45,
children: <Widget>[
Icon(cartonIconData),
],
onPressed: (int index) {
setState(() {
_showCartons = !_showCartons;
});
},
isSelected: [_showCartons],
);
final headerBox = Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(dateFormatter.format(_invoice.invoiceDate)),
SizedBox(
height: 5,
),
Text(_invoice?.userName ?? ""),
Text(
_invoice?.fcsID ?? "",
style: TextStyle(fontSize: 12),
)
],
),
Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
toggleButtonsBox,
],
),
],
);
final paymentMethodBox = DisplayText(
labelTextKey: "invoice.payment_method",
text: _invoice.paymentMethod.name,
);
final cancelBtn = LocalButton(
textKey: "invoice.cancel.btn",
callBack: _cancel,
);
return LocalProgress( return LocalProgress(
inAsyncCall: _isLoading, inAsyncCall: _isLoading,
@@ -205,71 +161,31 @@ class _InvoiceInfoPageState extends State<InvoiceInfoPage> {
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
title: LocalText(context, 'invoice.form.title', title: LocalText(context, 'invoice.form.title',
color: primaryColor, fontSize: 20), color: primaryColor, fontSize: 20),
actions: [
new IconButton(
icon: Icon(
Icons.delete,
color: primaryColor,
),
onPressed: () {}),
new IconButton(
icon: Icon(
Icons.edit,
color: primaryColor,
),
onPressed: () {
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => InvoiceEditor(invoice: _invoice)));
})
],
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: ListView( child: ListView(
children: <Widget>[ children: <Widget>[
LocalTitle(textKey: "invoice.customer_info"), getInvoiceStatus(context, _invoice),
DisplayText( headerBox,
labelTextKey: 'invoice.date', _showCartons ? cartonTable : Container(),
iconData: Icons.date_range, _showCartons
text: _dateController.text), ? Divider(
widget.invoice == null color: primaryColor,
? Container() thickness: 2,
: DisplayText( )
labelTextKey: 'invoice.number',
iconData: FontAwesomeIcons.fileInvoice,
text: _invoiceNumberController.text),
fcsIDBox,
nameBox,
statusBox,
LocalTitle(textKey: "invoice.box_info"),
Center(child: Column(children: getCartonRows(context))),
SizedBox(height: 20),
LocalTitle(textKey: "invoice.custom_fee"),
Column(children: getCustomFeeRows(context)),
SizedBox(height: 20),
LocalTitle(textKey: "invoice.cargo_type"),
Column(children: getCargoTableByBox(context)),
SizedBox(height: 20),
Container(
padding: EdgeInsets.only(top: 5, left: 18),
child: Row(
children: <Widget>[
Expanded(
child: LocalText(context, 'invoice.payment_method',
fontSize: 16,
color: Colors.grey,
fontWeight: FontWeight.bold),
),
Text(
'${paymentMethod != null ? paymentMethod.accountName : "KBZ Bank"}',
style: TextStyle(fontSize: 16)),
],
),
),
SizedBox(height: 30),
!isNew
? LocalTitle(textKey: "invoice.payment_attachment")
: Container(), : Container(),
invoiceTableBox,
SizedBox(
height: 10,
),
paymentMethodBox,
SizedBox(
height: 10,
),
isCanceled || isPaid || widget.forCustomer
? Container()
: cancelBtn,
], ],
), ),
), ),
@@ -277,528 +193,27 @@ class _InvoiceInfoPageState extends State<InvoiceInfoPage> {
); );
} }
getCartonRows(BuildContext context) { _cancel() {
List<Widget> dataRow = []; showConfirmDialog(context, "invoice.cancel.confirm", _cancelInvoice);
dataRow = _boxes.map((box) {
return Container(
height: 50,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding:
const EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0, bottom: 5.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(box.packageNumber),
Text(box.shipmentNumber),
],
)),
Expanded(
flex: 2,
child: Text(
box.length == null
? ""
: box.length.toString() +
' x ' +
box.length.toString() +
' x ' +
box.height.toString(),
textAlign: TextAlign.center)),
Expanded(
flex: 2,
child: Center(
child: Text(
box.getShipmentWeight(volumetricRatio).toString()))),
],
),
);
}).toList();
dataRow.insert(
0,
Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 5.0, bottom: 15.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Center(
child: LocalText(
context,
"invoice.box.number",
color: Colors.grey,
),
)),
Expanded(
flex: 2,
child: Center(
child: Text('L x W x H',
style: TextStyle(color: Colors.grey)))),
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
"invoice.shipment_weight",
color: Colors.grey,
),
)),
],
),
));
return dataRow;
} }
getCustomFeeRows(BuildContext context) { _cancelInvoice() async {
customFee = 0; setState(() {
List<Widget> dataRow = []; _isLoading = true;
});
dataRow = customs.map((custom) { try {
customFee += custom.fee; InvoiceModel invoiceModel =
return Container( Provider.of<InvoiceModel>(context, listen: false);
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding:
const EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0, bottom: 5.0),
child: Row(
children: [
Expanded(flex: 2, child: Text('${custom.productType}')),
Expanded(
flex: 1,
child: Text('\$ ${custom.fee}', textAlign: TextAlign.center)),
Expanded(child: SizedBox(height: 40))
],
),
);
}).toList();
dataRow.insert( await invoiceModel.cancelInvoice(_invoice);
0, Navigator.pop(context, true);
Container( } catch (e) {
decoration: BoxDecoration( showMsgDialog(context, "Error", e.toString());
border: Border(bottom: BorderSide(color: Colors.grey))), } finally {
padding: const EdgeInsets.only( setState(() {
left: 5.0, right: 5.0, top: 15.0, bottom: 15.0), _isLoading = false;
child: Row(
children: [
Expanded(
flex: 2,
child: Text('Product', style: TextStyle(color: Colors.grey))),
Expanded(
flex: 1,
child: Text('Fee',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey))),
Expanded(flex: 1, child: Container())
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 15.0, bottom: 15.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.total_custom_fee',
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
flex: 1,
child: Center(
child: Text('\$ $customFee',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold)))),
Expanded(
child: Container(),
)
],
),
));
return dataRow;
}
getCargoTableByBox(BuildContext context) {
total = 0;
List<Widget> dataRow = _cargoTypes.map((cargo) {
var amount = cargo.weight * cargo.rate;
total += amount;
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 15.0, bottom: 15.0),
child: Row(
children: [
Expanded(flex: 2, child: Text('${cargo.rate}')),
Expanded(
flex: 2,
child: Text('${cargo.weight} x ${cargo.rate}',
textAlign: TextAlign.center)),
Expanded(
child: Text('\$ $amount',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)))
],
),
);
}).toList();
dataRow.insert(
0,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 15.0, bottom: 15.0),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(getLocalString(context, 'invoice.box.cargo_type'),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey))),
Expanded(
flex: 2,
child: Text(
getLocalString(context, 'cargo.weight') +
' x ' +
getLocalString(context, 'cargo.rate'),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey))),
Expanded(
child: Text(getLocalString(context, 'invoice.amount'),
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.total',
color: Colors.black,
),
),
),
Expanded(
child: Text(
'\$ $total',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
textAlign: TextAlign.end,
))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.discount_value',
color: Colors.black,
),
),
),
Expanded(
child: Text(
'\$ ${_discount != null ? _discount.amount.toInt() : 0}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 0.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.custom_fee',
color: Colors.black,
),
)),
Expanded(
child: Text('\$ $customFee',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)),
),
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 20.0, bottom: 5.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.handling_fee',
color: Colors.black,
),
)),
Expanded(
child: Text('\$ 10.0',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)),
),
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.delivery_fee',
color: Colors.black,
),
),
),
Expanded(
child: Text('\$ $deliveryfee',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
child: Row(
children: [
Expanded(child: Text('')),
Expanded(
flex: 2,
child: Divider(
thickness: 3,
)),
],
)));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.net_amount',
color: Colors.black,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Text('\$ ${getTotalBalance(total)}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryColor)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'invoice.balance',
color: Colors.black,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Text('\$ ${getTotalBalance(total)}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryColor)))
],
),
));
return dataRow;
}
getTotalBalance(total) {
double balance = 0;
double custom = customFee != 0 ? customFee.toDouble() : 0;
double deliveryFee = deliveryfee != 0 ? deliveryfee.toDouble() : 0;
double discount = _discount != null ? _discount.amount.toDouble() : 0;
balance = (total + custom + deliveryFee) - discount;
return balance;
}
List<MyDataRow> getBoxRow(BuildContext context) {
return _boxes.map((p) {
p.cargoTypes.map((cargo) {
_cargoTypes.asMap().map((index, _cargo) {
if (_cargo.id == cargo.id) {
setState(() {
_cargoTypes[index].weight += cargo.weight;
});
}
});
}); });
return MyDataRow( }
onSelectChanged: (bool selected) {},
cells: [
MyDataCell(Checkbox(
value: true,
onChanged: (value) {
selectedBoxes.add(p);
},
)),
MyDataCell(new Text(
p.boxNumber == null
? ""
: '${p.shipmentNumber}-${p.receiverNumber} #${p.boxNumber}',
style: textStyle,
)),
MyDataCell(new Text(
p.length == null
? ""
: p.length.toString() +
' x ' +
p.length.toString() +
' x ' +
p.height.toString(),
style: textStyle,
)),
],
);
}).toList();
}
List<MyDataRow> getCargoDataRow(BuildContext context) {
return _cargoTypes.asMap().entries.map((c) {
var cargo = c.value;
var amt = cargo.weight * cargo.rate;
return MyDataRow(
onSelectChanged: (bool selected) {},
cells: [
MyDataCell(new Text(
cargo.name,
style: textStyle,
)),
MyDataCell(new Text(
cargo.weight.toString() + ' x ' + cargo.rate.toString(),
style: textStyle,
)),
MyDataCell(new Text(
"\$$amt",
style: textStyle,
)),
],
);
}).toList()
// .insert(_cargoTypes.length,MyDataRow(cells: [
// MyDataCell(new Text('')),
// MyDataCell(new Text('Total')),
// MyDataCell(new Text(
// "\$5000",
// style: textStyle,
// )),
// ])
// )
;
} }
} }

View File

@@ -21,7 +21,6 @@ class InvoiceList extends StatefulWidget {
class _InvoiceListState extends State<InvoiceList> { class _InvoiceListState extends State<InvoiceList> {
bool _isLoading = false; bool _isLoading = false;
bool _showPaid = false;
var _controller = ScrollController(); var _controller = ScrollController();
@override @override
@@ -34,7 +33,9 @@ class _InvoiceListState extends State<InvoiceList> {
} }
}); });
Provider.of<InvoiceModel>(context, listen: false).initData(false); InvoiceModel invoiceModel =
Provider.of<InvoiceModel>(context, listen: false);
invoiceModel.initData(widget.forCustomer, true, false);
} }
@override @override
@@ -44,22 +45,35 @@ class _InvoiceListState extends State<InvoiceList> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var owner = true;
var invoiceModel = Provider.of<InvoiceModel>(context); var invoiceModel = Provider.of<InvoiceModel>(context);
final popupMenu = LocalPopupMenuButton( final popupMenu = LocalPopupMenuButton(
popmenus: [ popmenus: [
LocalPopupMenu( LocalPopupMenu(
id: 1, id: 1,
textKey: "invoice.popupmenu.pending", textKey: "invoice.popupmenu.issused",
selected: invoiceModel.selectedIndex == 1), selected: invoiceModel.selectedIndex == 1),
LocalPopupMenu( LocalPopupMenu(
id: 2, id: 2,
textKey: "invoice.popupmenu.paid", textKey: "invoice.popupmenu.paid",
selected: invoiceModel.selectedIndex == 2) selected: invoiceModel.selectedIndex == 2),
LocalPopupMenu(
id: 3,
textKey: "invoice.popupmenu.cancel",
selected: invoiceModel.selectedIndex == 3)
], ],
popupMenuCallback: (p) => this.setState(() { popupMenuCallback: (p) => this.setState(() {
_showPaid = p.id == 2; invoiceModel.selectedIndex = p.id;
if (p.id == 2) {
Provider.of<InvoiceModel>(context, listen: false)
.initData(widget.forCustomer, false, true);
} else if (p.id == 3) {
Provider.of<InvoiceModel>(context, listen: false)
.initData(widget.forCustomer, true, false);
} else {
Provider.of<InvoiceModel>(context, listen: false)
.initData(widget.forCustomer, true, false);
}
}), }),
); );
@@ -79,8 +93,9 @@ class _InvoiceListState extends State<InvoiceList> {
color: Colors.white, fontSize: 20), color: Colors.white, fontSize: 20),
actions: <Widget>[popupMenu], actions: <Widget>[popupMenu],
), ),
floatingActionButton: owner floatingActionButton: widget.forCustomer
? FloatingActionButton.extended( ? null
: FloatingActionButton.extended(
onPressed: () { onPressed: () {
_newInvoice(); _newInvoice();
}, },
@@ -88,8 +103,7 @@ class _InvoiceListState extends State<InvoiceList> {
label: label:
LocalText(context, 'invoices.add', color: Colors.white), LocalText(context, 'invoices.add', color: Colors.white),
backgroundColor: primaryColor, backgroundColor: primaryColor,
) ),
: null,
body: Column( body: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
@@ -99,15 +113,17 @@ class _InvoiceListState extends State<InvoiceList> {
controller: _controller, controller: _controller,
separatorBuilder: (context, index) => Divider( separatorBuilder: (context, index) => Divider(
color: Colors.black, color: Colors.black,
height: 1,
), ),
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
padding: EdgeInsets.only(top: 15),
shrinkWrap: true, shrinkWrap: true,
itemCount: invoiceModel.invoices.length, itemCount: invoiceModel.invoices.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return InvoiceListRow( return InvoiceListRow(
key: ValueKey(invoiceModel.invoices[index].id), key: ValueKey(invoiceModel.invoices[index].id),
invoice: invoiceModel.invoices[index]); invoice: invoiceModel.invoices[index],
forCustomer: widget.forCustomer,
);
}), }),
onRefresh: () => invoiceModel.refresh(), onRefresh: () => invoiceModel.refresh(),
), ),
@@ -131,7 +147,7 @@ class _InvoiceListState extends State<InvoiceList> {
); );
} }
_newInvoice() { _newInvoice() async {
Navigator.of(context) Navigator.of(context)
.push(CupertinoPageRoute(builder: (context) => InvoiceShipmentList())); .push(CupertinoPageRoute(builder: (context) => InvoiceShipmentList()));
} }

View File

@@ -1,138 +1,76 @@
import 'dart:async'; import 'package:fcs/domain/constants.dart';
import 'dart:io';
import 'package:fcs/domain/entities/invoice.dart'; import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/invoice/invoice_info.dart'; import 'package:fcs/pages/invoice/invoice_info.dart';
import 'package:fcs/pages/widgets/bottom_up_page_route.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'invoice_editor.dart'; import 'payment/payment_page.dart';
import 'payment_page.dart'; import '../widgets/pdf_screen.dart';
import 'payment_pdf_screen.dart';
class InvoiceListRow extends StatefulWidget { class InvoiceListRow extends StatelessWidget {
final dateFormatter = new DateFormat('dd MMM yyyy');
final Invoice invoice; final Invoice invoice;
InvoiceListRow({Key key, this.invoice}) : super(key: key); final bool forCustomer;
InvoiceListRow({Key key, this.invoice, this.forCustomer}) : super(key: key);
@override
_InvoiceListRowState createState() => _InvoiceListRowState();
}
class _InvoiceListRowState extends State<InvoiceListRow> {
var dateFormatter = new DateFormat('dd MMM yyyy');
final double dotSize = 15.0;
Invoice _invoice = new Invoice();
String pdfPath = '';
@override
void initState() {
super.initState();
if (widget.invoice != null) {
_invoice = widget.invoice;
}
fromAsset('assets/Invoice-A092(A)-32.pdf', 'Invoice-A092(A)-32.pdf')
.then((f) {
setState(() {
pdfPath = f.path;
});
});
}
Future<File> fromAsset(String asset, String filename) async {
// To open from assets, you can copy them to the app storage folder, and the access them "locally"
Completer<File> completer = Completer();
print('asset => $asset');
print('assest => ${await rootBundle.load(asset)}');
try {
var dir = await getApplicationDocumentsDirectory();
File file = File("${dir.path}/$filename");
var data = await rootBundle.load(asset);
print('data => $data');
var bytes = data.buffer.asUint8List();
await file.writeAsBytes(bytes, flush: true);
completer.complete(file);
} catch (e) {
throw Exception('Error parsing asset file! ===> ' + e.toString());
}
return completer.future;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var owner = true; return InkWell(
return Container( onTap: () {
padding: EdgeInsets.only(left: 15, right: 15), Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => PDFScreen(
title: invoice.invoiceNumber,
url: invoice.invoiceURL,
)));
},
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: new Padding( child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0), padding: const EdgeInsets.symmetric(vertical: 10.0),
child: InkWell( child: new Row(
onTap: () { children: <Widget>[
owner Container(
? Navigator.of(context).push(CupertinoPageRoute( padding: EdgeInsets.only(left: 5, right: 10),
builder: (context) => PaymentPDFScreen( child: Icon(
path: pdfPath, FontAwesomeIcons.fileInvoice,
))) color: primaryColor,
: Navigator.of(context).push(CupertinoPageRoute( size: 30,
builder: (context) => PaymentPDFScreen(
path: pdfPath,
)));
},
child: new Row(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 5, right: 10),
child: Icon(
FontAwesomeIcons.fileInvoice,
color: primaryColor,
size: 30,
),
), ),
new Expanded( ),
new Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 0),
child: new Column( child: new Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Padding( new Text(
padding: const EdgeInsets.only(left: 8.0), invoice.invoiceNumber ?? "",
child: new Text( style: new TextStyle(
_invoice.invoiceNumber == null fontSize: 15.0, color: Colors.black),
? ''
: _invoice.invoiceNumber,
style: new TextStyle(
fontSize: 15.0, color: Colors.black),
),
), ),
Padding( new Text(
padding: const EdgeInsets.only(left: 10.0, top: 10), invoice.status ?? "",
child: new Text( style: new TextStyle(
dateFormatter.format(_invoice.invoiceDate), fontSize: 13.0, color: primaryColor),
style: new TextStyle( ),
fontSize: 15.0, color: Colors.grey), new Text(
), dateFormatter.format(invoice.invoiceDate),
style: new TextStyle(
fontSize: 15.0, color: Colors.grey),
) )
], ],
), ),
), ),
], ),
), ],
), ),
), ),
), ),
// Padding( invoice.status == invoice_issued_status
// padding: const EdgeInsets.all(0),
// child: getStatus(_invoice.status),
// ),
_invoice.status == "Pending"
? Padding( ? Padding(
padding: const EdgeInsets.only(left: 10.0), padding: const EdgeInsets.only(left: 10.0),
child: InkWell( child: InkWell(
@@ -144,27 +82,32 @@ class _InvoiceListRowState extends State<InvoiceListRow> {
color: primaryColor, color: primaryColor,
), ),
Padding( Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 3.0),
child: Text("Payment"), child: Text(
"Payment",
style: TextStyle(fontSize: 12, color: Colors.black),
),
) )
], ],
), ),
onPressed: () { onPressed: () {
Navigator.of(context).push(CupertinoPageRoute( Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => builder: (context) => PaymentPage(
PaymentPage(invoice: _invoice))); invoice: invoice,
forCustomer: forCustomer,
)));
}, },
)), )),
) )
: Container(), : Container(),
Padding( Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: InkWell( child: IconButton(
child: Icon( icon: Icon(
Icons.more_vert, Icons.more_vert,
color: primaryColor, color: primaryColor,
), ),
onTap: () { onPressed: () {
var act = actionSheet(context); var act = actionSheet(context);
showCupertinoModalPopup( showCupertinoModalPopup(
context: context, builder: (BuildContext context) => act); context: context, builder: (BuildContext context) => act);
@@ -191,10 +134,34 @@ class _InvoiceListRowState extends State<InvoiceListRow> {
), ),
], ],
), ),
onPressed: () { onPressed: () async {
//to go invoice info page //to go invoice info page
Navigator.pop(context);
Navigator.of(context).push(CupertinoPageRoute( Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => InvoiceInfoPage(invoice: _invoice))); builder: (context) =>
InvoiceInfo(invoice: invoice, forCustomer: forCustomer)));
},
),
CupertinoActionSheetAction(
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
'Payments',
style: TextStyle(fontSize: 16, color: Colors.black),
),
),
],
),
onPressed: () async {
//to go invoice info page
Navigator.pop(context);
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => PaymentPage(
invoice: invoice,
forCustomer: forCustomer,
)));
}, },
) )
], ],

View File

@@ -1,13 +1,13 @@
import 'package:fcs/domain/entities/fcs_shipment.dart'; import 'package:fcs/domain/entities/fcs_shipment.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/fcs_shipment/model/fcs_shipment_model.dart'; import 'package:fcs/pages/fcs_shipment/model/fcs_shipment_model.dart';
import 'package:fcs/pages/shipment/model/shipment_model.dart';
import 'package:fcs/pages/widgets/local_text.dart'; import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/progress.dart'; import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'invoice_customer_list.dart';
import 'invoice_shipment_list_row.dart'; import 'invoice_shipment_list_row.dart';
class InvoiceShipmentList extends StatefulWidget { class InvoiceShipmentList extends StatefulWidget {
@@ -65,7 +65,14 @@ class _InvoiceShipmentListState extends State<InvoiceShipmentList> {
itemCount: _fcsShipments.length, itemCount: _fcsShipments.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return InvoiceShipmentListRow( return InvoiceShipmentListRow(
fcsShipment: _fcsShipments[index]); fcsShipment: _fcsShipments[index],
onSelect: (f) {
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => InvoiceCustomerList(
fcsShipment: f,
)));
},
);
}), }),
), ),
), ),

View File

@@ -8,9 +8,12 @@ import 'package:intl/intl.dart';
import '../main/util.dart'; import '../main/util.dart';
import 'invoice_customer_list.dart'; import 'invoice_customer_list.dart';
typedef OnSelect(FcsShipment fcsShipment);
class InvoiceShipmentListRow extends StatefulWidget { class InvoiceShipmentListRow extends StatefulWidget {
final OnSelect onSelect;
final FcsShipment fcsShipment; final FcsShipment fcsShipment;
const InvoiceShipmentListRow({this.fcsShipment}); const InvoiceShipmentListRow({this.fcsShipment, this.onSelect});
@override @override
_InvoiceShipmentListRowState createState() => _InvoiceShipmentListRowState(); _InvoiceShipmentListRowState createState() => _InvoiceShipmentListRowState();
@@ -35,10 +38,7 @@ class _InvoiceShipmentListRowState extends State<InvoiceShipmentListRow> {
padding: EdgeInsets.only(left: 15, right: 15), padding: EdgeInsets.only(left: 15, right: 15),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
Navigator.of(context).push(CupertinoPageRoute( if (widget.onSelect != null) widget.onSelect(widget.fcsShipment);
builder: (context) => InvoiceCustomerList(
fcsShipment: _fcsShipment,
)));
}, },
child: Row( child: Row(
children: <Widget>[ children: <Widget>[

View File

@@ -0,0 +1,230 @@
import 'package:fcs/domain/entities/cargo_type.dart';
import 'package:fcs/domain/entities/discount.dart';
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/rate.dart';
import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
typedef OnDiscountSelected(Discount discount);
typedef OnDeliveryFeeSelected(bool selected);
typedef OnRemove(InvoiceTableRow row);
final formatter = new NumberFormat("#,###.00");
enum InvoiceDataType {
CargoDataType,
DiscountDataType,
CustomFeeDataType,
HandlingFeeType,
DeliveryFeeType,
}
class InvoiceTableRow {
final dynamic data;
final String id;
final InvoiceDataType invoiceDataType;
final String desc;
final String rate;
final String amount;
InvoiceTableRow(
{this.id,
this.data,
this.invoiceDataType,
this.desc,
this.rate,
this.amount});
}
class InvoiceTable extends StatelessWidget {
final Invoice invoice;
final Rate rate;
final OnDiscountSelected discountSelected;
final OnDeliveryFeeSelected deliveryFeeSelected;
final OnRemove onRemove;
const InvoiceTable(
{Key key,
this.invoice,
this.discountSelected,
this.deliveryFeeSelected,
this.onRemove,
this.rate})
: super(key: key);
@override
Widget build(BuildContext context) {
return Column(children: getRows(context));
}
List<InvoiceTableRow> getTableRows() {
List<InvoiceTableRow> tableRows = [];
// add cargo types
List<CargoType> _cargoTypes = invoice.getCargoTypes(rate) ?? [];
_cargoTypes.forEach((c) {
tableRows.add(InvoiceTableRow(
invoiceDataType: InvoiceDataType.CargoDataType,
desc: c.name,
rate:
"${c.calWeight.toStringAsFixed(2)} x ${c.calRate.toStringAsFixed(2)}",
amount: "${c.calAmount.toStringAsFixed(2)}"));
});
invoice.shipments.where((ss) => (ss.isSelected ?? false)).forEach((s) {
tableRows.add(InvoiceTableRow(
data: s,
invoiceDataType: InvoiceDataType.HandlingFeeType,
desc: "Handling fee\n${s.shipmentNumber}",
rate: "",
amount: "${s.handlingFee.toStringAsFixed(2)}"));
});
// // add custom fee
invoice.customDuties.forEach((c) {
tableRows.add(InvoiceTableRow(
data: c,
invoiceDataType: InvoiceDataType.CustomFeeDataType,
desc: "${c.productType} custom fee",
rate: "",
amount: "${c.fee.toStringAsFixed(2)}"));
});
// // add delivery fee
tableRows.add(InvoiceTableRow(
data: invoice.deliveryFee == null || invoice.deliveryFee == 0
? null
: invoice.deliveryFee,
invoiceDataType: InvoiceDataType.DeliveryFeeType,
desc: "Delivery fee",
rate: "",
amount: "${invoice?.deliveryFee?.toStringAsFixed(2) ?? '0'}"));
// // add discounts
if (invoice.discount != null) {
tableRows.add(InvoiceTableRow(
data: invoice.discount,
invoiceDataType: InvoiceDataType.DiscountDataType,
desc: "Discount\n${invoice?.discount?.code ?? ""}",
rate: "",
amount: "(${invoice?.discount?.amount?.toStringAsFixed(2) ?? ''})"));
}
return tableRows;
}
getRows(BuildContext context) {
List<InvoiceTableRow> tableRows = getTableRows();
List<Widget> dataRow = tableRows.map((r) {
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Row(
children: [
Flexible(child: Text('${r.desc}')),
SizedBox(
width: 5,
),
r.data == null || onRemove == null
? Container()
: InkWell(
onTap: () => onRemove(r),
child: Icon(
Icons.remove_circle,
color: Colors.black45,
),
)
],
)),
Expanded(
flex: 1,
child: Text(
'${r.rate}',
textAlign: TextAlign.end,
style: TextStyle(fontSize: 12),
)),
Expanded(
flex: 1,
child: Text('\$ ${r.amount}',
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
)))
],
),
);
}).toList();
dataRow.insert(
0,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 15.0, bottom: 15.0),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(getLocalString(context, 'invoice.box.desc'),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey))),
Expanded(
flex: 2,
child: Text(
getLocalString(context, 'invoice.weight') +
' x ' +
getLocalString(context, 'invoice.rate'),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey))),
Expanded(
child: Text(getLocalString(context, 'invoice.amount'),
textAlign: TextAlign.end,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey)))
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 10.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerRight,
child: LocalText(
context,
'invoice.total',
color: Colors.black,
),
),
),
SizedBox(width: 20),
Text(
'\$ ${invoice.getNetAmount(rate).toStringAsFixed(2)}',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
textAlign: TextAlign.end,
)
],
),
));
return dataRow;
}
}

View File

@@ -1,60 +1,28 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fcs/data/services/services.dart'; import 'package:fcs/data/services/services.dart';
import 'package:fcs/domain/constants.dart';
import 'package:fcs/domain/entities/invoice.dart'; import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/payment.dart';
import 'package:fcs/domain/entities/receipt.dart'; import 'package:fcs/helpers/firebase_helper.dart';
import 'package:fcs/domain/vo/message.dart';
import 'package:fcs/helpers/paginator.dart'; import 'package:fcs/helpers/paginator.dart';
import 'package:fcs/pages/main/model/base_model.dart'; import 'package:fcs/pages/main/model/base_model.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:fcs/domain/constants.dart'; import 'package:path/path.dart' as Path;
class InvoiceModel extends BaseModel { class InvoiceModel extends BaseModel {
final log = Logger('InvoiceModel'); final log = Logger('InvoiceModel');
StreamSubscription<QuerySnapshot> listener; StreamSubscription<QuerySnapshot> listener;
List<Invoice> _invoices = [ List<Invoice> _invoices = [];
Invoice(
invoiceNumber: 'A092(A)-33',
invoiceDate: DateTime(2020, 4, 6, 12, 15),
customerName: 'Ko Myo Min',
customerPhoneNumber: '+959 555555555',
amount: 300,
status: 'Pending',
receipts: [
Receipt(amount: 200, date: '1 Jun 2020'),
],
packages: [
Package(
shipmentNumber: "A201",
receiverNumber: "1",
boxNumber: "1",
rate: 9,
packageType: "Dangerous",
weight: 25,
status: "Delivered",
arrivedDate: DateTime(2020, 5, 21),
receiverAddress: '3 Kambzwza St, Bahan Tsp, Yangon'),
Package(
shipmentNumber: "A201",
receiverNumber: "1",
boxNumber: "2",
rate: 7,
packageType: "General",
weight: 5,
status: "Delivered",
arrivedDate: DateTime(2020, 5, 21),
receiverAddress: '3 Kambzwza St, Bahan Tsp, Yangon'),
])
];
List<Invoice> get invoices => List<Invoice> get invoices =>
_selectedIndex == 1 ? _invoices : List<Invoice>.from(_paid.values); _selectedIndex == 1 ? _invoices : List<Invoice>.from(_paginator.values);
Paginator _paid; Paginator _paginator;
bool endOfPaidInvoices = false; bool endOfPaidInvoices = false;
bool isLoading = false; bool isLoading = false;
@@ -72,15 +40,17 @@ class InvoiceModel extends BaseModel {
super.privilegeChanged(); super.privilegeChanged();
} }
initData(bool forCustomer) { initData(bool forCustomer, bool isCanceled, bool isPaid) {
_selectedIndex = 1; _loadInvoices(forCustomer);
// _loadFcsInvoices(forCustomer);
if (_paid != null) _paid.close(); if (_paginator != null) _paginator.close();
_paid = _getPaid(forCustomer); _paginator = _getPaginator(forCustomer, isCanceled, isPaid);
_paginator.load(onFinished: () {
notifyListeners();
});
} }
Future<void> _loadFcsInvoices(bool forCustomer) async { Future<void> _loadInvoices(bool forCustomer) async {
if (user == null) return; if (user == null) return;
if (!forCustomer && !user.hasInvoices()) return; if (!forCustomer && !user.hasInvoices()) return;
String path = "/$invoices_collection"; String path = "/$invoices_collection";
@@ -90,7 +60,7 @@ class InvoiceModel extends BaseModel {
try { try {
var q = Firestore.instance var q = Firestore.instance
.collection("$path") .collection("$path")
.where("is_paid", isEqualTo: false) .where("status", isEqualTo: invoice_issued_status)
.where("is_deleted", isEqualTo: false); .where("is_deleted", isEqualTo: false);
if (forCustomer) { if (forCustomer) {
@@ -111,18 +81,24 @@ class InvoiceModel extends BaseModel {
} }
} }
Paginator _getPaid(bool isCustomer) { Paginator _getPaginator(bool isCustomer, bool isCanceled, bool isPaid) {
if (!isCustomer) { if (!isCustomer) {
if (user == null || !(user.hasInvoices())) throw "No privilege"; if (user == null || !(user.hasInvoices())) throw "No privilege";
} }
var pageQuery = Firestore.instance var pageQuery = Firestore.instance
.collection("/$packages_collection") .collection("/$invoices_collection")
.where("is_delivered", isEqualTo: true)
.where("is_deleted", isEqualTo: false); .where("is_deleted", isEqualTo: false);
if (isCustomer) { if (isCustomer) {
pageQuery = pageQuery.where("user_id", isEqualTo: user.id); pageQuery = pageQuery.where("user_id", isEqualTo: user.id);
} }
pageQuery = pageQuery.orderBy("status_date", descending: true); if (isCanceled) {
pageQuery = pageQuery.where("status", isEqualTo: invoice_cancel_status);
}
if (isPaid) {
pageQuery = pageQuery.where("status", isEqualTo: invoice_paid_status);
}
pageQuery = pageQuery.orderBy("created_at", descending: true);
var paginator = new Paginator(pageQuery, rowPerLoad: 20, toObj: (data, id) { var paginator = new Paginator(pageQuery, rowPerLoad: 20, toObj: (data, id) {
return Invoice.fromMap(data, id); return Invoice.fromMap(data, id);
}); });
@@ -130,11 +106,11 @@ class InvoiceModel extends BaseModel {
} }
Future<void> loadMore({bool isCustomer}) async { Future<void> loadMore({bool isCustomer}) async {
if (_paid.ended || _selectedIndex == 1) if (_paginator.ended || _selectedIndex == 1)
return; // when paid menu is not selected return return; // when paid menu is not selected return
isLoading = true; isLoading = true;
notifyListeners(); notifyListeners();
await _paid.load(onFinished: () { await _paginator.load(onFinished: () {
isLoading = false; isLoading = false;
notifyListeners(); notifyListeners();
}); });
@@ -142,7 +118,7 @@ class InvoiceModel extends BaseModel {
Future<void> refresh({bool isCustomer}) async { Future<void> refresh({bool isCustomer}) async {
if (_selectedIndex == 1) return; // when paid menu is not selected return if (_selectedIndex == 1) return; // when paid menu is not selected return
await _paid.refresh(onFinished: () { await _paginator.refresh(onFinished: () {
notifyListeners(); notifyListeners();
}); });
} }
@@ -152,16 +128,46 @@ class InvoiceModel extends BaseModel {
} }
logout() async { logout() async {
if (_paid != null) _paid.close(); if (_paginator != null) _paginator.close();
if (listener != null) await listener.cancel(); if (listener != null) await listener.cancel();
_invoices = []; _invoices = [];
} }
Future<void> createInvoice(Invoice invoice) { Future<Invoice> getInvoice(String id) async {
// return Services.instance.invoiceService.createInvoice(invoice); String path = "/$invoices_collection";
try {
var ref = Firestore.instance.collection("$path").document(id);
var snap = await ref.get(source: Source.server);
if (snap.exists) {
var s = Invoice.fromMap(snap.data, snap.documentID);
return s;
}
} catch (e) {
log.warning("Error!! $e");
}
return null;
}
Future<void> pay(Payment payment, File file) async {
String path = Path.join(receipt_labels_files_path, user.id);
String url = await uploadStorage(path, file);
payment.paymentReceiptURL = url;
return Services.instance.invoiceService.pay(payment);
}
Future<void> updatePaymentStatus(Payment payment) async {
return Services.instance.invoiceService.updatPaymentStatus(payment);
}
Future<void> createInvoice(Invoice invoice) async {
return Services.instance.invoiceService.createInvoice(invoice);
} }
Future<void> updateInvoice(Invoice invoice) { Future<void> updateInvoice(Invoice invoice) {
// return Services.instance.invoiceService.updateInvoice(invoice); return Services.instance.invoiceService.updateInvoice(invoice);
}
Future<void> cancelInvoice(Invoice invoice) {
return Services.instance.invoiceService.cancelInvoice(invoice);
} }
} }

View File

@@ -0,0 +1,327 @@
import 'dart:io';
import 'package:fcs/domain/constants.dart';
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/domain/entities/payment.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/localization/app_translations.dart';
import 'package:fcs/pages/invoice/model/invoice_model.dart';
import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/widgets/img_picker.dart';
import 'package:fcs/pages/widgets/input_text.dart';
import 'package:fcs/pages/widgets/local_button.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/local_title.dart';
import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class PaymentPage extends StatefulWidget {
final Invoice invoice;
final bool forCustomer;
PaymentPage({this.invoice, this.forCustomer});
@override
_PaymentPageState createState() => _PaymentPageState();
}
class _PaymentPageState extends State<PaymentPage> {
TextEditingController _amountController = new TextEditingController();
var dateFormatter = new DateFormat('dd MMM yyyy');
Invoice _invoice = new Invoice();
bool _isLoading = false;
bool isNew;
File _file;
bool _hasBalance;
@override
void initState() {
super.initState();
_invoice = widget.invoice;
_hasBalance = widget.invoice.balance > 0;
_loadInvoice();
}
_loadInvoice() async {
InvoiceModel invoiceModel =
Provider.of<InvoiceModel>(context, listen: false);
Invoice i = await invoiceModel.getInvoice(_invoice.id);
setState(() {
_invoice = i;
});
}
@override
void dispose() {
super.dispose();
}
final DateFormat dateFormat = DateFormat("d MMM yyyy");
@override
Widget build(BuildContext context) {
final amountBox = InputText(
labelTextKey: 'pm.amount',
controller: _amountController,
iconData: FontAwesomeIcons.moneyBill);
final receiptFileBox = Row(children: [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text("Attach receipt"),
),
LocalImagePicker(
color: primaryColor,
title: "Receipt",
onFile: (f) => _file = f,
)
]);
final payBtnBox = LocalButton(
callBack: _pay,
textKey: "pm.pay",
);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
leading: new IconButton(
icon: new Icon(CupertinoIcons.back),
onPressed: () => Navigator.of(context).pop(),
),
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("pm_.title")),
),
body: ListView(
padding: const EdgeInsets.all(10.0),
children: <Widget>[
_hasBalance ? amountBox : Container(),
SizedBox(height: 10),
_hasBalance
? Align(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: receiptFileBox,
),
alignment: Alignment.centerLeft,
)
: Container(),
_hasBalance ? payBtnBox : Container(),
SizedBox(height: 15),
LocalTitle(textKey: "pm.receipt"),
Column(
children: getCustomFeeRows(context),
),
SizedBox(height: 25),
],
),
),
);
}
getCustomFeeRows(BuildContext context) {
List<Widget> dataRow = [];
dataRow = _invoice?.payments?.map((p) {
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 5.0, bottom: 5.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
'${p.paymentDate != null ? dateFormatter.format(p.paymentDate) : ""}'),
),
SizedBox(
height: 5,
),
LocalImagePicker(
key: ValueKey(p.id),
enabled: false,
initialImgUrl: p.paymentReceiptURL,
title: "Receipt",
color: primaryColor,
)
],
)),
Expanded(
flex: 1,
child: Center(
child: Column(
children: [Text('\$ ${p.amount}'), Text('${p.status}')],
))),
widget.forCustomer
? Container()
: Expanded(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: p.status == payment_pending_status
? [
InkWell(
onTap: () => _confirm(p),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
Icons.check,
color: primaryColor,
),
),
),
InkWell(
onTap: () => _cancel(p),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.close,
color: primaryColor),
),
),
]
: [],
)),
],
),
);
})?.toList() ??
[];
dataRow.insert(
0,
Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 15.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Text('Receipt', style: TextStyle(color: Colors.grey))),
Expanded(
flex: 1,
child: Text('Amount',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey))),
widget.forCustomer
? Container()
: Expanded(
flex: 1,
child: Text('Actions',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey))),
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 15.0, bottom: 15.0),
child: Row(
children: [
Expanded(
flex: 1,
child: Center(
child: LocalText(
context,
'pm.remaining_balance',
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
flex: 1,
child: Center(
child: Text(
'\$ ${widget.invoice.balance.toStringAsFixed(2)}',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 16.0)))),
widget.forCustomer
? Container()
: Expanded(
flex: 1,
child: Container(),
),
],
),
));
return dataRow;
}
_confirm(Payment payment) {
payment.status = payment_confirmed_status;
showConfirmDialog(context, "invoice.payment.confirm.confirm",
() => _updatePayment(payment));
}
_cancel(Payment payment) {
payment.status = payment_canceled_status;
showConfirmDialog(context, "invoice.payment.cancel.confirm",
() => _updatePayment(payment));
}
_updatePayment(Payment payment) async {
payment.invoiceID = widget.invoice.id;
setState(() {
_isLoading = true;
});
try {
InvoiceModel invoiceModel =
Provider.of<InvoiceModel>(context, listen: false);
await invoiceModel.updatePaymentStatus(payment);
Navigator.pop(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
_pay() async {
if (_file == null) {
showMsgDialog(context, "Error", "Expect receipt attachment");
return;
}
double amount = double.tryParse(_amountController.text) ?? 0;
if (amount <= 0) {
showMsgDialog(context, "Error", "Expect valid amount");
return;
}
Payment payment = Payment(invoiceID: _invoice.id, amount: amount);
setState(() {
_isLoading = true;
});
try {
InvoiceModel invoiceModel =
Provider.of<InvoiceModel>(context, listen: false);
await invoiceModel.pay(payment, _file);
Navigator.pop(context, true);
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
}

View File

@@ -1,301 +0,0 @@
import 'dart:io';
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/localization/app_translations.dart';
import 'package:fcs/pages/invoice/payment_page_edit.dart';
import 'package:fcs/pages/widgets/image_file_picker.dart';
import 'package:fcs/pages/widgets/input_text.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:fcs/pages/widgets/local_title.dart';
import 'package:fcs/pages/widgets/progress.dart';
import 'package:fcs/pages/widgets/show_img.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
class PaymentPage extends StatefulWidget {
final Invoice invoice;
PaymentPage({this.invoice});
@override
_PaymentPageState createState() => _PaymentPageState();
}
class _PaymentPageState extends State<PaymentPage> {
TextEditingController _amountController = new TextEditingController();
var dateFormatter = new DateFormat('dd MMM yyyy');
Invoice _invoice = new Invoice();
bool _isLoading = false;
bool isNew;
File _file;
@override
void initState() {
if (widget.invoice != null) {
_invoice = widget.invoice;
}
super.initState();
}
@override
void dispose() {
super.dispose();
}
final DateFormat dateFormat = DateFormat("d MMM yyyy");
@override
Widget build(BuildContext context) {
final amountBox = InputText(
labelTextKey: 'pm.amount',
controller: _amountController,
iconData: FontAwesomeIcons.moneyBill);
final receiptFileBox = Row(children: [
LocalText(context, 'pm.attachment', fontSize: 16, color: Colors.grey),
_file != null
? InkWell(
onTap: () async {
await _dialog(context);
},
child: Chip(
label: Icon(Icons.image, color: primaryColor),
),
)
: IconButton(
icon: Icon(Icons.attachment, color: primaryColor),
onPressed: () async {
await _dialog(context);
}),
]);
final payBox = Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Container(
height: 50,
width: 100,
alignment: Alignment.center,
child: Text(AppTranslations.of(context).text("pm.pay"),
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 14)),
color: primaryColor)
]);
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
leading: new IconButton(
icon: new Icon(CupertinoIcons.back),
onPressed: () => Navigator.of(context).pop(),
),
backgroundColor: primaryColor,
title: Text(AppTranslations.of(context).text("pm_.title")),
),
body: ListView(
padding: const EdgeInsets.all(10.0),
children: <Widget>[
amountBox,
SizedBox(height: 10),
receiptFileBox,
SizedBox(height: 10),
payBox,
Divider(),
SizedBox(height: 10),
LocalTitle(textKey: "pm.receipt"),
Column(
children: getCustomFeeRows(context),
),
SizedBox(height: 25),
],
),
),
);
}
getCustomFeeRows(BuildContext context) {
List<Widget> dataRow = [];
dataRow = _invoice.receipts.asMap().entries.map((receipt) {
var r = receipt.value;
var k = receipt.key + 1;
return Container(
height: 50,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding:
const EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0, bottom: 5.0),
child: InkWell(
onTap: () {
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => PaymentPageEdit(receipt: r)));
},
child: Row(
children: [
Expanded(flex: 2, child: Text('${r.date}')),
Expanded(
flex: 2,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ShowImage(
// localImage: attachment.value.fileUrl,
localImage: 'assets/logo.jpg',
url: null,
fileName: 'image')));
},
child: Chip(
label: Icon(Icons.image, color: primaryColor),
),
),
),
Expanded(flex: 1, child: Center(child: Text('pending'))),
Expanded(flex: 1, child: Center(child: Text('\$ ${r.amount}'))),
],
),
),
);
}).toList();
dataRow.insert(
0,
Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey))),
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 10.0, bottom: 15.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Text('Date', style: TextStyle(color: Colors.grey))),
Expanded(
flex: 2,
child: Text('File',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey))),
Expanded(
flex: 1,
child: Text('Status',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey))),
Expanded(
flex: 1,
child: Text('Fee',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey))),
],
),
));
dataRow.insert(
dataRow.length,
Container(
padding: const EdgeInsets.only(
left: 5.0, right: 5.0, top: 15.0, bottom: 15.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Center(
child: LocalText(
context,
'pm.remaining_balance',
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
flex: 2,
child: Container(),
),
Expanded(
flex: 1,
child: Center(
child: Text('\$ 300',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 16.0)))),
],
),
));
return dataRow;
}
Future<void> _dialog(BuildContext context) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
FontAwesomeIcons.camera,
size: 30,
color: primaryColor,
),
onPressed: () async {
// Navigator.pop(context);
// cameraPress();
var selectedFile =
await pickImage(ImageSource.camera);
setState(() {
_file = selectedFile;
});
Navigator.pop(context);
}),
Text("Camera")
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
Icons.photo_library,
size: 30,
color: primaryColor,
),
onPressed: () async {
// Navigator.pop(context);
// photoPress();
var selectedFile =
await pickImage(ImageSource.gallery);
setState(() {
_file = selectedFile;
});
Navigator.pop(context);
}),
Text("Gallery")
],
),
],
),
),
),
);
},
);
}
pickImage(ImageSource source) async {
var tempImage = await ImagePicker.pickImage(source: source);
return tempImage;
}
}

View File

@@ -1,117 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/main/model/main_model.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:share/share.dart';
class PaymentPDFScreen extends StatefulWidget {
final String path;
PaymentPDFScreen({Key key, this.path}) : super(key: key);
_PaymentPDFScreenState createState() => _PaymentPDFScreenState();
}
class _PaymentPDFScreenState extends State<PaymentPDFScreen>
with WidgetsBindingObserver {
final Completer<PDFViewController> _controller =
Completer<PDFViewController>();
int pages = 0;
int currentPage = 0;
bool isReady = false;
String errorMessage = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
backgroundColor: primaryColor,
title: LocalText(context, 'invoice.pdf',
color: Colors.white, fontSize: 20),
leading: new IconButton(
icon: new Icon(CupertinoIcons.back),
onPressed: () {
Navigator.of(context).pop();
}),
actions: <Widget>[
IconButton(
icon: Icon(Icons.file_download),
onPressed: () async {
var file = await copyAsset();
print('file=> $file');
},
),
IconButton(
icon: Icon(Icons.share),
onPressed: () => _share(widget.path),
),
],
),
body: Stack(
children: <Widget>[
PDFView(
filePath: widget.path,
enableSwipe: true,
swipeHorizontal: true,
autoSpacing: false,
pageFling: true,
pageSnap: true,
defaultPage: currentPage,
fitPolicy: FitPolicy.BOTH,
preventLinkNavigation:
false, // if set to true the link is handled in flutter
onRender: (_pages) {
print(('pages => $pages'));
setState(() {
pages = _pages;
isReady = true;
});
},
onViewCreated: (PDFViewController pdfViewController) {
_controller.complete(pdfViewController);
},
onLinkHandler: (String uri) {
print('goto uri: $uri');
},
onPageChanged: (int page, int total) {
print('page change: $page/$total');
setState(() {
currentPage = page;
});
},
),
],
),
);
}
_share(String url) async {
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
String appUrl = mainModel.setting.appUrl;
final RenderBox box = context.findRenderObject();
await Share.share(
"Join us on FCS Logistics App. Here is the link:\n $appUrl\n" + url,
subject: "Invitation to FCS Logistics App",
sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size);
}
Future<File> copyAsset() async {
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
File tempFile = File('$tempPath/Invoice-A092(A)-32.pdf');
ByteData bd = await rootBundle.load('assets/Invoice-A092(A)-32.pdf');
await tempFile.writeAsBytes(bd.buffer.asUint8List(), flush: true);
return tempFile;
}
}

View File

@@ -0,0 +1,24 @@
import 'package:fcs/domain/entities/invoice.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/widgets/local_text.dart';
import 'package:flutter/material.dart';
Widget getInvoiceStatus(BuildContext context, Invoice invoice) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LocalText(
context,
'',
text: invoice.invoiceNumber ?? "",
color: primaryColor,
fontSize: 18,
fontWeight: FontWeight.bold,
),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Chip(label: Text(invoice.status ?? "")),
),
],
);
}

View File

@@ -16,7 +16,7 @@ import 'package:fcs/pages/delivery/delivery_list.dart';
import 'package:fcs/pages/discount/discount_list.dart'; import 'package:fcs/pages/discount/discount_list.dart';
import 'package:fcs/pages/faq/faq_list_page.dart'; import 'package:fcs/pages/faq/faq_list_page.dart';
import 'package:fcs/pages/fcs_shipment/fcs_shipment_list.dart'; import 'package:fcs/pages/fcs_shipment/fcs_shipment_list.dart';
import 'package:fcs/pages/invoice/invoce_list.dart'; import 'package:fcs/pages/invoice/invoice_list.dart';
import 'package:fcs/pages/main/model/language_model.dart'; import 'package:fcs/pages/main/model/language_model.dart';
import 'package:fcs/pages/main/model/main_model.dart'; import 'package:fcs/pages/main/model/main_model.dart';
import 'package:fcs/pages/main/util.dart'; import 'package:fcs/pages/main/util.dart';
@@ -298,11 +298,11 @@ class _HomePageState extends State<HomePage> {
final invoicesBtn = TaskButton("invoices.btn", final invoicesBtn = TaskButton("invoices.btn",
icon: FontAwesomeIcons.fileInvoice, icon: FontAwesomeIcons.fileInvoice,
btnCallback: () => Navigator.of(context).push<void>(CupertinoPageRoute( btnCallback: () => Navigator.of(context).push<void>(CupertinoPageRoute(
builder: (context) => InvoiceList(forCustomer: false)))); builder: (context) => InvoiceList(forCustomer: true))));
final invoicesBtnFcs = TaskButton("invoices.btn", final invoicesBtnFcs = TaskButton("invoices.btn",
icon: FontAwesomeIcons.fileInvoice, icon: FontAwesomeIcons.fileInvoice,
btnCallback: () => Navigator.of(context).push<void>(CupertinoPageRoute( btnCallback: () => Navigator.of(context).push<void>(CupertinoPageRoute(
builder: (context) => InvoiceList(forCustomer: true)))); builder: (context) => InvoiceList(forCustomer: false))));
final discountBtn = TaskButton("discount.btn", final discountBtn = TaskButton("discount.btn",
icon: Entypo.price_ribbon, icon: Entypo.price_ribbon,

View File

@@ -7,7 +7,6 @@ import 'package:fcs/domain/constants.dart';
import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/package.dart';
import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/domain/vo/delivery_address.dart'; import 'package:fcs/domain/vo/delivery_address.dart';
import 'package:fcs/domain/vo/message.dart';
import 'package:fcs/helpers/firebase_helper.dart'; import 'package:fcs/helpers/firebase_helper.dart';
import 'package:fcs/helpers/paginator.dart'; import 'package:fcs/helpers/paginator.dart';
import 'package:fcs/pages/main/model/base_model.dart'; import 'package:fcs/pages/main/model/base_model.dart';
@@ -186,7 +185,6 @@ class PackageModel extends BaseModel {
.where("status", whereIn: status) .where("status", whereIn: status)
.where("user_id", isEqualTo: userID) .where("user_id", isEqualTo: userID)
.where("is_deleted", isEqualTo: false) .where("is_deleted", isEqualTo: false)
.where("is_delivered", isEqualTo: false)
.getDocuments(source: Source.server); .getDocuments(source: Source.server);
packages = snaps.documents.map((documentSnapshot) { packages = snaps.documents.map((documentSnapshot) {
var p = var p =

View File

@@ -125,6 +125,7 @@ class _ShipmentRatesCalState extends State<ShipmentRatesCal> {
callback: (v) { callback: (v) {
setState(() { setState(() {
_cargoType = v; _cargoType = v;
_calShipmentWeight();
}); });
}, },
labelKey: "cargo.type", labelKey: "cargo.type",

View File

@@ -1,4 +1,3 @@
import 'package:barcode_scan/barcode_scan.dart';
import 'package:fcs/domain/entities/package.dart'; import 'package:fcs/domain/entities/package.dart';
import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/domain/entities/user.dart';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
@@ -137,7 +136,8 @@ class _ReceivingEditorState extends State<ReceivingEditor> {
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
leading: new IconButton( leading: new IconButton(
icon: new Icon(CupertinoIcons.back, color: primaryColor, size: 30), icon:
new Icon(CupertinoIcons.back, color: primaryColor, size: 30),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
shadowColor: Colors.transparent, shadowColor: Colors.transparent,

View File

@@ -1,13 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:fcs/helpers/firebase_helper.dart';
import 'package:path/path.dart' as Path;
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fcs/data/services/services.dart'; import 'package:fcs/data/services/services.dart';
import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/constants.dart';
import 'package:fcs/domain/entities/shipment.dart'; import 'package:fcs/domain/entities/shipment.dart';
import 'package:fcs/domain/vo/message.dart';
import 'package:fcs/helpers/paginator.dart'; import 'package:fcs/helpers/paginator.dart';
import 'package:fcs/pages/main/model/base_model.dart'; import 'package:fcs/pages/main/model/base_model.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@@ -36,10 +32,12 @@ class ShipmentModel extends BaseModel {
initData(bool forCustomer, {bool myPickup = false}) { initData(bool forCustomer, {bool myPickup = false}) {
logout(); logout();
_menuSelectedIndex = 1;
_loadShipments(forCustomer, myPickup); _loadShipments(forCustomer, myPickup);
_delivered = _getDelivered(forCustomer); _delivered = _getDelivered(forCustomer);
_delivered.load(); _delivered.load(onFinished: () {
isLoading = false;
notifyListeners();
});
} }
@override @override
@@ -132,6 +130,11 @@ class ShipmentModel extends BaseModel {
shipment_courier_dropoff shipment_courier_dropoff
]; ];
Shipment getActiveShipment(String shipmentID) {
return _shipments?.firstWhere((e) => e.id == shipmentID,
orElse: () => null);
}
Future<Shipment> getShipment(String shipmentID) async { Future<Shipment> getShipment(String shipmentID) async {
String path = "/$shipments_collection"; String path = "/$shipments_collection";
try { try {
@@ -147,6 +150,30 @@ class ShipmentModel extends BaseModel {
return null; return null;
} }
Future<List<Shipment>> getShipmentWithHandlingFee(
String fcsShipmentID, String userID) async {
String path = "/$shipments_collection";
try {
var q = Firestore.instance
.collection("$path")
.where("user_id", isEqualTo: userID)
.where("is_deleted", isEqualTo: false)
.where("handling_fee", isGreaterThan: 0)
.where("fcs_shipment_id", isEqualTo: fcsShipmentID);
var snaps = await q.getDocuments(source: Source.server);
List<Shipment> shipments = snaps.documents.map((snap) {
if (snap.exists) {
var s = Shipment.fromMap(snap.data, snap.documentID);
return s;
}
}).toList();
return shipments;
} catch (e) {
log.warning("Error!! $e");
}
return null;
}
void initUser(user) { void initUser(user) {
super.initUser(user); super.initUser(user);
} }

View File

@@ -311,9 +311,7 @@ class _ShipmentInfoState extends State<ShipmentInfo> {
children: getBoxList(context, _shipment.boxes), children: getBoxList(context, _shipment.boxes),
), ),
!_isCustomer ? fcsShipmentNumberBox : Container(), !_isCustomer ? fcsShipmentNumberBox : Container(),
...(_shipment.isAssigned || ...(!_shipment.isPending
_shipment.isConfirmed ||
_shipment.isPending
? [ ? [
handlingFeeBox, handlingFeeBox,
] ]

View File

@@ -68,9 +68,9 @@ class _ShipmentListState extends State<ShipmentList> {
if (p.id == 3) { if (p.id == 3) {
Provider.of<ShipmentModel>(context, listen: false) Provider.of<ShipmentModel>(context, listen: false)
.initData(widget.forCustomer, myPickup: true); .initData(widget.forCustomer, myPickup: true);
} else if (p.id == 1) { } else {
Provider.of<ShipmentModel>(context, listen: false) Provider.of<ShipmentModel>(context, listen: false)
.initData(widget.forCustomer); .initData(widget.forCustomer, myPickup: false);
} }
}), }),
); );

View File

@@ -0,0 +1,5 @@
import 'package:flutter_icons/flutter_icons.dart';
const cartonIconData = MaterialCommunityIcons.package;
const customFeeIconData = MaterialCommunityIcons.security;
const shipmentIconData = SimpleLineIcons.direction;

View File

@@ -0,0 +1,162 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:image_picker/image_picker.dart';
import 'show_img.dart';
typedef OnFile = void Function(File);
class LocalImagePicker extends StatefulWidget {
final Color color;
final String title;
final OnFile onFile;
final bool enabled;
final String initialImgUrl;
final ImageSource imageSource;
const LocalImagePicker(
{Key key,
this.title,
this.onFile,
this.enabled = true,
this.initialImgUrl,
this.imageSource = ImageSource.gallery,
this.color})
: super(key: key);
@override
_LocalImagePickerState createState() => _LocalImagePickerState();
}
class _LocalImagePickerState extends State<LocalImagePicker> {
String url;
File file;
@override
void initState() {
super.initState();
this.url = widget.initialImgUrl == null || widget.initialImgUrl == ""
? null
: widget.initialImgUrl;
}
@override
Widget build(BuildContext context) {
return Container(
height: 30,
child: this.file == null && this.url == null
? IconButton(
padding: const EdgeInsets.all(3.0),
icon: Icon(Icons.attach_file, color: widget.color ?? Colors.blue),
onPressed: () async {
if (!widget.enabled) return;
bool camera = false, gallery = false;
await _dialog(
context, () => camera = true, () => gallery = true);
if (camera || gallery) {
var selectedFile = await ImagePicker.pickImage(
source: camera ? ImageSource.camera : ImageSource.gallery,
imageQuality: 80,
maxWidth: 1000);
if (selectedFile != null) {
setState(() {
this.file = selectedFile;
});
if (widget.onFile != null) {
widget.onFile(selectedFile);
}
}
}
},
)
: InkWell(
onTap: () => {
Navigator.of(context).push(CupertinoPageRoute(
builder: (context) => ShowImage(
imageFile: file,
url: file == null ? widget.initialImgUrl : null,
fileName: widget.title))),
},
child: Padding(
padding: const EdgeInsets.only(left: 3.0),
child: Chip(
avatar: Icon(
Icons.image,
color: widget.color ?? Colors.blue,
),
onDeleted: !widget.enabled
? null
: () {
setState(() {
this.file = null;
this.url = null;
if (widget.onFile != null) {
widget.onFile(null);
}
});
},
deleteIcon: Icon(
Icons.close,
color: widget.color ?? Colors.blue,
),
label: Text(widget.title),
),
),
),
);
}
Future<void> _dialog(BuildContext context, cameraPress(), photoPress()) {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
FontAwesomeIcons.camera,
size: 30,
color: widget.color ?? Colors.blue,
),
onPressed: () {
Navigator.pop(context);
cameraPress();
}),
Text("Camera")
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
Icons.photo_library,
size: 30,
color: widget.color ?? Colors.blue,
),
onPressed: () {
Navigator.pop(context);
photoPress();
}),
Text("Gallery")
],
),
],
),
),
),
);
},
);
}
}

View File

@@ -12,6 +12,7 @@ class LocalPopupMenuButton extends StatefulWidget {
final bool multiSelect; final bool multiSelect;
final bool selectable; final bool selectable;
final IconData buttonIcon; final IconData buttonIcon;
final Color buttonColor;
const LocalPopupMenuButton( const LocalPopupMenuButton(
{Key key, {Key key,
@@ -19,7 +20,8 @@ class LocalPopupMenuButton extends StatefulWidget {
this.popmenus, this.popmenus,
this.buttonIcon, this.buttonIcon,
this.selectable = true, this.selectable = true,
this.multiSelect = false}) this.multiSelect = false,
this.buttonColor = primaryColor})
: super(key: key); : super(key: key);
@override @override
@@ -76,7 +78,7 @@ class _LocalPopupMenuButtonState extends State<LocalPopupMenuButton> {
children: <Widget>[ children: <Widget>[
Icon( Icon(
widget.buttonIcon ?? Icons.filter_list, widget.buttonIcon ?? Icons.filter_list,
color: primaryColor, color: widget.buttonColor ?? primaryColor,
), ),
hightlight hightlight
? Positioned( ? Positioned(

View File

@@ -0,0 +1,125 @@
import 'dart:async';
import 'dart:io';
import 'package:fcs/helpers/cache_mgr.dart';
import 'package:fcs/helpers/theme.dart';
import 'package:fcs/pages/main/util.dart';
import 'package:fcs/pages/widgets/progress.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:share/share.dart';
class PDFScreen extends StatefulWidget {
final String title;
final String url;
PDFScreen({Key key, this.url, this.title}) : super(key: key);
_PDFScreenState createState() => _PDFScreenState();
}
class _PDFScreenState extends State<PDFScreen> with WidgetsBindingObserver {
final Completer<PDFViewController> _controller =
Completer<PDFViewController>();
int pages = 0;
int currentPage = 0;
bool isReady = false;
String errorMessage = '';
bool _isLoading = true;
void initState() {
super.initState();
download();
}
File file;
Future<void> download() async {
try {
File f = await PdfCacheMgr.pdfs.getSingleFile(widget.url);
setState(() {
file = f;
});
} catch (e) {
showMsgDialog(context, "Error", e.toString());
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return LocalProgress(
inAsyncCall: _isLoading,
child: Scaffold(
appBar: AppBar(
centerTitle: true,
backgroundColor: Colors.white,
shadowColor: Colors.transparent,
title:
Text(widget.title ?? "", style: TextStyle(color: primaryColor)),
leading: new IconButton(
icon: new Icon(CupertinoIcons.back, color: primaryColor),
onPressed: () => Navigator.of(context).pop(),
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.share,
color: primaryColor,
),
onPressed: _share,
),
],
),
body: Stack(
children: <Widget>[
_isLoading
? Container()
: PDFView(
filePath: file?.path ?? "",
enableSwipe: true,
swipeHorizontal: true,
autoSpacing: false,
pageFling: true,
pageSnap: true,
defaultPage: currentPage,
fitPolicy: FitPolicy.BOTH,
preventLinkNavigation:
false, // if set to true the link is handled in flutter
onRender: (_pages) {
print(('pages => $pages'));
setState(() {
pages = _pages;
isReady = true;
});
},
onViewCreated: (PDFViewController pdfViewController) {
_controller.complete(pdfViewController);
},
onLinkHandler: (String uri) {
print('goto uri: $uri');
},
onPageChanged: (int page, int total) {
print('page change: $page/$total');
setState(() {
currentPage = page;
});
},
),
],
),
),
);
}
_share() async {
final RenderBox box = context.findRenderObject();
await Share.shareFiles([file.path],
mimeTypes: ["application/pdf"],
subject: "File",
sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size);
}
}

View File

@@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:fcs/helpers/theme.dart'; import 'package:fcs/helpers/theme.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
@@ -21,12 +22,19 @@ class _ShowImageState extends State<ShowImage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
leading: new IconButton(
icon: new Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
backgroundColor: primaryColor, backgroundColor: primaryColor,
title: Text(widget.fileName, style: TextStyle(color: Colors.white)), shadowColor: Colors.transparent,
iconTheme: new IconThemeData(color: Colors.white), iconTheme: new IconThemeData(color: Colors.white),
), ),
body: Center( body: Center(
child: PhotoView( child: PhotoView(
backgroundDecoration: const BoxDecoration(
color: primaryColor,
),
imageProvider: widget.url != null imageProvider: widget.url != null
? NetworkImage(widget.url) ? NetworkImage(widget.url)
: widget.imageFile != null : widget.imageFile != null

View File

@@ -28,7 +28,7 @@ packages:
name: cached_network_image name: cached_network_image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.2+1" version: "2.3.3"
camera: camera:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -176,6 +176,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
file: file:
dependency: transitive dependency: transitive
description: description:
@@ -259,12 +266,12 @@ packages:
source: hosted source: hosted
version: "0.5.0" version: "0.5.0"
flutter_cache_manager: flutter_cache_manager:
dependency: transitive dependency: "direct main"
description: description:
name: flutter_cache_manager name: flutter_cache_manager
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.4.2" version: "2.0.0"
flutter_datetime_picker: flutter_datetime_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -447,7 +454,7 @@ packages:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.14" version: "1.6.22"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@@ -469,6 +476,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "1.0.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4+1"
pedantic: pedantic:
dependency: transitive dependency: transitive
description: description:
@@ -754,6 +768,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.8" version: "2.0.8"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@@ -56,6 +56,7 @@ dependencies:
flutter_local_notifications: ^1.4.4+4 flutter_local_notifications: ^1.4.4+4
share: ^0.6.5 share: ^0.6.5
cached_network_image: ^2.3.2+1 cached_network_image: ^2.3.2+1
flutter_cache_manager: ^2.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: