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.MetisDataEditState;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
22  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
23  import io.github.tonywasher.joceanus.metis.data.MetisDataState;
24  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
25  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase.MoneyWiseAssetBaseList;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTax.MoneyWiseTaxCredit;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTax.MoneyWiseTaxFactory;
29  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory.MoneyWiseTransCategoryList;
30  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransInfo.MoneyWiseTransInfoList;
31  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
32  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoClass;
33  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoType.MoneyWiseTransInfoTypeList;
34  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
35  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseLogicException;
36  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
37  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
38  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateFormatter;
39  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
40  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
41  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
42  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
43  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
44  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
45  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
46  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
47  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataList;
48  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataMapItem;
49  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
50  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
51  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues.PrometheusInfoItem;
52  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues.PrometheusInfoSetItem;
53  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditEntry;
54  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
55  
56  import java.util.Iterator;
57  import java.util.List;
58  
59  /**
60   * New version of Event DataItem utilising EventInfo.
61   *
62   * @author Tony Washer
63   */
64  public class MoneyWiseTransaction
65          extends MoneyWiseTransBase
66          implements PrometheusInfoSetItem {
67      /**
68       * The name of the object.
69       */
70      public static final String OBJECT_NAME = MoneyWiseBasicDataType.TRANSACTION.getItemName();
71  
72      /**
73       * The name of the object.
74       */
75      public static final String LIST_NAME = MoneyWiseBasicDataType.TRANSACTION.getListName();
76  
77      /**
78       * Local Report fields.
79       */
80      private static final MetisFieldVersionedSet<MoneyWiseTransaction> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(MoneyWiseTransaction.class);
81  
82      /*
83       * FieldIds.
84       */
85      static {
86          FIELD_DEFS.declareDateField(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE);
87          FIELD_DEFS.declareDerivedVersionedField(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_TAXYEAR);
88          FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAINFOSET_NAME, MoneyWiseTransaction::getInfoSet);
89          FIELD_DEFS.buildFieldMap(MoneyWiseTransInfoClass.class, MoneyWiseTransaction::getFieldValue);
90      }
91  
92      /**
93       * Bad InfoSet Error Text.
94       */
95      private static final String ERROR_BADINFOSET = PrometheusDataResource.DATAINFOSET_ERROR_BADSET.getValue();
96  
97      /**
98       * Circular update Error Text.
99       */
100     public static final String ERROR_CIRCULAR = MoneyWiseBasicResource.TRANSACTION_ERROR_CIRCLE.getValue();
101 
102     /**
103      * Do we have an InfoSet.
104      */
105     private final boolean hasInfoSet;
106 
107     /**
108      * Should we use infoSet for DataState etc.
109      */
110     private final boolean useInfoSet;
111 
112     /**
113      * TransactionInfoSet.
114      */
115     private final MoneyWiseTransInfoSet theInfoSet;
116 
117     /**
118      * Copy Constructor.
119      *
120      * @param pList        the transaction list
121      * @param pTransaction The Transaction to copy
122      */
123     public MoneyWiseTransaction(final MoneyWiseTransactionList pList,
124                                 final MoneyWiseTransaction pTransaction) {
125         /* Set standard values */
126         super(pList, pTransaction);
127 
128         /* switch on list type */
129         switch (getList().getStyle()) {
130             case EDIT:
131                 theInfoSet = new MoneyWiseTransInfoSet(this, pList.getTransInfoTypes(), pList.getTransactionInfo());
132                 theInfoSet.cloneDataInfoSet(pTransaction.getInfoSet());
133                 hasInfoSet = true;
134                 useInfoSet = true;
135                 break;
136             case CLONE:
137             case CORE:
138                 theInfoSet = new MoneyWiseTransInfoSet(this, pList.getTransInfoTypes(), pList.getTransactionInfo());
139                 hasInfoSet = true;
140                 useInfoSet = false;
141                 break;
142             default:
143                 theInfoSet = null;
144                 hasInfoSet = false;
145                 useInfoSet = false;
146                 break;
147         }
148     }
149 
150     /**
151      * Edit constructor.
152      *
153      * @param pList the list
154      */
155     public MoneyWiseTransaction(final MoneyWiseTransactionList pList) {
156         super(pList);
157 
158         /* Build InfoSet */
159         theInfoSet = new MoneyWiseTransInfoSet(this, pList.getTransInfoTypes(), pList.getTransactionInfo());
160         hasInfoSet = true;
161         useInfoSet = true;
162     }
163 
164     /**
165      * Values constructor.
166      *
167      * @param pList   the List to add to
168      * @param pValues the values
169      * @throws OceanusException on error
170      */
171     private MoneyWiseTransaction(final MoneyWiseTransactionList pList,
172                                  final PrometheusDataValues pValues) throws OceanusException {
173         /* Initialise the item */
174         super(pList, pValues);
175 
176         /* Access formatter */
177         final OceanusDataFormatter myFormatter = getDataSet().getDataFormatter();
178 
179         /* Protect against exceptions */
180         try {
181             /* Store the Date */
182             final Object myValue = pValues.getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE);
183             if (myValue instanceof OceanusDate d) {
184                 setValueDate(d);
185             } else if (myValue instanceof String s) {
186                 final OceanusDateFormatter myParser = myFormatter.getDateFormatter();
187                 setValueDate(myParser.parseDate(s));
188             }
189             /* Catch Exceptions */
190         } catch (IllegalArgumentException e) {
191             /* Pass on exception */
192             throw new MoneyWiseDataException(this, ERROR_CREATEITEM, e);
193         }
194 
195         /* Create the InfoSet */
196         theInfoSet = new MoneyWiseTransInfoSet(this, pList.getTransInfoTypes(), pList.getTransactionInfo());
197         hasInfoSet = true;
198         useInfoSet = false;
199     }
200 
201     @Override
202     public MetisFieldSetDef getDataFieldSet() {
203         return FIELD_DEFS;
204     }
205 
206     @Override
207     public boolean includeXmlField(final MetisDataFieldId pField) {
208         /* Determine whether fields should be included */
209         if (MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE.equals(pField)) {
210             return true;
211         }
212         if (MoneyWiseBasicResource.MONEYWISEDATA_FIELD_TAXYEAR.equals(pField)) {
213             return false;
214         }
215 
216         /* Pass call on */
217         return super.includeXmlField(pField);
218     }
219 
220     /**
221      * Obtain Date.
222      *
223      * @return the date
224      */
225     public OceanusDate getDate() {
226         return getValues().getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE, OceanusDate.class);
227     }
228 
229     /**
230      * Set date value.
231      *
232      * @param pValue the value
233      */
234     private void setValueDate(final OceanusDate pValue) {
235         getValues().setUncheckedValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE, pValue);
236         final MoneyWiseTaxFactory myFactory = getDataSet().getTaxFactory();
237         setValueTaxYear(myFactory.findTaxYearForDate(pValue));
238     }
239 
240     /**
241      * Obtain TaxYear.
242      *
243      * @return the taxYear
244      */
245     public MoneyWiseTaxCredit getTaxYear() {
246         return getValues().getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_TAXYEAR, MoneyWiseTaxCredit.class);
247     }
248 
249     /**
250      * Set taxYear value.
251      *
252      * @param pValue the value
253      */
254     private void setValueTaxYear(final MoneyWiseTaxCredit pValue) {
255         getValues().setUncheckedValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_TAXYEAR, pValue);
256     }
257 
258     @Override
259     public MoneyWiseTransInfoSet getInfoSet() {
260         return theInfoSet;
261     }
262 
263     /**
264      * Obtain fieldValue for infoSet.
265      *
266      * @param pFieldId the fieldId
267      * @return the value
268      */
269     private Object getFieldValue(final MetisDataFieldId pFieldId) {
270         return theInfoSet != null ? theInfoSet.getFieldValue(pFieldId) : null;
271     }
272 
273     /**
274      * Obtain Account Delta Units.
275      *
276      * @return the Account Delta Units
277      */
278     public final OceanusUnits getAccountDeltaUnits() {
279         return hasInfoSet
280                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.ACCOUNTDELTAUNITS, OceanusUnits.class)
281                 : null;
282     }
283 
284     /**
285      * Obtain Partner Delta Units.
286      *
287      * @return the Partner Delta Units
288      */
289     public final OceanusUnits getPartnerDeltaUnits() {
290         return hasInfoSet
291                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.PARTNERDELTAUNITS, OceanusUnits.class)
292                 : null;
293     }
294 
295     /**
296      * Obtain Tax Credit.
297      *
298      * @return the Tax Credit
299      */
300     public final OceanusMoney getTaxCredit() {
301         return hasInfoSet
302                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.TAXCREDIT, OceanusMoney.class)
303                 : null;
304     }
305 
306     /**
307      * Obtain Price.
308      *
309      * @return the Price
310      */
311     public final OceanusPrice getPrice() {
312         return hasInfoSet
313                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.PRICE, OceanusPrice.class)
314                 : null;
315     }
316 
317     /**
318      * Obtain Commission.
319      *
320      * @return the Commission
321      */
322     public final OceanusMoney getCommission() {
323         return hasInfoSet
324                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.COMMISSION, OceanusMoney.class)
325                 : null;
326     }
327 
328     /**
329      * Obtain Dilution.
330      *
331      * @return the Dilution
332      */
333     public final OceanusRatio getDilution() {
334         return hasInfoSet
335                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.DILUTION, OceanusRatio.class)
336                 : null;
337     }
338 
339     /**
340      * Obtain Qualifying Years.
341      *
342      * @return the Years
343      */
344     public final Integer getYears() {
345         return hasInfoSet
346                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.QUALIFYYEARS, Integer.class)
347                 : null;
348     }
349 
350     /**
351      * Obtain Employer National Insurance.
352      *
353      * @return the NatInsurance
354      */
355     public final OceanusMoney getEmployerNatIns() {
356         return hasInfoSet
357                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.EMPLOYERNATINS, OceanusMoney.class)
358                 : null;
359     }
360 
361     /**
362      * Obtain Employee National Insurance.
363      *
364      * @return the NatInsurance
365      */
366     public final OceanusMoney getEmployeeNatIns() {
367         return hasInfoSet
368                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.EMPLOYEENATINS, OceanusMoney.class)
369                 : null;
370     }
371 
372     /**
373      * Obtain Deemed Benefit.
374      *
375      * @return the Benefit
376      */
377     public final OceanusMoney getDeemedBenefit() {
378         return hasInfoSet
379                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.DEEMEDBENEFIT, OceanusMoney.class)
380                 : null;
381     }
382 
383     /**
384      * Obtain Withheld amount.
385      *
386      * @return the Withheld
387      */
388     public final OceanusMoney getWithheld() {
389         return hasInfoSet
390                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.WITHHELD, OceanusMoney.class)
391                 : null;
392     }
393 
394     /**
395      * Obtain Reference.
396      *
397      * @return the Reference
398      */
399     public final String getReference() {
400         return hasInfoSet
401                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.REFERENCE, String.class)
402                 : null;
403     }
404 
405     /**
406      * Obtain Comments.
407      *
408      * @return the Comments
409      */
410     public final String getComments() {
411         return hasInfoSet
412                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.COMMENTS, String.class)
413                 : null;
414     }
415 
416     /**
417      * Obtain ReturnedCash Account.
418      *
419      * @return the ReturnedCash
420      */
421     public final MoneyWiseTransAsset getReturnedCashAccount() {
422         return hasInfoSet
423                 ? theInfoSet.getTransAsset(MoneyWiseTransInfoClass.RETURNEDCASHACCOUNT)
424                 : null;
425     }
426 
427     /**
428      * Obtain Partner Amount.
429      *
430      * @return the Partner Amount
431      */
432     public final OceanusMoney getPartnerAmount() {
433         return hasInfoSet
434                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.PARTNERAMOUNT, OceanusMoney.class)
435                 : null;
436     }
437 
438     /**
439      * Obtain ExchangeRate.
440      *
441      * @return the ExchangeRate
442      */
443     public final OceanusRatio getExchangeRate() {
444         return hasInfoSet
445                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.XCHANGERATE, OceanusRatio.class)
446                 : null;
447     }
448 
449     /**
450      * Obtain ReturnedCash.
451      *
452      * @return the ReturnedCash
453      */
454     public final OceanusMoney getReturnedCash() {
455         return hasInfoSet
456                 ? theInfoSet.getValue(MoneyWiseTransInfoClass.RETURNEDCASH, OceanusMoney.class)
457                 : null;
458     }
459 
460     /**
461      * Obtain the Transaction tagList.
462      *
463      * @return the list of transaction tags
464      */
465     @SuppressWarnings("unchecked")
466     public List<MoneyWiseTransTag> getTransactionTags() {
467         return hasInfoSet
468                 ? (List<MoneyWiseTransTag>) theInfoSet.getListValue(MoneyWiseTransInfoClass.TRANSTAG)
469                 : null;
470     }
471 
472     @Override
473     public MetisDataState getState() {
474         /* Pop history for self */
475         MetisDataState myState = super.getState();
476 
477         /* If we should use the InfoSet */
478         if ((myState == MetisDataState.CLEAN) && useInfoSet) {
479             /* Get state for infoSet */
480             myState = theInfoSet.getState();
481         }
482 
483         /* Return the state */
484         return myState;
485     }
486 
487     @Override
488     public MetisDataEditState getEditState() {
489         /* Pop history for self */
490         MetisDataEditState myState = super.getEditState();
491 
492         /* If we should use the InfoSet */
493         if (myState == MetisDataEditState.CLEAN
494                 && useInfoSet) {
495             /* Get state for infoSet */
496             myState = theInfoSet.getEditState();
497         }
498 
499         /* Return the state */
500         return myState;
501     }
502 
503     @Override
504     public boolean hasHistory() {
505         /* Check for history for self */
506         boolean hasHistory = super.hasHistory();
507 
508         /* If we should use the InfoSet */
509         if (!hasHistory && useInfoSet) {
510             /* Check history for infoSet */
511             hasHistory = theInfoSet.hasHistory();
512         }
513 
514         /* Return details */
515         return hasHistory;
516     }
517 
518     @Override
519     public void pushHistory() {
520         /* Push history for self */
521         super.pushHistory();
522 
523         /* If we should use the InfoSet */
524         if (useInfoSet) {
525             /* Push history for infoSet */
526             theInfoSet.pushHistory();
527         }
528     }
529 
530     @Override
531     public void popHistory() {
532         /* Pop history for self */
533         super.popHistory();
534 
535         /* If we should use the InfoSet */
536         if (useInfoSet) {
537             /* Pop history for infoSet */
538             theInfoSet.popHistory();
539         }
540     }
541 
542     @Override
543     public boolean checkForHistory() {
544         /* Check for history for self */
545         boolean bChanges = super.checkForHistory();
546 
547         /* If we should use the InfoSet */
548         if (useInfoSet) {
549             /* Check for history for infoSet */
550             bChanges |= theInfoSet.checkForHistory();
551         }
552 
553         /* return result */
554         return bChanges;
555     }
556 
557     @Override
558     public MetisDataDifference fieldChanged(final MetisDataFieldId pField) {
559         /* Handle InfoSet fields */
560         final MoneyWiseTransInfoClass myClass = MoneyWiseTransInfoSet.getClassForField(pField);
561         if (myClass != null) {
562             return useInfoSet
563                     ? theInfoSet.fieldChanged(myClass)
564                     : MetisDataDifference.IDENTICAL;
565         }
566 
567         /* Check super fields */
568         return super.fieldChanged(pField);
569     }
570 
571     @Override
572     public void setDeleted(final boolean bDeleted) {
573         /* Pass call to infoSet if required */
574         if (useInfoSet) {
575             theInfoSet.setDeleted(bDeleted);
576         }
577 
578         /* Pass call onwards */
579         super.setDeleted(bDeleted);
580     }
581 
582     @Override
583     public boolean isLocked() {
584         /* Check credit/debit */
585         if (super.isLocked()) {
586             return true;
587         }
588 
589         /* Check ReturnedCash Account */
590         final MoneyWiseTransAsset myReturnedCash = getReturnedCashAccount();
591         return myReturnedCash != null && myReturnedCash.isClosed();
592     }
593 
594     @Override
595     public MoneyWiseTransaction getBase() {
596         return (MoneyWiseTransaction) super.getBase();
597     }
598 
599     @Override
600     public MoneyWiseTransactionList getList() {
601         return (MoneyWiseTransactionList) super.getList();
602     }
603 
604     /**
605      * Compare this event to another to establish sort order.
606      *
607      * @param pThat The Event to compare to
608      * @return (-1,0,1) depending of whether this object is before, equal, or after the passed
609      * object in the sort order
610      */
611     @Override
612     public int compareValues(final PrometheusDataItem pThat) {
613         /* Handle the trivial cases */
614         final MoneyWiseTransaction myThat = (MoneyWiseTransaction) pThat;
615 
616         /* If header settings differ */
617         if (isHeader() != pThat.isHeader()) {
618             return isHeader()
619                     ? -1
620                     : 1;
621         }
622 
623         /* If the dates differ */
624         final int iDiff = MetisDataDifference.compareObject(getDate(), myThat.getDate());
625         if (iDiff != 0) {
626             return iDiff;
627         }
628 
629         /* Compare the underlying details */
630         return super.compareValues(pThat);
631     }
632 
633     @Override
634     public void resolveDataSetLinks() throws OceanusException {
635         /* Update the Transaction details */
636         super.resolveDataSetLinks();
637 
638         /* Sort linkSets */
639         if (hasInfoSet) {
640             theInfoSet.sortLinkSets();
641         }
642     }
643 
644     /**
645      * resolve editSet links.
646      *
647      * @throws OceanusException on error
648      */
649     public void resolveEditSetLinks() throws OceanusException {
650         /* Access the editSet */
651         final PrometheusEditSet myEditSet = getList().getEditSet();
652 
653         /* Resolve data links */
654         resolveTransactionAsset(myEditSet, this, MoneyWiseBasicResource.TRANSACTION_ACCOUNT);
655         resolveTransactionAsset(myEditSet, this, MoneyWiseBasicResource.TRANSACTION_PARTNER);
656         resolveDataLink(MoneyWiseBasicDataType.TRANSCATEGORY, myEditSet.getDataList(MoneyWiseBasicDataType.TRANSCATEGORY, MoneyWiseTransCategoryList.class));
657 
658         /* Resolve links in infoSet */
659         theInfoSet.resolveEditSetLinks(myEditSet);
660     }
661 
662     /**
663      * Calculate the tax credit for a transaction.
664      *
665      * @return the calculated tax credit
666      */
667     public final OceanusMoney calculateTaxCredit() {
668         final MoneyWiseTaxCredit myTax = getTaxYear();
669         final OceanusMoney myCredit = getTaxCredit();
670 
671         /* Ignore unless tax credit is null/zero */
672         if (myCredit != null
673                 && myCredit.isNonZero()) {
674             return myCredit;
675         }
676 
677         /* Ignore unless category is interest/dividend */
678         if ((getCategory() == null)
679                 || (!isInterest()
680                 && !isDividend())) {
681             return myCredit;
682         }
683 
684         /* Determine the tax credit rate */
685         final OceanusRate myRate = isInterest()
686                 ? myTax.getTaxCreditRateForInterest()
687                 : myTax.getTaxCreditRateForDividend();
688 
689         /* Calculate the tax credit */
690         return getAmount().taxCreditAtRate(myRate);
691     }
692 
693     /**
694      * Set a new date.
695      *
696      * @param pDate the new date
697      */
698     public void setDate(final OceanusDate pDate) {
699         setValueDate((pDate == null)
700                 ? null
701                 : new OceanusDate(pDate));
702     }
703 
704     /**
705      * Set a new AccountDeltaUnits.
706      *
707      * @param pUnits the new units
708      * @throws OceanusException on error
709      */
710     public final void setAccountDeltaUnits(final OceanusUnits pUnits) throws OceanusException {
711         setInfoSetValue(MoneyWiseTransInfoClass.ACCOUNTDELTAUNITS, pUnits);
712     }
713 
714     /**
715      * Set a new PartnerDeltaUnits.
716      *
717      * @param pUnits the new units
718      * @throws OceanusException on error
719      */
720     public final void setPartnerDeltaUnits(final OceanusUnits pUnits) throws OceanusException {
721         setInfoSetValue(MoneyWiseTransInfoClass.PARTNERDELTAUNITS, pUnits);
722     }
723 
724     /**
725      * Set a new TaxCredit.
726      *
727      * @param pCredit the new credit
728      * @throws OceanusException on error
729      */
730     public final void setTaxCredit(final OceanusMoney pCredit) throws OceanusException {
731         setInfoSetValue(MoneyWiseTransInfoClass.TAXCREDIT, pCredit);
732     }
733 
734     /**
735      * Set a new Price.
736      *
737      * @param pPrice the new price
738      * @throws OceanusException on error
739      */
740     public final void setPrice(final OceanusPrice pPrice) throws OceanusException {
741         setInfoSetValue(MoneyWiseTransInfoClass.PRICE, pPrice);
742     }
743 
744     /**
745      * Set a new Commission.
746      *
747      * @param pCommission the new commission
748      * @throws OceanusException on error
749      */
750     public final void setCommission(final OceanusMoney pCommission) throws OceanusException {
751         setInfoSetValue(MoneyWiseTransInfoClass.COMMISSION, pCommission);
752     }
753 
754     /**
755      * Set a new Dilution.
756      *
757      * @param pDilution the new dilution
758      * @throws OceanusException on error
759      */
760     public final void setDilution(final OceanusRatio pDilution) throws OceanusException {
761         setInfoSetValue(MoneyWiseTransInfoClass.DILUTION, pDilution);
762     }
763 
764     /**
765      * Set a new Qualifying Years.
766      *
767      * @param pYears the new years
768      * @throws OceanusException on error
769      */
770     public final void setYears(final Integer pYears) throws OceanusException {
771         setInfoSetValue(MoneyWiseTransInfoClass.QUALIFYYEARS, pYears);
772     }
773 
774     /**
775      * Set a new Employer NatInsurance.
776      *
777      * @param pNatIns the new insurance
778      * @throws OceanusException on error
779      */
780     public final void setEmployerNatIns(final OceanusMoney pNatIns) throws OceanusException {
781         setInfoSetValue(MoneyWiseTransInfoClass.EMPLOYERNATINS, pNatIns);
782     }
783 
784     /**
785      * Set a new Employee NatInsurance.
786      *
787      * @param pNatIns the new insurance
788      * @throws OceanusException on error
789      */
790     public final void setEmployeeNatIns(final OceanusMoney pNatIns) throws OceanusException {
791         setInfoSetValue(MoneyWiseTransInfoClass.EMPLOYEENATINS, pNatIns);
792     }
793 
794     /**
795      * Set a new deemed`Benefit.
796      *
797      * @param pBenefit the new benefit
798      * @throws OceanusException on error
799      */
800     public final void setDeemedBenefit(final OceanusMoney pBenefit) throws OceanusException {
801         setInfoSetValue(MoneyWiseTransInfoClass.DEEMEDBENEFIT, pBenefit);
802     }
803 
804     /**
805      * Set a new Withheld.
806      *
807      * @param pWithheld the new withheld
808      * @throws OceanusException on error
809      */
810     public final void setWithheld(final OceanusMoney pWithheld) throws OceanusException {
811         setInfoSetValue(MoneyWiseTransInfoClass.WITHHELD, pWithheld);
812     }
813 
814     /**
815      * Set a new Partner Amount.
816      *
817      * @param pValue the new partner amount
818      * @throws OceanusException on error
819      */
820     public final void setPartnerAmount(final OceanusMoney pValue) throws OceanusException {
821         setInfoSetValue(MoneyWiseTransInfoClass.PARTNERAMOUNT, pValue);
822     }
823 
824     /**
825      * Set a new ExchangeRate.
826      *
827      * @param pRate the new rate
828      * @throws OceanusException on error
829      */
830     public final void setExchangeRate(final OceanusRatio pRate) throws OceanusException {
831         setInfoSetValue(MoneyWiseTransInfoClass.XCHANGERATE, pRate);
832     }
833 
834     /**
835      * Set a new ReturnedCash.
836      *
837      * @param pValue the new returned cash
838      * @throws OceanusException on error
839      */
840     public final void setReturnedCash(final OceanusMoney pValue) throws OceanusException {
841         setInfoSetValue(MoneyWiseTransInfoClass.RETURNEDCASH, pValue);
842     }
843 
844     /**
845      * Set a new Reference.
846      *
847      * @param pReference the new reference
848      * @throws OceanusException on error
849      */
850     public final void setReference(final String pReference) throws OceanusException {
851         setInfoSetValue(MoneyWiseTransInfoClass.REFERENCE, pReference);
852     }
853 
854     /**
855      * Set new Comments.
856      *
857      * @param pComments the new comments
858      * @throws OceanusException on error
859      */
860     public final void setComments(final String pComments) throws OceanusException {
861         setInfoSetValue(MoneyWiseTransInfoClass.COMMENTS, pComments);
862     }
863 
864     /**
865      * Set a new ReturnedCasah Account.
866      *
867      * @param pAccount the new returned cash account
868      * @throws OceanusException on error
869      */
870     public final void setReturnedCashAccount(final MoneyWiseTransAsset pAccount) throws OceanusException {
871         setInfoSetValue(MoneyWiseTransInfoClass.RETURNEDCASHACCOUNT, pAccount);
872     }
873 
874     /**
875      * Set the transaction tags.
876      *
877      * @param pTags the tags
878      * @throws OceanusException on error
879      */
880     public final void setTransactionTags(final List<MoneyWiseTransTag> pTags) throws OceanusException {
881         /* Reject if there is no infoSet */
882         if (!hasInfoSet) {
883             throw new MoneyWiseLogicException(ERROR_BADINFOSET);
884         }
885 
886         /* Link the value */
887         theInfoSet.setListValue(MoneyWiseTransInfoClass.TRANSTAG, pTags);
888     }
889 
890     /**
891      * Set an infoSet value.
892      *
893      * @param pInfoClass the class of info to set
894      * @param pValue     the value to set
895      * @throws OceanusException on error
896      */
897     private void setInfoSetValue(final MoneyWiseTransInfoClass pInfoClass,
898                                  final Object pValue) throws OceanusException {
899         /* Reject if there is no infoSet */
900         if (!hasInfoSet) {
901             throw new MoneyWiseLogicException(ERROR_BADINFOSET);
902         }
903 
904         /* Set the value */
905         theInfoSet.setValue(pInfoClass, pValue);
906     }
907 
908     @Override
909     public void touchUnderlyingItems() {
910         /* touch underlying items */
911         super.touchUnderlyingItems();
912 
913         /* touch infoSet items */
914         theInfoSet.touchUnderlyingItems();
915     }
916 
917     /**
918      * Update base transaction from an edited transaction.
919      *
920      * @param pTrans the edited transaction
921      * @return whether changes have been made
922      */
923     @Override
924     public boolean applyChanges(final PrometheusDataItem pTrans) {
925         /* Can only update from a transaction */
926         if (!(pTrans instanceof MoneyWiseTransaction myTrans)) {
927             return false;
928         }
929 
930         /* Store the current detail into history */
931         pushHistory();
932 
933         /* Update the Date if required */
934         if (!MetisDataDifference.isEqual(getDate(), myTrans.getDate())) {
935             setValueDate(myTrans.getDate());
936         }
937 
938         /* Apply basic changes */
939         applyBasicChanges(myTrans);
940 
941         /* Check for changes */
942         return checkForHistory();
943     }
944 
945     @Override
946     public void flipAssets() throws OceanusException {
947         /* Handle underlying values */
948         super.flipAssets();
949 
950         /* Flip deltas */
951         final OceanusUnits myActDelta = getAccountDeltaUnits();
952         final OceanusUnits myPartDelta = getPartnerDeltaUnits();
953         setAccountDeltaUnits(myPartDelta);
954         setPartnerDeltaUnits(myActDelta);
955 
956         /* If we need to wipe memory of AccountDelta/PartnerDelta */
957         if (myActDelta != null && myPartDelta == null) {
958             theInfoSet.wipeInfo(MoneyWiseTransInfoClass.ACCOUNTDELTAUNITS);
959         }
960         if (myPartDelta != null && myActDelta == null) {
961             theInfoSet.wipeInfo(MoneyWiseTransInfoClass.PARTNERDELTAUNITS);
962         }
963     }
964 
965     @Override
966     public void removeItem() {
967         theInfoSet.removeItems();
968         super.removeItem();
969     }
970 
971     /**
972      * The Transaction List class.
973      */
974     public static class MoneyWiseTransactionList
975             extends MoneyWiseTransBaseList<MoneyWiseTransaction> {
976         /**
977          * Report fields.
978          */
979         private static final MetisFieldSet<MoneyWiseTransactionList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseTransactionList.class);
980 
981         /**
982          * The TransactionInfo List.
983          */
984         private MoneyWiseTransInfoList theInfoList;
985 
986         /**
987          * The TransactionInfoType list.
988          */
989         private MoneyWiseTransInfoTypeList theInfoTypeList;
990 
991         /**
992          * The EditSet.
993          */
994         private PrometheusEditSet theEditSet;
995 
996         /**
997          * Construct an empty CORE list.
998          *
999          * @param pData the DataSet for the list
1000          */
1001         public MoneyWiseTransactionList(final MoneyWiseDataSet pData) {
1002             super(pData, MoneyWiseTransaction.class, MoneyWiseBasicDataType.TRANSACTION);
1003         }
1004 
1005         /**
1006          * Constructor for a cloned List.
1007          *
1008          * @param pSource the source List
1009          */
1010         protected MoneyWiseTransactionList(final MoneyWiseTransactionList pSource) {
1011             super(pSource);
1012         }
1013 
1014         @Override
1015         public MetisFieldSet<MoneyWiseTransactionList> getDataFieldSet() {
1016             return FIELD_DEFS;
1017         }
1018 
1019         @Override
1020         public String listName() {
1021             return LIST_NAME;
1022         }
1023 
1024         @Override
1025         public MetisFieldSetDef getItemFields() {
1026             return MoneyWiseTransaction.FIELD_DEFS;
1027         }
1028 
1029         /**
1030          * Obtain the transactionInfoList.
1031          *
1032          * @return the transaction info list
1033          */
1034         public MoneyWiseTransInfoList getTransactionInfo() {
1035             if (theInfoList == null) {
1036                 theInfoList = getDataSet().getTransactionInfo();
1037             }
1038             return theInfoList;
1039         }
1040 
1041         /**
1042          * Set the transactionInfoTypeList.
1043          *
1044          * @param pInfoList the info type list
1045          */
1046         protected void setTransactionInfo(final MoneyWiseTransInfoList pInfoList) {
1047             theInfoList = pInfoList;
1048         }
1049 
1050         /**
1051          * Obtain the transactionInfoTypeList.
1052          *
1053          * @return the transaction info type list
1054          */
1055         public MoneyWiseTransInfoTypeList getTransInfoTypes() {
1056             if (theInfoTypeList == null) {
1057                 theInfoTypeList = getDataSet().getTransInfoTypes();
1058             }
1059             return theInfoTypeList;
1060         }
1061 
1062         /**
1063          * Set the transactionInfoTypeList.
1064          *
1065          * @param pInfoTypeList the info type list
1066          */
1067         protected void setTransInfoTypes(final MoneyWiseTransInfoTypeList pInfoTypeList) {
1068             theInfoTypeList = pInfoTypeList;
1069         }
1070 
1071         /**
1072          * Obtain editSet.
1073          *
1074          * @return the editSet
1075          */
1076         public PrometheusEditSet getEditSet() {
1077             return theEditSet;
1078         }
1079 
1080         /**
1081          * Set the editSet.
1082          *
1083          * @param pEditSet the editSet
1084          */
1085         public void setEditSet(final PrometheusEditSet pEditSet) {
1086             theEditSet = pEditSet;
1087         }
1088 
1089         @Override
1090         protected MoneyWiseTransactionList getEmptyList(final PrometheusListStyle pStyle) {
1091             final MoneyWiseTransactionList myList = new MoneyWiseTransactionList(this);
1092             myList.setStyle(pStyle);
1093             return myList;
1094         }
1095 
1096         /**
1097          * Derive Edit list.
1098          *
1099          * @param pEditSet the editSet
1100          * @return the edit list
1101          * @throws OceanusException on error
1102          */
1103         public MoneyWiseTransactionList deriveEditList(final PrometheusEditSet pEditSet) throws OceanusException {
1104             /* Build an empty List */
1105             final MoneyWiseTransactionList myList = getEmptyList(PrometheusListStyle.EDIT);
1106             pEditSet.setEditEntryList(MoneyWiseBasicDataType.TRANSACTION, myList);
1107 
1108             /* Store InfoType list */
1109             myList.theInfoTypeList = pEditSet.getDataList(MoneyWiseStaticDataType.TRANSINFOTYPE, MoneyWiseTransInfoTypeList.class);
1110 
1111             /* Create info List */
1112             final MoneyWiseTransInfoList myTransInfo = getTransactionInfo();
1113             myList.theInfoList = myTransInfo.getEmptyList(PrometheusListStyle.EDIT);
1114             pEditSet.setEditEntryList(MoneyWiseBasicDataType.TRANSACTIONINFO, myList.theInfoList);
1115 
1116             /* Store the editSet */
1117             myList.theEditSet = pEditSet;
1118             myList.getValidator().setEditSet(pEditSet);
1119 
1120             /* Loop through the portfolios */
1121             final Iterator<MoneyWiseTransaction> myIterator = iterator();
1122             while (myIterator.hasNext()) {
1123                 final MoneyWiseTransaction myCurr = myIterator.next();
1124 
1125                 /* Ignore deleted transactions */
1126                 if (myCurr.isDeleted()) {
1127                     continue;
1128                 }
1129 
1130                 /* Build the new linked transaction and add it to the list */
1131                 final MoneyWiseTransaction myTrans = new MoneyWiseTransaction(myList, myCurr);
1132                 myList.add(myTrans);
1133                 myTrans.resolveEditSetLinks();
1134 
1135                 /* Adjust the map */
1136                 myTrans.adjustMapForItem();
1137             }
1138 
1139             /* Return the list */
1140             return myList;
1141         }
1142 
1143         /**
1144          * Relink LastEvent etc. for assets.
1145          *
1146          * @throws OceanusException on error
1147          */
1148         public void relinkEditAssetEvents() throws OceanusException {
1149             final PrometheusEditSet myEditSet = getEditSet();
1150             final Iterator<PrometheusEditEntry<?>> myIterator = myEditSet.listIterator();
1151             while (myIterator.hasNext()) {
1152                 final PrometheusEditEntry<?> myEntry = myIterator.next();
1153                 final PrometheusDataList<?> myList = myEntry.getDataList();
1154                 if (myList instanceof MoneyWiseAssetBaseList<?> myAssetList) {
1155                     myAssetList.resolveLateEditSetLinks();
1156                 }
1157             }
1158         }
1159 
1160         /**
1161          * Add a new item to the list.
1162          *
1163          * @param pItem the item to add
1164          * @return the newly added item
1165          */
1166         @Override
1167         public MoneyWiseTransaction addCopyItem(final PrometheusDataItem pItem) {
1168             if (pItem instanceof MoneyWiseTransaction myItem) {
1169                 final MoneyWiseTransaction myTrans = new MoneyWiseTransaction(this, myItem);
1170                 add(myTrans);
1171                 return myTrans;
1172             }
1173             throw new UnsupportedOperationException();
1174         }
1175 
1176         /**
1177          * Add a new item to the edit list.
1178          *
1179          * @return the newly added item
1180          */
1181         @Override
1182         public MoneyWiseTransaction addNewItem() {
1183             /* Create a new Transaction */
1184             final MoneyWiseTransaction myTrans = new MoneyWiseTransaction(this);
1185 
1186             /* Set the Date as the start of the range */
1187             OceanusDate myDate = new OceanusDate();
1188             final OceanusDateRange myRange = getDataSet().getDateRange();
1189             if (myRange.compareToDate(myDate) != 0) {
1190                 myDate = myRange.getStart();
1191             }
1192             myTrans.setDate(myDate);
1193 
1194             /* Add to list and return */
1195             add(myTrans);
1196             return myTrans;
1197         }
1198 
1199         @Override
1200         public MoneyWiseTransaction addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
1201             /* Create the transaction */
1202             final MoneyWiseTransaction myTrans = new MoneyWiseTransaction(this, pValues);
1203 
1204             /* Check that this TransId has not been previously added */
1205             if (!isIdUnique(myTrans.getIndexedId())) {
1206                 myTrans.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
1207                 throw new MoneyWiseDataException(myTrans, ERROR_VALIDATION);
1208             }
1209 
1210             /* Add to the list */
1211             add(myTrans);
1212 
1213             /* Loop through the info items */
1214             if (pValues.hasInfoItems()) {
1215                 /* Loop through the items */
1216                 final Iterator<PrometheusInfoItem> myIterator = pValues.infoIterator();
1217                 while (myIterator.hasNext()) {
1218                     final PrometheusInfoItem myItem = myIterator.next();
1219 
1220                     /* Build info */
1221                     final PrometheusDataValues myValues = myItem.getValues(myTrans);
1222                     getTransactionInfo().addValuesItem(myValues);
1223                 }
1224             }
1225 
1226             /* Return it */
1227             return myTrans;
1228         }
1229 
1230         @Override
1231         protected PrometheusDataMapItem allocateDataMap() {
1232             /* Unused */
1233             throw new UnsupportedOperationException();
1234         }
1235 
1236         @Override
1237         public void postProcessOnLoad() throws OceanusException {
1238             /* Resolve links and sort the data */
1239             super.resolveDataSetLinks();
1240             reSort();
1241         }
1242 
1243         @Override
1244         public void updateMaps() {
1245             /* Null operation */
1246         }
1247     }
1248 }