// 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:collection/collection.dart' show IterableExtension; 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 { factory PaymentListResponse([void updates(PaymentListResponseBuilder b)]) = _$PaymentListResponse; PaymentListResponse._(); @override @memoized int get hashCode; BuiltList get data; static Serializer get serializer => _$paymentListResponseSerializer; } abstract class PaymentItemResponse implements Built { factory PaymentItemResponse([void updates(PaymentItemResponseBuilder b)]) = _$PaymentItemResponse; PaymentItemResponse._(); @override @memoized int get hashCode; PaymentEntity get data; static Serializer 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 gatewayType = 'gateway_type'; 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 { 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: 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(), invoices: BuiltList(), credits: BuiltList(), assignedUserId: '', createdAt: 0, createdUserId: '', vendorId: '', projectId: '', number: '', sendEmail: settings.clientManualPaymentNotification ?? false, companyGatewayId: '', clientContactId: '', currencyId: '', transactionId: '', invitationId: '', isApplying: false, gatewayTypeId: '', ); } 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; @BuiltValueField(wireName: 'idempotency_key') String? get idempotencyKey; @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: 'transaction_id') String get transactionId; @BuiltValueField(wireName: 'client_contact_id') String get clientContactId; @BuiltValueField(wireName: 'company_gateway_id') String get companyGatewayId; @BuiltValueField(wireName: 'currency_id') String get currencyId; @BuiltValueField(wireName: 'gateway_type_id') String get gatewayTypeId; bool? get isApplying; bool? get sendEmail; bool? get gatewayRefund; bool get hasExchangeRate => exchangeRate != 1 && exchangeRate != 0; String get transactionReferenceOrNumber => transactionReference.isNotEmpty ? transactionReference : number; BuiltList get paymentables; BuiltList get invoices; BuiltList get credits; bool get canBeAppliedOrRefunded => [ kPaymentStatusCompleted, kPaymentStatusPartiallyRefunded, ].contains(statusId); String get calculatedStatusId { if (applied < amount) { return applied == 0 ? kPaymentStatusUnapplied : kPaymentStatusPartiallyUnapplied; } return statusId; } int compareTo({ PaymentEntity? payment, String? sortField, required bool sortAscending, BuiltMap? invoiceMap, BuiltMap? clientMap, BuiltMap? userMap, BuiltMap? 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(); response = 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); final stateB = EntityState.valueOf(paymentB!.entityState); 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 matchesStatuses(BuiltList statuses) { if (statuses.isEmpty) { return true; } for (final status in statuses) { if (status.id == statusId || status.id == calculatedStatusId) { return true; } } return false; } @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 getActions( {UserCompanyEntity? userCompany, ClientEntity? client, bool includeEdit = false, bool includePreview = false, bool multiselect = false}) { final actions = []; if (!isDeleted!) { if (userCompany!.canEditEntity(this)) { if (!multiselect) { if (includeEdit) { actions.add(EntityAction.edit); } if (applied < amount && canBeAppliedOrRefunded) { actions.add(EntityAction.applyPayment); } if (completedAmount > 0 && canBeAppliedOrRefunded) { 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 get invoicePaymentables => paymentables.where((p) => p.entityType == EntityType.invoice).toList(); List get creditPaymentables => paymentables.where((p) => p.entityType == EntityType.credit).toList(); String? get invoiceId { final invoicePaymentables = paymentables .firstWhereOrNull((p) => p.entityType == EntityType.invoice); 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; } // ignore: unused_element static void _initializeBuilder(PaymentEntityBuilder builder) => builder ..transactionId = '' ..gatewayTypeId = ''; static Serializer get serializer => _$paymentEntitySerializer; } abstract class PaymentableEntity extends Object with SelectableEntity implements Built { 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; @BuiltValueField(wireName: 'created_at') int? get createdAt; @BuiltValueField(wireName: 'updated_at') int? get updatedAt; @BuiltValueField(wireName: 'invoice_id') String? get invoiceId; @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 get serializer => _$paymentableEntitySerializer; }