Recurring invoices

This commit is contained in:
Hillel Coren 2020-09-13 10:18:06 +03:00
parent 70e71df729
commit b49332b2e5
7 changed files with 286 additions and 43 deletions

View File

@ -68,7 +68,6 @@ class InvoiceFields {
static const String privateNotes = 'private_notes'; static const String privateNotes = 'private_notes';
static const String isRecurring = 'is_recurring'; static const String isRecurring = 'is_recurring';
static const String frequencyId = 'frequency_id'; static const String frequencyId = 'frequency_id';
static const String startDate = 'start_date';
static const String endDate = 'end_date'; static const String endDate = 'end_date';
static const String documents = 'documents'; static const String documents = 'documents';
static const String customValue1 = 'custom1'; static const String customValue1 = 'custom1';
@ -169,7 +168,6 @@ abstract class InvoiceEntity extends Object
nextSendDate: '', nextSendDate: '',
frequencyId: '', frequencyId: '',
remainingCycles: 0, remainingCycles: 0,
startDate: '',
); );
} }
@ -371,10 +369,6 @@ abstract class InvoiceEntity extends Object
@BuiltValueField(wireName: 'next_send_date') @BuiltValueField(wireName: 'next_send_date')
String get nextSendDate; String get nextSendDate;
@nullable
@BuiltValueField(wireName: 'start_date')
String get startDate;
@nullable @nullable
@BuiltValueField(wireName: 'remaining_cycles') @BuiltValueField(wireName: 'remaining_cycles')
int get remainingCycles; int get remainingCycles;

View File

