invoice/lib/data/models/payment_model.dart

589 lines
16 KiB
Dart

// Package imports:
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
// Project imports:
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/client/client_selectors.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/strings.dart';
part 'payment_model.g.dart';
abstract class PaymentListResponse
implements Built<PaymentListResponse, PaymentListResponseBuilder> {
factory PaymentListResponse([void updates(PaymentListResponseBuilder b)]) =
_$PaymentListResponse;
PaymentListResponse._();
@override
@memoized
int get hashCode;
BuiltList<PaymentEntity> get data;
static Serializer<PaymentListResponse> get serializer =>
_$paymentListResponseSerializer;
}
abstract class PaymentItemResponse
implements Built<PaymentItemResponse, PaymentItemResponseBuilder> {
factory PaymentItemResponse([void updates(PaymentItemResponseBuilder b)]) =
_$PaymentItemResponse;
PaymentItemResponse._();
@override
@memoized
int get hashCode;
PaymentEntity get data;
static Serializer<PaymentItemResponse> get serializer =>
_$paymentItemResponseSerializer;
}
class PaymentFields {
static const String number = 'number';
static const String amount = 'amount';
static const String refunded = 'refunded';
static const String transactionReference = 'transaction_reference';
static const String date = 'date';
static const String type = 'type';
static const String typeId = 'type_id';
static const String client = 'client';
static const String clientId = 'client_id';
static const String invoiceId = 'invoice_id';
static const String invoiceNumber = 'invoice_number';
static const String creditNumber = 'credit_number';
static const String privateNotes = 'private_notes';
static const String exchangeRate = 'exchange_rate';
static const String convertedAmount = 'converted_amount';
static const String exchangeCurrencyId = 'exchange_currency_id';
static const String status = 'status';
static const String gateway = 'gateway';
static const String customValue1 = 'custom1';
static const String customValue2 = 'custom2';
static const String customValue3 = 'custom3';
static const String customValue4 = 'custom4';
}
abstract class PaymentEntity extends Object
with BaseEntity, SelectableEntity, BelongsToClient
implements Built<PaymentEntity, PaymentEntityBuilder> {
factory PaymentEntity({String id, AppState state, ClientEntity client}) {
final settings = getClientSettings(state, client);
return _$PaymentEntity._(
id: id ?? BaseEntity.nextId,
idempotencyKey: BaseEntity.nextIdempotencyKey,
isChanged: false,
amount: 0.0,
transactionReference: '',
date: convertDateTimeToSqlDate(),
typeId: state?.company != null &&
(state.company.settings.defaultPaymentTypeId ?? '').isNotEmpty
? state.company.settings.defaultPaymentTypeId
: '',
clientId: client?.id ?? '',
privateNotes: '',
exchangeRate: 1,
exchangeCurrencyId: state?.company?.currencyId ?? '',
refunded: 0.0,
applied: 0,
statusId: '',
updatedAt: 0,
archivedAt: 0,
isDeleted: false,
isManual: true,
customValue1: '',
customValue2: '',
customValue3: '',
customValue4: '',
paymentables: BuiltList<PaymentableEntity>(),
invoices: BuiltList<PaymentableEntity>(),
credits: BuiltList<PaymentableEntity>(),
assignedUserId: '',
createdAt: 0,
createdUserId: '',
vendorId: '',
projectId: '',
number: '',
sendEmail: settings.clientManualPaymentNotification ?? false,
companyGatewayId: '',
clientContactId: '',
currencyId: '',
invitationId: '',
isApplying: false,
);
}
PaymentEntity._();
@override
@memoized
int get hashCode;
@override
EntityType get entityType {
return EntityType.payment;
}
double get amount;
double get applied;
double get refunded;
String get number;
@override
@BuiltValueField(wireName: 'client_id')
String get clientId;
@BuiltValueField(wireName: 'status_id')
String get statusId;
@BuiltValueField(wireName: 'transaction_reference')
String get transactionReference;
String get date;
@BuiltValueField(wireName: 'type_id')
String get typeId;
@BuiltValueField(wireName: 'private_notes')
String get privateNotes;
@BuiltValueField(wireName: 'custom_value1')
String get customValue1;
@BuiltValueField(wireName: 'custom_value2')
String get customValue2;
@BuiltValueField(wireName: 'custom_value3')
String get customValue3;
@BuiltValueField(wireName: 'custom_value4')
String get customValue4;
@BuiltValueField(wireName: 'exchange_rate')
double get exchangeRate;
@BuiltValueField(wireName: 'exchange_currency_id')
String get exchangeCurrencyId;
@BuiltValueField(wireName: 'is_manual')
bool get isManual;
@BuiltValueField(wireName: 'project_id')
String get projectId;
@BuiltValueField(wireName: 'vendor_id')
String get vendorId;
@BuiltValueField(wireName: 'invitation_id')
String get invitationId;
@BuiltValueField(wireName: 'client_contact_id')
String get clientContactId;
@BuiltValueField(wireName: 'company_gateway_id')
String get companyGatewayId;
@BuiltValueField(wireName: 'currency_id')
String get currencyId;
@nullable
bool get isApplying;
@nullable
bool get sendEmail;
@nullable
bool get gatewayRefund;
bool get hasExchangeRate => exchangeRate != 1 && exchangeRate != 0;
String get transactionReferenceOrNumber =>
transactionReference.isNotEmpty ? transactionReference : number;
BuiltList<PaymentableEntity> get paymentables;
BuiltList<PaymentableEntity> get invoices;
BuiltList<PaymentableEntity> get credits;
String get calculatedStatusId {
if (applied < amount) {
return applied == 0
? kPaymentStatusUnapplied
: kPaymentStatusPartiallyUnapplied;
}
return statusId;
}
int compareTo({
PaymentEntity payment,
String sortField,
bool sortAscending,
BuiltMap<String, InvoiceEntity> invoiceMap,
BuiltMap<String, ClientEntity> clientMap,
BuiltMap<String, UserEntity> userMap,
BuiltMap<String, PaymentTypeEntity> paymentTypeMap,
}) {
int response = 0;
final PaymentEntity paymentA = sortAscending ? this : payment;
final PaymentEntity paymentB = sortAscending ? payment : this;
switch (sortField) {
case PaymentFields.amount:
response = paymentA.amount.compareTo(paymentB.amount);
break;
case PaymentFields.exchangeRate:
response = paymentA.exchangeRate.compareTo(paymentB.exchangeRate);
break;
case PaymentFields.refunded:
response = paymentA.refunded.compareTo(paymentB.refunded);
break;
case PaymentFields.number:
response = paymentA.number
.toLowerCase()
.compareTo(paymentB.number.toLowerCase());
break;
case PaymentFields.transactionReference:
response = paymentA.transactionReference
.compareTo(paymentB.transactionReference);
break;
case PaymentFields.date:
response = paymentA.date.compareTo(paymentB.date);
break;
case PaymentFields.privateNotes:
response = paymentA.privateNotes
.toLowerCase()
.compareTo(paymentB.date.toLowerCase());
break;
case EntityFields.updatedAt:
response = paymentA.updatedAt.compareTo(paymentB.updatedAt);
break;
case EntityFields.createdAt:
response = paymentA.createdAt.compareTo(paymentB.createdAt);
break;
case EntityFields.archivedAt:
response = paymentA.archivedAt.compareTo(paymentB.archivedAt);
break;
case PaymentFields.status:
response = paymentA.statusId.compareTo(paymentB.statusId);
break;
case PaymentFields.customValue1:
response = paymentA.customValue1
.toLowerCase()
.compareTo(paymentB.customValue1.toLowerCase());
break;
case PaymentFields.customValue2:
response = paymentA.customValue2
.toLowerCase()
.compareTo(paymentB.customValue2.toLowerCase());
break;
case PaymentFields.customValue3:
response = paymentA.customValue3
.toLowerCase()
.compareTo(paymentB.customValue3.toLowerCase());
break;
case PaymentFields.customValue4:
response = paymentA.customValue4
.toLowerCase()
.compareTo(paymentB.customValue4.toLowerCase());
break;
case PaymentFields.invoiceNumber:
final invoiceA = invoiceMap[paymentA.invoiceId] ?? InvoiceEntity();
final invoiceB = invoiceMap[paymentB.invoiceId] ?? InvoiceEntity();
response = invoiceA.number
.toLowerCase()
.compareTo(invoiceB.number.toLowerCase());
break;
case PaymentFields.client:
final clientA = clientMap[paymentA.clientId] ?? ClientEntity();
final clientB = clientMap[paymentB.clientId] ?? ClientEntity();
response = clientA.displayName
.toLowerCase()
.compareTo(clientB.displayName.toLowerCase());
break;
case PaymentFields.type:
final typeA = paymentTypeMap[paymentA.typeId] ?? PaymentTypeEntity();
final typeB = paymentTypeMap[paymentB.typeId] ?? PaymentTypeEntity();
return typeA.name.toLowerCase().compareTo(typeB.name.toLowerCase());
break;
case EntityFields.assignedTo:
final userA = userMap[paymentA.assignedUserId] ?? UserEntity();
final userB = userMap[paymentB.assignedUserId] ?? UserEntity();
response = userA.listDisplayName
.toLowerCase()
.compareTo(userB.listDisplayName.toLowerCase());
break;
case EntityFields.createdBy:
final userA = userMap[paymentA.createdUserId] ?? UserEntity();
final userB = userMap[paymentB.createdUserId] ?? UserEntity();
response = userA.listDisplayName
.toLowerCase()
.compareTo(userB.listDisplayName.toLowerCase());
break;
case EntityFields.state:
final stateA =
EntityState.valueOf(paymentA.entityState) ?? EntityState.active;
final stateB =
EntityState.valueOf(paymentB.entityState) ?? EntityState.active;
response =
stateA.name.toLowerCase().compareTo(stateB.name.toLowerCase());
break;
default:
print('## ERROR: sort by payment.$sortField is not implemented');
break;
}
if (response == 0) {
response = payment.number.toLowerCase().compareTo(number.toLowerCase());
}
return response;
}
@override
bool matchesFilter(String filter) {
return matchesStrings(
haystacks: [
number,
transactionReference,
privateNotes,
customValue1,
customValue2,
customValue3,
customValue4,
],
needle: filter,
);
}
@override
String matchesFilterValue(String filter) {
return matchesStringsValue(
haystacks: [
number,
transactionReference,
privateNotes,
customValue1,
customValue2,
customValue3,
customValue4,
],
needle: filter,
);
}
@override
List<EntityAction> getActions(
{UserCompanyEntity userCompany,
ClientEntity client,
bool includeEdit = false,
bool includePreview = false,
bool multiselect = false}) {
final actions = <EntityAction>[];
if (!isDeleted) {
if (userCompany.canEditEntity(this)) {
if (!multiselect) {
if (includeEdit) {
actions.add(EntityAction.edit);
}
if (applied < amount) {
actions.add(EntityAction.applyPayment);
}
if (completedAmount > 0) {
actions.add(EntityAction.refundPayment);
}
}
if (client != null && client.hasEmailAddress) {
actions.add(EntityAction.sendEmail);
}
}
}
if (actions.isNotEmpty && actions.last != null) {
actions.add(null);
}
// We're overriding the default behavior to
// prevent users from restoring deleted payments
if (userCompany.canEditEntity(this) && isArchived) {
actions.add(EntityAction.restore);
}
if (userCompany.canEditEntity(this) && isActive) {
actions.add(EntityAction.archive);
}
if (userCompany.canEditEntity(this) && (isActive || isArchived)) {
actions.add(EntityAction.delete);
}
return actions;
}
@override
String get listDisplayName => number ?? '';
@override
double get listDisplayAmount => amount;
List<PaymentableEntity> get invoicePaymentables =>
paymentables.where((p) => p.entityType == EntityType.invoice).toList();
List<PaymentableEntity> get creditPaymentables =>
paymentables.where((p) => p.entityType == EntityType.credit).toList();
String get invoiceId {
final invoicePaymentables = paymentables.firstWhere(
(p) => p.entityType == EntityType.invoice,
orElse: () => null);
if (invoicePaymentables == null) {
return null;
}
return invoicePaymentables.isEmpty ? null : invoicePaymentables.invoiceId;
}
bool isBetween(String startDate, String endDate) {
return startDate.compareTo(date) <= 0 && endDate.compareTo(date) >= 0;
}
bool get isOnline => (companyGatewayId ?? '').isNotEmpty;
bool get isCompletedOrPartiallyRefunded => [
kPaymentStatusCompleted,
kPaymentStatusPartiallyRefunded
].contains(statusId);
@override
bool get isRestorable => false;
@override
FormatNumberType get listDisplayAmountType => FormatNumberType.money;
double get completedAmount {
if (isDeleted) {
return 0;
}
if ([kPaymentStatusCancelled, kPaymentStatusFailed].contains(statusId)) {
return 0;
}
return amount - (refunded ?? 0);
}
static Serializer<PaymentEntity> get serializer => _$paymentEntitySerializer;
}
abstract class PaymentableEntity extends Object
with SelectableEntity
implements Built<PaymentableEntity, PaymentableEntityBuilder> {
factory PaymentableEntity(
{String id, String invoiceId, String creditId, double amount}) {
return _$PaymentableEntity._(
id: id ?? BaseEntity.nextId,
invoiceId: invoiceId ?? '',
creditId: creditId ?? '',
amount: amount ?? 0,
);
}
factory PaymentableEntity.fromInvoice(InvoiceEntity invoice) {
return PaymentableEntity(
invoiceId: invoice.id,
amount: invoice.partial != 0 ? invoice.partial : invoice.balanceOrAmount,
);
}
factory PaymentableEntity.fromCredit(InvoiceEntity credit) {
return PaymentableEntity(
creditId: credit.id,
amount: credit.partial != 0 ? credit.partial : credit.balanceOrAmount,
);
}
PaymentableEntity._();
@override
@memoized
int get hashCode;
@nullable
@BuiltValueField(wireName: 'created_at')
int get createdAt;
@nullable
@BuiltValueField(wireName: 'updated_at')
int get updatedAt;
@nullable
@BuiltValueField(wireName: 'invoice_id')
String get invoiceId;
@nullable
@BuiltValueField(wireName: 'credit_id')
String get creditId;
double get amount;
bool get isEmpty =>
(invoiceId ?? '').isEmpty && (creditId ?? '').isEmpty && amount == 0;
EntityType get entityType =>
(invoiceId ?? '').isEmpty ? EntityType.credit : EntityType.invoice;
/*
@override
bool matchesFilter(String filter) {
if (filter == null || filter.isEmpty) {
return true;
}
filter = filter.toLowerCase();
return false;
}
@override
String matchesFilterValue(String filter) {
if (filter == null || filter.isEmpty) {
return null;
}
filter = filter.toLowerCase();
return null;
}
@override
String get listDisplayName {
return name;
}
@override
double get listDisplayAmount => null;
*/
static Serializer<PaymentableEntity> get serializer =>
_$paymentableEntitySerializer;
}