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