@ -321,12 +321,6 @@ class _$InvoiceEntitySerializer implements StructuredSerializer<InvoiceEntity> {
..add(serializers.serialize(object.frequencyId, ..add(serializers.serialize(object.frequencyId,
specifiedType: const FullType(String))); specifiedType: const FullType(String)));
} }
if (object.startDate != null) {
result
..add('start_date')
..add(serializers.serialize(object.startDate,
specifiedType: const FullType(String)));
}
if (object.remainingCycles != null) { if (object.remainingCycles != null) {
result result
..add('remaining_cycles') ..add('remaining_cycles')
@ -595,10 +589,6 @@ class _$InvoiceEntitySerializer implements StructuredSerializer<InvoiceEntity> {
result.nextSendDate = serializers.deserialize(value, result.nextSendDate = serializers.deserialize(value,
specifiedType: const FullType(String)) as String; specifiedType: const FullType(String)) as String;
break; break;
case 'start_date':
result.startDate = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
break;
case 'remaining_cycles': case 'remaining_cycles':
result.remainingCycles = serializers.deserialize(value, result.remainingCycles = serializers.deserialize(value,
specifiedType: const FullType(int)) as int; specifiedType: const FullType(int)) as int;
@ -1380,8 +1370,6 @@ class _$InvoiceEntity extends InvoiceEntity {
@override @override
final String nextSendDate; final String nextSendDate;
@override @override
final String startDate;
@override
final int remainingCycles; final int remainingCycles;
@override @override
final String invoiceId; final String invoiceId;
@ -1468,7 +1456,6 @@ class _$InvoiceEntity extends InvoiceEntity {
this.frequencyId, this.frequencyId,
this.lastSentDate, this.lastSentDate,
this.nextSendDate, this.nextSendDate,
this.startDate,
this.remainingCycles, this.remainingCycles,
this.invoiceId, this.invoiceId,
this.filename, this.filename,
@ -1683,7 +1670,6 @@ class _$InvoiceEntity extends InvoiceEntity {
frequencyId == other.frequencyId && frequencyId == other.frequencyId &&
lastSentDate == other.lastSentDate && lastSentDate == other.lastSentDate &&
nextSendDate == other.nextSendDate && nextSendDate == other.nextSendDate &&
startDate == other.startDate &&
remainingCycles == other.remainingCycles && remainingCycles == other.remainingCycles &&
invoiceId == other.invoiceId && invoiceId == other.invoiceId &&
filename == other.filename && filename == other.filename &&
@ -1724,9 +1710,9 @@ class _$InvoiceEntity extends InvoiceEntity {
$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, amount.hashCode), balance.hashCode), clientId.hashCode), statusId.hashCode), number.hashCode), discount.hashCode), poNumber.hashCode), date.hashCode), dueDate.hashCode), publicNotes.hashCode), privateNotes.hashCode), terms.hashCode), footer.hashCode), designId.hashCode), usesInclusiveTaxes.hashCode), taxName1.hashCode), taxRate1.hashCode), taxName2.hashCode), taxRate2.hashCode), taxName3.hashCode), taxRate3.hashCode), isAmountDiscount.hashCode), partial.hashCode), taxAmount.hashCode), partialDueDate.hashCode), hasTasks.hashCode), autoBill.hashCode), customValue1.hashCode), customValue2.hashCode), customValue3.hashCode), customValue4.hashCode), customSurcharge1.hashCode), customSurcharge2.hashCode), customSurcharge3.hashCode), customSurcharge4.hashCode), customTaxes1.hashCode), customTaxes2.hashCode), customTaxes3.hashCode), customTaxes4.hashCode), hasExpenses.hashCode), exchangeRate.hashCode), reminder1Sent.hashCode), reminder2Sent.hashCode), reminder3Sent.hashCode), reminderLastSent.hashCode), frequencyId.hashCode), lastSentDate.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(0, amount.hashCode), balance.hashCode), clientId.hashCode), statusId.hashCode), number.hashCode), discount.hashCode), poNumber.hashCode), date.hashCode), dueDate.hashCode), publicNotes.hashCode), privateNotes.hashCode), terms.hashCode), footer.hashCode), designId.hashCode), usesInclusiveTaxes.hashCode), taxName1.hashCode), taxRate1.hashCode), taxName2.hashCode), taxRate2.hashCode), taxName3.hashCode), taxRate3.hashCode), isAmountDiscount.hashCode), partial.hashCode), taxAmount.hashCode), partialDueDate.hashCode), hasTasks.hashCode), autoBill.hashCode), customValue1.hashCode), customValue2.hashCode), customValue3.hashCode), customValue4.hashCode), customSurcharge1.hashCode), customSurcharge2.hashCode), customSurcharge3.hashCode), customSurcharge4.hashCode), customTaxes1.hashCode), customTaxes2.hashCode), customTaxes3.hashCode), customTaxes4.hashCode), hasExpenses.hashCode), exchangeRate.hashCode), reminder1Sent.hashCode), reminder2Sent.hashCode), reminder3Sent.hashCode), reminderLastSent.hashCode), frequencyId.hashCode),
nextSendDate.hashCode), lastSentDate.hashCode),
startDate.hashCode), nextSendDate.hashCode),
remainingCycles.hashCode), remainingCycles.hashCode),
invoiceId.hashCode), invoiceId.hashCode),
filename.hashCode), filename.hashCode),
@ -1797,7 +1783,6 @@ class _$InvoiceEntity extends InvoiceEntity {
..add('frequencyId', frequencyId) ..add('frequencyId', frequencyId)
..add('lastSentDate', lastSentDate) ..add('lastSentDate', lastSentDate)
..add('nextSendDate', nextSendDate) ..add('nextSendDate', nextSendDate)
..add('startDate', startDate)
..add('remainingCycles', remainingCycles) ..add('remainingCycles', remainingCycles)
..add('invoiceId', invoiceId) ..add('invoiceId', invoiceId)
..add('filename', filename) ..add('filename', filename)
@ -2026,10 +2011,6 @@ class InvoiceEntityBuilder
String get nextSendDate => _$this._nextSendDate; String get nextSendDate => _$this._nextSendDate;
set nextSendDate(String nextSendDate) => _$this._nextSendDate = nextSendDate; set nextSendDate(String nextSendDate) => _$this._nextSendDate = nextSendDate;
String _startDate;
String get startDate => _$this._startDate;
set startDate(String startDate) => _$this._startDate = startDate;
int _remainingCycles; int _remainingCycles;
int get remainingCycles => _$this._remainingCycles; int get remainingCycles => _$this._remainingCycles;
set remainingCycles(int remainingCycles) => set remainingCycles(int remainingCycles) =>
@ -2161,7 +2142,6 @@ class InvoiceEntityBuilder
_frequencyId = _$v.frequencyId; _frequencyId = _$v.frequencyId;
_lastSentDate = _$v.lastSentDate; _lastSentDate = _$v.lastSentDate;
_nextSendDate = _$v.nextSendDate; _nextSendDate = _$v.nextSendDate;
_startDate = _$v.startDate;
_remainingCycles = _$v.remainingCycles; _remainingCycles = _$v.remainingCycles;
_invoiceId = _$v.invoiceId; _invoiceId = _$v.invoiceId;
_filename = _$v.filename; _filename = _$v.filename;
@ -2251,7 +2231,6 @@ class InvoiceEntityBuilder
frequencyId: frequencyId, frequencyId: frequencyId,
lastSentDate: lastSentDate, lastSentDate: lastSentDate,
nextSendDate: nextSendDate, nextSendDate: nextSendDate,
startDate: startDate,
remainingCycles: remainingCycles, remainingCycles: remainingCycles,
invoiceId: invoiceId, invoiceId: invoiceId,
filename: filename, filename: filename,

View File

@ -59,6 +59,13 @@ class UpdateRecurringInvoice implements PersistUI {
final InvoiceEntity recurringInvoice; final InvoiceEntity recurringInvoice;
} }
class UpdateRecurringInvoiceClient implements PersistUI {
UpdateRecurringInvoiceClient({this.client});
final ClientEntity client;
}
class LoadRecurringInvoice { class LoadRecurringInvoice {
LoadRecurringInvoice({this.completer, this.recurringInvoiceId}); LoadRecurringInvoice({this.completer, this.recurringInvoiceId});
@ -153,6 +160,12 @@ class AddQuoteItem implements PersistUI {
final InvoiceItemEntity quoteItem; final InvoiceItemEntity quoteItem;
} }
class AddRecurringInvoiceItem implements PersistUI {
AddRecurringInvoiceItem({this.invoiceItem});
final InvoiceItemEntity invoiceItem;
}
class AddRecurringInvoiceItems implements PersistUI { class AddRecurringInvoiceItems implements PersistUI {
AddRecurringInvoiceItems(this.items); AddRecurringInvoiceItems(this.items);

View File

@ -1,8 +1,10 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart'; import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/quote/edit/quote_edit_details_vm.dart';
import 'package:invoiceninja_flutter/ui/quote/edit/quote_edit_items_vm.dart';
import 'package:invoiceninja_flutter/ui/quote/edit/quote_edit_notes_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
class RecurringInvoiceEdit extends StatefulWidget { class RecurringInvoiceEdit extends StatefulWidget {
@ -99,18 +101,25 @@ class _RecurringInvoiceEditState extends State<RecurringInvoiceEdit>
], ],
), ),
body: Form( body: Form(
key: _formKey, key: _formKey,
child: Builder(builder: (BuildContext context) { child: state.prefState.isDesktop
return ListView( ? QuoteEditDetailsScreen(
children: <Widget>[ viewModel: widget.viewModel,
FormCard( )
children: <Widget>[ : TabBarView(
// STARTER: widgets - do not remove comment key: ValueKey('__quote_${viewModel.invoice.id}__'),
], controller: _controller,
), children: <Widget>[
], QuoteEditDetailsScreen(
); viewModel: widget.viewModel,
})), ),
QuoteEditItemsScreen(
viewModel: widget.viewModel,
),
QuoteEditNotesScreen(),
],
),
),
); );
} }
} }

