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.date.OceanusDate;
21  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateFormatter;
22  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
23  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
24  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
25  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
26  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
27  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
28  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
29  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
30  import io.github.tonywasher.joceanus.metis.data.MetisDataEditState;
31  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
32  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
33  import io.github.tonywasher.joceanus.metis.data.MetisDataState;
34  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
35  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
36  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase.MoneyWiseAssetBaseList;
37  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTax.MoneyWiseTaxCredit;
38  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTax.MoneyWiseTaxFactory;
39  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory.MoneyWiseTransCategoryList;
40  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransInfo.MoneyWiseTransInfoList;
41  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
42  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoClass;
43  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoType.MoneyWiseTransInfoTypeList;
44  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
45  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseLogicException;
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)) {
927             return false;
928         }
929         final MoneyWiseTransaction myTrans = (MoneyWiseTransaction) pTrans;
930 
931         /* Store the current detail into history */
932         pushHistory();
933 
934         /* Update the Date if required */
935         if (!MetisDataDifference.isEqual(getDate(), myTrans.getDate())) {
936             setValueDate(myTrans.getDate());
937         }
938 
939         /* Apply basic changes */
940         applyBasicChanges(myTrans);
941 
942         /* Check for changes */
943         return checkForHistory();
944     }
945 
946     @Override
947     public void flipAssets() throws OceanusException {
948         /* Handle underlying values */
949         super.flipAssets();
950 
951         /* Flip deltas */
952         final OceanusUnits myActDelta = getAccountDeltaUnits();
953         final OceanusUnits myPartDelta = getPartnerDeltaUnits();
954         setAccountDeltaUnits(myPartDelta);
955         setPartnerDeltaUnits(myActDelta);
956 
957         /* If we need to wipe memory of AccountDelta/PartnerDelta */
958         if (myActDelta != null && myPartDelta == null) {
959             theInfoSet.wipeInfo(MoneyWiseTransInfoClass.ACCOUNTDELTAUNITS);
960         }
961         if (myPartDelta != null && myActDelta == null) {
962             theInfoSet.wipeInfo(MoneyWiseTransInfoClass.PARTNERDELTAUNITS);
963         }
964     }
965 
966     @Override
967     public void removeItem() {
968         theInfoSet.removeItems();
969         super.removeItem();
970     }
971 
972     /**
973      * The Transaction List class.
974      */
975     public static class MoneyWiseTransactionList
976             extends MoneyWiseTransBaseList<MoneyWiseTransaction> {
977         /**
978          * Report fields.
979          */
980         private static final MetisFieldSet<MoneyWiseTransactionList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseTransactionList.class);
981 
982         /**
983          * The TransactionInfo List.
984          */
985         private MoneyWiseTransInfoList theInfoList;
986 
987         /**
988          * The TransactionInfoType list.
989          */
990         private MoneyWiseTransInfoTypeList theInfoTypeList;
991 
992         /**
993          * The EditSet.
994          */
995         private PrometheusEditSet theEditSet;
996 
997         /**
998          * Construct an empty CORE list.
999          *
1000          * @param pData the DataSet for the list
1001          */
1002         public MoneyWiseTransactionList(final MoneyWiseDataSet pData) {
1003             super(pData, MoneyWiseTransaction.class, MoneyWiseBasicDataType.TRANSACTION);
1004         }
1005 
1006         /**
1007          * Constructor for a cloned List.
1008          *
1009          * @param pSource the source List
1010          */
1011         protected MoneyWiseTransactionList(final MoneyWiseTransactionList pSource) {
1012             super(pSource);
1013         }
1014 
1015         @Override
1016         public MetisFieldSet<MoneyWiseTransactionList> getDataFieldSet() {
1017             return FIELD_DEFS;
1018         }
1019 
1020         @Override
1021         public String listName() {
1022             return LIST_NAME;
1023         }
1024 
1025         @Override
1026         public MetisFieldSetDef getItemFields() {
1027             return MoneyWiseTransaction.FIELD_DEFS;
1028         }
1029 
1030         /**
1031          * Obtain the transactionInfoList.
1032          *
1033          * @return the transaction info list
1034          */
1035         public MoneyWiseTransInfoList getTransactionInfo() {
1036             if (theInfoList == null) {
1037                 theInfoList = getDataSet().getTransactionInfo();
1038             }
1039             return theInfoList;
1040         }
1041 
1042         /**
1043          * Set the transactionInfoTypeList.
1044          *
1045          * @param pInfoList the info type list
1046          */
1047         protected void setTransactionInfo(final MoneyWiseTransInfoList pInfoList) {
1048             theInfoList = pInfoList;
1049         }
1050 
1051         /**
1052          * Obtain the transactionInfoTypeList.
1053          *
1054          * @return the transaction info type list
1055          */
1056         public MoneyWiseTransInfoTypeList getTransInfoTypes() {
1057             if (theInfoTypeList == null) {
1058                 theInfoTypeList = getDataSet().getTransInfoTypes();
1059             }
1060             return theInfoTypeList;
1061         }
1062 
1063         /**
1064          * Set the transactionInfoTypeList.
1065          *
1066          * @param pInfoTypeList the info type list
1067          */
1068         protected void setTransInfoTypes(final MoneyWiseTransInfoTypeList pInfoTypeList) {
1069             theInfoTypeList = pInfoTypeList;
1070         }
1071 
1072         /**
1073          * Obtain editSet.
1074          *
1075          * @return the editSet
1076          */
1077         public PrometheusEditSet getEditSet() {
1078             return theEditSet;
1079         }
1080 
1081         /**
1082          * Set the editSet.
1083          *
1084          * @param pEditSet the editSet
1085          */
1086         public void setEditSet(final PrometheusEditSet pEditSet) {
1087             theEditSet = pEditSet;
1088         }
1089 
1090         @Override
1091         protected MoneyWiseTransactionList getEmptyList(final PrometheusListStyle pStyle) {
1092             final MoneyWiseTransactionList myList = new MoneyWiseTransactionList(this);
1093             myList.setStyle(pStyle);
1094             return myList;
1095         }
1096 
1097         /**
1098          * Derive Edit list.
1099          *
1100          * @param pEditSet the editSet
1101          * @return the edit list
1102          * @throws OceanusException on error
1103          */
1104         public MoneyWiseTransactionList deriveEditList(final PrometheusEditSet pEditSet) throws OceanusException {
1105             /* Build an empty List */
1106             final MoneyWiseTransactionList myList = getEmptyList(PrometheusListStyle.EDIT);
1107             pEditSet.setEditEntryList(MoneyWiseBasicDataType.TRANSACTION, myList);
1108 
1109             /* Store InfoType list */
1110             myList.theInfoTypeList = pEditSet.getDataList(MoneyWiseStaticDataType.TRANSINFOTYPE, MoneyWiseTransInfoTypeList.class);
1111 
1112             /* Create info List */
1113             final MoneyWiseTransInfoList myTransInfo = getTransactionInfo();
1114             myList.theInfoList = myTransInfo.getEmptyList(PrometheusListStyle.EDIT);
1115             pEditSet.setEditEntryList(MoneyWiseBasicDataType.TRANSACTIONINFO, myList.theInfoList);
1116 
1117             /* Store the editSet */
1118             myList.theEditSet = pEditSet;
1119             myList.getValidator().setEditSet(pEditSet);
1120 
1121             /* Loop through the portfolios */
1122             final Iterator<MoneyWiseTransaction> myIterator = iterator();
1123             while (myIterator.hasNext()) {
1124                 final MoneyWiseTransaction myCurr = myIterator.next();
1125 
1126                 /* Ignore deleted transactions */
1127                 if (myCurr.isDeleted()) {
1128                     continue;
1129                 }
1130 
1131                 /* Build the new linked transaction and add it to the list */
1132                 final MoneyWiseTransaction myTrans = new MoneyWiseTransaction(myList, myCurr);
1133                 myList.add(myTrans);
1134                 myTrans.resolveEditSetLinks();
1135 
1136                 /* Adjust the map */
1137                 myTrans.adjustMapForItem();
1138             }
1139 
1140             /* Return the list */
1141             return myList;
1142         }
1143 
1144         /**
1145          * Relink LastEvent etc. for assets.
1146          *
1147          * @throws OceanusException on error
1148          */
1149         public void relinkEditAssetEvents() throws OceanusException {
1150             final PrometheusEditSet myEditSet = getEditSet();
1151             final Iterator<PrometheusEditEntry<?>> myIterator = myEditSet.listIterator();
1152             while (myIterator.hasNext()) {
1153                 final PrometheusEditEntry<?> myEntry = myIterator.next();
1154                 final PrometheusDataList<?> myList = myEntry.getDataList();
1155                 if (myList instanceof MoneyWiseAssetBaseList<?> myAssetList) {
1156                     myAssetList.resolveLateEditSetLinks();
1157                 }
1158             }
1159         }
1160 
1161         /**
1162          * Add a new item to the list.
1163          *
1164          * @param pItem the item to add
1165          * @return the newly added item
1166          */
1167         @Override
1168         public MoneyWiseTransaction addCopyItem(final PrometheusDataItem pItem) {
1169             if (pItem instanceof MoneyWiseTransaction) {
1170                 final MoneyWiseTransaction myTrans = new MoneyWiseTransaction(this, (MoneyWiseTransaction) pItem);
1171                 add(myTrans);
1172                 return myTrans;
1173             }
1174             throw new UnsupportedOperationException();
1175         }
1176 
1177         /**
1178          * Add a new item to the edit list.
1179          *
1180          * @return the newly added item
1181          */
1182         @Override
1183         public MoneyWiseTransaction addNewItem() {
1184             /* Create a new Transaction */
1185             final MoneyWiseTransaction myTrans = new MoneyWiseTransaction(this);
1186 
1187             /* Set the Date as the start of the range */
1188             OceanusDate myDate = new OceanusDate();
1189             final OceanusDateRange myRange = getDataSet().getDateRange();
1190             if (myRange.compareToDate(myDate) != 0) {
1191                 myDate = myRange.getStart();
1192             }
1193             myTrans.setDate(myDate);
1194 
1195             /* Add to list and return */
1196             add(myTrans);
1197             return myTrans;
1198         }
1199 
1200         @Override
1201         public MoneyWiseTransaction addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
1202             /* Create the transaction */
1203             final MoneyWiseTransaction myTrans = new MoneyWiseTransaction(this, pValues);
1204 
1205             /* Check that this TransId has not been previously added */
1206             if (!isIdUnique(myTrans.getIndexedId())) {
1207                 myTrans.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
1208                 throw new MoneyWiseDataException(myTrans, ERROR_VALIDATION);
1209             }
1210 
1211             /* Add to the list */
1212             add(myTrans);
1213 
1214             /* Loop through the info items */
1215             if (pValues.hasInfoItems()) {
1216                 /* Loop through the items */
1217                 final Iterator<PrometheusInfoItem> myIterator = pValues.infoIterator();
1218                 while (myIterator.hasNext()) {
1219                     final PrometheusInfoItem myItem = myIterator.next();
1220 
1221                     /* Build info */
1222                     final PrometheusDataValues myValues = myItem.getValues(myTrans);
1223                     getTransactionInfo().addValuesItem(myValues);
1224                 }
1225             }
1226 
1227             /* Return it */
1228             return myTrans;
1229         }
1230 
1231         @Override
1232         protected PrometheusDataMapItem allocateDataMap() {
1233             /* Unused */
1234             throw new UnsupportedOperationException();
1235         }
1236 
1237         @Override
1238         public void postProcessOnLoad() throws OceanusException {
1239             /* Resolve links and sort the data */
1240             super.resolveDataSetLinks();
1241             reSort();
1242         }
1243 
1244         @Override
1245         public void updateMaps() {
1246             /* Null operation */
1247         }
1248     }
1249 }