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.data.basic;
18  
19  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
21  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
22  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCash.MoneyWiseCashList;
23  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDataValidator.MoneyWiseDataValidatorAutoCorrect;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit.MoneyWiseDepositList;
25  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoan.MoneyWiseLoanList;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee.MoneyWisePayeeList;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio.MoneyWisePortfolioList;
28  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
29  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
30  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
31  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
32  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
33  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
34  import io.github.tonywasher.joceanus.prometheus.data.PrometheusData.PrometheusDataSetCtl;
35  import io.github.tonywasher.joceanus.prometheus.data.PrometheusData.PrometheusDataSetListCtl;
36  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
37  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
38  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet;
39  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
40  import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncryptedDataItem;
41  import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncryptedFieldSet;
42  import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncryptedPair;
43  import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncryptedValues;
44  import io.github.tonywasher.joceanus.prometheus.data.PrometheusListStyle;
45  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
46  
47  /**
48   * Transaction data type.
49   *
50   * @author Tony Washer
51   */
52  public abstract class MoneyWiseTransBase
53          extends PrometheusEncryptedDataItem
54          implements MoneyWiseAssetTouch {
55      /**
56       * Object name.
57       */
58      public static final String OBJECT_NAME = MoneyWiseTransBase.class.getSimpleName();
59  
60      /**
61       * Blank character.
62       */
63      private static final char CHAR_BLANK = ' ';
64  
65      /**
66       * Local Report fields.
67       */
68      private static final PrometheusEncryptedFieldSet<MoneyWiseTransBase> FIELD_DEFS = PrometheusEncryptedFieldSet.newEncryptedFieldSet(MoneyWiseTransBase.class);
69  
70      /*
71       * FieldIds.
72       */
73      static {
74          FIELD_DEFS.declareLinkField(MoneyWiseBasicResource.TRANSACTION_ACCOUNT);
75          FIELD_DEFS.declareEnumField(MoneyWiseBasicResource.TRANSACTION_DIRECTION);
76          FIELD_DEFS.declareLinkField(MoneyWiseBasicResource.TRANSACTION_PARTNER);
77          FIELD_DEFS.declareEncryptedMoneyField(MoneyWiseBasicResource.TRANSACTION_AMOUNT);
78          FIELD_DEFS.declareLinkField(MoneyWiseBasicDataType.TRANSCATEGORY);
79          FIELD_DEFS.declareBooleanField(MoneyWiseBasicResource.TRANSACTION_RECONCILED);
80      }
81  
82      /**
83       * Invalid Debit/Credit/Category Combination Error Text.
84       */
85      public static final String ERROR_COMBO = MoneyWiseBasicResource.TRANSACTION_ERROR_ASSETPAIR.getValue();
86  
87      /**
88       * Zero Amount Error Text.
89       */
90      protected static final String ERROR_ZEROAMOUNT = MoneyWiseBasicResource.TRANSACTION_ERROR_ZERO.getValue();
91  
92      /**
93       * Currency Error Text.
94       */
95      public static final String ERROR_CURRENCY = MoneyWiseBasicResource.MONEYWISEDATA_ERROR_CURRENCY.getValue();
96  
97      /**
98       * Copy Constructor.
99       *
100      * @param pList  the event list
101      * @param pTrans The Transaction to copy
102      */
103     protected MoneyWiseTransBase(final MoneyWiseTransBaseList<?> pList,
104                                  final MoneyWiseTransBase pTrans) {
105         /* Set standard values */
106         super(pList, pTrans);
107     }
108 
109     /**
110      * Edit constructor.
111      *
112      * @param pList the list
113      */
114     protected MoneyWiseTransBase(final MoneyWiseTransBaseList<?> pList) {
115         super(pList, 0);
116         setNextDataKeySet();
117         setValueReconciled(Boolean.FALSE);
118     }
119 
120     /**
121      * Values constructor.
122      *
123      * @param pList   the List to add to
124      * @param pValues the values constructor
125      * @throws OceanusException on error
126      */
127     protected MoneyWiseTransBase(final MoneyWiseTransBaseList<?> pList,
128                                  final PrometheusDataValues pValues) throws OceanusException {
129         /* Initialise the item */
130         super(pList, pValues);
131 
132         /* Access formatter */
133         final OceanusDataFormatter myFormatter = getDataSet().getDataFormatter();
134 
135         /* Protect against exceptions */
136         try {
137             /* Store the AssetPair */
138             Object myValue = pValues.getValue(MoneyWiseBasicResource.TRANSACTION_DIRECTION);
139             if (myValue instanceof Boolean b) {
140                 setValueDirection(b);
141             } else if (myValue instanceof String s) {
142                 setValueDirection(s);
143             } else if (myValue instanceof MoneyWiseAssetDirection d) {
144                 setValueDirection(d);
145             }
146 
147             /* Store the Account */
148             myValue = pValues.getValue(MoneyWiseBasicResource.TRANSACTION_ACCOUNT);
149             if (myValue instanceof Long l) {
150                 setValueAccount(l);
151             } else if (myValue instanceof String s) {
152                 setValueAccount(s);
153             } else if (myValue instanceof MoneyWiseTransAsset a) {
154                 setValueAccount(a);
155             }
156 
157             /* Store the Partner */
158             myValue = pValues.getValue(MoneyWiseBasicResource.TRANSACTION_PARTNER);
159             if (myValue instanceof Long l) {
160                 setValuePartner(l);
161             } else if (myValue instanceof String s) {
162                 setValuePartner(s);
163             } else if (myValue instanceof MoneyWiseTransAsset a) {
164                 setValuePartner(a);
165             }
166 
167             /* Store the Category */
168             myValue = pValues.getValue(MoneyWiseBasicDataType.TRANSCATEGORY);
169             if (myValue instanceof Integer i) {
170                 setValueCategory(i);
171             } else if (myValue instanceof String s) {
172                 setValueCategory(s);
173             } else if (myValue instanceof MoneyWiseTransCategory c) {
174                 setValueCategory(c);
175             }
176 
177             /* Store the Amount */
178             myValue = pValues.getValue(MoneyWiseBasicResource.TRANSACTION_AMOUNT);
179             if (myValue instanceof OceanusMoney m) {
180                 setValueAmount(m);
181             } else if (myValue instanceof byte[] ba) {
182                 setValueAmount(ba);
183             } else if (myValue instanceof String myString) {
184                 setValueAmount(myString);
185                 setValueAmount(myFormatter.parseValue(myString, OceanusMoney.class));
186             }
187 
188             /* Store the reconciled flag */
189             myValue = pValues.getValue(MoneyWiseBasicResource.TRANSACTION_RECONCILED);
190             if (myValue instanceof Boolean b) {
191                 setValueReconciled(b);
192             } else if (myValue instanceof String s) {
193                 setValueReconciled(myFormatter.parseValue(s, Boolean.class));
194             }
195 
196             /* Catch Exceptions */
197         } catch (IllegalArgumentException
198                  | OceanusException e) {
199             /* Pass on exception */
200             throw new MoneyWiseDataException(this, ERROR_CREATEITEM, e);
201         }
202     }
203 
204     @Override
205     public boolean includeXmlField(final MetisDataFieldId pField) {
206         /* Determine whether fields should be included */
207         if (MoneyWiseBasicResource.TRANSACTION_DIRECTION.equals(pField)) {
208             return true;
209         }
210         if (MoneyWiseBasicDataType.TRANSCATEGORY.equals(pField)) {
211             return true;
212         }
213         if (MoneyWiseBasicResource.TRANSACTION_ACCOUNT.equals(pField)) {
214             return true;
215         }
216         if (MoneyWiseBasicResource.TRANSACTION_PARTNER.equals(pField)) {
217             return true;
218         }
219         if (MoneyWiseBasicResource.TRANSACTION_AMOUNT.equals(pField)) {
220             return getAmount() != null;
221         }
222         if (MoneyWiseBasicResource.TRANSACTION_RECONCILED.equals(pField)) {
223             return isReconciled();
224         }
225 
226         /* Pass call on */
227         return super.includeXmlField(pField);
228     }
229 
230     @Override
231     public String formatObject(final OceanusDataFormatter pFormatter) {
232         return toString();
233     }
234 
235     @Override
236     public String toString() {
237         /* Handle header */
238         if (isHeader()) {
239             return PrometheusDataResource.DATAITEM_HEADER.getValue();
240         }
241 
242         /* Access Key Values */
243         final PrometheusEncryptedValues myValues = getValues();
244         final Object myAccount = myValues.getValue(MoneyWiseBasicResource.TRANSACTION_ACCOUNT);
245         final Object myPartner = myValues.getValue(MoneyWiseBasicResource.TRANSACTION_PARTNER);
246         final Object myDir = myValues.getValue(MoneyWiseBasicResource.TRANSACTION_DIRECTION);
247         final Object myCategory = myValues.getValue(MoneyWiseBasicDataType.TRANSCATEGORY);
248         final Object myAmount = myValues.getValue(MoneyWiseBasicResource.TRANSACTION_AMOUNT);
249 
250         /* Access formatter */
251         final OceanusDataFormatter myFormatter = getDataSet().getDataFormatter();
252 
253         /* Create string builder */
254         final StringBuilder myBuilder = new StringBuilder();
255         myBuilder.append(myFormatter.formatObject(myCategory));
256         myBuilder.append(CHAR_BLANK);
257         myBuilder.append(myAmount == null
258                 ? ""
259                 : myFormatter.formatObject(myAmount));
260         myBuilder.append(CHAR_BLANK);
261         myBuilder.append(myFormatter.formatObject(myAccount));
262         if (myDir == null) {
263             myBuilder.append("??");
264         } else {
265             myBuilder.append(MoneyWiseAssetDirection.FROM.equals(myDir)
266                     ? "<-"
267                     : "->");
268         }
269         myBuilder.append(myFormatter.formatObject(myPartner));
270 
271         /* return it */
272         return myBuilder.toString();
273     }
274 
275     /**
276      * Obtain category.
277      *
278      * @return the category
279      */
280     public final MoneyWiseTransCategory getCategory() {
281         return getValues().getValue(MoneyWiseBasicDataType.TRANSCATEGORY, MoneyWiseTransCategory.class);
282     }
283 
284     /**
285      * Obtain CategoryId.
286      *
287      * @return the categoryId
288      */
289     public Integer getCategoryId() {
290         final MoneyWiseTransCategory myCategory = getCategory();
291         return myCategory == null
292                 ? null
293                 : myCategory.getIndexedId();
294     }
295 
296     /**
297      * Obtain categoryName.
298      *
299      * @return the categoryName
300      */
301     public String getCategoryName() {
302         final MoneyWiseTransCategory myCategory = getCategory();
303         return myCategory == null
304                 ? null
305                 : myCategory.getName();
306     }
307 
308     /**
309      * Obtain EventCategoryClass.
310      *
311      * @return the eventCategoryClass
312      */
313     public MoneyWiseTransCategoryClass getCategoryClass() {
314         final MoneyWiseTransCategory myCategory = getCategory();
315         return myCategory == null
316                 ? null
317                 : myCategory.getCategoryTypeClass();
318     }
319 
320     /**
321      * Obtain Amount.
322      *
323      * @return the amount
324      */
325     public OceanusMoney getAmount() {
326         return getValues().getValue(MoneyWiseBasicResource.TRANSACTION_AMOUNT, OceanusMoney.class);
327     }
328 
329     /**
330      * Obtain Encrypted amount.
331      *
332      * @return the bytes
333      */
334     public byte[] getAmountBytes() {
335         return getValues().getEncryptedBytes(MoneyWiseBasicResource.TRANSACTION_AMOUNT);
336     }
337 
338     /**
339      * Obtain Encrypted Amount Field.
340      *
341      * @return the Field
342      */
343     protected PrometheusEncryptedPair getAmountField() {
344         return getValues().getEncryptedPair(MoneyWiseBasicResource.TRANSACTION_AMOUNT);
345     }
346 
347     /**
348      * Obtain Account asset.
349      *
350      * @return the account
351      */
352     public MoneyWiseTransAsset getAccount() {
353         return getValues().getValue(MoneyWiseBasicResource.TRANSACTION_ACCOUNT, MoneyWiseTransAsset.class);
354     }
355 
356     /**
357      * Obtain AccountId.
358      *
359      * @return the accountId
360      */
361     public Long getAccountId() {
362         final MoneyWiseTransAsset myAccount = getAccount();
363         return myAccount == null
364                 ? null
365                 : myAccount.getExternalId();
366     }
367 
368     /**
369      * Obtain Partner asset.
370      *
371      * @return the partner
372      */
373     public MoneyWiseTransAsset getPartner() {
374         return getValues().getValue(MoneyWiseBasicResource.TRANSACTION_PARTNER, MoneyWiseTransAsset.class);
375     }
376 
377     /**
378      * Obtain PartnerId.
379      *
380      * @return the partnerId
381      */
382     public Long getPartnerId() {
383         final MoneyWiseTransAsset myPartner = getPartner();
384         return myPartner == null
385                 ? null
386                 : myPartner.getExternalId();
387     }
388 
389     /**
390      * Obtain Direction.
391      *
392      * @return the direction
393      */
394     public MoneyWiseAssetDirection getDirection() {
395         return getValues().getValue(MoneyWiseBasicResource.TRANSACTION_DIRECTION, MoneyWiseAssetDirection.class);
396     }
397 
398     @Override
399     public Boolean isReconciled() {
400         return getValues().getValue(MoneyWiseBasicResource.TRANSACTION_RECONCILED, Boolean.class);
401     }
402 
403     /**
404      * Set category value.
405      *
406      * @param pValue the value
407      */
408     private void setValueCategory(final MoneyWiseTransCategory pValue) {
409         getValues().setUncheckedValue(MoneyWiseBasicDataType.TRANSCATEGORY, pValue);
410     }
411 
412     /**
413      * Set category id.
414      *
415      * @param pId the id
416      */
417     private void setValueCategory(final Integer pId) {
418         getValues().setUncheckedValue(MoneyWiseBasicDataType.TRANSCATEGORY, pId);
419     }
420 
421     /**
422      * Set category name.
423      *
424      * @param pName the name
425      */
426     private void setValueCategory(final String pName) {
427         getValues().setUncheckedValue(MoneyWiseBasicDataType.TRANSCATEGORY, pName);
428     }
429 
430     /**
431      * Set description value.
432      *
433      * @param pValue the value
434      * @throws OceanusException on error
435      */
436     private void setValueAmount(final OceanusMoney pValue) throws OceanusException {
437         setEncryptedValue(MoneyWiseBasicResource.TRANSACTION_AMOUNT, pValue);
438     }
439 
440     /**
441      * Set amount value.
442      *
443      * @param pBytes the value
444      * @throws OceanusException on error
445      */
446     private void setValueAmount(final byte[] pBytes) throws OceanusException {
447         setEncryptedValue(MoneyWiseBasicResource.TRANSACTION_AMOUNT, pBytes, OceanusMoney.class);
448     }
449 
450     /**
451      * Set amount value.
452      *
453      * @param pValue the value
454      */
455     protected final void setValueAmount(final PrometheusEncryptedPair pValue) {
456         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_AMOUNT, pValue);
457     }
458 
459     /**
460      * Set amount value.
461      *
462      * @param pValue the value
463      */
464     private void setValueAmount(final String pValue) {
465         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_AMOUNT, pValue);
466     }
467 
468     /**
469      * Set account value.
470      *
471      * @param pValue the value
472      */
473     protected final void setValueAccount(final MoneyWiseTransAsset pValue) {
474         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_ACCOUNT, pValue);
475     }
476 
477     /**
478      * Set debit name.
479      *
480      * @param pName the name
481      */
482     private void setValueAccount(final String pName) {
483         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_ACCOUNT, pName);
484     }
485 
486     /**
487      * Set debit id.
488      *
489      * @param pId the value
490      */
491     private void setValueAccount(final Long pId) {
492         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_ACCOUNT, pId);
493     }
494 
495     /**
496      * Set partner value.
497      *
498      * @param pValue the value
499      */
500     protected final void setValuePartner(final MoneyWiseTransAsset pValue) {
501         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_PARTNER, pValue);
502     }
503 
504     /**
505      * Set partner id.
506      *
507      * @param pId the id
508      */
509     private void setValuePartner(final Long pId) {
510         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_PARTNER, pId);
511     }
512 
513     /**
514      * Set partner name.
515      *
516      * @param pName the name
517      */
518     private void setValuePartner(final String pName) {
519         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_PARTNER, pName);
520     }
521 
522     /**
523      * Set direction state.
524      *
525      * @param pValue the value
526      */
527     protected final void setValueDirection(final MoneyWiseAssetDirection pValue) {
528         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_DIRECTION, pValue);
529     }
530 
531     /**
532      * Set direction state.
533      *
534      * @param pValue the value
535      */
536     private void setValueDirection(final Boolean pValue) {
537         final MoneyWiseAssetDirection myValue = Boolean.TRUE.equals(pValue)
538                 ? MoneyWiseAssetDirection.FROM
539                 : MoneyWiseAssetDirection.TO;
540         setValueDirection(myValue);
541     }
542 
543     /**
544      * Set direction state.
545      *
546      * @param pValue the value
547      */
548     private void setValueDirection(final String pValue) {
549         final MoneyWiseAssetDirection myValue = MoneyWiseAssetDirection.fromName(pValue);
550         setValueDirection(myValue);
551     }
552 
553     /**
554      * Set reconciled state.
555      *
556      * @param pValue the value
557      */
558     protected final void setValueReconciled(final Boolean pValue) {
559         getValues().setUncheckedValue(MoneyWiseBasicResource.TRANSACTION_RECONCILED, pValue);
560     }
561 
562     @Override
563     public MoneyWiseTransBaseList<?> getList() {
564         return (MoneyWiseTransBaseList<?>) super.getList();
565     }
566 
567     /**
568      * Obtain portfolio for transaction.
569      *
570      * @return the portfolio (or null)
571      */
572     public MoneyWisePortfolio getPortfolio() {
573         /* Access account portfolio if it is a security holding */
574         MoneyWiseTransAsset myAsset = getAccount();
575         if (myAsset instanceof MoneyWiseSecurityHolding myHolding) {
576             return myHolding.getPortfolio();
577         }
578 
579         /* Access partner portfolio if it is a security holding */
580         myAsset = getPartner();
581         if (myAsset instanceof MoneyWiseSecurityHolding myHolding) {
582             return myHolding.getPortfolio();
583         }
584 
585         /* No portfolio */
586         return null;
587     }
588 
589     /**
590      * Compare this event to another to establish sort order.
591      *
592      * @param pThat The Event to compare to
593      * @return (-1,0,1) depending of whether this object is before, equal, or after the passed
594      * object in the sort order
595      */
596     @Override
597     public int compareValues(final PrometheusDataItem pThat) {
598         /* Check the category */
599         final MoneyWiseTransBase myThat = (MoneyWiseTransBase) pThat;
600         return MetisDataDifference.compareObject(getCategory(), myThat.getCategory());
601     }
602 
603     @Override
604     public void resolveDataSetLinks() throws OceanusException {
605         /* Update the Encryption details */
606         super.resolveDataSetLinks();
607 
608         /* Resolve data links */
609         final PrometheusDataSetCtl myData = getDataSet();
610         final PrometheusEncryptedValues myValues = getValues();
611 
612         /* Adjust Reconciled */
613         final Object myReconciled = myValues.getValue(MoneyWiseBasicResource.TRANSACTION_RECONCILED);
614         if (myReconciled == null) {
615             setValueReconciled(Boolean.FALSE);
616         }
617 
618         /* Adjust Reconciled */
619         final Object myDirection = myValues.getValue(MoneyWiseBasicResource.TRANSACTION_DIRECTION);
620         if (myDirection == null) {
621             setValueDirection(MoneyWiseAssetDirection.TO);
622         }
623 
624         /* Resolve data links */
625         resolveTransactionAsset(myData, this, MoneyWiseBasicResource.TRANSACTION_ACCOUNT);
626         resolveTransactionAsset(myData, this, MoneyWiseBasicResource.TRANSACTION_PARTNER);
627         resolveDataLink(MoneyWiseBasicDataType.TRANSCATEGORY, MoneyWiseBasicDataType.TRANSCATEGORY);
628     }
629 
630     @Override
631     public boolean isLocked() {
632         final MoneyWiseTransAsset myAccount = getAccount();
633         final MoneyWiseTransAsset myPartner = getPartner();
634 
635         /* Check credit and debit accounts */
636         return (myAccount != null && myAccount.isClosed())
637                 || (myPartner != null && myPartner.isClosed());
638     }
639 
640     /**
641      * Is this event category the required class.
642      *
643      * @param pClass the required category class.
644      * @return true/false
645      */
646     public boolean isCategoryClass(final MoneyWiseTransCategoryClass pClass) {
647         /* Check for match */
648         return getCategoryClass().equals(pClass);
649     }
650 
651     /**
652      * Determines whether an event is a dividend re-investment.
653      *
654      * @return dividend re-investment true/false
655      */
656     public boolean isDividendReInvestment() {
657         /* Check for dividend re-investment */
658         if (!isDividend()) {
659             return false;
660         }
661         return getAccount() != null
662                 && MetisDataDifference.isEqual(getAccount(), getPartner());
663     }
664 
665     /**
666      * Determines whether an event is an interest payment.
667      *
668      * @return interest true/false
669      */
670     public boolean isInterest() {
671         /* Check for interest */
672         final MoneyWiseTransCategoryClass myClass = getCategoryClass();
673         return myClass != null
674                 && myClass.isInterest();
675     }
676 
677     /**
678      * Determines whether an event is a dividend payment.
679      *
680      * @return dividend true/false
681      */
682     public boolean isDividend() {
683         final MoneyWiseTransCategoryClass myClass = getCategoryClass();
684         return myClass != null
685                 && myClass.isDividend();
686     }
687 
688     /**
689      * Determines whether an event needs a zero amount.
690      *
691      * @return true/false
692      */
693     public boolean needsNullAmount() {
694         final MoneyWiseTransCategoryClass myClass = getCategoryClass();
695         return myClass != null
696                 && myClass.needsNullAmount();
697     }
698 
699     /**
700      * Determines whether we can switch direction.
701      *
702      * @return true/false
703      */
704     public boolean canSwitchDirection() {
705         return getList().getValidator().isValidDirection(getAccount(), getCategory(), getDirection().reverse());
706     }
707 
708     /**
709      * Set a new account.
710      *
711      * @param pAccount the account
712      */
713     public void setAccount(final MoneyWiseTransAsset pAccount) {
714         /* Set account value */
715         setValueAccount(pAccount);
716     }
717 
718     /**
719      * Set a new partner.
720      *
721      * @param pPartner the partner
722      */
723     public void setPartner(final MoneyWiseTransAsset pPartner) {
724         /* Set partner value */
725         setValuePartner(pPartner);
726     }
727 
728     /**
729      * Set a direction.
730      *
731      * @param pDirection the direction
732      */
733     public void setDirection(final MoneyWiseAssetDirection pDirection) {
734         /* Set partner value */
735         setValueDirection(pDirection);
736     }
737 
738     /**
739      * Switch direction.
740      */
741     public void switchDirection() {
742         setValueDirection(getDirection().reverse());
743     }
744 
745     /**
746      * Flip assets.
747      *
748      * @throws OceanusException on error
749      */
750     public void flipAssets() throws OceanusException {
751         /* Flip details */
752         final MoneyWiseTransAsset myAccount = getAccount();
753         final MoneyWiseTransAsset myPartner = getPartner();
754         setValueAccount(myPartner);
755         setValuePartner(myAccount);
756         switchDirection();
757     }
758 
759     /**
760      * Set a new category.
761      *
762      * @param pCategory the category
763      */
764     public void setCategory(final MoneyWiseTransCategory pCategory) {
765         setValueCategory(pCategory);
766     }
767 
768     /**
769      * Set a new amount.
770      *
771      * @param pAmount the amount
772      * @throws OceanusException on error
773      */
774     public void setAmount(final OceanusMoney pAmount) throws OceanusException {
775         setValueAmount(pAmount);
776     }
777 
778     /**
779      * Set a reconciled indication.
780      *
781      * @param pReconciled the reconciled state
782      */
783     public void setReconciled(final Boolean pReconciled) {
784         setValueReconciled(pReconciled);
785     }
786 
787     @Override
788     public void touchUnderlyingItems() {
789         /* touch the event category referred to */
790         getCategory().touchItem(this);
791 
792         /* Touch the account and partner */
793         getAccount().touchItem(this);
794         getPartner().touchItem(this);
795     }
796 
797     /**
798      * Resolve transAsset.
799      *
800      * @param pData  the dataSet
801      * @param pOwner the owning object
802      * @param pField the fieldId
803      * @throws OceanusException on error
804      */
805     public void resolveTransactionAsset(final PrometheusDataSetListCtl pData,
806                                         final PrometheusDataItem pOwner,
807                                         final MetisDataFieldId pField) throws OceanusException {
808         /* Obtain baseValue, and then resolve */
809         final Object myBaseValue = pOwner.getValues().getValue(pField);
810         final Object myValue = resolveTransactionAsset(pData, myBaseValue);
811         if (myValue == null) {
812             pOwner.addError(ERROR_UNKNOWN, pField);
813             throw new MoneyWiseDataException(this, ERROR_RESOLUTION);
814         }
815         pOwner.getValues().setUncheckedValue(pField, myValue);
816     }
817 
818     /**
819      * Resolve transAsset.
820      *
821      * @param pData  the dataSet
822      * @param pValue the value to convert
823      * @return the asset
824      * @throws OceanusException on error
825      */
826     private MoneyWiseTransAsset resolveTransactionAsset(final PrometheusDataSetListCtl pData,
827                                                         final Object pValue) throws OceanusException {
828         /* If we are being passed a TransactionAsset, convert to Id */
829         Object myValue = pValue;
830         if (myValue instanceof MoneyWiseTransAsset myAsset) {
831             myValue = myAsset.getExternalId();
832         }
833 
834         /* Access the values */
835         if (myValue instanceof String s) {
836             return resolveTransAsset(pData, s);
837         } else if (myValue instanceof Long l) {
838             return resolveTransAsset(pData, l);
839         }
840         return null;
841     }
842 
843     /**
844      * Resolve transAsset.
845      *
846      * @param pData the owning dataSet
847      * @param pId   the item id
848      * @return the asset
849      * @throws OceanusException on error
850      */
851     private static MoneyWiseTransAsset resolveTransAsset(final PrometheusDataSetListCtl pData,
852                                                          final Long pId) throws OceanusException {
853         /* Access the assetType */
854         final MoneyWiseAssetType myAssetType = MoneyWiseAssetType.getAssetType(pId);
855 
856         /* If the name is a security holding */
857         if (myAssetType.isSecurityHolding()) {
858             final MoneyWiseSecurityHoldingMap myMap
859                     = (MoneyWiseSecurityHoldingMap) pData.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class).getSecurityHoldingsMap();
860             return myMap.findHoldingById(pId);
861         } else if (myAssetType.isPayee()) {
862             return pData.getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class).findItemById(MoneyWiseAssetType.getBaseId(pId));
863         } else if (myAssetType.isDeposit()) {
864             return pData.getDataList(MoneyWiseBasicDataType.DEPOSIT, MoneyWiseDepositList.class).findItemById(MoneyWiseAssetType.getBaseId(pId));
865         } else if (myAssetType.isCash() || myAssetType.isAutoExpense()) {
866             return pData.getDataList(MoneyWiseBasicDataType.CASH, MoneyWiseCashList.class).findItemById(MoneyWiseAssetType.getBaseId(pId));
867         } else if (myAssetType.isLoan()) {
868             return pData.getDataList(MoneyWiseBasicDataType.LOAN, MoneyWiseLoanList.class).findItemById(MoneyWiseAssetType.getBaseId(pId));
869         } else if (myAssetType.isPortfolio()) {
870             return pData.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class).findItemById(MoneyWiseAssetType.getBaseId(pId));
871         } else {
872             return null;
873         }
874     }
875 
876     /**
877      * Resolve transAsset.
878      *
879      * @param pData the owning dataSet
880      * @param pName the item name
881      * @return the asset
882      */
883     private static MoneyWiseTransAsset resolveTransAsset(final PrometheusDataSetListCtl pData,
884                                                          final String pName) {
885         /* If the name is a security holding */
886         if (pName.contains(MoneyWiseSecurityHolding.SECURITYHOLDING_SEP)) {
887             final MoneyWiseSecurityHoldingMap myMap
888                     = (MoneyWiseSecurityHoldingMap) pData.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class).getSecurityHoldingsMap();
889             return myMap.findHoldingByName(pName);
890         } else {
891             MoneyWiseTransAsset myAsset = pData.getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class).findItemByName(pName);
892             if (myAsset == null) {
893                 myAsset = pData.getDataList(MoneyWiseBasicDataType.DEPOSIT, MoneyWiseDepositList.class).findItemByName(pName);
894             }
895             if (myAsset == null) {
896                 myAsset = pData.getDataList(MoneyWiseBasicDataType.CASH, MoneyWiseCashList.class).findItemByName(pName);
897             }
898             if (myAsset == null) {
899                 myAsset = pData.getDataList(MoneyWiseBasicDataType.LOAN, MoneyWiseLoanList.class).findItemByName(pName);
900             }
901             if (myAsset == null) {
902                 myAsset = pData.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class).findItemByName(pName);
903             }
904             return myAsset;
905         }
906     }
907 
908     /**
909      * Update base transaction from an edited transaction.
910      *
911      * @param pTrans the edited transaction
912      */
913     protected void applyBasicChanges(final MoneyWiseTransBase pTrans) {
914         /* Update the assetPair if required */
915         if (!MetisDataDifference.isEqual(getDirection(), pTrans.getDirection())) {
916             setValueDirection(pTrans.getDirection());
917         }
918 
919         /* Update the category if required */
920         if (!MetisDataDifference.isEqual(getCategory(), pTrans.getCategory())) {
921             setValueCategory(pTrans.getCategory());
922         }
923 
924         /* Update the account if required */
925         if (!MetisDataDifference.isEqual(getAccount(), pTrans.getAccount())) {
926             setValueAccount(pTrans.getAccount());
927         }
928 
929         /* Update the partner if required */
930         if (!MetisDataDifference.isEqual(getPartner(), pTrans.getPartner())) {
931             setValuePartner(pTrans.getPartner());
932         }
933 
934         /* Update the amount if required */
935         if (!MetisDataDifference.isEqual(getAmount(), pTrans.getAmount())) {
936             setValueAmount(pTrans.getAmountField());
937         }
938 
939         /* Update the reconciled state if required */
940         if (!MetisDataDifference.isEqual(isReconciled(), pTrans.isReconciled())) {
941             setValueReconciled(pTrans.isReconciled());
942         }
943     }
944 
945     /**
946      * The Event List class.
947      *
948      * @param <T> the dataType
949      */
950     public abstract static class MoneyWiseTransBaseList<T extends MoneyWiseTransBase>
951             extends PrometheusEncryptedList<T> {
952         /*
953          * Report fields.
954          */
955         static {
956             MetisFieldSet.newFieldSet(MoneyWiseTransBaseList.class);
957         }
958 
959         /**
960          * Construct an empty CORE Event list.
961          *
962          * @param pData     the DataSet for the list
963          * @param pClass    the class of the item
964          * @param pItemType the item type
965          */
966         protected MoneyWiseTransBaseList(final PrometheusDataSet pData,
967                                          final Class<T> pClass,
968                                          final MoneyWiseBasicDataType pItemType) {
969             /* Call super-constructor */
970             super(pClass, pData, pItemType, PrometheusListStyle.CORE);
971         }
972 
973         /**
974          * Constructor for a cloned List.
975          *
976          * @param pSource the source List
977          */
978         protected MoneyWiseTransBaseList(final MoneyWiseTransBaseList<T> pSource) {
979             /* Call super-constructor */
980             super(pSource);
981         }
982 
983         @Override
984         @SuppressWarnings("unchecked")
985         public MoneyWiseDataValidatorTrans<T> getValidator() {
986             return (MoneyWiseDataValidatorTrans<T>) super.getValidator();
987         }
988     }
989 
990     /**
991      * Transaction validator.
992      *
993      * @param <T> the itemType
994      */
995     public interface MoneyWiseDataValidatorTrans<T extends MoneyWiseTransBase>
996             extends MoneyWiseDataValidatorAutoCorrect<T> {
997         /**
998          * Is the account valid as the base account in a transaction?
999          *
1000          * @param pAccount the account
1001          * @return true/false
1002          */
1003         boolean isValidAccount(MoneyWiseTransAsset pAccount);
1004 
1005         /**
1006          * Is the transaction valid for the base account in the transaction?.
1007          *
1008          * @param pAccount  the account
1009          * @param pCategory The category of the event
1010          * @return true/false
1011          */
1012         boolean isValidCategory(MoneyWiseTransAsset pAccount,
1013                                 MoneyWiseTransCategory pCategory);
1014 
1015         /**
1016          * Is the direction valid for the base account and category in the transaction?.
1017          *
1018          * @param pAccount   the account
1019          * @param pCategory  The category of the event
1020          * @param pDirection the direction
1021          * @return true/false
1022          */
1023         boolean isValidDirection(MoneyWiseTransAsset pAccount,
1024                                  MoneyWiseTransCategory pCategory,
1025                                  MoneyWiseAssetDirection pDirection);
1026 
1027         /**
1028          * Is the partner valid for the base account and category in the transaction?.
1029          *
1030          * @param pAccount  the account
1031          * @param pCategory The category of the event
1032          * @param pPartner  the partner
1033          * @return true/false
1034          */
1035         boolean isValidPartner(MoneyWiseTransAsset pAccount,
1036                                MoneyWiseTransCategory pCategory,
1037                                MoneyWiseTransAsset pPartner);
1038 
1039         /**
1040          * Build default transaction.
1041          *
1042          * @param pKey the key to base the new transaction around (or null)
1043          * @return the new transaction (or null if no possible transaction)
1044          */
1045         MoneyWiseTransBase buildTransaction(Object pKey);
1046 
1047         /**
1048          * Set range.
1049          *
1050          * @param pRange the date range
1051          */
1052         void setRange(OceanusDateRange pRange);
1053 
1054         /**
1055          * Get range.
1056          *
1057          * @return the date range
1058          */
1059         OceanusDateRange getRange();
1060 
1061         /**
1062          * Obtain the editSet.
1063          *
1064          * @return the editSet
1065          */
1066         PrometheusEditSet getEditSet();
1067     }
1068 }