View File

@ -0,0 +1,111 @@
import 'dart:async';
import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart';
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/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/app/screen_imports.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_desktop.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_details.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_details_vm.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/edit/recurring_invoice_edit_vm.dart';
import 'package:invoiceninja_flutter/utils/money.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class RecurringInvoiceEditDetailsScreen extends StatelessWidget {
const RecurringInvoiceEditDetailsScreen({Key key, @required this.viewModel})
: super(key: key);
final EntityEditVM viewModel;
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, RecurringInvoiceEditDetailsVM>(
converter: (Store<AppState> store) {
return RecurringInvoiceEditDetailsVM.fromStore(store);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
return InvoiceEditDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,
key: ValueKey('__quote_${viewModel.invoice.id}__'),
entityType: EntityType.quote,
);
} else {
return InvoiceEditDetails(
viewModel: viewModel,
entityType: EntityType.quote,
);
}
},
);
}
}
class RecurringInvoiceEditDetailsVM extends EntityEditDetailsVM {
RecurringInvoiceEditDetailsVM({
AppState state,
CompanyEntity company,
InvoiceEntity invoice,
Function(InvoiceEntity) onChanged,
Function(BuildContext, InvoiceEntity, ClientEntity) onClientChanged,
BuiltMap<String, ClientEntity> clientMap,
BuiltList<String> clientList,
Function(BuildContext context, Completer<SelectableEntity> completer)
onAddClientPressed,
}) : super(
state: state,
company: company,
invoice: invoice,
onChanged: onChanged,
onClientChanged: onClientChanged,
clientMap: clientMap,
clientList: clientList,
onAddClientPressed: onAddClientPressed,
);
factory RecurringInvoiceEditDetailsVM.fromStore(Store<AppState> store) {
final AppState state = store.state;
final quote = state.quoteUIState.editing;
final company = state.company;
return RecurringInvoiceEditDetailsVM(
state: state,
company: company,
invoice: quote,
onChanged: (InvoiceEntity quote) => store.dispatch(UpdateRecurringInvoice(quote)),
clientMap: state.clientState.map,
clientList: state.clientState.list,
onClientChanged: (context, quote, client) {
if (client != null) {
final exchangeRate = getExchangeRate(context,
fromCurrencyId: company.currencyId,
toCurrencyId: client.currencyId);
store.dispatch(UpdateRecurringInvoice(
quote.rebuild((b) => b..exchangeRate = exchangeRate)));
}
store.dispatch(UpdateRecurringInvoiceClient(client: client));
},
onAddClientPressed: (context, completer) {
createEntity(
context: context,
entity: ClientEntity(),
force: true,
completer: completer,
cancelCompleter: Completer<Null>()
..future.then((_) {
store.dispatch(UpdateCurrentRoute(RecurringInvoiceEditScreen.route));
}));
completer.future.then((SelectableEntity client) {
store.dispatch(UpdateCurrentRoute(RecurringInvoiceEditScreen.route));
});
},
);
}
}

