diff --git a/lib/data/models/company_model.dart b/lib/data/models/company_model.dart index b2e53aa7c..80073264e 100644 --- a/lib/data/models/company_model.dart +++ b/lib/data/models/company_model.dart @@ -140,6 +140,7 @@ abstract class CompanyEntity extends Object subscriptions: BuiltList(), systemLogs: BuiltList(), clientRegistrationFields: BuiltList(), + purchaseOrders: BuiltList(), ); } @@ -314,6 +315,9 @@ abstract class CompanyEntity extends Object BuiltList get credits; + @BuiltValueField(wireName: 'purchase_orders') + BuiltList get purchaseOrders; + BuiltList get tasks; BuiltList get projects; @@ -563,6 +567,7 @@ abstract class CompanyEntity extends Object ..invoices.clear() ..payments.clear() ..quotes.clear() + ..purchaseOrders.clear() ..credits.clear() ..tasks.clear() ..projects.clear() @@ -633,7 +638,8 @@ abstract class CompanyEntity extends Object ..systemLogs.replace(BuiltList()) ..subscriptions.replace(BuiltList()) ..recurringExpenses.replace(BuiltList()) - ..clientRegistrationFields.replace(BuiltList()); + ..clientRegistrationFields.replace(BuiltList()) + ..purchaseOrders.replace(BuiltList()); static Serializer get serializer => _$companyEntitySerializer; } diff --git a/lib/data/models/company_model.g.dart b/lib/data/models/company_model.g.dart index 0bc4d5815..0a8511d10 100644 --- a/lib/data/models/company_model.g.dart +++ b/lib/data/models/company_model.g.dart @@ -222,6 +222,10 @@ class _$CompanyEntitySerializer implements StructuredSerializer { serializers.serialize(object.credits, specifiedType: const FullType(BuiltList, const [const FullType(InvoiceEntity)])), + 'purchase_orders', + serializers.serialize(object.purchaseOrders, + specifiedType: + const FullType(BuiltList, const [const FullType(InvoiceEntity)])), 'tasks', serializers.serialize(object.tasks, specifiedType: @@ -643,6 +647,12 @@ class _$CompanyEntitySerializer implements StructuredSerializer { BuiltList, const [const FullType(InvoiceEntity)])) as BuiltList); break; + case 'purchase_orders': + result.purchaseOrders.replace(serializers.deserialize(value, + specifiedType: const FullType( + BuiltList, const [const FullType(InvoiceEntity)])) + as BuiltList); + break; case 'tasks': result.tasks.replace(serializers.deserialize(value, specifiedType: const FullType( @@ -1511,6 +1521,8 @@ class _$CompanyEntity extends CompanyEntity { @override final BuiltList credits; @override + final BuiltList purchaseOrders; + @override final BuiltList tasks; @override final BuiltList projects; @@ -1646,6 +1658,7 @@ class _$CompanyEntity extends CompanyEntity { this.payments, this.quotes, this.credits, + this.purchaseOrders, this.tasks, this.projects, this.expenses, @@ -1791,6 +1804,8 @@ class _$CompanyEntity extends CompanyEntity { payments, 'CompanyEntity', 'payments'); BuiltValueNullFieldError.checkNotNull(quotes, 'CompanyEntity', 'quotes'); BuiltValueNullFieldError.checkNotNull(credits, 'CompanyEntity', 'credits'); + BuiltValueNullFieldError.checkNotNull( + purchaseOrders, 'CompanyEntity', 'purchaseOrders'); BuiltValueNullFieldError.checkNotNull(tasks, 'CompanyEntity', 'tasks'); BuiltValueNullFieldError.checkNotNull( projects, 'CompanyEntity', 'projects'); @@ -1920,6 +1935,7 @@ class _$CompanyEntity extends CompanyEntity { payments == other.payments && quotes == other.quotes && credits == other.credits && + purchaseOrders == other.purchaseOrders && tasks == other.tasks && projects == other.projects && expenses == other.expenses && @@ -1980,7 +1996,7 @@ class _$CompanyEntity extends CompanyEntity { $jc( $jc( $jc( - $jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, enableCustomSurchargeTaxes1.hashCode), enableCustomSurchargeTaxes2.hashCode), enableCustomSurchargeTaxes3.hashCode), enableCustomSurchargeTaxes4.hashCode), sizeId.hashCode), industryId.hashCode), subdomain.hashCode), portalMode.hashCode), portalDomain.hashCode), updateProducts.hashCode), convertProductExchangeRate.hashCode), convertRateToClient.hashCode), fillProducts.hashCode), enableProductCost.hashCode), enableProductQuantity.hashCode), enableProductDiscount.hashCode), defaultTaskIsDateBased.hashCode), defaultQuantity.hashCode), showProductDetails.hashCode), clientCanRegister.hashCode), isLarge.hashCode), isDisabled.hashCode), enableShopApi.hashCode), companyKey.hashCode), firstDayOfWeek.hashCode), firstMonthOfYear.hashCode), numberOfInvoiceTaxRates.hashCode), numberOfItemTaxRates.hashCode), expenseInclusiveTaxes.hashCode), sessionTimeout.hashCode), passwordTimeout.hashCode), oauthPasswordRequired.hashCode), markdownEnabled.hashCode), markdownEmailEnabled.hashCode), useCommaAsDecimalPlace.hashCode), reportIncludeDrafts.hashCode), useQuoteTermsOnConversion.hashCode), enableApplyingPayments.hashCode), trackInventory.hashCode), stockNotificationThreshold.hashCode), stockNotification.hashCode), groups.hashCode), activities.hashCode), taxRates.hashCode), taskStatuses.hashCode), taskStatusMap.hashCode), companyGateways.hashCode), expenseCategories.hashCode), users.hashCode), clients.hashCode), products.hashCode), invoices.hashCode), recurringInvoices.hashCode), recurringExpenses.hashCode), payments.hashCode), quotes.hashCode), credits.hashCode), tasks.hashCode), projects.hashCode), expenses.hashCode), vendors.hashCode), designs.hashCode), documents.hashCode), tokens.hashCode), webhooks.hashCode), subscriptions.hashCode), paymentTerms.hashCode), systemLogs.hashCode), clientRegistrationFields.hashCode), customFields.hashCode), slackWebhookUrl.hashCode), googleAnalyticsKey.hashCode), markExpensesInvoiceable.hashCode), markExpensesPaid.hashCode), invoiceExpenseDocuments.hashCode), + $jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, enableCustomSurchargeTaxes1.hashCode), enableCustomSurchargeTaxes2.hashCode), enableCustomSurchargeTaxes3.hashCode), enableCustomSurchargeTaxes4.hashCode), sizeId.hashCode), industryId.hashCode), subdomain.hashCode), portalMode.hashCode), portalDomain.hashCode), updateProducts.hashCode), convertProductExchangeRate.hashCode), convertRateToClient.hashCode), fillProducts.hashCode), enableProductCost.hashCode), enableProductQuantity.hashCode), enableProductDiscount.hashCode), defaultTaskIsDateBased.hashCode), defaultQuantity.hashCode), showProductDetails.hashCode), clientCanRegister.hashCode), isLarge.hashCode), isDisabled.hashCode), enableShopApi.hashCode), companyKey.hashCode), firstDayOfWeek.hashCode), firstMonthOfYear.hashCode), numberOfInvoiceTaxRates.hashCode), numberOfItemTaxRates.hashCode), expenseInclusiveTaxes.hashCode), sessionTimeout.hashCode), passwordTimeout.hashCode), oauthPasswordRequired.hashCode), markdownEnabled.hashCode), markdownEmailEnabled.hashCode), useCommaAsDecimalPlace.hashCode), reportIncludeDrafts.hashCode), useQuoteTermsOnConversion.hashCode), enableApplyingPayments.hashCode), trackInventory.hashCode), stockNotificationThreshold.hashCode), stockNotification.hashCode), groups.hashCode), activities.hashCode), taxRates.hashCode), taskStatuses.hashCode), taskStatusMap.hashCode), companyGateways.hashCode), expenseCategories.hashCode), users.hashCode), clients.hashCode), products.hashCode), invoices.hashCode), recurringInvoices.hashCode), recurringExpenses.hashCode), payments.hashCode), quotes.hashCode), credits.hashCode), purchaseOrders.hashCode), tasks.hashCode), projects.hashCode), expenses.hashCode), vendors.hashCode), designs.hashCode), documents.hashCode), tokens.hashCode), webhooks.hashCode), subscriptions.hashCode), paymentTerms.hashCode), systemLogs.hashCode), clientRegistrationFields.hashCode), customFields.hashCode), slackWebhookUrl.hashCode), googleAnalyticsKey.hashCode), markExpensesInvoiceable.hashCode), markExpensesPaid.hashCode), invoiceExpenseDocuments.hashCode), invoiceTaskDocuments.hashCode), invoiceTaskTimelog.hashCode), invoiceTaskDatelog.hashCode), @@ -2062,6 +2078,7 @@ class _$CompanyEntity extends CompanyEntity { ..add('payments', payments) ..add('quotes', quotes) ..add('credits', credits) + ..add('purchaseOrders', purchaseOrders) ..add('tasks', tasks) ..add('projects', projects) ..add('expenses', expenses) @@ -2394,6 +2411,12 @@ class CompanyEntityBuilder _$this._credits ??= new ListBuilder(); set credits(ListBuilder credits) => _$this._credits = credits; + ListBuilder _purchaseOrders; + ListBuilder get purchaseOrders => + _$this._purchaseOrders ??= new ListBuilder(); + set purchaseOrders(ListBuilder purchaseOrders) => + _$this._purchaseOrders = purchaseOrders; + ListBuilder _tasks; ListBuilder get tasks => _$this._tasks ??= new ListBuilder(); @@ -2647,6 +2670,7 @@ class CompanyEntityBuilder _payments = $v.payments.toBuilder(); _quotes = $v.quotes.toBuilder(); _credits = $v.credits.toBuilder(); + _purchaseOrders = $v.purchaseOrders.toBuilder(); _tasks = $v.tasks.toBuilder(); _projects = $v.projects.toBuilder(); _expenses = $v.expenses.toBuilder(); @@ -2777,6 +2801,7 @@ class CompanyEntityBuilder payments: payments.build(), quotes: quotes.build(), credits: credits.build(), + purchaseOrders: purchaseOrders.build(), tasks: tasks.build(), projects: projects.build(), expenses: expenses.build(), @@ -2849,6 +2874,8 @@ class CompanyEntityBuilder quotes.build(); _$failedField = 'credits'; credits.build(); + _$failedField = 'purchaseOrders'; + purchaseOrders.build(); _$failedField = 'tasks'; tasks.build(); _$failedField = 'projects'; diff --git a/lib/data/models/entities.dart b/lib/data/models/entities.dart index c39390381..9345c6600 100644 --- a/lib/data/models/entities.dart +++ b/lib/data/models/entities.dart @@ -62,6 +62,7 @@ class EntityType extends EnumClass { static const EntityType timezone = _$timezone; static const EntityType dateFormat = _$dateFormat; static const EntityType font = _$font; + static const EntityType purchaseOrder = _$purchaseOrder; String get plural { if (this == EntityType.expenseCategory) { diff --git a/lib/data/models/entities.g.dart b/lib/data/models/entities.g.dart index 46f5ee294..4c39a7269 100644 --- a/lib/data/models/entities.g.dart +++ b/lib/data/models/entities.g.dart @@ -50,6 +50,7 @@ const EntityType _$document = const EntityType._('document'); const EntityType _$timezone = const EntityType._('timezone'); const EntityType _$dateFormat = const EntityType._('dateFormat'); const EntityType _$font = const EntityType._('font'); +const EntityType _$purchaseOrder = const EntityType._('purchaseOrder'); EntityType _$typeValueOf(String name) { switch (name) { @@ -141,6 +142,8 @@ EntityType _$typeValueOf(String name) { return _$dateFormat; case 'font': return _$font; + case 'purchaseOrder': + return _$purchaseOrder; default: throw new ArgumentError(name); } @@ -192,6 +195,7 @@ final BuiltSet _$typeValues = _$timezone, _$dateFormat, _$font, + _$purchaseOrder, ]); const EntityState _$active = const EntityState._('active'); diff --git a/lib/data/models/purchase_order_model.dart b/lib/data/models/purchase_order_model.dart new file mode 100644 index 000000000..db8e320c0 --- /dev/null +++ b/lib/data/models/purchase_order_model.dart @@ -0,0 +1,52 @@ +class PurchaseOrderFields { + static const String total = 'total'; + static const String amount = 'amount'; + static const String balance = 'balance'; + static const String balanceDue = 'balance_due'; + static const String clientId = 'client_id'; + static const String client = 'client'; + static const String project = 'project'; + static const String vendor = 'vendor'; + static const String statusId = 'status_id'; + static const String status = 'status'; + static const String number = 'number'; + static const String discount = 'discount'; + static const String poNumber = 'po_number'; + static const String date = 'date'; + static const String dueDate = 'due_date'; + static const String nextSendDate = 'next_send_date'; + static const String lastSentDate = 'last_sent_date'; + static const String terms = 'terms'; + static const String footer = 'footer'; + static const String partial = 'partial_due'; + static const String partialDueDate = 'partial_due_date'; + static const String publicNotes = 'public_notes'; + static const String privateNotes = 'private_notes'; + static const String isRecurring = 'is_recurring'; + static const String frequencyId = 'frequency_id'; + static const String documents = 'documents'; + static const String customValue1 = 'custom1'; + static const String customValue2 = 'custom2'; + static const String customValue3 = 'custom3'; + static const String customValue4 = 'custom4'; + static const String customSurcharge1 = 'custom_surcharge1'; + static const String customSurcharge2 = 'custom_surcharge2'; + static const String customSurcharge3 = 'custom_surcharge3'; + static const String customSurcharge4 = 'custom_surcharge4'; + static const String taxAmount = 'tax_amount'; + static const String reminder1Sent = 'reminder1_sent'; + static const String reminder2Sent = 'reminder2_sent'; + static const String reminder3Sent = 'reminder3_sent'; + static const String reminderLastSent = 'reminder_last_sent'; + static const String exchangeRate = 'exchange_rate'; + static const String isViewed = 'is_viewed'; + static const String autoBillEnabled = 'auto_bill_enabled'; + static const String contactName = 'contact_name'; + static const String contactEmail = 'contact_email'; + static const String clientCity = 'client_city'; + static const String clientState = 'client_state'; + static const String clientPostalCode = 'client_postal_code'; + static const String clientCountry = 'client_country'; + static const String quote = 'quote'; + static const String recurringInvoice = 'recurring_invoice'; +} diff --git a/lib/data/models/quote_model.dart b/lib/data/models/quote_model.dart index 92ff7c3b1..4640820ea 100644 --- a/lib/data/models/quote_model.dart +++ b/lib/data/models/quote_model.dart @@ -1,47 +1,3 @@ -// 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/data/models/models.dart'; - -part 'quote_model.g.dart'; - -abstract class QuoteListResponse - implements Built { - factory QuoteListResponse([void updates(QuoteListResponseBuilder b)]) = - _$QuoteListResponse; - - QuoteListResponse._(); - - @override - @memoized - int get hashCode; - - BuiltList get data; - - static Serializer get serializer => - _$quoteListResponseSerializer; -} - -abstract class QuoteItemResponse - implements Built { - factory QuoteItemResponse([void updates(QuoteItemResponseBuilder b)]) = - _$QuoteItemResponse; - - QuoteItemResponse._(); - - @override - @memoized - int get hashCode; - - InvoiceEntity get data; - - static Serializer get serializer => - _$quoteItemResponseSerializer; -} - class QuoteFields { static const String total = 'total'; static const String amount = 'amount'; diff --git a/lib/data/models/quote_model.g.dart b/lib/data/models/quote_model.g.dart deleted file mode 100644 index 1626b61e6..000000000 --- a/lib/data/models/quote_model.g.dart +++ /dev/null @@ -1,282 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'quote_model.dart'; - -// ************************************************************************** -// BuiltValueGenerator -// ************************************************************************** - -Serializer _$quoteListResponseSerializer = - new _$QuoteListResponseSerializer(); -Serializer _$quoteItemResponseSerializer = - new _$QuoteItemResponseSerializer(); - -class _$QuoteListResponseSerializer - implements StructuredSerializer { - @override - final Iterable types = const [QuoteListResponse, _$QuoteListResponse]; - @override - final String wireName = 'QuoteListResponse'; - - @override - Iterable serialize(Serializers serializers, QuoteListResponse object, - {FullType specifiedType = FullType.unspecified}) { - final result = [ - 'data', - serializers.serialize(object.data, - specifiedType: - const FullType(BuiltList, const [const FullType(InvoiceEntity)])), - ]; - - return result; - } - - @override - QuoteListResponse deserialize( - Serializers serializers, Iterable serialized, - {FullType specifiedType = FullType.unspecified}) { - final result = new QuoteListResponseBuilder(); - - final iterator = serialized.iterator; - while (iterator.moveNext()) { - final key = iterator.current as String; - iterator.moveNext(); - final Object value = iterator.current; - switch (key) { - case 'data': - result.data.replace(serializers.deserialize(value, - specifiedType: const FullType( - BuiltList, const [const FullType(InvoiceEntity)])) - as BuiltList); - break; - } - } - - return result.build(); - } -} - -class _$QuoteItemResponseSerializer - implements StructuredSerializer { - @override - final Iterable types = const [QuoteItemResponse, _$QuoteItemResponse]; - @override - final String wireName = 'QuoteItemResponse'; - - @override - Iterable serialize(Serializers serializers, QuoteItemResponse object, - {FullType specifiedType = FullType.unspecified}) { - final result = [ - 'data', - serializers.serialize(object.data, - specifiedType: const FullType(InvoiceEntity)), - ]; - - return result; - } - - @override - QuoteItemResponse deserialize( - Serializers serializers, Iterable serialized, - {FullType specifiedType = FullType.unspecified}) { - final result = new QuoteItemResponseBuilder(); - - final iterator = serialized.iterator; - while (iterator.moveNext()) { - final key = iterator.current as String; - iterator.moveNext(); - final Object value = iterator.current; - switch (key) { - case 'data': - result.data.replace(serializers.deserialize(value, - specifiedType: const FullType(InvoiceEntity)) as InvoiceEntity); - break; - } - } - - return result.build(); - } -} - -class _$QuoteListResponse extends QuoteListResponse { - @override - final BuiltList data; - - factory _$QuoteListResponse( - [void Function(QuoteListResponseBuilder) updates]) => - (new QuoteListResponseBuilder()..update(updates)).build(); - - _$QuoteListResponse._({this.data}) : super._() { - BuiltValueNullFieldError.checkNotNull(data, 'QuoteListResponse', 'data'); - } - - @override - QuoteListResponse rebuild(void Function(QuoteListResponseBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - QuoteListResponseBuilder toBuilder() => - new QuoteListResponseBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is QuoteListResponse && data == other.data; - } - - int __hashCode; - @override - int get hashCode { - return __hashCode ??= $jf($jc(0, data.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('QuoteListResponse')..add('data', data)) - .toString(); - } -} - -class QuoteListResponseBuilder - implements Builder { - _$QuoteListResponse _$v; - - ListBuilder _data; - ListBuilder get data => - _$this._data ??= new ListBuilder(); - set data(ListBuilder data) => _$this._data = data; - - QuoteListResponseBuilder(); - - QuoteListResponseBuilder get _$this { - final $v = _$v; - if ($v != null) { - _data = $v.data.toBuilder(); - _$v = null; - } - return this; - } - - @override - void replace(QuoteListResponse other) { - ArgumentError.checkNotNull(other, 'other'); - _$v = other as _$QuoteListResponse; - } - - @override - void update(void Function(QuoteListResponseBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$QuoteListResponse build() { - _$QuoteListResponse _$result; - try { - _$result = _$v ?? new _$QuoteListResponse._(data: data.build()); - } catch (_) { - String _$failedField; - try { - _$failedField = 'data'; - data.build(); - } catch (e) { - throw new BuiltValueNestedFieldError( - 'QuoteListResponse', _$failedField, e.toString()); - } - rethrow; - } - replace(_$result); - return _$result; - } -} - -class _$QuoteItemResponse extends QuoteItemResponse { - @override - final InvoiceEntity data; - - factory _$QuoteItemResponse( - [void Function(QuoteItemResponseBuilder) updates]) => - (new QuoteItemResponseBuilder()..update(updates)).build(); - - _$QuoteItemResponse._({this.data}) : super._() { - BuiltValueNullFieldError.checkNotNull(data, 'QuoteItemResponse', 'data'); - } - - @override - QuoteItemResponse rebuild(void Function(QuoteItemResponseBuilder) updates) => - (toBuilder()..update(updates)).build(); - - @override - QuoteItemResponseBuilder toBuilder() => - new QuoteItemResponseBuilder()..replace(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) return true; - return other is QuoteItemResponse && data == other.data; - } - - int __hashCode; - @override - int get hashCode { - return __hashCode ??= $jf($jc(0, data.hashCode)); - } - - @override - String toString() { - return (newBuiltValueToStringHelper('QuoteItemResponse')..add('data', data)) - .toString(); - } -} - -class QuoteItemResponseBuilder - implements Builder { - _$QuoteItemResponse _$v; - - InvoiceEntityBuilder _data; - InvoiceEntityBuilder get data => _$this._data ??= new InvoiceEntityBuilder(); - set data(InvoiceEntityBuilder data) => _$this._data = data; - - QuoteItemResponseBuilder(); - - QuoteItemResponseBuilder get _$this { - final $v = _$v; - if ($v != null) { - _data = $v.data.toBuilder(); - _$v = null; - } - return this; - } - - @override - void replace(QuoteItemResponse other) { - ArgumentError.checkNotNull(other, 'other'); - _$v = other as _$QuoteItemResponse; - } - - @override - void update(void Function(QuoteItemResponseBuilder) updates) { - if (updates != null) updates(this); - } - - @override - _$QuoteItemResponse build() { - _$QuoteItemResponse _$result; - try { - _$result = _$v ?? new _$QuoteItemResponse._(data: data.build()); - } catch (_) { - String _$failedField; - try { - _$failedField = 'data'; - data.build(); - } catch (e) { - throw new BuiltValueNestedFieldError( - 'QuoteItemResponse', _$failedField, e.toString()); - } - rethrow; - } - replace(_$result); - return _$result; - } -} - -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/lib/data/models/serializers.dart b/lib/data/models/serializers.dart index 4f0b204ce..b073ff034 100644 --- a/lib/data/models/serializers.dart +++ b/lib/data/models/serializers.dart @@ -31,6 +31,7 @@ import 'package:invoiceninja_flutter/redux/payment/payment_state.dart'; import 'package:invoiceninja_flutter/redux/payment_term/payment_term_state.dart'; import 'package:invoiceninja_flutter/redux/product/product_state.dart'; import 'package:invoiceninja_flutter/redux/project/project_state.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_state.dart'; import 'package:invoiceninja_flutter/redux/quote/quote_state.dart'; import 'package:invoiceninja_flutter/redux/recurring_expense/recurring_expense_state.dart'; import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_state.dart'; @@ -113,6 +114,9 @@ part 'serializers.g.dart'; TaxRateItemResponse, TaxRateListResponse, // STARTER: serializers - do not remove comment + InvoiceListResponse, + InvoiceItemResponse, + SubscriptionEntity, SubscriptionListResponse, SubscriptionItemResponse, diff --git a/lib/data/models/serializers.g.dart b/lib/data/models/serializers.g.dart index 6dfa8d2a3..c021e4043 100644 --- a/lib/data/models/serializers.g.dart +++ b/lib/data/models/serializers.g.dart @@ -138,6 +138,8 @@ Serializers _$serializers = (new Serializers().toBuilder() ..add(ProjectListResponse.serializer) ..add(ProjectState.serializer) ..add(ProjectUIState.serializer) + ..add(PurchaseOrderState.serializer) + ..add(PurchaseOrderUIState.serializer) ..add(QuoteState.serializer) ..add(QuoteUIState.serializer) ..add(RecurringExpenseState.serializer) @@ -402,6 +404,9 @@ Serializers _$serializers = (new Serializers().toBuilder() ..addBuilderFactory( const FullType(BuiltList, const [const FullType(InvoiceEntity)]), () => new ListBuilder()) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(InvoiceEntity)]), + () => new ListBuilder()) ..addBuilderFactory( const FullType(BuiltList, const [const FullType(TaskEntity)]), () => new ListBuilder()) @@ -752,6 +757,13 @@ Serializers _$serializers = (new Serializers().toBuilder() ..addBuilderFactory( const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder()) + ..addBuilderFactory( + const FullType(BuiltMap, + const [const FullType(String), const FullType(InvoiceEntity)]), + () => new MapBuilder()) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(String)]), + () => new ListBuilder()) ..addBuilderFactory( const FullType(BuiltMap, const [const FullType(String), const FullType(PaymentEntity)]), diff --git a/lib/data/repositories/purchase_order_repository.dart b/lib/data/repositories/purchase_order_repository.dart new file mode 100644 index 000000000..e989d67df --- /dev/null +++ b/lib/data/repositories/purchase_order_repository.dart @@ -0,0 +1,75 @@ +import 'dart:convert'; +import 'dart:core'; +import 'package:built_collection/built_collection.dart'; +import 'package:invoiceninja_flutter/constants.dart'; +import 'package:invoiceninja_flutter/data/models/serializers.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/data/web_client.dart'; + +class PurchaseOrderRepository { + const PurchaseOrderRepository({ + this.webClient = const WebClient(), + }); + + final WebClient webClient; + + Future loadItem( + Credentials credentials, String entityId) async { + final dynamic response = await webClient.get( + '${credentials.url}/purchase_orders/$entityId', credentials.token); + + final InvoiceItemResponse purchaseOrderResponse = + serializers.deserializeWith(InvoiceItemResponse.serializer, response); + + return purchaseOrderResponse.data; + } + + Future> loadList(Credentials credentials) async { + final String url = credentials.url + '/purchase_orders?'; + final dynamic response = await webClient.get(url, credentials.token); + + final InvoiceListResponse purchaseOrderResponse = + serializers.deserializeWith(InvoiceListResponse.serializer, response); + + return purchaseOrderResponse.data; + } + + Future> bulkAction( + Credentials credentials, List ids, EntityAction action) async { + if (ids.length > kMaxEntitiesPerBulkAction) { + ids = ids.sublist(0, kMaxEntitiesPerBulkAction); + } + + final url = credentials.url + '/purchase_orders/bulk'; + final dynamic response = await webClient.post(url, credentials.token, + data: json.encode({'ids': ids, 'action': action.toApiParam()})); + + final InvoiceListResponse purchaseOrderResponse = + serializers.deserializeWith(InvoiceListResponse.serializer, response); + + return purchaseOrderResponse.data.toList(); + } + + Future saveData( + Credentials credentials, InvoiceEntity purchaseOrder) async { + final data = + serializers.serializeWith(InvoiceEntity.serializer, purchaseOrder); + dynamic response; + + if (purchaseOrder.isNew) { + response = await webClient.post( + credentials.url + '/purchase_orders', credentials.token, + data: json.encode(data)); + } else { + final url = '${credentials.url}/purchase_orders/${purchaseOrder.id}'; + response = + await webClient.put(url, credentials.token, data: json.encode(data)); + } + + final InvoiceItemResponse purchaseOrderResponse = + serializers.deserializeWith(InvoiceItemResponse.serializer, response); + + return purchaseOrderResponse.data; + } +} diff --git a/lib/main.dart b/lib/main.dart index ff46277d7..689cae0e5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -57,6 +57,7 @@ import 'package:invoiceninja_flutter/utils/web_stub.dart' if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_middleware.dart'; void main({bool isTesting = false}) async { WidgetsFlutterBinding.ensureInitialized(); @@ -79,6 +80,7 @@ void main({bool isTesting = false}) async { ..addAll(createStoreSettingsMiddleware()) ..addAll(createStoreReportsMiddleware()) // STARTER: middleware - do not remove comment + ..addAll(createStorePurchaseOrdersMiddleware()) ..addAll(createStoreRecurringExpensesMiddleware()) ..addAll(createStoreSubscriptionsMiddleware()) ..addAll(createStoreTaskStatusesMiddleware()) diff --git a/lib/main_app.dart b/lib/main_app.dart index 537fb20af..cade64be1 100644 --- a/lib/main_app.dart +++ b/lib/main_app.dart @@ -94,6 +94,10 @@ import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_screen.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/edit/purchase_order_edit_vm.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/view/purchase_order_view_vm.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_screen_vm.dart'; import 'package:invoiceninja_flutter/utils/web_stub.dart' if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart'; @@ -490,6 +494,13 @@ class InvoiceNinjaAppState extends State { QuoteEmailScreen.route: (context) => QuoteEmailScreen(), QuotePdfScreen.route: (context) => QuotePdfScreen(), // STARTER: routes - do not remove comment + PurchaseOrderScreen.route: (context) => + PurchaseOrderScreenBuilder(), + PurchaseOrderViewScreen.route: (context) => + PurchaseOrderViewScreen(), + PurchaseOrderEditScreen.route: (context) => + PurchaseOrderEditScreen(), + RecurringExpenseScreen.route: (context) => RecurringExpenseScreenBuilder(), RecurringExpenseViewScreen.route: (context) => diff --git a/lib/redux/app/app_actions.dart b/lib/redux/app/app_actions.dart index c61f45c46..8900031dd 100644 --- a/lib/redux/app/app_actions.dart +++ b/lib/redux/app/app_actions.dart @@ -64,6 +64,7 @@ import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; class PersistUI {} @@ -380,6 +381,9 @@ void viewEntitiesByType({ action = ViewGroupList(); break; // STARTER: view list - do not remove comment + case EntityType.purchaseOrder: + action = ViewPurchaseOrderList(); + break; case EntityType.recurringExpense: action = ViewRecurringExpenseList(); break; @@ -589,6 +593,13 @@ void viewEntityById({ )); break; // STARTER: view - do not remove comment + case EntityType.purchaseOrder: + store.dispatch(ViewPurchaseOrder( + purchaseOrderId: entityId, + force: force, + )); + break; + case EntityType.recurringExpense: store.dispatch(ViewRecurringExpense( recurringExpenseId: entityId, @@ -814,6 +825,13 @@ void createEntityByType({ )); break; // STARTER: create type - do not remove comment + case EntityType.purchaseOrder: + store.dispatch(EditPurchaseOrder( + force: force, + purchaseOrder: InvoiceEntity(state: state), + )); + break; + case EntityType.recurringExpense: store.dispatch(EditRecurringExpense( force: force, @@ -1018,6 +1036,14 @@ void createEntity({ )); break; // STARTER: create - do not remove comment + case EntityType.purchaseOrder: + store.dispatch(EditPurchaseOrder( + purchaseOrder: entity, + force: force, + completer: completer, + )); + break; + case EntityType.recurringExpense: store.dispatch(EditRecurringExpense( recurringExpense: entity, @@ -1204,6 +1230,11 @@ void editEntity({ )); break; // STARTER: edit - do not remove comment + case EntityType.purchaseOrder: + store.dispatch( + EditPurchaseOrder(purchaseOrder: entity, completer: completer)); + break; + case EntityType.recurringExpense: store.dispatch(EditRecurringExpense( recurringExpense: entity, completer: completer)); @@ -1371,6 +1402,10 @@ void handleEntitiesActions(List entities, EntityAction action, handleDocumentAction(context, entities, action); break; // STARTER: actions - do not remove comment + case EntityType.purchaseOrder: + handlePurchaseOrderAction(context, entities, action); + break; + case EntityType.recurringExpense: handleRecurringExpenseAction(context, entities, action); break; diff --git a/lib/redux/app/app_reducer.dart b/lib/redux/app/app_reducer.dart index e118d95df..c19d19fb1 100644 --- a/lib/redux/app/app_reducer.dart +++ b/lib/redux/app/app_reducer.dart @@ -32,6 +32,7 @@ import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart'; import 'package:invoiceninja_flutter/redux/webhook/webhook_actions.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; // We create the State reducer by combining many smaller reducers into one! AppState appReducer(AppState state, dynamic action) { @@ -101,6 +102,10 @@ final lastErrorReducer = combineReducers([ return '${action.error}'; }), // STARTER: errors - do not remove comment + TypedReducer((state, action) { + return '${action.error}'; + }), + TypedReducer((state, action) { return '${action.error}'; }), diff --git a/lib/redux/app/app_state.dart b/lib/redux/app/app_state.dart index ad2f37c16..db62e790b 100644 --- a/lib/redux/app/app_state.dart +++ b/lib/redux/app/app_state.dart @@ -88,6 +88,9 @@ import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_state.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/edit/purchase_order_edit_vm.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_selectors.dart'; part 'app_state.g.dart'; @@ -289,6 +292,9 @@ abstract class AppState implements Built { case EntityType.invoice: return invoiceState.map; // STARTER: states switch map - do not remove comment + case EntityType.purchaseOrder: + return purchaseOrderState.map; + case EntityType.recurringExpense: return recurringExpenseState.map; @@ -369,6 +375,9 @@ abstract class AppState implements Built { case EntityType.invoice: return invoiceState.list; // STARTER: states switch list - do not remove comment + case EntityType.purchaseOrder: + return purchaseOrderState.list; + case EntityType.recurringExpense: return recurringExpenseState.list; @@ -438,6 +447,9 @@ abstract class AppState implements Built { case EntityType.invoice: return invoiceUIState; // STARTER: states switch - do not remove comment + case EntityType.purchaseOrder: + return purchaseOrderUIState; + case EntityType.recurringExpense: return recurringExpenseUIState; case EntityType.subscription: @@ -508,6 +520,12 @@ abstract class AppState implements Built { ListUIState get invoiceListState => uiState.invoiceUIState.listUIState; // STARTER: state getters - do not remove comment + PurchaseOrderState get purchaseOrderState => + userCompanyState.purchaseOrderState; + ListUIState get purchaseOrderListState => + uiState.purchaseOrderUIState.listUIState; + PurchaseOrderUIState get purchaseOrderUIState => uiState.purchaseOrderUIState; + RecurringExpenseState get recurringExpenseState => userCompanyState.recurringExpenseState; ListUIState get recurringExpenseListState => @@ -678,6 +696,10 @@ abstract class AppState implements Built { case CreditEditScreen.route: return hasCreditChanges(creditUIState.editing, creditState.map); // STARTER: has changes - do not remove comment + case PurchaseOrderEditScreen.route: + return hasPurchaseOrderChanges( + purchaseOrderUIState.editing, purchaseOrderState.map); + case RecurringExpenseEditScreen.route: return hasRecurringExpenseChanges( recurringExpenseUIState.editing, recurringExpenseState.map); diff --git a/lib/redux/company/company_reducer.dart b/lib/redux/company/company_reducer.dart index dc6357e56..11abcc1d2 100644 --- a/lib/redux/company/company_reducer.dart +++ b/lib/redux/company/company_reducer.dart @@ -35,6 +35,7 @@ import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart'; import 'package:invoiceninja_flutter/redux/webhook/webhook_reducer.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_reducer.dart'; UserCompanyState companyReducer(UserCompanyState state, dynamic action) { if (action is DeleteCompanySuccess) { @@ -52,6 +53,8 @@ UserCompanyState companyReducer(UserCompanyState state, dynamic action) { ..vendorState.replace(vendorsReducer(state.vendorState, action)) ..taskState.replace(tasksReducer(state.taskState, action)) // STARTER: reducer - do not remove comment + ..purchaseOrderState + .replace(purchaseOrdersReducer(state.purchaseOrderState, action)) ..recurringExpenseState .replace(recurringExpensesReducer(state.recurringExpenseState, action)) ..subscriptionState diff --git a/lib/redux/company/company_state.dart b/lib/redux/company/company_state.dart index 96c62c407..d0697d0d2 100644 --- a/lib/redux/company/company_state.dart +++ b/lib/redux/company/company_state.dart @@ -32,6 +32,7 @@ import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart'; import 'package:invoiceninja_flutter/redux/webhook/webhook_state.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_state.dart'; part 'company_state.g.dart'; @@ -52,6 +53,8 @@ abstract class UserCompanyState paymentState: PaymentState(), quoteState: QuoteState(), // STARTER: constructor - do not remove comment + purchaseOrderState: PurchaseOrderState(), + recurringExpenseState: RecurringExpenseState(), subscriptionState: SubscriptionState(), taskStatusState: TaskStatusState(), @@ -101,6 +104,8 @@ abstract class UserCompanyState QuoteState get quoteState; // STARTER: fields - do not remove comment + PurchaseOrderState get purchaseOrderState; + RecurringExpenseState get recurringExpenseState; SubscriptionState get subscriptionState; diff --git a/lib/redux/company/company_state.g.dart b/lib/redux/company/company_state.g.dart index f40bbe01a..29c456487 100644 --- a/lib/redux/company/company_state.g.dart +++ b/lib/redux/company/company_state.g.dart @@ -55,6 +55,9 @@ class _$UserCompanyStateSerializer 'quoteState', serializers.serialize(object.quoteState, specifiedType: const FullType(QuoteState)), + 'purchaseOrderState', + serializers.serialize(object.purchaseOrderState, + specifiedType: const FullType(PurchaseOrderState)), 'recurringExpenseState', serializers.serialize(object.recurringExpenseState, specifiedType: const FullType(RecurringExpenseState)), @@ -170,6 +173,11 @@ class _$UserCompanyStateSerializer result.quoteState.replace(serializers.deserialize(value, specifiedType: const FullType(QuoteState)) as QuoteState); break; + case 'purchaseOrderState': + result.purchaseOrderState.replace(serializers.deserialize(value, + specifiedType: const FullType(PurchaseOrderState)) + as PurchaseOrderState); + break; case 'recurringExpenseState': result.recurringExpenseState.replace(serializers.deserialize(value, specifiedType: const FullType(RecurringExpenseState)) @@ -417,6 +425,8 @@ class _$UserCompanyState extends UserCompanyState { @override final QuoteState quoteState; @override + final PurchaseOrderState purchaseOrderState; + @override final RecurringExpenseState recurringExpenseState; @override final SubscriptionState subscriptionState; @@ -462,6 +472,7 @@ class _$UserCompanyState extends UserCompanyState { this.projectState, this.paymentState, this.quoteState, + this.purchaseOrderState, this.recurringExpenseState, this.subscriptionState, this.taskStatusState, @@ -499,6 +510,8 @@ class _$UserCompanyState extends UserCompanyState { paymentState, 'UserCompanyState', 'paymentState'); BuiltValueNullFieldError.checkNotNull( quoteState, 'UserCompanyState', 'quoteState'); + BuiltValueNullFieldError.checkNotNull( + purchaseOrderState, 'UserCompanyState', 'purchaseOrderState'); BuiltValueNullFieldError.checkNotNull( recurringExpenseState, 'UserCompanyState', 'recurringExpenseState'); BuiltValueNullFieldError.checkNotNull( @@ -553,6 +566,7 @@ class _$UserCompanyState extends UserCompanyState { projectState == other.projectState && paymentState == other.paymentState && quoteState == other.quoteState && + purchaseOrderState == other.purchaseOrderState && recurringExpenseState == other.recurringExpenseState && subscriptionState == other.subscriptionState && taskStatusState == other.taskStatusState && @@ -590,12 +604,12 @@ class _$UserCompanyState extends UserCompanyState { $jc( $jc( $jc( - $jc($jc($jc($jc($jc($jc($jc($jc(0, lastUpdated.hashCode), userCompany.hashCode), documentState.hashCode), productState.hashCode), clientState.hashCode), invoiceState.hashCode), expenseState.hashCode), - vendorState.hashCode), - taskState.hashCode), - projectState.hashCode), - paymentState.hashCode), - quoteState.hashCode), + $jc($jc($jc($jc($jc($jc($jc($jc($jc(0, lastUpdated.hashCode), userCompany.hashCode), documentState.hashCode), productState.hashCode), clientState.hashCode), invoiceState.hashCode), expenseState.hashCode), vendorState.hashCode), + taskState.hashCode), + projectState.hashCode), + paymentState.hashCode), + quoteState.hashCode), + purchaseOrderState.hashCode), recurringExpenseState.hashCode), subscriptionState.hashCode), taskStatusState.hashCode), @@ -627,6 +641,7 @@ class _$UserCompanyState extends UserCompanyState { ..add('projectState', projectState) ..add('paymentState', paymentState) ..add('quoteState', quoteState) + ..add('purchaseOrderState', purchaseOrderState) ..add('recurringExpenseState', recurringExpenseState) ..add('subscriptionState', subscriptionState) ..add('taskStatusState', taskStatusState) @@ -718,6 +733,12 @@ class UserCompanyStateBuilder set quoteState(QuoteStateBuilder quoteState) => _$this._quoteState = quoteState; + PurchaseOrderStateBuilder _purchaseOrderState; + PurchaseOrderStateBuilder get purchaseOrderState => + _$this._purchaseOrderState ??= new PurchaseOrderStateBuilder(); + set purchaseOrderState(PurchaseOrderStateBuilder purchaseOrderState) => + _$this._purchaseOrderState = purchaseOrderState; + RecurringExpenseStateBuilder _recurringExpenseState; RecurringExpenseStateBuilder get recurringExpenseState => _$this._recurringExpenseState ??= new RecurringExpenseStateBuilder(); @@ -820,6 +841,7 @@ class UserCompanyStateBuilder _projectState = $v.projectState.toBuilder(); _paymentState = $v.paymentState.toBuilder(); _quoteState = $v.quoteState.toBuilder(); + _purchaseOrderState = $v.purchaseOrderState.toBuilder(); _recurringExpenseState = $v.recurringExpenseState.toBuilder(); _subscriptionState = $v.subscriptionState.toBuilder(); _taskStatusState = $v.taskStatusState.toBuilder(); @@ -869,6 +891,7 @@ class UserCompanyStateBuilder projectState: projectState.build(), paymentState: paymentState.build(), quoteState: quoteState.build(), + purchaseOrderState: purchaseOrderState.build(), recurringExpenseState: recurringExpenseState.build(), subscriptionState: subscriptionState.build(), taskStatusState: taskStatusState.build(), @@ -908,6 +931,8 @@ class UserCompanyStateBuilder paymentState.build(); _$failedField = 'quoteState'; quoteState.build(); + _$failedField = 'purchaseOrderState'; + purchaseOrderState.build(); _$failedField = 'recurringExpenseState'; recurringExpenseState.build(); _$failedField = 'subscriptionState'; diff --git a/lib/redux/purchase_order/purchase_order_actions.dart b/lib/redux/purchase_order/purchase_order_actions.dart new file mode 100644 index 000000000..4359aef10 --- /dev/null +++ b/lib/redux/purchase_order/purchase_order_actions.dart @@ -0,0 +1,323 @@ +import 'dart:async'; +import 'package:built_collection/built_collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/utils/completers.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart'; + +class ViewPurchaseOrderList implements PersistUI { + ViewPurchaseOrderList({this.force = false}); + + final bool force; +} + +class ViewPurchaseOrder implements PersistUI, PersistPrefs { + ViewPurchaseOrder({ + @required this.purchaseOrderId, + this.force = false, + }); + + final String purchaseOrderId; + final bool force; +} + +class EditPurchaseOrder implements PersistUI, PersistPrefs { + EditPurchaseOrder( + {@required this.purchaseOrder, + this.completer, + this.cancelCompleter, + this.force = false}); + + final InvoiceEntity purchaseOrder; + final Completer completer; + final Completer cancelCompleter; + final bool force; +} + +class UpdatePurchaseOrder implements PersistUI { + UpdatePurchaseOrder(this.purchaseOrder); + + final InvoiceEntity purchaseOrder; +} + +class LoadPurchaseOrder { + LoadPurchaseOrder({this.completer, this.purchaseOrderId}); + + final Completer completer; + final String purchaseOrderId; +} + +class LoadPurchaseOrderActivity { + LoadPurchaseOrderActivity({this.completer, this.purchaseOrderId}); + + final Completer completer; + final String purchaseOrderId; +} + +class LoadPurchaseOrders { + LoadPurchaseOrders({this.completer}); + + final Completer completer; +} + +class LoadPurchaseOrderRequest implements StartLoading {} + +class LoadPurchaseOrderFailure implements StopLoading { + LoadPurchaseOrderFailure(this.error); + + final dynamic error; + + @override + String toString() { + return 'LoadPurchaseOrderFailure{error: $error}'; + } +} + +class LoadPurchaseOrderSuccess implements StopLoading, PersistData { + LoadPurchaseOrderSuccess(this.purchaseOrder); + + final InvoiceEntity purchaseOrder; + + @override + String toString() { + return 'LoadPurchaseOrderSuccess{purchaseOrder: $purchaseOrder}'; + } +} + +class LoadPurchaseOrdersRequest implements StartLoading {} + +class LoadPurchaseOrdersFailure implements StopLoading { + LoadPurchaseOrdersFailure(this.error); + + final dynamic error; + + @override + String toString() { + return 'LoadPurchaseOrdersFailure{error: $error}'; + } +} + +class LoadPurchaseOrdersSuccess implements StopLoading { + LoadPurchaseOrdersSuccess(this.purchaseOrders); + + final BuiltList purchaseOrders; + + @override + String toString() { + return 'LoadPurchaseOrdersSuccess{purchaseOrders: $purchaseOrders}'; + } +} + +class SavePurchaseOrderRequest implements StartSaving { + SavePurchaseOrderRequest({this.completer, this.purchaseOrder}); + + final Completer completer; + final InvoiceEntity purchaseOrder; +} + +class SavePurchaseOrderSuccess implements StopSaving, PersistData, PersistUI { + SavePurchaseOrderSuccess(this.purchaseOrder); + + final InvoiceEntity purchaseOrder; +} + +class AddPurchaseOrderSuccess implements StopSaving, PersistData, PersistUI { + AddPurchaseOrderSuccess(this.purchaseOrder); + + final InvoiceEntity purchaseOrder; +} + +class SavePurchaseOrderFailure implements StopSaving { + SavePurchaseOrderFailure(this.error); + + final Object error; +} + +class ArchivePurchaseOrdersRequest implements StartSaving { + ArchivePurchaseOrdersRequest(this.completer, this.purchaseOrderIds); + + final Completer completer; + final List purchaseOrderIds; +} + +class ArchivePurchaseOrdersSuccess implements StopSaving, PersistData { + ArchivePurchaseOrdersSuccess(this.purchaseOrders); + + final List purchaseOrders; +} + +class ArchivePurchaseOrdersFailure implements StopSaving { + ArchivePurchaseOrdersFailure(this.purchaseOrders); + + final List purchaseOrders; +} + +class DeletePurchaseOrdersRequest implements StartSaving { + DeletePurchaseOrdersRequest(this.completer, this.purchaseOrderIds); + + final Completer completer; + final List purchaseOrderIds; +} + +class DeletePurchaseOrdersSuccess implements StopSaving, PersistData { + DeletePurchaseOrdersSuccess(this.purchaseOrders); + + final List purchaseOrders; +} + +class DeletePurchaseOrdersFailure implements StopSaving { + DeletePurchaseOrdersFailure(this.purchaseOrders); + + final List purchaseOrders; +} + +class RestorePurchaseOrdersRequest implements StartSaving { + RestorePurchaseOrdersRequest(this.completer, this.purchaseOrderIds); + + final Completer completer; + final List purchaseOrderIds; +} + +class RestorePurchaseOrdersSuccess implements StopSaving, PersistData { + RestorePurchaseOrdersSuccess(this.purchaseOrders); + + final List purchaseOrders; +} + +class RestorePurchaseOrdersFailure implements StopSaving { + RestorePurchaseOrdersFailure(this.purchaseOrders); + + final List purchaseOrders; +} + +class FilterPurchaseOrders implements PersistUI { + FilterPurchaseOrders(this.filter); + + final String filter; +} + +class SortPurchaseOrders implements PersistUI, PersistPrefs { + SortPurchaseOrders(this.field); + + final String field; +} + +class FilterPurchaseOrdersByState implements PersistUI { + FilterPurchaseOrdersByState(this.state); + + final EntityState state; +} + +class FilterPurchaseOrdersByCustom1 implements PersistUI { + FilterPurchaseOrdersByCustom1(this.value); + + final String value; +} + +class FilterPurchaseOrdersByCustom2 implements PersistUI { + FilterPurchaseOrdersByCustom2(this.value); + + final String value; +} + +class FilterPurchaseOrdersByCustom3 implements PersistUI { + FilterPurchaseOrdersByCustom3(this.value); + + final String value; +} + +class FilterPurchaseOrdersByCustom4 implements PersistUI { + FilterPurchaseOrdersByCustom4(this.value); + + final String value; +} + +class StartPurchaseOrderMultiselect { + StartPurchaseOrderMultiselect(); +} + +class AddToPurchaseOrderMultiselect { + AddToPurchaseOrderMultiselect({@required this.entity}); + + final BaseEntity entity; +} + +class RemoveFromPurchaseOrderMultiselect { + RemoveFromPurchaseOrderMultiselect({@required this.entity}); + + final BaseEntity entity; +} + +class ClearPurchaseOrderMultiselect { + ClearPurchaseOrderMultiselect(); +} + +class UpdatePurchaseOrderTab implements PersistUI { + UpdatePurchaseOrderTab({this.tabIndex}); + + final int tabIndex; +} + +void handlePurchaseOrderAction(BuildContext context, + List purchaseOrders, EntityAction action) { + if (purchaseOrders.isEmpty) { + return; + } + + final store = StoreProvider.of(context); + final localization = AppLocalization.of(context); + final purchaseOrder = purchaseOrders.first as InvoiceEntity; + final purchaseOrderIds = + purchaseOrders.map((purchaseOrder) => purchaseOrder.id).toList(); + + switch (action) { + case EntityAction.edit: + editEntity(entity: purchaseOrder); + break; + case EntityAction.restore: + store.dispatch(RestorePurchaseOrdersRequest( + snackBarCompleter(context, localization.restoredPurchaseOrder), + purchaseOrderIds)); + break; + case EntityAction.archive: + store.dispatch(ArchivePurchaseOrdersRequest( + snackBarCompleter(context, localization.archivedPurchaseOrder), + purchaseOrderIds)); + break; + case EntityAction.delete: + store.dispatch(DeletePurchaseOrdersRequest( + snackBarCompleter(context, localization.deletedPurchaseOrder), + purchaseOrderIds)); + break; + case EntityAction.toggleMultiselect: + if (!store.state.purchaseOrderListState.isInMultiselect()) { + store.dispatch(StartPurchaseOrderMultiselect()); + } + + if (purchaseOrders.isEmpty) { + break; + } + + for (final purchaseOrder in purchaseOrders) { + if (!store.state.purchaseOrderListState.isSelected(purchaseOrder.id)) { + store.dispatch(AddToPurchaseOrderMultiselect(entity: purchaseOrder)); + } else { + store.dispatch( + RemoveFromPurchaseOrderMultiselect(entity: purchaseOrder)); + } + } + break; + case EntityAction.more: + showEntityActionsDialog( + entities: [purchaseOrder], + ); + break; + default: + print('## ERROR: unhandled action $action in purchase_order_actions'); + break; + } +} diff --git a/lib/redux/purchase_order/purchase_order_middleware.dart b/lib/redux/purchase_order/purchase_order_middleware.dart new file mode 100644 index 000000000..2dd82cb5a --- /dev/null +++ b/lib/redux/purchase_order/purchase_order_middleware.dart @@ -0,0 +1,245 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:redux/redux.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_screen.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/edit/purchase_order_edit_vm.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/view/purchase_order_view_vm.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/data/repositories/purchase_order_repository.dart'; + +List> createStorePurchaseOrdersMiddleware([ + PurchaseOrderRepository repository = const PurchaseOrderRepository(), +]) { + final viewPurchaseOrderList = _viewPurchaseOrderList(); + final viewPurchaseOrder = _viewPurchaseOrder(); + final editPurchaseOrder = _editPurchaseOrder(); + final loadPurchaseOrders = _loadPurchaseOrders(repository); + final loadPurchaseOrder = _loadPurchaseOrder(repository); + final savePurchaseOrder = _savePurchaseOrder(repository); + final archivePurchaseOrder = _archivePurchaseOrder(repository); + final deletePurchaseOrder = _deletePurchaseOrder(repository); + final restorePurchaseOrder = _restorePurchaseOrder(repository); + + return [ + TypedMiddleware(viewPurchaseOrderList), + TypedMiddleware(viewPurchaseOrder), + TypedMiddleware(editPurchaseOrder), + TypedMiddleware(loadPurchaseOrders), + TypedMiddleware(loadPurchaseOrder), + TypedMiddleware(savePurchaseOrder), + TypedMiddleware( + archivePurchaseOrder), + TypedMiddleware(deletePurchaseOrder), + TypedMiddleware( + restorePurchaseOrder), + ]; +} + +Middleware _editPurchaseOrder() { + return (Store store, dynamic dynamicAction, NextDispatcher next) { + final action = dynamicAction as EditPurchaseOrder; + + next(action); + + store.dispatch(UpdateCurrentRoute(PurchaseOrderEditScreen.route)); + + if (store.state.prefState.isMobile) { + navigatorKey.currentState.pushNamed(PurchaseOrderEditScreen.route); + } + }; +} + +Middleware _viewPurchaseOrder() { + return (Store store, dynamic dynamicAction, + NextDispatcher next) async { + final action = dynamicAction as ViewPurchaseOrder; + + next(action); + + store.dispatch(UpdateCurrentRoute(PurchaseOrderViewScreen.route)); + + if (store.state.prefState.isMobile) { + navigatorKey.currentState.pushNamed(PurchaseOrderViewScreen.route); + } + }; +} + +Middleware _viewPurchaseOrderList() { + return (Store store, dynamic dynamicAction, NextDispatcher next) { + final action = dynamicAction as ViewPurchaseOrderList; + + next(action); + + if (store.state.staticState.isStale) { + store.dispatch(RefreshData()); + } + + store.dispatch(UpdateCurrentRoute(PurchaseOrderScreen.route)); + + if (store.state.prefState.isMobile) { + navigatorKey.currentState.pushNamedAndRemoveUntil( + PurchaseOrderScreen.route, (Route route) => false); + } + }; +} + +Middleware _archivePurchaseOrder(PurchaseOrderRepository repository) { + return (Store store, dynamic dynamicAction, NextDispatcher next) { + final action = dynamicAction as ArchivePurchaseOrdersRequest; + final prevPurchaseOrders = action.purchaseOrderIds + .map((id) => store.state.purchaseOrderState.map[id]) + .toList(); + repository + .bulkAction(store.state.credentials, action.purchaseOrderIds, + EntityAction.archive) + .then((List purchaseOrders) { + store.dispatch(ArchivePurchaseOrdersSuccess(purchaseOrders)); + if (action.completer != null) { + action.completer.complete(null); + } + }).catchError((Object error) { + print(error); + store.dispatch(ArchivePurchaseOrdersFailure(prevPurchaseOrders)); + if (action.completer != null) { + action.completer.completeError(error); + } + }); + + next(action); + }; +} + +Middleware _deletePurchaseOrder(PurchaseOrderRepository repository) { + return (Store store, dynamic dynamicAction, NextDispatcher next) { + final action = dynamicAction as DeletePurchaseOrdersRequest; + final prevPurchaseOrders = action.purchaseOrderIds + .map((id) => store.state.purchaseOrderState.map[id]) + .toList(); + repository + .bulkAction(store.state.credentials, action.purchaseOrderIds, + EntityAction.delete) + .then((List purchaseOrders) { + store.dispatch(DeletePurchaseOrdersSuccess(purchaseOrders)); + if (action.completer != null) { + action.completer.complete(null); + } + }).catchError((Object error) { + print(error); + store.dispatch(DeletePurchaseOrdersFailure(prevPurchaseOrders)); + if (action.completer != null) { + action.completer.completeError(error); + } + }); + + next(action); + }; +} + +Middleware _restorePurchaseOrder(PurchaseOrderRepository repository) { + return (Store store, dynamic dynamicAction, NextDispatcher next) { + final action = dynamicAction as RestorePurchaseOrdersRequest; + final prevPurchaseOrders = action.purchaseOrderIds + .map((id) => store.state.purchaseOrderState.map[id]) + .toList(); + repository + .bulkAction(store.state.credentials, action.purchaseOrderIds, + EntityAction.restore) + .then((List purchaseOrders) { + store.dispatch(RestorePurchaseOrdersSuccess(purchaseOrders)); + if (action.completer != null) { + action.completer.complete(null); + } + }).catchError((Object error) { + print(error); + store.dispatch(RestorePurchaseOrdersFailure(prevPurchaseOrders)); + if (action.completer != null) { + action.completer.completeError(error); + } + }); + + next(action); + }; +} + +Middleware _savePurchaseOrder(PurchaseOrderRepository repository) { + return (Store store, dynamic dynamicAction, NextDispatcher next) { + final action = dynamicAction as SavePurchaseOrderRequest; + repository + .saveData(store.state.credentials, action.purchaseOrder) + .then((InvoiceEntity purchaseOrder) { + if (action.purchaseOrder.isNew) { + store.dispatch(AddPurchaseOrderSuccess(purchaseOrder)); + } else { + store.dispatch(SavePurchaseOrderSuccess(purchaseOrder)); + } + + action.completer.complete(purchaseOrder); + }).catchError((Object error) { + print(error); + store.dispatch(SavePurchaseOrderFailure(error)); + action.completer.completeError(error); + }); + + next(action); + }; +} + +Middleware _loadPurchaseOrder(PurchaseOrderRepository repository) { + return (Store store, dynamic dynamicAction, NextDispatcher next) { + final action = dynamicAction as LoadPurchaseOrder; + final AppState state = store.state; + + store.dispatch(LoadPurchaseOrderRequest()); + repository + .loadItem(state.credentials, action.purchaseOrderId) + .then((purchaseOrder) { + store.dispatch(LoadPurchaseOrderSuccess(purchaseOrder)); + + if (action.completer != null) { + action.completer.complete(null); + } + }).catchError((Object error) { + print(error); + store.dispatch(LoadPurchaseOrderFailure(error)); + if (action.completer != null) { + action.completer.completeError(error); + } + }); + + next(action); + }; +} + +Middleware _loadPurchaseOrders(PurchaseOrderRepository repository) { + return (Store store, dynamic dynamicAction, NextDispatcher next) { + final action = dynamicAction as LoadPurchaseOrders; + final AppState state = store.state; + + store.dispatch(LoadPurchaseOrdersRequest()); + repository.loadList(state.credentials).then((data) { + store.dispatch(LoadPurchaseOrdersSuccess(data)); + + if (action.completer != null) { + action.completer.complete(null); + } + /* + if (state.productState.isStale) { + store.dispatch(LoadProducts()); + } + */ + }).catchError((Object error) { + print(error); + store.dispatch(LoadPurchaseOrdersFailure(error)); + if (action.completer != null) { + action.completer.completeError(error); + } + }); + + next(action); + }; +} diff --git a/lib/redux/purchase_order/purchase_order_reducer.dart b/lib/redux/purchase_order/purchase_order_reducer.dart new file mode 100644 index 000000000..347ffdedc --- /dev/null +++ b/lib/redux/purchase_order/purchase_order_reducer.dart @@ -0,0 +1,280 @@ +import 'package:redux/redux.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/company/company_actions.dart'; +import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; +import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_state.dart'; + +EntityUIState purchaseOrderUIReducer( + PurchaseOrderUIState state, dynamic action) { + return state.rebuild((b) => b + ..listUIState.replace(purchaseOrderListReducer(state.listUIState, action)) + ..editing.replace(editingReducer(state.editing, action)) + ..selectedId = selectedIdReducer(state.selectedId, action) + ..forceSelected = forceSelectedReducer(state.forceSelected, action) + ..tabIndex = tabIndexReducer(state.tabIndex, action)); +} + +final forceSelectedReducer = combineReducers([ + TypedReducer((completer, action) => true), + TypedReducer((completer, action) => false), + TypedReducer((completer, action) => false), + TypedReducer((completer, action) => false), + TypedReducer( + (completer, action) => false), + TypedReducer( + (completer, action) => false), + TypedReducer( + (completer, action) => false), + TypedReducer( + (completer, action) => false), +]); + +final tabIndexReducer = combineReducers([ + TypedReducer((completer, action) { + return action.tabIndex; + }), + TypedReducer((completer, action) { + return 0; + }), +]); + +Reducer selectedIdReducer = combineReducers([ + TypedReducer((completer, action) => ''), + TypedReducer((completer, action) => ''), + TypedReducer((selectedId, action) => + action.entityType == EntityType.purchaseOrder + ? action.entityId + : selectedId), + TypedReducer( + (String selectedId, dynamic action) => action.purchaseOrderId), + TypedReducer( + (String selectedId, dynamic action) => action.purchaseOrder.id), + TypedReducer( + (selectedId, action) => action.clearSelection ? '' : selectedId), + TypedReducer((selectedId, action) => ''), + TypedReducer((selectedId, action) => ''), + TypedReducer((selectedId, action) => ''), + TypedReducer((selectedId, action) => ''), + TypedReducer( + (selectedId, action) => ''), + TypedReducer( + (selectedId, action) => ''), + TypedReducer( + (selectedId, action) => ''), + TypedReducer( + (selectedId, action) => ''), + TypedReducer( + (selectedId, action) => action.clearSelection + ? '' + : action.entityType == EntityType.purchaseOrder + ? action.entityId + : selectedId), +]); + +final editingReducer = combineReducers([ + TypedReducer(_updateEditing), + TypedReducer(_updateEditing), + TypedReducer( + (purchaseOrders, action) { + return action.purchaseOrders[0]; + }), + TypedReducer( + (purchaseOrders, action) { + return action.purchaseOrders[0]; + }), + TypedReducer( + (purchaseOrders, action) { + return action.purchaseOrders[0]; + }), + TypedReducer(_updateEditing), + TypedReducer((purchaseOrder, action) { + return action.purchaseOrder.rebuild((b) => b..isChanged = true); + }), + TypedReducer(_clearEditing), +]); + +InvoiceEntity _clearEditing(InvoiceEntity purchaseOrder, dynamic action) { + return InvoiceEntity(); +} + +InvoiceEntity _updateEditing(InvoiceEntity purchaseOrder, dynamic action) { + return action.purchaseOrder; +} + +final purchaseOrderListReducer = combineReducers([ + TypedReducer(_sortPurchaseOrders), + TypedReducer( + _filterPurchaseOrdersByState), + TypedReducer(_filterPurchaseOrders), + TypedReducer( + _filterPurchaseOrdersByCustom1), + TypedReducer( + _filterPurchaseOrdersByCustom2), + TypedReducer( + _startListMultiselect), + TypedReducer( + _addToListMultiselect), + TypedReducer( + _removeFromListMultiselect), + TypedReducer( + _clearListMultiselect), + TypedReducer(_viewPurchaseOrderList), +]); + +ListUIState _viewPurchaseOrderList( + ListUIState purchaseOrderListState, ViewPurchaseOrderList action) { + return purchaseOrderListState.rebuild((b) => b + ..selectedIds = null + ..filter = null + ..filterClearedAt = DateTime.now().millisecondsSinceEpoch); +} + +ListUIState _filterPurchaseOrdersByCustom1( + ListUIState purchaseOrderListState, FilterPurchaseOrdersByCustom1 action) { + if (purchaseOrderListState.custom1Filters.contains(action.value)) { + return purchaseOrderListState + .rebuild((b) => b..custom1Filters.remove(action.value)); + } else { + return purchaseOrderListState + .rebuild((b) => b..custom1Filters.add(action.value)); + } +} + +ListUIState _filterPurchaseOrdersByCustom2( + ListUIState purchaseOrderListState, FilterPurchaseOrdersByCustom2 action) { + if (purchaseOrderListState.custom2Filters.contains(action.value)) { + return purchaseOrderListState + .rebuild((b) => b..custom2Filters.remove(action.value)); + } else { + return purchaseOrderListState + .rebuild((b) => b..custom2Filters.add(action.value)); + } +} + +ListUIState _filterPurchaseOrdersByState( + ListUIState purchaseOrderListState, FilterPurchaseOrdersByState action) { + if (purchaseOrderListState.stateFilters.contains(action.state)) { + return purchaseOrderListState + .rebuild((b) => b..stateFilters.remove(action.state)); + } else { + return purchaseOrderListState + .rebuild((b) => b..stateFilters.add(action.state)); + } +} + +ListUIState _filterPurchaseOrders( + ListUIState purchaseOrderListState, FilterPurchaseOrders action) { + return purchaseOrderListState.rebuild((b) => b + ..filter = action.filter + ..filterClearedAt = action.filter == null + ? DateTime.now().millisecondsSinceEpoch + : purchaseOrderListState.filterClearedAt); +} + +ListUIState _sortPurchaseOrders( + ListUIState purchaseOrderListState, SortPurchaseOrders action) { + return purchaseOrderListState.rebuild((b) => b + ..sortAscending = b.sortField != action.field || !b.sortAscending + ..sortField = action.field); +} + +ListUIState _startListMultiselect( + ListUIState productListState, StartPurchaseOrderMultiselect action) { + return productListState.rebuild((b) => b..selectedIds = ListBuilder()); +} + +ListUIState _addToListMultiselect( + ListUIState productListState, AddToPurchaseOrderMultiselect action) { + return productListState.rebuild((b) => b..selectedIds.add(action.entity.id)); +} + +ListUIState _removeFromListMultiselect( + ListUIState productListState, RemoveFromPurchaseOrderMultiselect action) { + return productListState + .rebuild((b) => b..selectedIds.remove(action.entity.id)); +} + +ListUIState _clearListMultiselect( + ListUIState productListState, ClearPurchaseOrderMultiselect action) { + return productListState.rebuild((b) => b..selectedIds = null); +} + +final purchaseOrdersReducer = combineReducers([ + TypedReducer( + _updatePurchaseOrder), + TypedReducer(_addPurchaseOrder), + TypedReducer( + _setLoadedPurchaseOrders), + TypedReducer( + _setLoadedPurchaseOrder), + TypedReducer(_setLoadedCompany), + TypedReducer( + _archivePurchaseOrderSuccess), + TypedReducer( + _deletePurchaseOrderSuccess), + TypedReducer( + _restorePurchaseOrderSuccess), +]); + +PurchaseOrderState _archivePurchaseOrderSuccess( + PurchaseOrderState purchaseOrderState, + ArchivePurchaseOrdersSuccess action) { + return purchaseOrderState.rebuild((b) { + for (final purchaseOrder in action.purchaseOrders) { + b.map[purchaseOrder.id] = purchaseOrder; + } + }); +} + +PurchaseOrderState _deletePurchaseOrderSuccess( + PurchaseOrderState purchaseOrderState, DeletePurchaseOrdersSuccess action) { + return purchaseOrderState.rebuild((b) { + for (final purchaseOrder in action.purchaseOrders) { + b.map[purchaseOrder.id] = purchaseOrder; + } + }); +} + +PurchaseOrderState _restorePurchaseOrderSuccess( + PurchaseOrderState purchaseOrderState, + RestorePurchaseOrdersSuccess action) { + return purchaseOrderState.rebuild((b) { + for (final purchaseOrder in action.purchaseOrders) { + b.map[purchaseOrder.id] = purchaseOrder; + } + }); +} + +PurchaseOrderState _addPurchaseOrder( + PurchaseOrderState purchaseOrderState, AddPurchaseOrderSuccess action) { + return purchaseOrderState.rebuild((b) => b + ..map[action.purchaseOrder.id] = action.purchaseOrder + ..list.add(action.purchaseOrder.id)); +} + +PurchaseOrderState _updatePurchaseOrder( + PurchaseOrderState purchaseOrderState, SavePurchaseOrderSuccess action) { + return purchaseOrderState + .rebuild((b) => b..map[action.purchaseOrder.id] = action.purchaseOrder); +} + +PurchaseOrderState _setLoadedPurchaseOrder( + PurchaseOrderState purchaseOrderState, LoadPurchaseOrderSuccess action) { + return purchaseOrderState + .rebuild((b) => b..map[action.purchaseOrder.id] = action.purchaseOrder); +} + +PurchaseOrderState _setLoadedPurchaseOrders( + PurchaseOrderState purchaseOrderState, + LoadPurchaseOrdersSuccess action) => + purchaseOrderState.loadPurchaseOrders(action.purchaseOrders); + +PurchaseOrderState _setLoadedCompany( + PurchaseOrderState purchaseOrderState, LoadCompanySuccess action) { + final company = action.userCompany.company; + return purchaseOrderState.loadPurchaseOrders(company.purchaseOrders); +} diff --git a/lib/redux/purchase_order/purchase_order_selectors.dart b/lib/redux/purchase_order/purchase_order_selectors.dart new file mode 100644 index 000000000..b45966b02 --- /dev/null +++ b/lib/redux/purchase_order/purchase_order_selectors.dart @@ -0,0 +1,155 @@ +import 'package:invoiceninja_flutter/data/models/purchase_order_model.dart'; +import 'package:invoiceninja_flutter/redux/static/static_state.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:memoize/memoize.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart'; + +var memoizedDropdownPurchaseOrderList = memo6( + (BuiltMap purchaseOrderMap, + BuiltList purchaseOrderList, + StaticState staticState, + BuiltMap userMap, + BuiltMap clientMap, + String clientId) => + dropdownPurchaseOrdersSelector(purchaseOrderMap, purchaseOrderList, + staticState, userMap, clientMap, clientId)); + +List dropdownPurchaseOrdersSelector( + BuiltMap purchaseOrderMap, + BuiltList purchaseOrderList, + StaticState staticState, + BuiltMap userMap, + BuiltMap clientMap, + String clientId) { + final list = purchaseOrderList.where((purchaseOrderId) { + final purchaseOrder = purchaseOrderMap[purchaseOrderId]; + /* + if (clientId != null && clientId > 0 && purchaseOrder.clientId != clientId) { + return false; + } + */ + return purchaseOrder.isActive; + }).toList(); + + list.sort((purchaseOrderAId, purchaseOrderBId) { + final purchaseOrderA = purchaseOrderMap[purchaseOrderAId]; + final purchaseOrderB = purchaseOrderMap[purchaseOrderBId]; + return purchaseOrderA.compareTo( + invoice: purchaseOrderB, + sortAscending: false, + sortField: PurchaseOrderFields.number, + userMap: userMap, + clientMap: clientMap, + ); + }); + + return list; +} + +var memoizedFilteredPurchaseOrderList = memo6(( + SelectionState selectionState, + BuiltMap invoiceMap, + BuiltList invoiceList, + BuiltMap clientMap, + ListUIState invoiceListState, + BuiltMap userMap, +) => + filteredPurchaseOrdersSelector(selectionState, invoiceMap, invoiceList, + clientMap, invoiceListState, userMap)); + +List filteredPurchaseOrdersSelector( + SelectionState selectionState, + BuiltMap invoiceMap, + BuiltList invoiceList, + BuiltMap clientMap, + ListUIState invoiceListState, + BuiltMap userMap, +) { + final filterEntityId = selectionState.filterEntityId; + final filterEntityType = selectionState.filterEntityType; + + final list = invoiceList.where((invoiceId) { + final invoice = invoiceMap[invoiceId]; + final client = + clientMap[invoice.clientId] ?? ClientEntity(id: invoice.clientId); + + if (invoice.id == selectionState.selectedId) { + return true; + } + + if (!client.isActive && + !client.matchesEntityFilter(filterEntityType, filterEntityId)) { + return false; + } + + if (filterEntityType == EntityType.client && client.id != filterEntityId) { + return false; + } else if (filterEntityType == EntityType.user && + invoice.assignedUserId != filterEntityId) { + return false; + } else if (filterEntityType == EntityType.recurringInvoice && + invoice.recurringId != filterEntityId) { + return false; + } else if (filterEntityType == EntityType.subscription && + invoice.subscriptionId != filterEntityId) { + return false; + } else if (filterEntityType == EntityType.design && + invoice.designId != filterEntityId) { + return false; + } else if (filterEntityType == EntityType.group && + client.groupId != filterEntityId) { + return false; + } else if (filterEntityType == EntityType.project && + invoice.projectId != filterEntityId) { + return false; + } else if (filterEntityType == EntityType.quote && + invoice.invoiceId != filterEntityId) { + return false; + } + + if (!invoice.matchesStates(invoiceListState.stateFilters)) { + return false; + } + if (!invoice.matchesStatuses(invoiceListState.statusFilters)) { + return false; + } + if (!invoice.matchesFilter(invoiceListState.filter) && + !client.matchesNameOrEmail(invoiceListState.filter)) { + return false; + } + if (invoiceListState.custom1Filters.isNotEmpty && + !invoiceListState.custom1Filters.contains(invoice.customValue1)) { + return false; + } else if (invoiceListState.custom2Filters.isNotEmpty && + !invoiceListState.custom2Filters.contains(invoice.customValue2)) { + return false; + } else if (invoiceListState.custom3Filters.isNotEmpty && + !invoiceListState.custom3Filters.contains(invoice.customValue3)) { + return false; + } else if (invoiceListState.custom4Filters.isNotEmpty && + !invoiceListState.custom4Filters.contains(invoice.customValue4)) { + return false; + } + return true; + }).toList(); + + list.sort((invoiceAId, invoiceBId) { + return invoiceMap[invoiceAId].compareTo( + invoice: invoiceMap[invoiceBId], + sortField: invoiceListState.sortField, + sortAscending: invoiceListState.sortAscending, + clientMap: clientMap, + userMap: userMap, + ); + }); + + return list; +} + +bool hasPurchaseOrderChanges(InvoiceEntity purchaseOrder, + BuiltMap purchaseOrderMap) => + purchaseOrder.isNew + ? purchaseOrder.isChanged + : purchaseOrder != purchaseOrderMap[purchaseOrder.id]; diff --git a/lib/redux/purchase_order/purchase_order_state.dart b/lib/redux/purchase_order/purchase_order_state.dart new file mode 100644 index 000000000..42785b2b5 --- /dev/null +++ b/lib/redux/purchase_order/purchase_order_state.dart @@ -0,0 +1,83 @@ +import 'dart:async'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:invoiceninja_flutter/data/models/purchase_order_model.dart'; +import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart'; +import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/ui/pref_state.dart'; + +part 'purchase_order_state.g.dart'; + +abstract class PurchaseOrderState + implements Built { + factory PurchaseOrderState() { + return _$PurchaseOrderState._( + map: BuiltMap(), + list: BuiltList(), + ); + } + PurchaseOrderState._(); + + @override + @memoized + int get hashCode; + + BuiltMap get map; + BuiltList get list; + + InvoiceEntity get(String purchaseOrderId) { + if (map.containsKey(purchaseOrderId)) { + return map[purchaseOrderId]; + } else { + return InvoiceEntity(id: purchaseOrderId); + } + } + + PurchaseOrderState loadPurchaseOrders(BuiltList clients) { + final map = Map.fromIterable( + clients, + key: (dynamic item) => item.id, + value: (dynamic item) => item, + ); + + return rebuild((b) => b + ..map.addAll(map) + ..list.replace((map.keys.toList() + list.toList()).toSet().toList())); + } + + static Serializer get serializer => + _$purchaseOrderStateSerializer; +} + +abstract class PurchaseOrderUIState extends Object + with EntityUIState + implements Built { + factory PurchaseOrderUIState(PrefStateSortField sortField) { + return _$PurchaseOrderUIState._( + listUIState: ListUIState(sortField?.field ?? PurchaseOrderFields.number, + sortAscending: sortField?.ascending), + editing: InvoiceEntity(), + selectedId: '', + tabIndex: 0, + ); + } + PurchaseOrderUIState._(); + + @override + @memoized + int get hashCode; + + @nullable + InvoiceEntity get editing; + + @override + bool get isCreatingNew => editing.isNew; + + @override + String get editingId => editing.id; + + static Serializer get serializer => + _$purchaseOrderUIStateSerializer; +} diff --git a/lib/redux/purchase_order/purchase_order_state.g.dart b/lib/redux/purchase_order/purchase_order_state.g.dart new file mode 100644 index 000000000..c968647a3 --- /dev/null +++ b/lib/redux/purchase_order/purchase_order_state.g.dart @@ -0,0 +1,447 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'purchase_order_state.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$purchaseOrderStateSerializer = + new _$PurchaseOrderStateSerializer(); +Serializer _$purchaseOrderUIStateSerializer = + new _$PurchaseOrderUIStateSerializer(); + +class _$PurchaseOrderStateSerializer + implements StructuredSerializer { + @override + final Iterable types = const [PurchaseOrderState, _$PurchaseOrderState]; + @override + final String wireName = 'PurchaseOrderState'; + + @override + Iterable serialize(Serializers serializers, PurchaseOrderState object, + {FullType specifiedType = FullType.unspecified}) { + final result = [ + 'map', + serializers.serialize(object.map, + specifiedType: const FullType(BuiltMap, + const [const FullType(String), const FullType(InvoiceEntity)])), + 'list', + serializers.serialize(object.list, + specifiedType: + const FullType(BuiltList, const [const FullType(String)])), + ]; + + return result; + } + + @override + PurchaseOrderState deserialize( + Serializers serializers, Iterable serialized, + {FullType specifiedType = FullType.unspecified}) { + final result = new PurchaseOrderStateBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current as String; + iterator.moveNext(); + final Object value = iterator.current; + switch (key) { + case 'map': + result.map.replace(serializers.deserialize(value, + specifiedType: const FullType(BuiltMap, const [ + const FullType(String), + const FullType(InvoiceEntity) + ]))); + break; + case 'list': + result.list.replace(serializers.deserialize(value, + specifiedType: + const FullType(BuiltList, const [const FullType(String)])) + as BuiltList); + break; + } + } + + return result.build(); + } +} + +class _$PurchaseOrderUIStateSerializer + implements StructuredSerializer { + @override + final Iterable types = const [ + PurchaseOrderUIState, + _$PurchaseOrderUIState + ]; + @override + final String wireName = 'PurchaseOrderUIState'; + + @override + Iterable serialize( + Serializers serializers, PurchaseOrderUIState object, + {FullType specifiedType = FullType.unspecified}) { + final result = [ + 'listUIState', + serializers.serialize(object.listUIState, + specifiedType: const FullType(ListUIState)), + 'tabIndex', + serializers.serialize(object.tabIndex, + specifiedType: const FullType(int)), + ]; + Object value; + value = object.editing; + if (value != null) { + result + ..add('editing') + ..add(serializers.serialize(value, + specifiedType: const FullType(InvoiceEntity))); + } + value = object.selectedId; + if (value != null) { + result + ..add('selectedId') + ..add(serializers.serialize(value, + specifiedType: const FullType(String))); + } + value = object.forceSelected; + if (value != null) { + result + ..add('forceSelected') + ..add( + serializers.serialize(value, specifiedType: const FullType(bool))); + } + return result; + } + + @override + PurchaseOrderUIState deserialize( + Serializers serializers, Iterable serialized, + {FullType specifiedType = FullType.unspecified}) { + final result = new PurchaseOrderUIStateBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current as String; + iterator.moveNext(); + final Object value = iterator.current; + switch (key) { + case 'editing': + result.editing.replace(serializers.deserialize(value, + specifiedType: const FullType(InvoiceEntity)) as InvoiceEntity); + break; + case 'listUIState': + result.listUIState.replace(serializers.deserialize(value, + specifiedType: const FullType(ListUIState)) as ListUIState); + break; + case 'selectedId': + result.selectedId = serializers.deserialize(value, + specifiedType: const FullType(String)) as String; + break; + case 'forceSelected': + result.forceSelected = serializers.deserialize(value, + specifiedType: const FullType(bool)) as bool; + break; + case 'tabIndex': + result.tabIndex = serializers.deserialize(value, + specifiedType: const FullType(int)) as int; + break; + } + } + + return result.build(); + } +} + +class _$PurchaseOrderState extends PurchaseOrderState { + @override + final BuiltMap map; + @override + final BuiltList list; + + factory _$PurchaseOrderState( + [void Function(PurchaseOrderStateBuilder) updates]) => + (new PurchaseOrderStateBuilder()..update(updates)).build(); + + _$PurchaseOrderState._({this.map, this.list}) : super._() { + BuiltValueNullFieldError.checkNotNull(map, 'PurchaseOrderState', 'map'); + BuiltValueNullFieldError.checkNotNull(list, 'PurchaseOrderState', 'list'); + } + + @override + PurchaseOrderState rebuild( + void Function(PurchaseOrderStateBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + PurchaseOrderStateBuilder toBuilder() => + new PurchaseOrderStateBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is PurchaseOrderState && + map == other.map && + list == other.list; + } + + int __hashCode; + @override + int get hashCode { + return __hashCode ??= $jf($jc($jc(0, map.hashCode), list.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('PurchaseOrderState') + ..add('map', map) + ..add('list', list)) + .toString(); + } +} + +class PurchaseOrderStateBuilder + implements Builder { + _$PurchaseOrderState _$v; + + MapBuilder _map; + MapBuilder get map => + _$this._map ??= new MapBuilder(); + set map(MapBuilder map) => _$this._map = map; + + ListBuilder _list; + ListBuilder get list => _$this._list ??= new ListBuilder(); + set list(ListBuilder list) => _$this._list = list; + + PurchaseOrderStateBuilder(); + + PurchaseOrderStateBuilder get _$this { + final $v = _$v; + if ($v != null) { + _map = $v.map.toBuilder(); + _list = $v.list.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(PurchaseOrderState other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$PurchaseOrderState; + } + + @override + void update(void Function(PurchaseOrderStateBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$PurchaseOrderState build() { + _$PurchaseOrderState _$result; + try { + _$result = _$v ?? + new _$PurchaseOrderState._(map: map.build(), list: list.build()); + } catch (_) { + String _$failedField; + try { + _$failedField = 'map'; + map.build(); + _$failedField = 'list'; + list.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + 'PurchaseOrderState', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$PurchaseOrderUIState extends PurchaseOrderUIState { + @override + final InvoiceEntity editing; + @override + final ListUIState listUIState; + @override + final String selectedId; + @override + final bool forceSelected; + @override + final int tabIndex; + @override + final Completer saveCompleter; + @override + final Completer cancelCompleter; + + factory _$PurchaseOrderUIState( + [void Function(PurchaseOrderUIStateBuilder) updates]) => + (new PurchaseOrderUIStateBuilder()..update(updates)).build(); + + _$PurchaseOrderUIState._( + {this.editing, + this.listUIState, + this.selectedId, + this.forceSelected, + this.tabIndex, + this.saveCompleter, + this.cancelCompleter}) + : super._() { + BuiltValueNullFieldError.checkNotNull( + listUIState, 'PurchaseOrderUIState', 'listUIState'); + BuiltValueNullFieldError.checkNotNull( + tabIndex, 'PurchaseOrderUIState', 'tabIndex'); + } + + @override + PurchaseOrderUIState rebuild( + void Function(PurchaseOrderUIStateBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + PurchaseOrderUIStateBuilder toBuilder() => + new PurchaseOrderUIStateBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is PurchaseOrderUIState && + editing == other.editing && + listUIState == other.listUIState && + selectedId == other.selectedId && + forceSelected == other.forceSelected && + tabIndex == other.tabIndex && + saveCompleter == other.saveCompleter && + cancelCompleter == other.cancelCompleter; + } + + int __hashCode; + @override + int get hashCode { + return __hashCode ??= $jf($jc( + $jc( + $jc( + $jc( + $jc($jc($jc(0, editing.hashCode), listUIState.hashCode), + selectedId.hashCode), + forceSelected.hashCode), + tabIndex.hashCode), + saveCompleter.hashCode), + cancelCompleter.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('PurchaseOrderUIState') + ..add('editing', editing) + ..add('listUIState', listUIState) + ..add('selectedId', selectedId) + ..add('forceSelected', forceSelected) + ..add('tabIndex', tabIndex) + ..add('saveCompleter', saveCompleter) + ..add('cancelCompleter', cancelCompleter)) + .toString(); + } +} + +class PurchaseOrderUIStateBuilder + implements Builder { + _$PurchaseOrderUIState _$v; + + InvoiceEntityBuilder _editing; + InvoiceEntityBuilder get editing => + _$this._editing ??= new InvoiceEntityBuilder(); + set editing(InvoiceEntityBuilder editing) => _$this._editing = editing; + + ListUIStateBuilder _listUIState; + ListUIStateBuilder get listUIState => + _$this._listUIState ??= new ListUIStateBuilder(); + set listUIState(ListUIStateBuilder listUIState) => + _$this._listUIState = listUIState; + + String _selectedId; + String get selectedId => _$this._selectedId; + set selectedId(String selectedId) => _$this._selectedId = selectedId; + + bool _forceSelected; + bool get forceSelected => _$this._forceSelected; + set forceSelected(bool forceSelected) => + _$this._forceSelected = forceSelected; + + int _tabIndex; + int get tabIndex => _$this._tabIndex; + set tabIndex(int tabIndex) => _$this._tabIndex = tabIndex; + + Completer _saveCompleter; + Completer get saveCompleter => _$this._saveCompleter; + set saveCompleter(Completer saveCompleter) => + _$this._saveCompleter = saveCompleter; + + Completer _cancelCompleter; + Completer get cancelCompleter => _$this._cancelCompleter; + set cancelCompleter(Completer cancelCompleter) => + _$this._cancelCompleter = cancelCompleter; + + PurchaseOrderUIStateBuilder(); + + PurchaseOrderUIStateBuilder get _$this { + final $v = _$v; + if ($v != null) { + _editing = $v.editing?.toBuilder(); + _listUIState = $v.listUIState.toBuilder(); + _selectedId = $v.selectedId; + _forceSelected = $v.forceSelected; + _tabIndex = $v.tabIndex; + _saveCompleter = $v.saveCompleter; + _cancelCompleter = $v.cancelCompleter; + _$v = null; + } + return this; + } + + @override + void replace(PurchaseOrderUIState other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$PurchaseOrderUIState; + } + + @override + void update(void Function(PurchaseOrderUIStateBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$PurchaseOrderUIState build() { + _$PurchaseOrderUIState _$result; + try { + _$result = _$v ?? + new _$PurchaseOrderUIState._( + editing: _editing?.build(), + listUIState: listUIState.build(), + selectedId: selectedId, + forceSelected: forceSelected, + tabIndex: BuiltValueNullFieldError.checkNotNull( + tabIndex, 'PurchaseOrderUIState', 'tabIndex'), + saveCompleter: saveCompleter, + cancelCompleter: cancelCompleter); + } catch (_) { + String _$failedField; + try { + _$failedField = 'editing'; + _editing?.build(); + _$failedField = 'listUIState'; + listUIState.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + 'PurchaseOrderUIState', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/lib/redux/ui/pref_reducer.dart b/lib/redux/ui/pref_reducer.dart index f9871f002..9c79e293f 100644 --- a/lib/redux/ui/pref_reducer.dart +++ b/lib/redux/ui/pref_reducer.dart @@ -40,6 +40,7 @@ import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart'; import 'package:invoiceninja_flutter/redux/webhook/webhook_actions.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; PrefState prefReducer( PrefState state, dynamic action, String selectedCompanyId) { @@ -529,6 +530,19 @@ Reducer> historyReducer = combineReducers([ _addToHistory(historyList, HistoryRecord(id: action.group.id, entityType: EntityType.group))), // STARTER: history - do not remove comment + TypedReducer, ViewPurchaseOrder>( + (historyList, action) => _addToHistory( + historyList, + HistoryRecord( + id: action.purchaseOrderId, + entityType: EntityType.purchaseOrder))), + TypedReducer, EditPurchaseOrder>( + (historyList, action) => _addToHistory( + historyList, + HistoryRecord( + id: action.purchaseOrder.id, + entityType: EntityType.purchaseOrder))), + TypedReducer, ViewRecurringExpense>( (historyList, action) => _addToHistory( historyList, diff --git a/lib/redux/ui/ui_reducer.dart b/lib/redux/ui/ui_reducer.dart index 305e7ef43..c013f3112 100644 --- a/lib/redux/ui/ui_reducer.dart +++ b/lib/redux/ui/ui_reducer.dart @@ -50,6 +50,7 @@ import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart'; import 'package:invoiceninja_flutter/redux/webhook/webhook_reducer.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_reducer.dart'; UIState uiReducer(UIState state, dynamic action) { final currentRoute = currentRouteReducer(state.currentRoute, action); @@ -76,6 +77,8 @@ UIState uiReducer(UIState state, dynamic action) { .replace(dashboardUIReducer(state.dashboardUIState, action)) ..reportsUIState.replace(reportsUIReducer(state.reportsUIState, action)) // STARTER: reducer - do not remove comment + ..purchaseOrderUIState + .replace(purchaseOrderUIReducer(state.purchaseOrderUIState, action)) ..recurringExpenseUIState.replace( recurringExpenseUIReducer(state.recurringExpenseUIState, action)) ..subscriptionUIState diff --git a/lib/redux/ui/ui_state.dart b/lib/redux/ui/ui_state.dart index 08a3f90d8..ba0b8a9ad 100644 --- a/lib/redux/ui/ui_state.dart +++ b/lib/redux/ui/ui_state.dart @@ -38,6 +38,7 @@ import 'package:invoiceninja_flutter/ui/auth/login_vm.dart'; import 'package:invoiceninja_flutter/utils/strings.dart'; // STARTER: import - do not remove comment +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_state.dart'; part 'ui_state.g.dart'; @@ -86,6 +87,9 @@ abstract class UIState implements Built { paymentUIState: PaymentUIState(sortFields[EntityType.payment]), quoteUIState: QuoteUIState(sortFields[EntityType.quote]), // STARTER: constructor - do not remove comment + purchaseOrderUIState: + PurchaseOrderUIState(sortFields[EntityType.purchaseOrder]), + recurringExpenseUIState: RecurringExpenseUIState(sortFields[EntityType.recurringExpense]), ); @@ -133,6 +137,8 @@ abstract class UIState implements Built { InvoiceUIState get invoiceUIState; // STARTER: properties - do not remove comment + PurchaseOrderUIState get purchaseOrderUIState; + RecurringExpenseUIState get recurringExpenseUIState; SubscriptionUIState get subscriptionUIState; diff --git a/lib/redux/ui/ui_state.g.dart b/lib/redux/ui/ui_state.g.dart index 10980cddd..d4b94c4f4 100644 --- a/lib/redux/ui/ui_state.g.dart +++ b/lib/redux/ui/ui_state.g.dart @@ -53,6 +53,9 @@ class _$UIStateSerializer implements StructuredSerializer { 'invoiceUIState', serializers.serialize(object.invoiceUIState, specifiedType: const FullType(InvoiceUIState)), + 'purchaseOrderUIState', + serializers.serialize(object.purchaseOrderUIState, + specifiedType: const FullType(PurchaseOrderUIState)), 'recurringExpenseUIState', serializers.serialize(object.recurringExpenseUIState, specifiedType: const FullType(RecurringExpenseUIState)), @@ -209,6 +212,11 @@ class _$UIStateSerializer implements StructuredSerializer { result.invoiceUIState.replace(serializers.deserialize(value, specifiedType: const FullType(InvoiceUIState)) as InvoiceUIState); break; + case 'purchaseOrderUIState': + result.purchaseOrderUIState.replace(serializers.deserialize(value, + specifiedType: const FullType(PurchaseOrderUIState)) + as PurchaseOrderUIState); + break; case 'recurringExpenseUIState': result.recurringExpenseUIState.replace(serializers.deserialize(value, specifiedType: const FullType(RecurringExpenseUIState)) @@ -345,6 +353,8 @@ class _$UIState extends UIState { @override final InvoiceUIState invoiceUIState; @override + final PurchaseOrderUIState purchaseOrderUIState; + @override final RecurringExpenseUIState recurringExpenseUIState; @override final SubscriptionUIState subscriptionUIState; @@ -408,6 +418,7 @@ class _$UIState extends UIState { this.productUIState, this.clientUIState, this.invoiceUIState, + this.purchaseOrderUIState, this.recurringExpenseUIState, this.subscriptionUIState, this.taskStatusUIState, @@ -454,6 +465,8 @@ class _$UIState extends UIState { clientUIState, 'UIState', 'clientUIState'); BuiltValueNullFieldError.checkNotNull( invoiceUIState, 'UIState', 'invoiceUIState'); + BuiltValueNullFieldError.checkNotNull( + purchaseOrderUIState, 'UIState', 'purchaseOrderUIState'); BuiltValueNullFieldError.checkNotNull( recurringExpenseUIState, 'UIState', 'recurringExpenseUIState'); BuiltValueNullFieldError.checkNotNull( @@ -526,6 +539,7 @@ class _$UIState extends UIState { productUIState == other.productUIState && clientUIState == other.clientUIState && invoiceUIState == other.invoiceUIState && + purchaseOrderUIState == other.purchaseOrderUIState && recurringExpenseUIState == other.recurringExpenseUIState && subscriptionUIState == other.subscriptionUIState && taskStatusUIState == other.taskStatusUIState && @@ -572,7 +586,7 @@ class _$UIState extends UIState { $jc( $jc( $jc( - $jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), loadingEntityType.hashCode), previewStack.hashCode), filterStack.hashCode), filter.hashCode), filterClearedAt.hashCode), lastActivityAt.hashCode), dashboardUIState.hashCode), productUIState.hashCode), clientUIState.hashCode), invoiceUIState.hashCode), recurringExpenseUIState.hashCode), subscriptionUIState.hashCode), taskStatusUIState.hashCode), expenseCategoryUIState.hashCode), + $jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), loadingEntityType.hashCode), previewStack.hashCode), filterStack.hashCode), filter.hashCode), filterClearedAt.hashCode), lastActivityAt.hashCode), dashboardUIState.hashCode), productUIState.hashCode), clientUIState.hashCode), invoiceUIState.hashCode), purchaseOrderUIState.hashCode), recurringExpenseUIState.hashCode), subscriptionUIState.hashCode), taskStatusUIState.hashCode), expenseCategoryUIState.hashCode), recurringInvoiceUIState.hashCode), webhookUIState.hashCode), tokenUIState.hashCode), @@ -610,6 +624,7 @@ class _$UIState extends UIState { ..add('productUIState', productUIState) ..add('clientUIState', clientUIState) ..add('invoiceUIState', invoiceUIState) + ..add('purchaseOrderUIState', purchaseOrderUIState) ..add('recurringExpenseUIState', recurringExpenseUIState) ..add('subscriptionUIState', subscriptionUIState) ..add('taskStatusUIState', taskStatusUIState) @@ -709,6 +724,12 @@ class UIStateBuilder implements Builder { set invoiceUIState(InvoiceUIStateBuilder invoiceUIState) => _$this._invoiceUIState = invoiceUIState; + PurchaseOrderUIStateBuilder _purchaseOrderUIState; + PurchaseOrderUIStateBuilder get purchaseOrderUIState => + _$this._purchaseOrderUIState ??= new PurchaseOrderUIStateBuilder(); + set purchaseOrderUIState(PurchaseOrderUIStateBuilder purchaseOrderUIState) => + _$this._purchaseOrderUIState = purchaseOrderUIState; + RecurringExpenseUIStateBuilder _recurringExpenseUIState; RecurringExpenseUIStateBuilder get recurringExpenseUIState => _$this._recurringExpenseUIState ??= new RecurringExpenseUIStateBuilder(); @@ -871,6 +892,7 @@ class UIStateBuilder implements Builder { _productUIState = $v.productUIState.toBuilder(); _clientUIState = $v.clientUIState.toBuilder(); _invoiceUIState = $v.invoiceUIState.toBuilder(); + _purchaseOrderUIState = $v.purchaseOrderUIState.toBuilder(); _recurringExpenseUIState = $v.recurringExpenseUIState.toBuilder(); _subscriptionUIState = $v.subscriptionUIState.toBuilder(); _taskStatusUIState = $v.taskStatusUIState.toBuilder(); @@ -934,6 +956,7 @@ class UIStateBuilder implements Builder { productUIState: productUIState.build(), clientUIState: clientUIState.build(), invoiceUIState: invoiceUIState.build(), + purchaseOrderUIState: purchaseOrderUIState.build(), recurringExpenseUIState: recurringExpenseUIState.build(), subscriptionUIState: subscriptionUIState.build(), taskStatusUIState: taskStatusUIState.build(), @@ -973,6 +996,8 @@ class UIStateBuilder implements Builder { clientUIState.build(); _$failedField = 'invoiceUIState'; invoiceUIState.build(); + _$failedField = 'purchaseOrderUIState'; + purchaseOrderUIState.build(); _$failedField = 'recurringExpenseUIState'; recurringExpenseUIState.build(); _$failedField = 'subscriptionUIState'; diff --git a/lib/ui/app/app_shortcuts.dart b/lib/ui/app/app_shortcuts.dart index 13b02d0ac..3dfbd1cac 100644 --- a/lib/ui/app/app_shortcuts.dart +++ b/lib/ui/app/app_shortcuts.dart @@ -125,4 +125,4 @@ enum _ShortcutIntentType { list, } -*/ \ No newline at end of file +*/ diff --git a/lib/ui/app/menu_drawer.dart b/lib/ui/app/menu_drawer.dart index ab4551de5..16964ed78 100644 --- a/lib/ui/app/menu_drawer.dart +++ b/lib/ui/app/menu_drawer.dart @@ -513,6 +513,13 @@ class MenuDrawer extends StatelessWidget { iconTooltip: localization.newExpense, ), // STARTER: menu - do not remove comment + DrawerTile( + company: company, + entityType: EntityType.purchaseOrder, + icon: getEntityIcon(EntityType.purchaseOrder), + title: localization.purchaseOrders, + ), + DrawerTile( company: company, entityType: EntityType.recurringExpense, diff --git a/lib/ui/purchase_order/edit/purchase_order_edit.dart b/lib/ui/purchase_order/edit/purchase_order_edit.dart new file mode 100644 index 000000000..401b1ab58 --- /dev/null +++ b/lib/ui/purchase_order/edit/purchase_order_edit.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart'; +import 'package:invoiceninja_flutter/ui/app/form_card.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/edit/purchase_order_edit_vm.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; + +class PurchaseOrderEdit extends StatefulWidget { + const PurchaseOrderEdit({ + Key key, + @required this.viewModel, + }) : super(key: key); + + final PurchaseOrderEditVM viewModel; + + @override + _PurchaseOrderEditState createState() => _PurchaseOrderEditState(); +} + +class _PurchaseOrderEditState extends State { + static final GlobalKey _formKey = + GlobalKey(debugLabel: '_purchaseOrderEdit'); + + List _controllers = []; + + @override + void didChangeDependencies() { + _controllers = [ + // STARTER: array - do not remove comment + ]; + + _controllers.forEach((controller) => controller.removeListener(_onChanged)); + + // STARTER: read value - do not remove comment + //_purchase_ordersController.text = purchase_order.purchase_orders; + + _controllers.forEach((controller) => controller.addListener(_onChanged)); + + super.didChangeDependencies(); + } + + @override + void dispose() { + _controllers.forEach((controller) { + controller.removeListener(_onChanged); + controller.dispose(); + }); + + super.dispose(); + } + + void _onChanged() { + // + } + + @override + Widget build(BuildContext context) { + final viewModel = widget.viewModel; + final localization = AppLocalization.of(context); + final purchaseOrder = viewModel.purchaseOrder; + + return EditScaffold( + title: purchaseOrder.isNew + ? localization.newPurchaseOrder + : localization.editPurchaseOrder, + onCancelPressed: (context) => viewModel.onCancelPressed(context), + onSavePressed: (context) { + final bool isValid = _formKey.currentState.validate(); + + /* + setState(() { + _autoValidate = !isValid; + }); + */ + + if (!isValid) { + return; + } + + viewModel.onSavePressed(context); + }, + body: Form( + key: _formKey, + child: Builder(builder: (BuildContext context) { + return ScrollableListView( + children: [ + FormCard( + children: [], + ), + ], + ); + })), + ); + } +} diff --git a/lib/ui/purchase_order/edit/purchase_order_edit_vm.dart b/lib/ui/purchase_order/edit/purchase_order_edit_vm.dart new file mode 100644 index 000000000..9042162e5 --- /dev/null +++ b/lib/ui/purchase_order/edit/purchase_order_edit_vm.dart @@ -0,0 +1,116 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart'; +import 'package:redux/redux.dart'; +import 'package:invoiceninja_flutter/utils/completers.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/view/purchase_order_view_vm.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/edit/purchase_order_edit.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:flutter_styled_toast/flutter_styled_toast.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; + +class PurchaseOrderEditScreen extends StatelessWidget { + const PurchaseOrderEditScreen({Key key}) : super(key: key); + static const String route = '/purchase_order/edit'; + + @override + Widget build(BuildContext context) { + return StoreConnector( + converter: (Store store) { + return PurchaseOrderEditVM.fromStore(store); + }, + builder: (context, viewModel) { + return PurchaseOrderEdit( + viewModel: viewModel, + key: ValueKey(viewModel.purchaseOrder.updatedAt), + ); + }, + ); + } +} + +class PurchaseOrderEditVM { + PurchaseOrderEditVM({ + @required this.state, + @required this.purchaseOrder, + @required this.company, + @required this.onChanged, + @required this.isSaving, + @required this.origPurchaseOrder, + @required this.onSavePressed, + @required this.onCancelPressed, + @required this.isLoading, + }); + + factory PurchaseOrderEditVM.fromStore(Store store) { + final state = store.state; + final purchaseOrder = state.purchaseOrderUIState.editing; + + return PurchaseOrderEditVM( + state: state, + isLoading: state.isLoading, + isSaving: state.isSaving, + origPurchaseOrder: state.purchaseOrderState.map[purchaseOrder.id], + purchaseOrder: purchaseOrder, + company: state.company, + onChanged: (InvoiceEntity purchaseOrder) { + store.dispatch(UpdatePurchaseOrder(purchaseOrder)); + }, + onCancelPressed: (BuildContext context) { + createEntity(context: context, entity: InvoiceEntity(), force: true); + if (state.purchaseOrderUIState.cancelCompleter != null) { + state.purchaseOrderUIState.cancelCompleter.complete(); + } else { + store.dispatch(UpdateCurrentRoute(state.uiState.previousRoute)); + } + }, + onSavePressed: (BuildContext context) { + Debouncer.runOnComplete(() { + final purchaseOrder = store.state.purchaseOrderUIState.editing; + final localization = AppLocalization.of(context); + final Completer completer = + new Completer(); + store.dispatch(SavePurchaseOrderRequest( + completer: completer, purchaseOrder: purchaseOrder)); + return completer.future.then((savedPurchaseOrder) { + showToast(purchaseOrder.isNew + ? localization.createdPurchaseOrder + : localization.updatedPurchaseOrder); + if (state.prefState.isMobile) { + store.dispatch(UpdateCurrentRoute(PurchaseOrderViewScreen.route)); + if (purchaseOrder.isNew) { + Navigator.of(context) + .pushReplacementNamed(PurchaseOrderViewScreen.route); + } else { + Navigator.of(context).pop(savedPurchaseOrder); + } + } else { + viewEntity(entity: savedPurchaseOrder, force: true); + } + }).catchError((Object error) { + showDialog( + context: context, + builder: (BuildContext context) { + return ErrorDialog(error); + }); + }); + }); + }, + ); + } + + final InvoiceEntity purchaseOrder; + final CompanyEntity company; + final Function(InvoiceEntity) onChanged; + final Function(BuildContext) onSavePressed; + final Function(BuildContext) onCancelPressed; + final bool isLoading; + final bool isSaving; + final InvoiceEntity origPurchaseOrder; + final AppState state; +} diff --git a/lib/ui/purchase_order/purchase_order_list_item.dart b/lib/ui/purchase_order/purchase_order_list_item.dart new file mode 100644 index 000000000..bd0fec74d --- /dev/null +++ b/lib/ui/purchase_order/purchase_order_list_item.dart @@ -0,0 +1,99 @@ +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/ui/app/entity_state_label.dart'; +import 'package:invoiceninja_flutter/utils/formatting.dart'; +import 'package:flutter/material.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/ui/app/dismissible_entity.dart'; + +class PurchaseOrderListItem extends StatelessWidget { + const PurchaseOrderListItem({ + @required this.user, + @required this.purchaseOrder, + @required this.filter, + this.onTap, + this.onLongPress, + this.onCheckboxChanged, + this.isChecked = false, + }); + + final UserEntity user; + final GestureTapCallback onTap; + final GestureTapCallback onLongPress; + final InvoiceEntity purchaseOrder; + final String filter; + final Function(bool) onCheckboxChanged; + final bool isChecked; + + @override + Widget build(BuildContext context) { + final store = StoreProvider.of(context); + final state = store.state; + final uiState = state.uiState; + final purchaseOrderUIState = uiState.purchaseOrderUIState; + final listUIState = purchaseOrderUIState.listUIState; + final isInMultiselect = listUIState.isInMultiselect(); + final showCheckbox = onCheckboxChanged != null || isInMultiselect; + + final filterMatch = filter != null && filter.isNotEmpty + ? purchaseOrder.matchesFilterValue(filter) + : null; + final subtitle = filterMatch; + + return DismissibleEntity( + userCompany: state.userCompany, + entity: purchaseOrder, + isSelected: purchaseOrder.id == + (uiState.isEditing + ? purchaseOrderUIState.editing.id + : purchaseOrderUIState.selectedId), + child: ListTile( + onTap: () => + onTap != null ? onTap() : selectEntity(entity: purchaseOrder), + onLongPress: () => onLongPress != null + ? onLongPress() + : selectEntity(entity: purchaseOrder, longPress: true), + leading: showCheckbox + ? IgnorePointer( + ignoring: listUIState.isInMultiselect(), + child: Checkbox( + value: isChecked, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onChanged: (value) => onCheckboxChanged(value), + activeColor: Theme.of(context).colorScheme.secondary, + ), + ) + : null, + title: Container( + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + Expanded( + child: Text( + purchaseOrder.number, + style: Theme.of(context).textTheme.subtitle1, + ), + ), + Text(formatNumber(purchaseOrder.listDisplayAmount, context), + style: Theme.of(context).textTheme.subtitle1), + ], + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + subtitle != null && subtitle.isNotEmpty + ? Text( + subtitle, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ) + : Container(), + EntityStateLabel(purchaseOrder), + ], + ), + ), + ); + } +} diff --git a/lib/ui/purchase_order/purchase_order_list_vm.dart b/lib/ui/purchase_order/purchase_order_list_vm.dart new file mode 100644 index 000000000..5cac5f3ba --- /dev/null +++ b/lib/ui/purchase_order/purchase_order_list_vm.dart @@ -0,0 +1,124 @@ +import 'dart:async'; +import 'package:invoiceninja_flutter/ui/app/tables/entity_list.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_list_item.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_presenter.dart'; +import 'package:redux/redux.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart'; +import 'package:invoiceninja_flutter/utils/completers.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_selectors.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; + +class PurchaseOrderListBuilder extends StatelessWidget { + const PurchaseOrderListBuilder({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return StoreConnector( + converter: PurchaseOrderListVM.fromStore, + builder: (context, viewModel) { + return EntityList( + entityType: EntityType.purchaseOrder, + presenter: PurchaseOrderPresenter(), + state: viewModel.state, + entityList: viewModel.purchaseOrderList, + tableColumns: viewModel.tableColumns, + onRefreshed: viewModel.onRefreshed, + onSortColumn: viewModel.onSortColumn, + onClearMultiselect: viewModel.onClearMultielsect, + itemBuilder: (BuildContext context, index) { + final state = viewModel.state; + final purchaseOrderId = viewModel.purchaseOrderList[index]; + final purchaseOrder = viewModel.purchaseOrderMap[purchaseOrderId]; + final listState = state.getListState(EntityType.purchaseOrder); + final isInMultiselect = listState.isInMultiselect(); + + return PurchaseOrderListItem( + user: viewModel.state.user, + filter: viewModel.filter, + purchaseOrder: purchaseOrder, + isChecked: + isInMultiselect && listState.isSelected(purchaseOrder.id), + ); + }); + }, + ); + } +} + +class PurchaseOrderListVM { + PurchaseOrderListVM({ + @required this.state, + @required this.userCompany, + @required this.purchaseOrderList, + @required this.purchaseOrderMap, + @required this.filter, + @required this.isLoading, + @required this.listState, + @required this.onRefreshed, + @required this.onEntityAction, + @required this.tableColumns, + @required this.onSortColumn, + @required this.onClearMultielsect, + }); + + static PurchaseOrderListVM fromStore(Store store) { + Future _handleRefresh(BuildContext context) { + if (store.state.isLoading) { + return Future(null); + } + final completer = snackBarCompleter( + context, AppLocalization.of(context).refreshComplete); + store.dispatch(RefreshData(completer: completer)); + return completer.future; + } + + final state = store.state; + + return PurchaseOrderListVM( + state: state, + userCompany: state.userCompany, + listState: state.purchaseOrderListState, + purchaseOrderList: memoizedFilteredPurchaseOrderList( + state.getUISelection(EntityType.purchaseOrder), + state.purchaseOrderState.map, + state.purchaseOrderState.list, + state.clientState.map, + state.purchaseOrderListState, + state.userState.map, + ), + purchaseOrderMap: state.purchaseOrderState.map, + isLoading: state.isLoading, + filter: state.purchaseOrderUIState.listUIState.filter, + onEntityAction: (BuildContext context, List purchaseOrders, + EntityAction action) => + handlePurchaseOrderAction(context, purchaseOrders, action), + onRefreshed: (context) => _handleRefresh(context), + tableColumns: state.userCompany.settings + ?.getTableColumns(EntityType.purchaseOrder) ?? + PurchaseOrderPresenter.getDefaultTableFields(state.userCompany), + onSortColumn: (field) => store.dispatch(SortPurchaseOrders(field)), + onClearMultielsect: () => store.dispatch(ClearPurchaseOrderMultiselect()), + ); + } + + final AppState state; + final UserCompanyEntity userCompany; + final List purchaseOrderList; + final BuiltMap purchaseOrderMap; + final ListUIState listState; + final String filter; + final bool isLoading; + final Function(BuildContext) onRefreshed; + final Function(BuildContext, List, EntityAction) onEntityAction; + final List tableColumns; + final Function(String) onSortColumn; + final Function onClearMultielsect; +} diff --git a/lib/ui/purchase_order/purchase_order_presenter.dart b/lib/ui/purchase_order/purchase_order_presenter.dart new file mode 100644 index 000000000..fef86daa9 --- /dev/null +++ b/lib/ui/purchase_order/purchase_order_presenter.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart'; + +class PurchaseOrderPresenter extends EntityPresenter { + static List getDefaultTableFields(UserCompanyEntity userCompany) { + return []; + } + + static List getAllTableFields(UserCompanyEntity userCompany) { + return [ + ...getDefaultTableFields(userCompany), + ...EntityPresenter.getBaseFields(), + ]; + } + + @override + Widget getField({String field, BuildContext context}) { + //final state = StoreProvider.of(context).state; + //final purchaseOrder = entity as InvoiceEntity; + + switch (field) { + } + + return super.getField(field: field, context: context); + } +} diff --git a/lib/ui/purchase_order/purchase_order_screen.dart b/lib/ui/purchase_order/purchase_order_screen.dart new file mode 100644 index 000000000..be0a73046 --- /dev/null +++ b/lib/ui/purchase_order/purchase_order_screen.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/data/models/purchase_order_model.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; +import 'package:invoiceninja_flutter/ui/app/app_bottom_bar.dart'; +import 'package:invoiceninja_flutter/ui/app/list_scaffold.dart'; +import 'package:invoiceninja_flutter/ui/app/list_filter.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_list_vm.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/purchase_order_presenter.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; + +import 'purchase_order_screen_vm.dart'; + +class PurchaseOrderScreen extends StatelessWidget { + const PurchaseOrderScreen({ + Key key, + @required this.viewModel, + }) : super(key: key); + + static const String route = '/purchase_order'; + + final PurchaseOrderScreenVM viewModel; + + @override + Widget build(BuildContext context) { + final store = StoreProvider.of(context); + final state = store.state; + final company = state.company; + final userCompany = state.userCompany; + final localization = AppLocalization.of(context); + + return ListScaffold( + entityType: EntityType.purchaseOrder, + onHamburgerLongPress: () => + store.dispatch(StartPurchaseOrderMultiselect()), + appBarTitle: ListFilter( + key: ValueKey( + '__filter_${state.purchaseOrderListState.filterClearedAt}__'), + entityType: EntityType.purchaseOrder, + entityIds: viewModel.purchaseOrderList, + filter: state.purchaseOrderListState.filter, + onFilterChanged: (value) { + store.dispatch(FilterPurchaseOrders(value)); + }, + onSelectedState: (EntityState state, value) { + store.dispatch(FilterPurchaseOrdersByState(state)); + }, + ), + onCheckboxPressed: () { + if (store.state.purchaseOrderListState.isInMultiselect()) { + store.dispatch(ClearPurchaseOrderMultiselect()); + } else { + store.dispatch(StartPurchaseOrderMultiselect()); + } + }, + body: PurchaseOrderListBuilder(), + bottomNavigationBar: AppBottomBar( + entityType: EntityType.purchaseOrder, + tableColumns: PurchaseOrderPresenter.getAllTableFields(userCompany), + defaultTableColumns: + PurchaseOrderPresenter.getDefaultTableFields(userCompany), + onSelectedSortField: (value) { + store.dispatch(SortPurchaseOrders(value)); + }, + sortFields: [ + PurchaseOrderFields.number, + PurchaseOrderFields.date, + PurchaseOrderFields.dueDate, + EntityFields.updatedAt, + ], + onSelectedState: (EntityState state, value) { + store.dispatch(FilterPurchaseOrdersByState(state)); + }, + onCheckboxPressed: () { + if (store.state.purchaseOrderListState.isInMultiselect()) { + store.dispatch(ClearPurchaseOrderMultiselect()); + } else { + store.dispatch(StartPurchaseOrderMultiselect()); + } + }, + customValues1: company.getCustomFieldValues(CustomFieldType.invoice1, + excludeBlank: true), + customValues2: company.getCustomFieldValues(CustomFieldType.invoice2, + excludeBlank: true), + customValues3: company.getCustomFieldValues(CustomFieldType.invoice3, + excludeBlank: true), + customValues4: company.getCustomFieldValues(CustomFieldType.invoice4, + excludeBlank: true), + onSelectedCustom1: (value) => + store.dispatch(FilterPurchaseOrdersByCustom1(value)), + onSelectedCustom2: (value) => + store.dispatch(FilterPurchaseOrdersByCustom2(value)), + onSelectedCustom3: (value) => + store.dispatch(FilterPurchaseOrdersByCustom3(value)), + onSelectedCustom4: (value) => + store.dispatch(FilterPurchaseOrdersByCustom4(value)), + ), + floatingActionButton: state.prefState.isMenuFloated && + userCompany.canCreate(EntityType.purchaseOrder) + ? FloatingActionButton( + heroTag: 'purchase_order_fab', + backgroundColor: Theme.of(context).primaryColorDark, + onPressed: () { + createEntityByType( + context: context, entityType: EntityType.purchaseOrder); + }, + child: Icon( + Icons.add, + color: Colors.white, + ), + tooltip: localization.newPurchaseOrder, + ) + : null, + ); + } +} diff --git a/lib/ui/purchase_order/purchase_order_screen_vm.dart b/lib/ui/purchase_order/purchase_order_screen_vm.dart new file mode 100644 index 000000000..e4a846b9c --- /dev/null +++ b/lib/ui/purchase_order/purchase_order_screen_vm.dart @@ -0,0 +1,64 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_selectors.dart'; +import 'package:redux/redux.dart'; + +import 'purchase_order_screen.dart'; + +class PurchaseOrderScreenBuilder extends StatelessWidget { + const PurchaseOrderScreenBuilder({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return StoreConnector( + converter: PurchaseOrderScreenVM.fromStore, + builder: (context, vm) { + return PurchaseOrderScreen( + viewModel: vm, + ); + }, + ); + } +} + +class PurchaseOrderScreenVM { + PurchaseOrderScreenVM({ + @required this.isInMultiselect, + @required this.purchaseOrderList, + @required this.userCompany, + @required this.onEntityAction, + @required this.purchaseOrderMap, + }); + + final bool isInMultiselect; + final UserCompanyEntity userCompany; + final List purchaseOrderList; + final Function(BuildContext, List, EntityAction) onEntityAction; + final BuiltMap purchaseOrderMap; + + static PurchaseOrderScreenVM fromStore(Store store) { + final state = store.state; + + return PurchaseOrderScreenVM( + purchaseOrderMap: state.purchaseOrderState.map, + purchaseOrderList: memoizedFilteredPurchaseOrderList( + state.getUISelection(EntityType.purchaseOrder), + state.purchaseOrderState.map, + state.purchaseOrderState.list, + state.clientState.map, + state.purchaseOrderListState, + state.userState.map, + ), + userCompany: state.userCompany, + isInMultiselect: state.purchaseOrderListState.isInMultiselect(), + onEntityAction: (BuildContext context, List purchaseOrders, + EntityAction action) => + handlePurchaseOrderAction(context, purchaseOrders, action), + ); + } +} diff --git a/lib/ui/purchase_order/view/purchase_order_view.dart b/lib/ui/purchase_order/view/purchase_order_view.dart new file mode 100644 index 000000000..a2ec399e6 --- /dev/null +++ b/lib/ui/purchase_order/view/purchase_order_view.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/view/purchase_order_view_vm.dart'; +import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart'; + +class PurchaseOrderView extends StatefulWidget { + const PurchaseOrderView({ + Key key, + @required this.viewModel, + @required this.isFilter, + }) : super(key: key); + + final PurchaseOrderViewVM viewModel; + final bool isFilter; + + @override + _PurchaseOrderViewState createState() => new _PurchaseOrderViewState(); +} + +class _PurchaseOrderViewState extends State { + @override + Widget build(BuildContext context) { + final viewModel = widget.viewModel; + final purchaseOrder = viewModel.purchaseOrder; + + return ViewScaffold( + isFilter: widget.isFilter, + entity: purchaseOrder, + body: ScrollableListView( + children: [], + ), + ); + } +} diff --git a/lib/ui/purchase_order/view/purchase_order_view_vm.dart b/lib/ui/purchase_order/view/purchase_order_view_vm.dart new file mode 100644 index 000000000..1f5d53617 --- /dev/null +++ b/lib/ui/purchase_order/view/purchase_order_view_vm.dart @@ -0,0 +1,84 @@ +import 'dart:async'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:flutter/material.dart'; +import 'package:invoiceninja_flutter/utils/completers.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:redux/redux.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart'; +import 'package:invoiceninja_flutter/data/models/models.dart'; +import 'package:invoiceninja_flutter/ui/purchase_order/view/purchase_order_view.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; + +class PurchaseOrderViewScreen extends StatelessWidget { + const PurchaseOrderViewScreen({ + Key key, + this.isFilter = false, + }) : super(key: key); + static const String route = '/purchase_order/view'; + final bool isFilter; + + @override + Widget build(BuildContext context) { + return StoreConnector( + converter: (Store store) { + return PurchaseOrderViewVM.fromStore(store); + }, + builder: (context, vm) { + return PurchaseOrderView( + viewModel: vm, + isFilter: isFilter, + ); + }, + ); + } +} + +class PurchaseOrderViewVM { + PurchaseOrderViewVM({ + @required this.state, + @required this.purchaseOrder, + @required this.company, + @required this.onEntityAction, + @required this.onRefreshed, + @required this.isSaving, + @required this.isLoading, + @required this.isDirty, + }); + + factory PurchaseOrderViewVM.fromStore(Store store) { + final state = store.state; + final purchaseOrder = + state.purchaseOrderState.map[state.purchaseOrderUIState.selectedId] ?? + InvoiceEntity(id: state.purchaseOrderUIState.selectedId); + + Future _handleRefresh(BuildContext context) { + final completer = snackBarCompleter( + context, AppLocalization.of(context).refreshComplete); + store.dispatch(LoadPurchaseOrder( + completer: completer, purchaseOrderId: purchaseOrder.id)); + return completer.future; + } + + return PurchaseOrderViewVM( + state: state, + company: state.company, + isSaving: state.isSaving, + isLoading: state.isLoading, + isDirty: purchaseOrder.isNew, + purchaseOrder: purchaseOrder, + onRefreshed: (context) => _handleRefresh(context), + onEntityAction: (BuildContext context, EntityAction action) => + handleEntitiesActions([purchaseOrder], action, autoPop: true), + ); + } + + final AppState state; + final InvoiceEntity purchaseOrder; + final CompanyEntity company; + final Function(BuildContext, EntityAction) onEntityAction; + final Function(BuildContext) onRefreshed; + final bool isSaving; + final bool isLoading; + final bool isDirty; +} diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 197af975e..9d381a8dd 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -16,6 +16,18 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'purchase_order': 'PurchaseOrder', + 'purchase_orders': 'PurchaseOrders', + 'new_purchase_order': 'New PurchaseOrder', + 'edit_purchase_order': 'Edit PurchaseOrder', + 'created_purchase_order': 'Successfully created purchase_order', + 'updated_purchase_order': 'Successfully updated purchase_order', + 'archived_purchase_order': 'Successfully archived purchase_order', + 'deleted_purchase_order': 'Successfully deleted purchase_order', + 'removed_purchase_order': 'Successfully removed purchase_order', + 'restored_purchase_order': 'Successfully restored purchase_order', + 'search_purchase_order': 'Search PurchaseOrder', + 'login_url': 'Login URL', 'enable_applying_payments': 'Enable Applying Payments', 'enable_applying_payments_help': @@ -70606,6 +70618,36 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues['en']['login_url']; // STARTER: lang field - do not remove comment + String get purchaseOrder => + _localizedValues[localeCode]['purchase_order'] ?? + _localizedValues['en']['purchase_order']; + String get purchaseOrders => + _localizedValues[localeCode]['purchase_orders'] ?? + _localizedValues['en']['purchase_orders']; + String get newPurchaseOrder => + _localizedValues[localeCode]['new_purchase_order'] ?? + _localizedValues['en']['new_purchase_order']; + String get createdPurchaseOrder => + _localizedValues[localeCode]['created_purchase_order'] ?? + _localizedValues['en']['created_purchase_order']; + String get updatedPurchaseOrder => + _localizedValues[localeCode]['updated_purchase_order'] ?? + _localizedValues['en']['updated_purchase_order']; + String get archivedPurchaseOrder => + _localizedValues[localeCode]['archived_purchase_order'] ?? + _localizedValues['en']['archived_purchase_order']; + String get deletedPurchaseOrder => + _localizedValues[localeCode]['deleted_purchase_order'] ?? + _localizedValues['en']['deleted_purchase_order']; + String get restoredPurchaseOrder => + _localizedValues[localeCode]['restored_purchase_order'] ?? + _localizedValues['en']['restored_purchase_order']; + String get editPurchaseOrder => + _localizedValues[localeCode]['edit_purchase_order'] ?? + _localizedValues['en']['edit_purchase_order']; + String get searchPurchaseOrder => + _localizedValues[localeCode]['search_purchase_order'] ?? + _localizedValues['en']['search_purchase_order']; String lookup(String key) { final lookupKey = toSnakeCase(key); diff --git a/starter.sh b/starter.sh index 1e0b1a499..bc38bd81d 100644 --- a/starter.sh +++ b/starter.sh @@ -443,7 +443,7 @@ else comment="STARTER: entity type - do not remove comment" code="static const EntityType ${module_camel} = _\$${module_camel};${lineBreak}" - #sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/data/models/entities.dart + sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/data/models/entities.dart echo "Generating built files.." flutter packages pub run build_runner clean diff --git a/stubs/data/repositories/stub_repository b/stubs/data/repositories/stub_repository index c74a9c635..09490ba5a 100644 --- a/stubs/data/repositories/stub_repository +++ b/stubs/data/repositories/stub_repository @@ -1,7 +1,7 @@ -import 'dart:async'; import 'dart:convert'; import 'dart:core'; import 'package:built_collection/built_collection.dart'; +import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/models/serializers.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; diff --git a/stubs/redux/stub/stub_actions b/stubs/redux/stub/stub_actions index 6c2d48393..ae829500d 100644 --- a/stubs/redux/stub/stub_actions +++ b/stubs/redux/stub/stub_actions @@ -276,14 +276,13 @@ void handleStubAction( } final store = StoreProvider.of(context); - final state = store.state; final localization = AppLocalization.of(context); final stub = stubs.first as StubEntity; final stubIds = stubs.map((stub) => stub.id).toList(); switch (action) { case EntityAction.edit: - editEntity(context: context, entity: stub); + editEntity(entity: stub); break; case EntityAction.restore: store.dispatch(RestoreStubsRequest( diff --git a/stubs/ui/stub/stub_list_item b/stubs/ui/stub/stub_list_item index ca0fa3800..1ebd13ea8 100644 --- a/stubs/ui/stub/stub_list_item +++ b/stubs/ui/stub/stub_list_item @@ -51,11 +51,11 @@ class StubListItem extends StatelessWidget { child: ListTile( onTap: () => onTap != null ? onTap() - : selectEntity(entity: stub, context: context), + : selectEntity(entity: stub), onLongPress: () => onLongPress != null ? onLongPress() : selectEntity( - entity: stub, context: context, longPress: true), + entity: stub, longPress: true), leading: showCheckbox ? IgnorePointer( ignoring: listUIState.isInMultiselect(),