View Javadoc
1   /*
2    * MoneyWise: Finance Application
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.moneywise.ui.dialog;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
22  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
23  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoan;
25  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoan.MoneyWiseLoanList;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoanCategory;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoanCategory.MoneyWiseLoanCategoryList;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee;
29  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee.MoneyWisePayeeList;
30  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAccountInfoClass;
31  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAssetCategory;
32  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
33  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency.MoneyWiseCurrencyList;
34  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseLoanCategoryClass;
35  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
36  import io.github.tonywasher.joceanus.moneywise.ui.MoneyWiseIcon;
37  import io.github.tonywasher.joceanus.moneywise.ui.base.MoneyWiseAssetTable;
38  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
39  import io.github.tonywasher.joceanus.prometheus.ui.fieldset.PrometheusFieldSet;
40  import io.github.tonywasher.joceanus.prometheus.ui.fieldset.PrometheusFieldSetEvent;
41  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
42  import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControl.TethysUIIconMapSet;
43  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
44  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUICharArrayEditField;
45  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUICharArrayTextAreaField;
46  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIIconButtonField;
47  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIMoneyEditField;
48  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIScrollButtonField;
49  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIStringEditField;
50  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIFieldFactory;
51  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
52  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
53  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollSubMenu;
54  
55  import java.util.HashMap;
56  import java.util.Iterator;
57  import java.util.Map;
58  
59  /**
60   * Panel to display/edit/create a Loan.
61   */
62  public class MoneyWiseLoanDialog
63          extends MoneyWiseAssetDialog<MoneyWiseLoan> {
64      /**
65       * The fieldSet.
66       */
67      private final PrometheusFieldSet<MoneyWiseLoan> theFieldSet;
68  
69      /**
70       * The Closed State.
71       */
72      private Boolean theClosedState = Boolean.FALSE;
73  
74      /**
75       * Constructor.
76       *
77       * @param pFactory the GUI factory
78       * @param pEditSet the edit set
79       * @param pOwner   the owning table
80       */
81      public MoneyWiseLoanDialog(final TethysUIFactory<?> pFactory,
82                                 final PrometheusEditSet pEditSet,
83                                 final MoneyWiseAssetTable<MoneyWiseLoan> pOwner) {
84          /* Initialise the panel */
85          super(pFactory, pEditSet, pOwner);
86  
87          /* Access the fieldSet */
88          theFieldSet = getFieldSet();
89          theFieldSet.setReporter(pOwner::showValidateError);
90  
91          /* Build the main panel */
92          buildMainPanel(pFactory);
93  
94          /* Build the account panel */
95          buildAccountPanel(pFactory);
96  
97          /* Build the notes panel */
98          buildNotesPanel(pFactory);
99      }
100 
101     /**
102      * Build Main subPanel.
103      *
104      * @param pFactory the GUI factory
105      */
106     private void buildMainPanel(final TethysUIFactory<?> pFactory) {
107         /* Create the text fields */
108         final TethysUIFieldFactory myFields = pFactory.fieldFactory();
109         final TethysUIStringEditField myName = myFields.newStringField();
110         final TethysUIStringEditField myDesc = myFields.newStringField();
111 
112         /* Create the buttons */
113         final TethysUIScrollButtonField<MoneyWiseAssetCategory> myCategoryButton = myFields.newScrollField(MoneyWiseAssetCategory.class);
114         final TethysUIScrollButtonField<MoneyWisePayee> myParentButton = myFields.newScrollField(MoneyWisePayee.class);
115         final TethysUIScrollButtonField<MoneyWiseCurrency> myCurrencyButton = myFields.newScrollField(MoneyWiseCurrency.class);
116         final TethysUIIconButtonField<Boolean> myClosedButton = myFields.newIconField(Boolean.class);
117 
118         /* Assign the fields to the panel */
119         theFieldSet.addField(PrometheusDataResource.DATAITEM_FIELD_NAME, myName, MoneyWiseLoan::getName);
120         theFieldSet.addField(PrometheusDataResource.DATAITEM_FIELD_DESC, myDesc, MoneyWiseLoan::getDesc);
121         theFieldSet.addField(MoneyWiseBasicResource.CATEGORY_NAME, myCategoryButton, MoneyWiseLoan::getCategory);
122         theFieldSet.addField(MoneyWiseBasicResource.ASSET_PARENT, myParentButton, MoneyWiseLoan::getParent);
123         theFieldSet.addField(MoneyWiseStaticDataType.CURRENCY, myCurrencyButton, MoneyWiseLoan::getAssetCurrency);
124         theFieldSet.addField(MoneyWiseBasicResource.ASSET_CLOSED, myClosedButton, MoneyWiseLoan::isClosed);
125 
126         /* Configure the menuBuilders */
127         myCategoryButton.setMenuConfigurator(c -> buildCategoryMenu(c, getItem()));
128         myParentButton.setMenuConfigurator(c -> buildParentMenu(c, getItem()));
129         myCurrencyButton.setMenuConfigurator(c -> buildCurrencyMenu(c, getItem()));
130         final Map<Boolean, TethysUIIconMapSet<Boolean>> myMapSets = MoneyWiseIcon.configureLockedIconButton(pFactory);
131         myClosedButton.setIconMapSet(() -> myMapSets.get(theClosedState));
132 
133         /* Configure validation checks */
134         myName.setValidator(this::isValidName);
135         myDesc.setValidator(this::isValidDesc);
136     }
137 
138     /**
139      * Build account subPanel.
140      *
141      * @param pFactory the GUI factory
142      */
143     private void buildAccountPanel(final TethysUIFactory<?> pFactory) {
144         /* Create a new panel */
145         theFieldSet.newPanel(TAB_ACCOUNT);
146 
147         /* Allocate fields */
148         final TethysUIFieldFactory myFields = pFactory.fieldFactory();
149         final TethysUICharArrayEditField mySortCode = myFields.newCharArrayField();
150         final TethysUICharArrayEditField myAccount = myFields.newCharArrayField();
151         final TethysUICharArrayEditField myReference = myFields.newCharArrayField();
152         final TethysUIMoneyEditField myOpening = myFields.newMoneyField();
153 
154         /* Assign the fields to the panel */
155         theFieldSet.addField(MoneyWiseAccountInfoClass.SORTCODE, mySortCode, MoneyWiseLoan::getSortCode);
156         theFieldSet.addField(MoneyWiseAccountInfoClass.ACCOUNT, myAccount, MoneyWiseLoan::getAccount);
157         theFieldSet.addField(MoneyWiseAccountInfoClass.REFERENCE, myReference, MoneyWiseLoan::getReference);
158         theFieldSet.addField(MoneyWiseAccountInfoClass.OPENINGBALANCE, myOpening, MoneyWiseLoan::getOpeningBalance);
159 
160         /* Configure the currency */
161         myOpening.setDeemedCurrency(() -> getItem().getCurrency());
162 
163         /* Configure validation checks */
164         mySortCode.setValidator(this::isValidSortCode);
165         myAccount.setValidator(this::isValidAccount);
166         myReference.setValidator(this::isValidReference);
167     }
168 
169     /**
170      * Build Notes subPanel.
171      *
172      * @param pFactory the GUI factory
173      */
174     private void buildNotesPanel(final TethysUIFactory<?> pFactory) {
175         /* Allocate fields */
176         final TethysUIFieldFactory myFields = pFactory.fieldFactory();
177         final TethysUICharArrayTextAreaField myNotes = myFields.newCharArrayAreaField();
178 
179         /* Assign the fields to the panel */
180         theFieldSet.newTextArea(TAB_NOTES, MoneyWiseAccountInfoClass.NOTES, myNotes, MoneyWiseLoan::getNotes);
181 
182         /* Configure validation checks */
183         myNotes.setValidator(this::isValidNotes);
184     }
185 
186     @Override
187     public void refreshData() {
188         /* If we have an item */
189         final MoneyWiseLoan myItem = getItem();
190         if (myItem != null) {
191             final MoneyWiseLoanList myLoans = getDataList(MoneyWiseBasicDataType.LOAN, MoneyWiseLoanList.class);
192             setItem(myLoans.findItemById(myItem.getIndexedId()));
193         }
194 
195         /* Make sure that the item is not editable */
196         setEditable(false);
197     }
198 
199     @Override
200     protected void adjustFields(final boolean isEditable) {
201         /* Access the item */
202         final MoneyWiseLoan myLoan = getItem();
203         final boolean bIsClosed = myLoan.isClosed();
204         final boolean bIsActive = myLoan.isActive();
205         final boolean bIsRelevant = myLoan.isRelevant();
206         final boolean bIsChangeable = !bIsActive && isEditable;
207 
208         /* Determine whether the closed button should be visible */
209         final boolean bShowClosed = bIsClosed || (bIsActive && !bIsRelevant);
210         theFieldSet.setFieldVisible(MoneyWiseBasicResource.ASSET_CLOSED, bShowClosed);
211 
212         /* Determine the state of the closed button */
213         final boolean bEditClosed = bIsClosed
214                 ? !myLoan.getParent().isClosed()
215                 : !bIsRelevant;
216         theFieldSet.setFieldEditable(MoneyWiseBasicResource.ASSET_CLOSED, isEditable && bEditClosed);
217         theClosedState = bEditClosed;
218 
219         /* Determine whether the description field should be visible */
220         final boolean bShowDesc = isEditable || myLoan.getDesc() != null;
221         theFieldSet.setFieldVisible(PrometheusDataResource.DATAITEM_FIELD_DESC, bShowDesc);
222 
223         /* Determine whether the account details should be visible */
224         final boolean bShowSortCode = isEditable || myLoan.getSortCode() != null;
225         theFieldSet.setFieldVisible(MoneyWiseAccountInfoClass.SORTCODE, bShowSortCode);
226         final boolean bShowAccount = isEditable || myLoan.getAccount() != null;
227         theFieldSet.setFieldVisible(MoneyWiseAccountInfoClass.ACCOUNT, bShowAccount);
228         final boolean bShowReference = isEditable || myLoan.getReference() != null;
229         theFieldSet.setFieldVisible(MoneyWiseAccountInfoClass.REFERENCE, bShowReference);
230         final boolean bHasOpening = myLoan.getOpeningBalance() != null;
231         final boolean bShowOpening = bIsChangeable || bHasOpening;
232         theFieldSet.setFieldVisible(MoneyWiseAccountInfoClass.OPENINGBALANCE, bShowOpening);
233         final boolean bShowNotes = isEditable || myLoan.getNotes() != null;
234         theFieldSet.setFieldVisible(MoneyWiseAccountInfoClass.NOTES, bShowNotes);
235 
236         /* Category/Currency cannot be changed if the item is active */
237         theFieldSet.setFieldEditable(MoneyWiseBasicResource.CATEGORY_NAME, bIsChangeable);
238         theFieldSet.setFieldEditable(MoneyWiseStaticDataType.CURRENCY, bIsChangeable && !bHasOpening);
239         theFieldSet.setFieldEditable(MoneyWiseAccountInfoClass.OPENINGBALANCE, bIsChangeable);
240 
241         /* Set editable value for parent */
242         theFieldSet.setFieldEditable(MoneyWiseBasicResource.ASSET_PARENT, isEditable && !bIsClosed);
243     }
244 
245     @Override
246     protected void updateField(final PrometheusFieldSetEvent pUpdate) throws OceanusException {
247         /* Access the field */
248         final MetisDataFieldId myField = pUpdate.getFieldId();
249         final MoneyWiseLoan myLoan = getItem();
250 
251         /* Process updates */
252         if (PrometheusDataResource.DATAITEM_FIELD_NAME.equals(myField)) {
253             /* Update the Name */
254             myLoan.setName(pUpdate.getValue(String.class));
255         } else if (PrometheusDataResource.DATAITEM_FIELD_DESC.equals(myField)) {
256             /* Update the Description */
257             myLoan.setDescription(pUpdate.getValue(String.class));
258         } else if (MoneyWiseBasicResource.CATEGORY_NAME.equals(myField)) {
259             /* Update the Category */
260             myLoan.setCategory(pUpdate.getValue(MoneyWiseLoanCategory.class));
261             myLoan.autoCorrect();
262         } else if (MoneyWiseBasicResource.ASSET_PARENT.equals(myField)) {
263             /* Update the Parent */
264             myLoan.setParent(pUpdate.getValue(MoneyWisePayee.class));
265         } else if (MoneyWiseStaticDataType.CURRENCY.equals(myField)) {
266             /* Update the Currency */
267             myLoan.setAssetCurrency(pUpdate.getValue(MoneyWiseCurrency.class));
268         } else if (MoneyWiseBasicResource.ASSET_CLOSED.equals(myField)) {
269             /* Update the Closed indication */
270             myLoan.setClosed(pUpdate.getValue(Boolean.class));
271         } else if (MoneyWiseAccountInfoClass.SORTCODE.equals(myField)) {
272             /* Update the SortCode */
273             myLoan.setSortCode(pUpdate.getValue(char[].class));
274         } else if (MoneyWiseAccountInfoClass.ACCOUNT.equals(myField)) {
275             /* Update the Account */
276             myLoan.setAccount(pUpdate.getValue(char[].class));
277         } else if (MoneyWiseAccountInfoClass.REFERENCE.equals(myField)) {
278             /* Update the Reference */
279             myLoan.setReference(pUpdate.getValue(char[].class));
280         } else if (MoneyWiseAccountInfoClass.OPENINGBALANCE.equals(myField)) {
281             /* Update the OpeningBalance */
282             myLoan.setOpeningBalance(pUpdate.getValue(OceanusMoney.class));
283         } else if (MoneyWiseAccountInfoClass.NOTES.equals(myField)) {
284             /* Update the Notes */
285             myLoan.setNotes(pUpdate.getValue(char[].class));
286         }
287     }
288 
289     @Override
290     protected void declareGoToItems(final boolean pUpdates) {
291         final MoneyWiseLoan myItem = getItem();
292         final MoneyWisePayee myParent = myItem.getParent();
293         if (!pUpdates) {
294             final MoneyWiseLoanCategory myCategory = myItem.getCategory();
295             final MoneyWiseCurrency myCurrency = myItem.getAssetCurrency();
296             declareGoToItem(myCategory);
297             declareGoToItem(myCurrency);
298         }
299         declareGoToItem(myParent);
300     }
301 
302     /**
303      * Build the category menu for an item.
304      *
305      * @param pMenu the menu
306      * @param pLoan the loan to build for
307      */
308     public void buildCategoryMenu(final TethysUIScrollMenu<MoneyWiseAssetCategory> pMenu,
309                                   final MoneyWiseLoan pLoan) {
310         /* Clear the menu */
311         pMenu.removeAllItems();
312 
313         /* Record active item */
314         final MoneyWiseLoanCategory myCurr = pLoan.getCategory();
315         TethysUIScrollItem<MoneyWiseAssetCategory> myActive = null;
316 
317         /* Access Loan Categories */
318         final MoneyWiseLoanCategoryList myCategories = getDataList(MoneyWiseBasicDataType.LOANCATEGORY, MoneyWiseLoanCategoryList.class);
319 
320         /* Create a simple map for top-level categories */
321         final Map<String, TethysUIScrollSubMenu<MoneyWiseAssetCategory>> myMap = new HashMap<>();
322 
323         /* Loop through the available category values */
324         final Iterator<MoneyWiseLoanCategory> myIterator = myCategories.iterator();
325         while (myIterator.hasNext()) {
326             final MoneyWiseLoanCategory myCategory = myIterator.next();
327 
328             /* Ignore deleted or parent */
329             final boolean bIgnore = myCategory.isDeleted() || myCategory.isCategoryClass(MoneyWiseLoanCategoryClass.PARENT);
330             if (bIgnore) {
331                 continue;
332             }
333 
334             /* Determine menu to add to */
335             final MoneyWiseLoanCategory myParent = myCategory.getParentCategory();
336             final String myParentName = myParent.getName();
337             final TethysUIScrollSubMenu<MoneyWiseAssetCategory> myMenu = myMap.computeIfAbsent(myParentName, pMenu::addSubMenu);
338 
339             /* Create a new MenuItem and add it to the popUp */
340             final TethysUIScrollItem<MoneyWiseAssetCategory> myItem = myMenu.getSubMenu().addItem(myCategory, myCategory.getSubCategory());
341 
342             /* Note active category */
343             if (myCategory.equals(myCurr)) {
344                 myActive = myItem;
345             }
346         }
347 
348         /* Ensure active item is visible */
349         if (myActive != null) {
350             myActive.scrollToItem();
351         }
352     }
353 
354     /**
355      * Build the parent menu for an item.
356      *
357      * @param pMenu the menu
358      * @param pLoan the loan to build for
359      */
360     public void buildParentMenu(final TethysUIScrollMenu<MoneyWisePayee> pMenu,
361                                 final MoneyWiseLoan pLoan) {
362         /* Clear the menu */
363         pMenu.removeAllItems();
364 
365         /* Record active item */
366         final MoneyWiseLoanCategoryClass myType = pLoan.getCategoryClass();
367         final MoneyWisePayee myCurr = pLoan.getParent();
368         TethysUIScrollItem<MoneyWisePayee> myActive = null;
369 
370         /* Access Payees */
371         final MoneyWisePayeeList myPayees = getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class);
372 
373         /* Loop through the Payees */
374         final Iterator<MoneyWisePayee> myIterator = myPayees.iterator();
375         while (myIterator.hasNext()) {
376             final MoneyWisePayee myPayee = myIterator.next();
377 
378             /* Ignore deleted or non-owner */
379             boolean bIgnore = myPayee.isDeleted() || !myPayee.getCategoryClass().canParentLoan(myType);
380             bIgnore |= myPayee.isClosed();
381             if (bIgnore) {
382                 continue;
383             }
384 
385             /* Create a new action for the payee */
386             final TethysUIScrollItem<MoneyWisePayee> myItem = pMenu.addItem(myPayee);
387 
388             /* If this is the active parent */
389             if (myPayee.equals(myCurr)) {
390                 /* Record it */
391                 myActive = myItem;
392             }
393         }
394 
395         /* Ensure active item is visible */
396         if (myActive != null) {
397             myActive.scrollToItem();
398         }
399     }
400 
401     /**
402      * Build the currency menu for an item.
403      *
404      * @param pMenu the menu
405      * @param pLoan the loan to build for
406      */
407     public void buildCurrencyMenu(final TethysUIScrollMenu<MoneyWiseCurrency> pMenu,
408                                   final MoneyWiseLoan pLoan) {
409         /* Clear the menu */
410         pMenu.removeAllItems();
411 
412         /* Record active item */
413         final MoneyWiseCurrency myCurr = pLoan.getAssetCurrency();
414         TethysUIScrollItem<MoneyWiseCurrency> myActive = null;
415 
416         /* Access Currencies */
417         final MoneyWiseCurrencyList myCurrencies = getDataList(MoneyWiseStaticDataType.CURRENCY, MoneyWiseCurrencyList.class);
418 
419         /* Loop through the AccountCurrencies */
420         final Iterator<MoneyWiseCurrency> myIterator = myCurrencies.iterator();
421         while (myIterator.hasNext()) {
422             final MoneyWiseCurrency myCurrency = myIterator.next();
423 
424             /* Ignore deleted or disabled */
425             final boolean bIgnore = myCurrency.isDeleted() || !myCurrency.getEnabled();
426             if (bIgnore) {
427                 continue;
428             }
429 
430             /* Create a new action for the currency */
431             final TethysUIScrollItem<MoneyWiseCurrency> myItem = pMenu.addItem(myCurrency);
432 
433             /* If this is the active currency */
434             if (myCurrency.equals(myCurr)) {
435                 /* Record it */
436                 myActive = myItem;
437             }
438         }
439 
440         /* Ensure active item is visible */
441         if (myActive != null) {
442             myActive.scrollToItem();
443         }
444     }
445 }