View File

@ -0,0 +1,87 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_items.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_items_desktop.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_items_vm.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_vm.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class RecurringInvoiceEditItemsScreen extends StatelessWidget {
const RecurringInvoiceEditItemsScreen({
Key key,
@required this.viewModel,
}) : super(key: key);
final EntityEditVM viewModel;
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, RecurringInvoiceEditItemsVM>(
converter: (Store<AppState> store) {
return RecurringInvoiceEditItemsVM.fromStore(store);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
return InvoiceEditItemsDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,
);
} else {
return InvoiceEditItems(
viewModel: viewModel,
entityViewModel: this.viewModel,
);
}
},
);
}
}
class RecurringInvoiceEditItemsVM extends EntityEditItemsVM {
RecurringInvoiceEditItemsVM({
AppState state,
CompanyEntity company,
InvoiceEntity invoice,
int invoiceItemIndex,
Function addLineItem,
Function deleteLineItem,
Function(int) onRemoveInvoiceItemPressed,
Function onDoneInvoiceItemPressed,
Function(InvoiceItemEntity, int) onChangedInvoiceItem,
}) : super(
state: state,
company: company,
invoice: invoice,
addLineItem: addLineItem,
deleteLineItem: deleteLineItem,
invoiceItemIndex: invoiceItemIndex,
onRemoveInvoiceItemPressed: onRemoveInvoiceItemPressed,
onDoneInvoiceItemPressed: onDoneInvoiceItemPressed,
onChangedInvoiceItem: onChangedInvoiceItem,
);
factory RecurringInvoiceEditItemsVM.fromStore(Store<AppState> store) {
return RecurringInvoiceEditItemsVM(
state: store.state,
company: store.state.company,
invoice: store.state.quoteUIState.editing,
invoiceItemIndex: store.state.quoteUIState.editingItemIndex,
onRemoveInvoiceItemPressed: (index) =>
store.dispatch(DeleteRecurringInvoiceItem(index)),
onDoneInvoiceItemPressed: () =>
store.dispatch(EditRecurringInvoiceItem()),
onChangedInvoiceItem: (item, index) {
final quote = store.state.quoteUIState.editing;
if (index == quote.lineItems.length) {
store.dispatch(AddRecurringInvoiceItem(invoiceItem: item));
} else {
store
.dispatch(UpdateRecurringInvoiceItem(item: item, index: index));
}
});
}
}

View File

@ -0,0 +1,50 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_notes.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_notes_vm.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class RecurringInvoiceEditNotesScreen extends StatelessWidget {
const RecurringInvoiceEditNotesScreen({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, RecurringInvoiceEditNotesVM>(
converter: (Store<AppState> store) {
return RecurringInvoiceEditNotesVM.fromStore(store);
},
builder: (context, viewModel) {
return InvoiceEditNotes(
viewModel: viewModel,
);
},
);
}
}
class RecurringInvoiceEditNotesVM extends EntityEditNotesVM {
RecurringInvoiceEditNotesVM({
CompanyEntity company,
InvoiceEntity invoice,
Function(InvoiceEntity) onChanged,
}) : super(
company: company,
invoice: invoice,
onChanged: onChanged,
);
factory RecurringInvoiceEditNotesVM.fromStore(Store<AppState> store) {
final AppState state = store.state;
final quote = state.quoteUIState.editing;
return RecurringInvoiceEditNotesVM(
company: state.company,
invoice: quote,
onChanged: (InvoiceEntity quote) => store.dispatch(UpdateRecurringInvoice(quote)),
);
}
}