From efdb89b7624a3e336e63e4edb0600cc0c5bdb4f1 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 2 Jul 2024 15:52:48 +0300 Subject: [PATCH 01/10] Add hidden option to show where local data is saved --- lib/ui/app/menu_drawer.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/ui/app/menu_drawer.dart b/lib/ui/app/menu_drawer.dart index 7e99adea4c..676f756d94 100644 --- a/lib/ui/app/menu_drawer.dart +++ b/lib/ui/app/menu_drawer.dart @@ -1,5 +1,6 @@ // Dart imports: import 'dart:convert'; +import 'dart:io'; // Flutter imports: import 'package:flutter/foundation.dart'; @@ -16,6 +17,7 @@ import 'package:invoiceninja_flutter/ui/app/sms_verification.dart'; import 'package:invoiceninja_flutter/ui/app/upgrade_dialog.dart'; import 'package:invoiceninja_flutter/utils/app_review.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:redux/redux.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -1396,10 +1398,15 @@ void _showAbout(BuildContext context) async { builder: (context) => UpgradeDialog(), ); } else { + final directory = + await getApplicationDocumentsDirectory(); showMessageDialog( message: FLUTTER_VERSION['channel']!.toUpperCase() + ' • ' + - FLUTTER_VERSION['frameworkVersion']!, + FLUTTER_VERSION['frameworkVersion']! + + '\n\n${directory.path}' + + Platform.pathSeparator + + 'invoiceninja', secondaryActions: [ TextButton( child: Text(localization.logout.toUpperCase()), From 1d7d57bb3a2ebce7a16f8ee1913d30400837efe0 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 2 Jul 2024 20:16:26 +0300 Subject: [PATCH 02/10] BUG: Expense shown as paid although paid-switch is off/deactivated #663 --- lib/ui/expense/edit/expense_edit_settings.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/ui/expense/edit/expense_edit_settings.dart b/lib/ui/expense/edit/expense_edit_settings.dart index 975372ad88..4798fef57d 100644 --- a/lib/ui/expense/edit/expense_edit_settings.dart +++ b/lib/ui/expense/edit/expense_edit_settings.dart @@ -184,8 +184,10 @@ class ExpenseEditSettingsState extends State { (b) => b..paymentDate = convertDateTimeToSqlDate())); } } else { - viewModel - .onChanged!(expense.rebuild((b) => b..paymentDate = '')); + viewModel.onChanged!(expense.rebuild((b) => b + ..paymentDate = '' + ..paymentTypeId = '' + ..transactionReference = '')); WidgetsBinding.instance.addPostFrameCallback((duration) { _transactionReferenceController.text = ''; }); From 6fc93cb24cf1a524e65d88018e1701b0b42ea81c Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 2 Jul 2024 20:50:36 +0300 Subject: [PATCH 03/10] Have the desktop version open full screen --- lib/constants.dart | 1 + lib/main.dart | 4 ++++ lib/ui/app/window_manager.dart | 27 +++++++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/lib/constants.dart b/lib/constants.dart index 2bf534ab25..5ee498a89f 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -118,6 +118,7 @@ const String kSharedPrefUrl = 'url'; const String kSharedPrefToken = 'checksum'; const String kSharedPrefWidth = 'width'; const String kSharedPrefHeight = 'height'; +const String kSharedPrefMaximized = 'maximized'; const String kProductProPlanMonth = 'pro_plan'; const String kProductEnterprisePlanMonth_2 = 'enterprise_plan'; diff --git a/lib/main.dart b/lib/main.dart index 7ea85e3ffa..c6c9b13ac0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -125,6 +125,10 @@ void main({bool isTesting = false}) async { ), () async { await windowManager.show(); await windowManager.focus(); + + if (prefs.getBool(kSharedPrefMaximized) == true) { + windowManager.maximize(); + } }); } diff --git a/lib/ui/app/window_manager.dart b/lib/ui/app/window_manager.dart index e0532084c5..bc31005fc2 100644 --- a/lib/ui/app/window_manager.dart +++ b/lib/ui/app/window_manager.dart @@ -73,6 +73,33 @@ class _WindowManagerState extends State with WindowListener { } } + @override + void onWindowMaximize() async { + if (!isDesktopOS()) { + return; + } + + final prefs = await SharedPreferences.getInstance(); + prefs.setBool(kSharedPrefMaximized, true); + } + + @override + void onWindowUnmaximize() async { + if (!isDesktopOS()) { + return; + } + + // This method is auto called when the app starts, we skip it + // if the app state hasn't been loaded yet + final store = StoreProvider.of(navigatorKey.currentContext!); + if (!store.state.isLoaded) { + return; + } + + final prefs = await SharedPreferences.getInstance(); + prefs.setBool(kSharedPrefMaximized, false); + } + @override void dispose() { if (isDesktopOS()) { From 35e6492fe47bc22f0f101391fa98948a58fe2832 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 2 Jul 2024 20:51:06 +0300 Subject: [PATCH 04/10] Code clean up --- lib/ui/app/window_manager.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/ui/app/window_manager.dart b/lib/ui/app/window_manager.dart index bc31005fc2..3280edde99 100644 --- a/lib/ui/app/window_manager.dart +++ b/lib/ui/app/window_manager.dart @@ -25,10 +25,6 @@ class _WindowManagerState extends State with WindowListener { _initManager(); } - if (isApple()) { - _initWidgets(); - } - super.initState(); } @@ -37,12 +33,6 @@ class _WindowManagerState extends State with WindowListener { setState(() {}); } - void _initWidgets() async { - //print("## SET DATA"); - //await UserDefaults.setString('widgetData', 'hello', 'group.com.invoiceninja.app'); - //await WidgetKit.reloadAllTimelines(); - } - @override void onWindowResize() async { if (!isDesktopOS()) { From 99ebe2d8e91528d4acc27a42416a73bbb5a71d9d Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 3 Jul 2024 10:18:25 +0300 Subject: [PATCH 05/10] Update Snapcraft meta data --- snap/gui/invoiceninja.desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/gui/invoiceninja.desktop b/snap/gui/invoiceninja.desktop index 547c37d7ab..2cb1a0f280 100644 --- a/snap/gui/invoiceninja.desktop +++ b/snap/gui/invoiceninja.desktop @@ -1,8 +1,8 @@ [Desktop Entry] Name=Invoice Ninja -Comment=Online Invoicing +Comment=Create invoices, accept payments, track expenses & time tasks Exec=invoiceninja Icon=${SNAP}/meta/gui/invoiceninja.png Terminal=false Type=Application -Categories=Office; \ No newline at end of file +Categories=Office;Finance; \ No newline at end of file From 211b22e02da0c09bfb3d2209b81ed3ed512bf46f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 3 Jul 2024 10:20:11 +0300 Subject: [PATCH 06/10] Snap: create a launcher in the system menu --- snap/snapcraft.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 107e7f933f..e1308398c8 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -37,4 +37,6 @@ parts: invoiceninja: source: . plugin: flutter - flutter-target: lib/main.dart \ No newline at end of file + flutter-target: lib/main.dart + organize: + invoiceninja.desktop: snap/gui/invoiceninja.desktop \ No newline at end of file From 4ef2767c14ef1e5651b84c613e24cd585f1532e5 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 3 Jul 2024 10:25:12 +0300 Subject: [PATCH 07/10] Add localization labels --- lib/constants.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/constants.dart b/lib/constants.dart index 5ee498a89f..8094e05fc2 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -955,6 +955,8 @@ List kCustomLabels = [ 'payment_date', 'phone', 'po_number', + 'product', + 'products', 'quantity', 'quote', 'rate', From e1eec9ae2b027e0c32bbd6b607a35bf9d546d4e8 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 3 Jul 2024 13:14:06 +0300 Subject: [PATCH 08/10] Refactor payment settings --- lib/ui/settings/company_details.dart | 81 ---- lib/ui/settings/company_details_vm.dart | 9 - lib/ui/settings/payment_settings.dart | 540 ++++++++++++++--------- lib/ui/settings/payment_settings_vm.dart | 9 + lib/ui/settings/settings_list.dart | 4 +- lib/ui/settings/workflow_settings.dart | 10 + lib/utils/i18n.dart | 5 + 7 files changed, 355 insertions(+), 303 deletions(-) diff --git a/lib/ui/settings/company_details.dart b/lib/ui/settings/company_details.dart index b61b9a7c60..abebb9c79c 100644 --- a/lib/ui/settings/company_details.dart +++ b/lib/ui/settings/company_details.dart @@ -12,7 +12,6 @@ import 'package:file_picker/file_picker.dart'; // Project imports: import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/main_app.dart'; -import 'package:invoiceninja_flutter/redux/payment_term/payment_term_selectors.dart'; import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:invoiceninja_flutter/redux/static/static_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/blank_screen.dart'; @@ -23,7 +22,6 @@ import 'package:invoiceninja_flutter/ui/app/entity_dropdown.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_form.dart'; -import 'package:invoiceninja_flutter/ui/app/forms/bool_dropdown_button.dart'; import 'package:invoiceninja_flutter/ui/app/forms/custom_field.dart'; import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; import 'package:invoiceninja_flutter/ui/app/forms/design_picker.dart'; @@ -33,7 +31,6 @@ import 'package:invoiceninja_flutter/ui/settings/company_details_vm.dart'; import 'package:invoiceninja_flutter/utils/completers.dart'; import 'package:invoiceninja_flutter/utils/dialogs.dart'; import 'package:invoiceninja_flutter/utils/files.dart'; -import 'package:invoiceninja_flutter/utils/icons.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; class CompanyDetails extends StatefulWidget { @@ -592,70 +589,6 @@ class _CompanyDetailsState extends State ScrollableListView( primary: true, children: [ - FormCard( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (company.isModuleEnabled(EntityType.invoice)) - AppDropdownButton( - showBlank: true, - labelText: localization.invoicePaymentTerms, - items: memoizedDropdownPaymentTermList( - state.paymentTermState.map, - state.paymentTermState.list) - .map((paymentTermId) { - final paymentTerm = - state.paymentTermState.map[paymentTermId]!; - return DropdownMenuItem( - child: Text(paymentTerm.numDays == 0 - ? localization.dueOnReceipt - : paymentTerm.name), - value: paymentTerm.numDays.toString(), - ); - }).toList(), - value: '${settings.defaultPaymentTerms}', - onChanged: (dynamic numDays) { - viewModel.onSettingsChanged(settings.rebuild((b) => b - ..defaultPaymentTerms = - numDays == null ? null : '$numDays')); - }, - ), - if (company.isModuleEnabled(EntityType.quote)) - AppDropdownButton( - showBlank: true, - labelText: localization.quoteValidUntil, - items: memoizedDropdownPaymentTermList( - state.paymentTermState.map, - state.paymentTermState.list) - .map((paymentTermId) { - final paymentTerm = - state.paymentTermState.map[paymentTermId]!; - return DropdownMenuItem( - child: Text(paymentTerm.numDays == 0 - ? localization.dueOnReceipt - : paymentTerm.name), - value: paymentTerm.numDays.toString(), - ); - }).toList(), - value: '${settings.defaultValidUntil}', - onChanged: (dynamic numDays) { - viewModel.onSettingsChanged(settings.rebuild((b) => b - ..defaultValidUntil = - numDays == null ? null : '$numDays')); - }, - ), - ], - ), - if (!state.uiState.settingsUIState.isFiltered) - Padding( - padding: - const EdgeInsets.only(bottom: 10, left: 16, right: 16), - child: AppButton( - iconData: Icons.settings, - label: localization.configurePaymentTerms.toUpperCase(), - onPressed: () => - viewModel.onConfigurePaymentTermsPressed(context), - ), - ), if (!state.isProPlan) FormCard(children: [ if (company.isModuleEnabled(EntityType.invoice)) @@ -691,20 +624,6 @@ class _CompanyDetailsState extends State b..defaultPurchaseOrderDesignId = value!.id)), ), ]), - if (!state.settingsUIState.isFiltered) - FormCard( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - BoolDropdownButton( - value: company.useQuoteTermsOnConversion, - onChanged: (value) => viewModel.onCompanyChanged( - company.rebuild( - (b) => b..useQuoteTermsOnConversion = value)), - label: localization.useQuoteTerms, - helpLabel: localization.useQuoteTermsHelp, - iconData: getEntityIcon(EntityType.quote), - ), - ]), FormCard( isLast: true, children: [ diff --git a/lib/ui/settings/company_details_vm.dart b/lib/ui/settings/company_details_vm.dart index 9d1d4fd024..b2efadfee5 100644 --- a/lib/ui/settings/company_details_vm.dart +++ b/lib/ui/settings/company_details_vm.dart @@ -56,7 +56,6 @@ class CompanyDetailsVM { required this.onSavePressed, required this.onUploadLogo, required this.onDeleteLogo, - required this.onConfigurePaymentTermsPressed, required this.onUploadDocuments, }); @@ -141,13 +140,6 @@ class CompanyDetailsVM { store.dispatch(UploadLogoRequest( completer: completer, multipartFile: multipartFile, type: type)); }, - onConfigurePaymentTermsPressed: (context) { - if (state.paymentTermState.list.isEmpty) { - store.dispatch(ViewSettings(section: kSettingsPaymentTermEdit)); - } else { - store.dispatch(ViewSettings(section: kSettingsPaymentTerms)); - } - }, onUploadDocuments: (BuildContext context, List multipartFile, bool isPrivate) { final completer = Completer>(); @@ -176,6 +168,5 @@ class CompanyDetailsVM { final Function(BuildContext) onSavePressed; final Function(BuildContext, MultipartFile) onUploadLogo; final Function(BuildContext) onDeleteLogo; - final Function(BuildContext) onConfigurePaymentTermsPressed; final Function(BuildContext, List, bool) onUploadDocuments; } diff --git a/lib/ui/settings/payment_settings.dart b/lib/ui/settings/payment_settings.dart index b13b121f3b..37727cc4de 100644 --- a/lib/ui/settings/payment_settings.dart +++ b/lib/ui/settings/payment_settings.dart @@ -1,10 +1,14 @@ // Flutter imports: import 'package:flutter/material.dart'; +import 'package:flutter_redux/flutter_redux.dart'; // Project imports: import 'package:invoiceninja_flutter/data/models/company_model.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/data/models/settings_model.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/payment_term/payment_term_selectors.dart'; +import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:invoiceninja_flutter/redux/static/static_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/autobill_dropdown_menu_item.dart'; import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart'; @@ -15,6 +19,7 @@ import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_form.dart'; import 'package:invoiceninja_flutter/ui/app/forms/bool_dropdown_button.dart'; import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; +import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; import 'package:invoiceninja_flutter/ui/settings/payment_settings_vm.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; @@ -31,10 +36,12 @@ class PaymentSettings extends StatefulWidget { _PaymentSettingsState createState() => _PaymentSettingsState(); } -class _PaymentSettingsState extends State { +class _PaymentSettingsState extends State + with SingleTickerProviderStateMixin { static final GlobalKey _formKey = GlobalKey(debugLabel: '_paymentSettings'); FocusScopeNode? _focusNode; + TabController? _controller; final _minimumUnderPaymentAmountController = TextEditingController(); final _minimumPaymentAmountController = TextEditingController(); List _controllers = []; @@ -43,6 +50,21 @@ class _PaymentSettingsState extends State { void initState() { super.initState(); _focusNode = FocusScopeNode(); + + final state = widget.viewModel.state; + final settingsUIState = state.settingsUIState; + + _controller = TabController( + vsync: this, + length: 3, + initialIndex: settingsUIState.tabIndex, + ); + _controller!.addListener(_onTabChanged); + } + + void _onTabChanged() { + final store = StoreProvider.of(context); + store.dispatch(UpdateSettingsTab(tabIndex: _controller!.index)); } @override @@ -98,238 +120,334 @@ class _PaymentSettingsState extends State { return EditScaffold( title: localization.paymentSettings, onSavePressed: viewModel.onSavePressed, - body: AppForm( + appBarBottom: TabBar( + key: ValueKey(state.settingsUIState.updatedAt), + controller: _controller, + tabs: [ + Tab( + text: localization.general, + ), + Tab( + text: localization.defaults, + ), + Tab( + text: localization.emails, + ), + ], + ), + body: AppTabForm( formKey: _formKey, focusNode: _focusNode, + tabController: _controller, children: [ - FormCard( + ScrollableListView( + primary: true, children: [ - AppDropdownButton( - blankValue: null, - showBlank: true, - labelText: localization.autoBillStandardInvoices, - value: state.settingsUIState.isFiltered - ? settings.autoBillStandardInvoices - : settings.autoBillStandardInvoices ?? false, - onChanged: (dynamic value) => viewModel.onSettingsChanged( - settings - .rebuild((b) => b..autoBillStandardInvoices = value)), - items: [ - DropdownMenuItem( - child: Text(localization.enabled), value: true), - DropdownMenuItem( - child: Text(localization.off), value: false), - ]), - AppDropdownButton( - labelText: localization.autoBillRecurringInvoices, - value: settings.autoBill, - onChanged: (dynamic value) => viewModel.onSettingsChanged( - settings.rebuild((b) => b..autoBill = value)), - selectedItemBuilder: (settings.autoBill ?? '').isEmpty - ? null - : (context) => [ - SettingsEntity.AUTO_BILL_ALWAYS, - SettingsEntity.AUTO_BILL_OPT_OUT, - SettingsEntity.AUTO_BILL_OPT_IN, - SettingsEntity.AUTO_BILL_OFF, - ] - .map((type) => Text(localization.lookup(type))) - .toList(), - items: [ - SettingsEntity.AUTO_BILL_ALWAYS, - SettingsEntity.AUTO_BILL_OPT_OUT, - SettingsEntity.AUTO_BILL_OPT_IN, - SettingsEntity.AUTO_BILL_OFF - ] - .map((value) => DropdownMenuItem( - child: AutobillDropdownMenuItem(type: value), - value: value, - )) - .toList()), - AppDropdownButton( - labelText: localization.autoBillOn, - value: settings.autoBillDate, - onChanged: (dynamic value) => viewModel.onSettingsChanged( - settings.rebuild((b) => b..autoBillDate = value)), - items: [ - DropdownMenuItem( - child: Text(localization.sendDate), - value: SettingsEntity.AUTO_BILL_ON_SEND_DATE, - ), - DropdownMenuItem( - child: Text(localization.dueDate), - value: SettingsEntity.AUTO_BILL_ON_DUE_DATE, + FormCard( + children: [ + AppDropdownButton( + blankValue: null, + showBlank: true, + labelText: localization.autoBillStandardInvoices, + value: state.settingsUIState.isFiltered + ? settings.autoBillStandardInvoices + : settings.autoBillStandardInvoices ?? false, + onChanged: (dynamic value) => viewModel.onSettingsChanged( + settings.rebuild( + (b) => b..autoBillStandardInvoices = value)), + items: [ + DropdownMenuItem( + child: Text(localization.enabled), value: true), + DropdownMenuItem( + child: Text(localization.off), value: false), + ]), + AppDropdownButton( + labelText: localization.autoBillRecurringInvoices, + value: settings.autoBill, + onChanged: (dynamic value) => viewModel.onSettingsChanged( + settings.rebuild((b) => b..autoBill = value)), + selectedItemBuilder: (settings.autoBill ?? '').isEmpty + ? null + : (context) => [ + SettingsEntity.AUTO_BILL_ALWAYS, + SettingsEntity.AUTO_BILL_OPT_OUT, + SettingsEntity.AUTO_BILL_OPT_IN, + SettingsEntity.AUTO_BILL_OFF, + ] + .map( + (type) => Text(localization.lookup(type))) + .toList(), + items: [ + SettingsEntity.AUTO_BILL_ALWAYS, + SettingsEntity.AUTO_BILL_OPT_OUT, + SettingsEntity.AUTO_BILL_OPT_IN, + SettingsEntity.AUTO_BILL_OFF + ] + .map((value) => DropdownMenuItem( + child: AutobillDropdownMenuItem(type: value), + value: value, + )) + .toList()), + AppDropdownButton( + labelText: localization.autoBillOn, + value: settings.autoBillDate, + onChanged: (dynamic value) => viewModel.onSettingsChanged( + settings.rebuild((b) => b..autoBillDate = value)), + items: [ + DropdownMenuItem( + child: Text(localization.sendDate), + value: SettingsEntity.AUTO_BILL_ON_SEND_DATE, + ), + DropdownMenuItem( + child: Text(localization.dueDate), + value: SettingsEntity.AUTO_BILL_ON_DUE_DATE, + ), + ], ), + AppDropdownButton( + labelText: localization.useAvailablePayments, + value: settings.useUnappliedPayment, + onChanged: (dynamic value) { + viewModel.onSettingsChanged(settings + .rebuild((b) => b..useUnappliedPayment = value)); + }, + items: [ + DropdownMenuItem( + child: Text(localization.always), + value: CompanyEntity.USE_ALWAYS, + ), + DropdownMenuItem( + child: Text(localization.showOption), + value: CompanyEntity.USE_OPTION, + ), + DropdownMenuItem( + child: Text(localization.off), + value: CompanyEntity.USE_OFF, + ), + ]), + AppDropdownButton( + labelText: localization.useAvailableCredits, + value: settings.useCreditsPayment, + onChanged: (dynamic value) { + viewModel.onSettingsChanged(settings + .rebuild((b) => b..useCreditsPayment = value)); + }, + items: [ + DropdownMenuItem( + child: Text(localization.always), + value: CompanyEntity.USE_ALWAYS, + ), + DropdownMenuItem( + child: Text(localization.showOption), + value: CompanyEntity.USE_OPTION, + ), + DropdownMenuItem( + child: Text(localization.off), + value: CompanyEntity.USE_OFF, + ), + ]), ], ), - AppDropdownButton( - labelText: localization.useAvailablePayments, - value: settings.useUnappliedPayment, - onChanged: (dynamic value) { - viewModel.onSettingsChanged(settings - .rebuild((b) => b..useUnappliedPayment = value)); - }, - items: [ - DropdownMenuItem( - child: Text(localization.always), - value: CompanyEntity.USE_ALWAYS, - ), - DropdownMenuItem( - child: Text(localization.showOption), - value: CompanyEntity.USE_OPTION, - ), - DropdownMenuItem( - child: Text(localization.off), - value: CompanyEntity.USE_OFF, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: AppButton( + iconData: Icons.settings, + label: localization.configureGateways.toUpperCase(), + onPressed: () => + viewModel.onConfigureGatewaysPressed(context), + ), + ), + SizedBox(height: 8), + FormCard( + isLast: true, + children: [ + if (!state.uiState.settingsUIState.isFiltered) + BoolDropdownButton( + label: localization.adminInitiatedPayments, + value: company.enableApplyingPayments, + helpLabel: localization.adminInitiatedPaymentsHelp, + onChanged: (value) => viewModel.onCompanyChanged(company + .rebuild((b) => b..enableApplyingPayments = value)), ), - ]), - AppDropdownButton( - labelText: localization.useAvailableCredits, - value: settings.useCreditsPayment, - onChanged: (dynamic value) { - viewModel.onSettingsChanged( - settings.rebuild((b) => b..useCreditsPayment = value)); - }, - items: [ - DropdownMenuItem( - child: Text(localization.always), - value: CompanyEntity.USE_ALWAYS, + BoolDropdownButton( + label: localization.clientInitiatedPayments, + value: settings.clientInitiatedPayments, + helpLabel: localization.clientInitiatedPaymentsHelp, + onChanged: (value) => viewModel.onSettingsChanged(settings + .rebuild((b) => b..clientInitiatedPayments = value)), + ), + if (settings.clientInitiatedPayments == true) + Padding( + padding: const EdgeInsets.only(top: 16), + child: DecoratedFormField( + label: localization.minimumPaymentAmount, + controller: _minimumPaymentAmountController, + isMoney: true, + keyboardType: TextInputType.numberWithOptions( + decimal: true, signed: true), + ), ), - DropdownMenuItem( - child: Text(localization.showOption), - value: CompanyEntity.USE_OPTION, + BoolDropdownButton( + label: localization.allowOverPayment, + value: settings.clientPortalAllowOverPayment, + helpLabel: localization.allowOverPaymentHelp, + onChanged: (value) => viewModel.onSettingsChanged( + settings.rebuild( + (b) => b..clientPortalAllowOverPayment = value)), + ), + BoolDropdownButton( + label: localization.allowUnderPayment, + value: settings.clientPortalAllowUnderPayment, + helpLabel: localization.allowUnderPaymentHelp, + onChanged: (value) => viewModel.onSettingsChanged( + settings.rebuild( + (b) => b..clientPortalAllowUnderPayment = value)), + ), + if (settings.clientPortalAllowUnderPayment == true) + Padding( + padding: const EdgeInsets.only(top: 16), + child: DecoratedFormField( + label: localization.minimumUnderPaymentAmount, + controller: _minimumUnderPaymentAmountController, + isMoney: true, + keyboardType: TextInputType.numberWithOptions( + decimal: true, signed: true), + ), ), - DropdownMenuItem( - child: Text(localization.off), - value: CompanyEntity.USE_OFF, + if (!state.uiState.settingsUIState.isFiltered) + BoolDropdownButton( + label: localization.convertCurrency, + value: company.convertPaymentCurrency, + helpLabel: localization.convertPaymentCurrencyHelp, + onChanged: (value) => viewModel.onCompanyChanged(company + .rebuild((b) => b..convertPaymentCurrency = value)), ), - ]), - EntityDropdown( - entityType: EntityType.paymentType, - entityList: - memoizedPaymentTypeList(state.staticState.paymentTypeMap), - labelText: localization.defaultPaymentType, - entityId: settings.defaultPaymentTypeId, - onSelected: (paymentType) => viewModel.onSettingsChanged( - settings.rebuild( - (b) => b..defaultPaymentTypeId = paymentType?.id)), + ], ), ], ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: AppButton( - iconData: Icons.settings, - label: localization.configureGateways.toUpperCase(), - onPressed: () => viewModel.onConfigureGatewaysPressed(context), - ), - ), - SizedBox(height: 8), - FormCard(children: [ - if (!state.uiState.settingsUIState.isFiltered) - BoolDropdownButton( - label: localization.adminInitiatedPayments, - value: company.enableApplyingPayments, - helpLabel: localization.adminInitiatedPaymentsHelp, - onChanged: (value) => viewModel.onCompanyChanged( - company.rebuild((b) => b..enableApplyingPayments = value)), - ), - BoolDropdownButton( - label: localization.clientInitiatedPayments, - value: settings.clientInitiatedPayments, - helpLabel: localization.clientInitiatedPaymentsHelp, - onChanged: (value) => viewModel.onSettingsChanged( - settings.rebuild((b) => b..clientInitiatedPayments = value)), - ), - if (settings.clientInitiatedPayments == true) - Padding( - padding: const EdgeInsets.only(top: 16), - child: DecoratedFormField( - label: localization.minimumPaymentAmount, - controller: _minimumPaymentAmountController, - isMoney: true, - keyboardType: TextInputType.numberWithOptions( - decimal: true, signed: true), + ScrollableListView(children: [ + FormCard( + children: [ + EntityDropdown( + entityType: EntityType.paymentType, + entityList: + memoizedPaymentTypeList(state.staticState.paymentTypeMap), + labelText: localization.defaultPaymentType, + entityId: settings.defaultPaymentTypeId, + onSelected: (paymentType) => viewModel.onSettingsChanged( + settings.rebuild( + (b) => b..defaultPaymentTypeId = paymentType?.id)), ), - ), - BoolDropdownButton( - label: localization.allowOverPayment, - value: settings.clientPortalAllowOverPayment, - helpLabel: localization.allowOverPaymentHelp, - onChanged: (value) => viewModel.onSettingsChanged(settings - .rebuild((b) => b..clientPortalAllowOverPayment = value)), - ), - BoolDropdownButton( - label: localization.allowUnderPayment, - value: settings.clientPortalAllowUnderPayment, - helpLabel: localization.allowUnderPaymentHelp, - onChanged: (value) => viewModel.onSettingsChanged(settings - .rebuild((b) => b..clientPortalAllowUnderPayment = value)), + if (company.isModuleEnabled(EntityType.invoice)) + AppDropdownButton( + showBlank: true, + labelText: localization.invoicePaymentTerms, + items: memoizedDropdownPaymentTermList( + state.paymentTermState.map, + state.paymentTermState.list) + .map((paymentTermId) { + final paymentTerm = + state.paymentTermState.map[paymentTermId]!; + return DropdownMenuItem( + child: Text(paymentTerm.numDays == 0 + ? localization.dueOnReceipt + : paymentTerm.name), + value: paymentTerm.numDays.toString(), + ); + }).toList(), + value: '${settings.defaultPaymentTerms}', + onChanged: (dynamic numDays) { + viewModel.onSettingsChanged(settings.rebuild((b) => b + ..defaultPaymentTerms = + numDays == null ? null : '$numDays')); + }, + ), + if (company.isModuleEnabled(EntityType.quote)) + AppDropdownButton( + showBlank: true, + labelText: localization.quoteValidUntil, + items: memoizedDropdownPaymentTermList( + state.paymentTermState.map, + state.paymentTermState.list) + .map((paymentTermId) { + final paymentTerm = + state.paymentTermState.map[paymentTermId]!; + return DropdownMenuItem( + child: Text(paymentTerm.numDays == 0 + ? localization.dueOnReceipt + : paymentTerm.name), + value: paymentTerm.numDays.toString(), + ); + }).toList(), + value: '${settings.defaultValidUntil}', + onChanged: (dynamic numDays) { + viewModel.onSettingsChanged(settings.rebuild((b) => b + ..defaultValidUntil = + numDays == null ? null : '$numDays')); + }, + ), + ], ), - if (settings.clientPortalAllowUnderPayment == true) + if (!state.uiState.settingsUIState.isFiltered) Padding( - padding: const EdgeInsets.only(top: 16), - child: DecoratedFormField( - label: localization.minimumUnderPaymentAmount, - controller: _minimumUnderPaymentAmountController, - isMoney: true, - keyboardType: TextInputType.numberWithOptions( - decimal: true, signed: true), + padding: const EdgeInsets.only(bottom: 10, left: 16, right: 16), + child: AppButton( + iconData: Icons.settings, + label: localization.configurePaymentTerms.toUpperCase(), + onPressed: () => + viewModel.onConfigurePaymentTermsPressed(context), ), ), - if (!state.uiState.settingsUIState.isFiltered) - BoolDropdownButton( - label: localization.convertCurrency, - value: company.convertPaymentCurrency, - helpLabel: localization.convertPaymentCurrencyHelp, - onChanged: (value) => viewModel.onCompanyChanged( - company.rebuild((b) => b..convertPaymentCurrency = value)), - ), ]), - FormCard( - isLast: true, - children: [ - BoolDropdownButton( - value: settings.clientOnlinePaymentNotification, - onChanged: (value) => viewModel.onSettingsChanged( - settings.rebuild( - (b) => b..clientOnlinePaymentNotification = value)), - label: localization.onlinePaymentEmail, - helpLabel: localization.onlinePaymentEmailHelp, - iconData: Icons.email, - ), - BoolDropdownButton( - value: settings.clientManualPaymentNotification, - onChanged: (value) => viewModel.onSettingsChanged( - settings.rebuild( - (b) => b..clientManualPaymentNotification = value)), - label: localization.manualPaymentEmail, - helpLabel: localization.manualPaymentEmailHelp, - iconData: Icons.email, - ), - BoolDropdownButton( - value: settings.clientMarkPaidPaymentNotification, - onChanged: (value) => viewModel.onSettingsChanged( - settings.rebuild( - (b) => b..clientMarkPaidPaymentNotification = value)), - label: localization.markPaidPaymentEmail, - helpLabel: localization.markPaidPaymentEmailHelp, - iconData: Icons.email, - ), - if (!state.settingsUIState.isFiltered) SizedBox(height: 10), - BoolDropdownButton( - value: state.settingsUIState.isFiltered - ? settings.paymentEmailAllContacts - : settings.paymentEmailAllContacts ?? false, - onChanged: (value) => viewModel.onSettingsChanged(settings - .rebuild((b) => b..paymentEmailAllContacts = value)), - label: localization.sendEmailsTo, - iconData: Icons.email, - enabledLabel: localization.primaryContact, - disabledLabel: localization.allContacts, + ScrollableListView( + children: [ + FormCard( + isLast: true, + children: [ + BoolDropdownButton( + value: settings.clientOnlinePaymentNotification, + onChanged: (value) => viewModel.onSettingsChanged( + settings.rebuild( + (b) => b..clientOnlinePaymentNotification = value)), + label: localization.onlinePaymentEmail, + helpLabel: localization.onlinePaymentEmailHelp, + iconData: Icons.email, + ), + BoolDropdownButton( + value: settings.clientManualPaymentNotification, + onChanged: (value) => viewModel.onSettingsChanged( + settings.rebuild( + (b) => b..clientManualPaymentNotification = value)), + label: localization.manualPaymentEmail, + helpLabel: localization.manualPaymentEmailHelp, + iconData: Icons.email, + ), + BoolDropdownButton( + value: settings.clientMarkPaidPaymentNotification, + onChanged: (value) => viewModel.onSettingsChanged( + settings.rebuild((b) => + b..clientMarkPaidPaymentNotification = value)), + label: localization.markPaidPaymentEmail, + helpLabel: localization.markPaidPaymentEmailHelp, + iconData: Icons.email, + ), + if (!state.settingsUIState.isFiltered) SizedBox(height: 10), + BoolDropdownButton( + value: state.settingsUIState.isFiltered + ? settings.paymentEmailAllContacts + : settings.paymentEmailAllContacts ?? false, + onChanged: (value) => viewModel.onSettingsChanged(settings + .rebuild((b) => b..paymentEmailAllContacts = value)), + label: localization.sendEmailsTo, + iconData: Icons.email, + enabledLabel: localization.primaryContact, + disabledLabel: localization.allContacts, + ), + ], ), ], - ) + ), ], ), ); diff --git a/lib/ui/settings/payment_settings_vm.dart b/lib/ui/settings/payment_settings_vm.dart index 929601b383..a0c4095806 100644 --- a/lib/ui/settings/payment_settings_vm.dart +++ b/lib/ui/settings/payment_settings_vm.dart @@ -49,6 +49,7 @@ class PaymentSettingsVM { required this.onSettingsChanged, required this.settings, required this.onConfigureGatewaysPressed, + required this.onConfigurePaymentTermsPressed, }); static PaymentSettingsVM fromStore(Store store) { @@ -87,6 +88,13 @@ class PaymentSettingsVM { } }); }, + onConfigurePaymentTermsPressed: (context) { + if (state.paymentTermState.list.isEmpty) { + store.dispatch(ViewSettings(section: kSettingsPaymentTermEdit)); + } else { + store.dispatch(ViewSettings(section: kSettingsPaymentTerms)); + } + }, onConfigureGatewaysPressed: (context) { store.dispatch(ViewSettings(section: kSettingsCompanyGateways)); }, @@ -99,5 +107,6 @@ class PaymentSettingsVM { final Function(BuildContext) onSavePressed; final Function(CompanyEntity) onCompanyChanged; final Function(SettingsEntity) onSettingsChanged; + final Function(BuildContext) onConfigurePaymentTermsPressed; final Function(BuildContext) onConfigureGatewaysPressed; } diff --git a/lib/ui/settings/settings_list.dart b/lib/ui/settings/settings_list.dart index 4c72027bd0..c16436b42f 100644 --- a/lib/ui/settings/settings_list.dart +++ b/lib/ui/settings/settings_list.dart @@ -370,14 +370,12 @@ class SettingsSearch extends StatelessWidget { ], [ 'defaults', - 'payment_terms', 'invoice_terms', 'invoice_footer', 'quote_terms', 'quote_footer', 'credit_terms', 'credit_footer', - 'use_quote_terms#2022-05-17', ], [ 'default_documents', @@ -418,6 +416,7 @@ class SettingsSearch extends StatelessWidget { ], kSettingsPaymentSettings: [ [ + 'payment_terms', 'company_gateways', 'auto_bill', 'auto_bill_on', @@ -497,6 +496,7 @@ class SettingsSearch extends StatelessWidget { 'auto_archive_paid_invoices', 'auto_archive_cancelled_invoices', 'lock_invoices', + 'use_quote_terms#2022-05-17', ], [ 'auto_convert', diff --git a/lib/ui/settings/workflow_settings.dart b/lib/ui/settings/workflow_settings.dart index be74ac5013..b2ae674fd6 100644 --- a/lib/ui/settings/workflow_settings.dart +++ b/lib/ui/settings/workflow_settings.dart @@ -178,6 +178,16 @@ class _WorkflowSettingsState extends State settings.rebuild((b) => b..autoArchiveQuote = value)), iconData: Icons.archive, ), + if (!state.settingsUIState.isFiltered) + BoolDropdownButton( + value: company.useQuoteTermsOnConversion, + onChanged: (value) => viewModel.onCompanyChanged( + company.rebuild( + (b) => b..useQuoteTermsOnConversion = value)), + label: localization.useQuoteTerms, + helpLabel: localization.useQuoteTermsHelp, + iconData: getEntityIcon(EntityType.quote), + ), ], ), ], diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index b7a9de205c..59263b6834 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,6 +18,7 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'emails': 'Emails', 'latest_requires_php_version': 'Note: the latest version requires PHP :version', 'quote_reminder1': 'First Quote Reminder', @@ -117515,6 +117516,10 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]!['latest_requires_php_version'] ?? _localizedValues['en']!['latest_requires_php_version']!; + String get emails => + _localizedValues[localeCode]!['emails'] ?? + _localizedValues['en']!['emails']!; + // STARTER: lang field - do not remove comment String lookup(String? key, {String? overrideLocaleCode}) { From f50f20cce89be3af4a496d6e5b6cc3eccc313160 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 3 Jul 2024 13:17:52 +0300 Subject: [PATCH 09/10] Refactor payment settings --- lib/ui/payment_term/payment_term_screen.dart | 4 ++-- lib/ui/settings/settings_list.dart | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/ui/payment_term/payment_term_screen.dart b/lib/ui/payment_term/payment_term_screen.dart index 4175a74b7f..2cfaab2a41 100644 --- a/lib/ui/payment_term/payment_term_screen.dart +++ b/lib/ui/payment_term/payment_term_screen.dart @@ -38,8 +38,8 @@ class PaymentTermScreen extends StatelessWidget { return ListScaffold( entityType: EntityType.paymentTerm, onHamburgerLongPress: () => store.dispatch(StartPaymentTermMultiselect()), - onCancelSettingsSection: kSettingsCompanyDetails, - onCancelSettingsIndex: 3, + onCancelSettingsSection: kSettingsPaymentSettings, + onCancelSettingsIndex: 1, appBarTitle: ListFilter( key: ValueKey( '__filter_${state.paymentTermListState.filterClearedAt}__'), diff --git a/lib/ui/settings/settings_list.dart b/lib/ui/settings/settings_list.dart index c16436b42f..13b317cc9c 100644 --- a/lib/ui/settings/settings_list.dart +++ b/lib/ui/settings/settings_list.dart @@ -416,22 +416,26 @@ class SettingsSearch extends StatelessWidget { ], kSettingsPaymentSettings: [ [ - 'payment_terms', 'company_gateways', 'auto_bill', 'auto_bill_on', - 'payment_type', - 'online_payment_email', - 'manual_payment_email', 'use_available_credits', 'admin_initiated_payments#2022-06-06', 'allow_over_payment', 'allow_under_payment', 'auto_bill_standard_invoices#2023-01-17', 'client_initiated_payments#2023-03-20', - 'send_emails_to#2023-11-30', 'use_available_payments#2024-02-19', - ] + ], + [ + 'payment_type', + 'payment_terms', + ], + [ + 'online_payment_email', + 'manual_payment_email', + 'send_emails_to#2023-11-30', + ], ], kSettingsTaxSettings: [ [ @@ -496,10 +500,10 @@ class SettingsSearch extends StatelessWidget { 'auto_archive_paid_invoices', 'auto_archive_cancelled_invoices', 'lock_invoices', - 'use_quote_terms#2022-05-17', ], [ 'auto_convert', + 'use_quote_terms#2022-05-17', ], ], kSettingsImportExport: [ From cee9dac2c53fb360d4a1459d0df9a01db960005f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 3 Jul 2024 13:29:06 +0300 Subject: [PATCH 10/10] Add merge e-invoice to PDF --- lib/data/models/settings_model.dart | 3 +++ lib/data/models/settings_model.g.dart | 32 +++++++++++++++++++++---- lib/ui/settings/e_invoice_settings.dart | 8 +++++++ lib/ui/settings/settings_list.dart | 1 + lib/utils/i18n.dart | 5 ++++ 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/lib/data/models/settings_model.dart b/lib/data/models/settings_model.dart index 636b5b749f..8ad4867cdb 100644 --- a/lib/data/models/settings_model.dart +++ b/lib/data/models/settings_model.dart @@ -861,6 +861,9 @@ abstract class SettingsEntity @BuiltValueField(wireName: 'quote_late_fee_percent1') double? get quoteLateFeePercent1; + @BuiltValueField(wireName: 'merge_e_invoice_to_pdf') + bool? get mergeEInvoiceToPdf; + bool? get taskRoundingEnabled => taskRoundToNearest == null ? null : taskRoundToNearest != 1; diff --git a/lib/data/models/settings_model.g.dart b/lib/data/models/settings_model.g.dart index 1d07164acf..b766b1615f 100644 --- a/lib/data/models/settings_model.g.dart +++ b/lib/data/models/settings_model.g.dart @@ -1674,6 +1674,13 @@ class _$SettingsEntitySerializer ..add(serializers.serialize(value, specifiedType: const FullType(double))); } + value = object.mergeEInvoiceToPdf; + if (value != null) { + result + ..add('merge_e_invoice_to_pdf') + ..add( + serializers.serialize(value, specifiedType: const FullType(bool))); + } return result; } @@ -2648,6 +2655,10 @@ class _$SettingsEntitySerializer result.quoteLateFeePercent1 = serializers.deserialize(value, specifiedType: const FullType(double)) as double?; break; + case 'merge_e_invoice_to_pdf': + result.mergeEInvoiceToPdf = serializers.deserialize(value, + specifiedType: const FullType(bool)) as bool?; + break; } } @@ -3203,6 +3214,8 @@ class _$SettingsEntity extends SettingsEntity { final double? quoteLateFeeAmount1; @override final double? quoteLateFeePercent1; + @override + final bool? mergeEInvoiceToPdf; factory _$SettingsEntity([void Function(SettingsEntityBuilder)? updates]) => (new SettingsEntityBuilder()..update(updates))._build(); @@ -3445,7 +3458,8 @@ class _$SettingsEntity extends SettingsEntity { this.numDaysQuoteReminder1, this.scheduleQuoteReminder1, this.quoteLateFeeAmount1, - this.quoteLateFeePercent1}) + this.quoteLateFeePercent1, + this.mergeEInvoiceToPdf}) : super._(); @override @@ -3702,7 +3716,8 @@ class _$SettingsEntity extends SettingsEntity { numDaysQuoteReminder1 == other.numDaysQuoteReminder1 && scheduleQuoteReminder1 == other.scheduleQuoteReminder1 && quoteLateFeeAmount1 == other.quoteLateFeeAmount1 && - quoteLateFeePercent1 == other.quoteLateFeePercent1; + quoteLateFeePercent1 == other.quoteLateFeePercent1 && + mergeEInvoiceToPdf == other.mergeEInvoiceToPdf; } int? __hashCode; @@ -3948,6 +3963,7 @@ class _$SettingsEntity extends SettingsEntity { _$hash = $jc(_$hash, scheduleQuoteReminder1.hashCode); _$hash = $jc(_$hash, quoteLateFeeAmount1.hashCode); _$hash = $jc(_$hash, quoteLateFeePercent1.hashCode); + _$hash = $jc(_$hash, mergeEInvoiceToPdf.hashCode); _$hash = $jf(_$hash); return __hashCode ??= _$hash; } @@ -4197,7 +4213,8 @@ class _$SettingsEntity extends SettingsEntity { ..add('numDaysQuoteReminder1', numDaysQuoteReminder1) ..add('scheduleQuoteReminder1', scheduleQuoteReminder1) ..add('quoteLateFeeAmount1', quoteLateFeeAmount1) - ..add('quoteLateFeePercent1', quoteLateFeePercent1)) + ..add('quoteLateFeePercent1', quoteLateFeePercent1) + ..add('mergeEInvoiceToPdf', mergeEInvoiceToPdf)) .toString(); } } @@ -5380,6 +5397,11 @@ class SettingsEntityBuilder set quoteLateFeePercent1(double? quoteLateFeePercent1) => _$this._quoteLateFeePercent1 = quoteLateFeePercent1; + bool? _mergeEInvoiceToPdf; + bool? get mergeEInvoiceToPdf => _$this._mergeEInvoiceToPdf; + set mergeEInvoiceToPdf(bool? mergeEInvoiceToPdf) => + _$this._mergeEInvoiceToPdf = mergeEInvoiceToPdf; + SettingsEntityBuilder(); SettingsEntityBuilder get _$this { @@ -5623,6 +5645,7 @@ class SettingsEntityBuilder _scheduleQuoteReminder1 = $v.scheduleQuoteReminder1; _quoteLateFeeAmount1 = $v.quoteLateFeeAmount1; _quoteLateFeePercent1 = $v.quoteLateFeePercent1; + _mergeEInvoiceToPdf = $v.mergeEInvoiceToPdf; _$v = null; } return this; @@ -5885,7 +5908,8 @@ class SettingsEntityBuilder numDaysQuoteReminder1: numDaysQuoteReminder1, scheduleQuoteReminder1: scheduleQuoteReminder1, quoteLateFeeAmount1: quoteLateFeeAmount1, - quoteLateFeePercent1: quoteLateFeePercent1); + quoteLateFeePercent1: quoteLateFeePercent1, + mergeEInvoiceToPdf: mergeEInvoiceToPdf); } catch (_) { late String _$failedField; try { diff --git a/lib/ui/settings/e_invoice_settings.dart b/lib/ui/settings/e_invoice_settings.dart index f13a4e410c..91ea04dca0 100644 --- a/lib/ui/settings/e_invoice_settings.dart +++ b/lib/ui/settings/e_invoice_settings.dart @@ -140,6 +140,14 @@ class _EInvoiceSettingsState extends State { settings.rebuild((b) => b..enableEInvoice = value)), ), if (settings.enableEInvoice == true) ...[ + BoolDropdownButton( + label: localization.mergeToPdf, + value: settings.mergeEInvoiceToPdf, + iconData: MdiIcons.callMerge, + onChanged: (value) => viewModel.onSettingsChanged( + settings.rebuild((b) => b..mergeEInvoiceToPdf = value), + ), + ), Padding( padding: EdgeInsets.only(top: settingsUIState.isFiltered ? 0 : 12), diff --git a/lib/ui/settings/settings_list.dart b/lib/ui/settings/settings_list.dart index 13b317cc9c..bf1e53bbcc 100644 --- a/lib/ui/settings/settings_list.dart +++ b/lib/ui/settings/settings_list.dart @@ -665,6 +665,7 @@ class SettingsSearch extends StatelessWidget { kSettingsEInvoiceSettings: [ [ 'e_invoice_settings#2024-05-20', + 'merge_to_pdf#2024-07-03', ], ], kSettingsTransactionRules: [ diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 59263b6834..4afe6faa0f 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,6 +18,7 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'merge_to_pdf': 'Merge to PDF', 'emails': 'Emails', 'latest_requires_php_version': 'Note: the latest version requires PHP :version', @@ -117520,6 +117521,10 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]!['emails'] ?? _localizedValues['en']!['emails']!; + String get mergeToPdf => + _localizedValues[localeCode]!['merge_to_pdf'] ?? + _localizedValues['en']!['merge_to_pdf']!; + // STARTER: lang field - do not remove comment String lookup(String? key, {String? overrideLocaleCode}) {