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.atlas.data.analysis.buckets;
18  
19  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
20  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
21  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusDecimal;
22  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
23  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
24  import io.github.tonywasher.joceanus.metis.data.MetisDataFieldValue;
25  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
26  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
27  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
28  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldTableItem;
29  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
30  import io.github.tonywasher.joceanus.metis.list.MetisListIndexed;
31  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisEvent;
32  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisHistory;
33  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisBucketRegister;
34  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisTaxBasisAccountBucket.MoneyWiseXAnalysisTaxBasisAccountBucketList;
35  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.values.MoneyWiseXAnalysisTaxBasisAttr;
36  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.values.MoneyWiseXAnalysisTaxBasisValues;
37  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetType;
38  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
39  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
40  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
41  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
42  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTaxBasis;
43  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTaxBasis.MoneyWiseTaxBasisList;
44  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTaxClass;
45  import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseChargeableGainSlice.MoneyWiseChargeableGainSliceList;
46  import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxSource;
47  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
48  
49  import java.util.Comparator;
50  import java.util.Currency;
51  import java.util.Iterator;
52  import java.util.List;
53  
54  /**
55   * The TaxBasis Bucket class.
56   */
57  public class MoneyWiseXAnalysisTaxBasisBucket
58          implements MetisFieldTableItem, MoneyWiseXAnalysisBucketRegister {
59      /**
60       * Local Report fields.
61       */
62      private static final MetisFieldSet<MoneyWiseXAnalysisTaxBasisBucket> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseXAnalysisTaxBasisBucket.class);
63  
64      /*
65       * Declare Fields.
66       */
67      static {
68          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_NAME, MoneyWiseXAnalysisTaxBasisBucket::getAnalysis);
69          FIELD_DEFS.declareLocalField(MoneyWiseStaticDataType.TAXBASIS, MoneyWiseXAnalysisTaxBasisBucket::getTaxBasis);
70          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.BUCKET_BASEVALUES, MoneyWiseXAnalysisTaxBasisBucket::getBaseValues);
71          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.BUCKET_HISTORY, MoneyWiseXAnalysisTaxBasisBucket::getHistoryMap);
72          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.TAXBASIS_ACCOUNTLIST, MoneyWiseXAnalysisTaxBasisBucket::getAccounts);
73          FIELD_DEFS.declareLocalFieldsForEnum(MoneyWiseXAnalysisTaxBasisAttr.class, MoneyWiseXAnalysisTaxBasisBucket::getAttributeValue);
74      }
75  
76      /**
77       * Totals bucket name.
78       */
79      private static final MetisDataFieldId NAME_TOTALS = MoneyWiseXAnalysisBucketResource.ANALYSIS_TOTALS;
80  
81      /**
82       * The analysis.
83       */
84      private final MoneyWiseXAnalysis theAnalysis;
85  
86      /**
87       * Tax Basis.
88       */
89      private final MoneyWiseTaxBasis theTaxBasis;
90  
91      /**
92       * Values.
93       */
94      private final MoneyWiseXAnalysisTaxBasisValues theValues;
95  
96      /**
97       * The base values.
98       */
99      private final MoneyWiseXAnalysisTaxBasisValues theBaseValues;
100 
101     /**
102      * History Map.
103      */
104     private final MoneyWiseXAnalysisHistory<MoneyWiseXAnalysisTaxBasisValues, MoneyWiseXAnalysisTaxBasisAttr> theHistory;
105 
106     /**
107      * Do we have accounts?
108      */
109     private final boolean hasAccounts;
110 
111     /**
112      * Are we an expense bucket?
113      */
114     private final boolean isExpense;
115 
116     /**
117      * AccountBucketList.
118      */
119     private final MoneyWiseXAnalysisTaxBasisAccountBucketList theAccounts;
120 
121     /**
122      * Constructor.
123      *
124      * @param pAnalysis the analysis
125      * @param pTaxBasis the basis
126      */
127     protected MoneyWiseXAnalysisTaxBasisBucket(final MoneyWiseXAnalysis pAnalysis,
128                                                final MoneyWiseTaxBasis pTaxBasis) {
129         /* Store the parameters */
130         theTaxBasis = pTaxBasis;
131         theAnalysis = pAnalysis;
132         isExpense = theTaxBasis != null
133                 && theTaxBasis.getTaxClass().isExpense();
134 
135         /* Create the history map */
136         final MoneyWiseCurrency myDefault = theAnalysis.getCurrency();
137         final Currency myCurrency = myDefault == null
138                 ? MoneyWiseXAnalysisAccountBucket.DEFAULT_CURRENCY
139                 : myDefault.getCurrency();
140         final MoneyWiseXAnalysisTaxBasisValues myValues = new MoneyWiseXAnalysisTaxBasisValues(myCurrency);
141         theHistory = new MoneyWiseXAnalysisHistory<>(myValues);
142 
143         /* Create the account list */
144         hasAccounts = theTaxBasis != null
145                 && !(this instanceof MoneyWiseXAnalysisTaxBasisAccountBucket)
146                 && theTaxBasis.getTaxClass().analyseAccounts();
147         theAccounts = hasAccounts
148                 ? new MoneyWiseXAnalysisTaxBasisAccountBucketList(theAnalysis, this)
149                 : null;
150 
151         /* Access the key value maps */
152         theValues = theHistory.getValues();
153         theBaseValues = theHistory.getBaseValues();
154     }
155 
156     /**
157      * Constructor.
158      *
159      * @param pAnalysis the analysis
160      * @param pBase     the underlying bucket
161      * @param pDate     the date for the bucket
162      */
163     protected MoneyWiseXAnalysisTaxBasisBucket(final MoneyWiseXAnalysis pAnalysis,
164                                                final MoneyWiseXAnalysisTaxBasisBucket pBase,
165                                                final OceanusDate pDate) {
166         /* Copy details from base */
167         theTaxBasis = pBase.getTaxBasis();
168         theAnalysis = pAnalysis;
169         isExpense = pBase.isExpense();
170 
171         /* Access the relevant history */
172         theHistory = new MoneyWiseXAnalysisHistory<>(pBase.getHistoryMap(), pDate);
173 
174         /* Create the account list */
175         hasAccounts = pBase.hasAccounts();
176         theAccounts = hasAccounts
177                 ? new MoneyWiseXAnalysisTaxBasisAccountBucketList(theAnalysis, this, pBase.getAccounts(), pDate)
178                 : null;
179 
180         /* Access the key value maps */
181         theValues = theHistory.getValues();
182         theBaseValues = theHistory.getBaseValues();
183     }
184 
185     /**
186      * Constructor.
187      *
188      * @param pAnalysis the analysis
189      * @param pBase     the underlying bucket
190      * @param pRange    the range for the bucket
191      */
192     protected MoneyWiseXAnalysisTaxBasisBucket(final MoneyWiseXAnalysis pAnalysis,
193                                                final MoneyWiseXAnalysisTaxBasisBucket pBase,
194                                                final OceanusDateRange pRange) {
195         /* Copy details from base */
196         theTaxBasis = pBase.getTaxBasis();
197         theAnalysis = pAnalysis;
198         isExpense = pBase.isExpense();
199 
200         /* Access the relevant history */
201         theHistory = new MoneyWiseXAnalysisHistory<>(pBase.getHistoryMap(), pRange);
202 
203         /* Create the account list */
204         hasAccounts = pBase.hasAccounts();
205         theAccounts = hasAccounts
206                 ? new MoneyWiseXAnalysisTaxBasisAccountBucketList(theAnalysis, this, pBase.getAccounts(), pRange)
207                 : null;
208 
209         /* Access the key value maps */
210         theValues = theHistory.getValues();
211         theBaseValues = theHistory.getBaseValues();
212     }
213 
214     @Override
215     public MetisFieldSet<? extends MoneyWiseXAnalysisTaxBasisBucket> getDataFieldSet() {
216         return FIELD_DEFS;
217     }
218 
219     @Override
220     public String formatObject(final OceanusDataFormatter pFormatter) {
221         return toString();
222     }
223 
224     @Override
225     public String toString() {
226         return getName() + " " + theValues;
227     }
228 
229     @Override
230     public Integer getIndexedId() {
231         return theTaxBasis.getIndexedId();
232     }
233 
234     @Override
235     public Long getBucketId() {
236         return MoneyWiseAssetType.createExternalId(MoneyWiseAssetType.TAXBASIS, getIndexedId());
237     }
238 
239     /**
240      * Obtain name.
241      *
242      * @return the name
243      */
244     public String getName() {
245         return theTaxBasis == null
246                 ? NAME_TOTALS.getId()
247                 : theTaxBasis.getName();
248     }
249 
250     /**
251      * Obtain tax basis.
252      *
253      * @return the basis
254      */
255     public MoneyWiseTaxBasis getTaxBasis() {
256         return theTaxBasis;
257     }
258 
259     /**
260      * Do we have accounts.
261      *
262      * @return true/false
263      */
264     public boolean hasAccounts() {
265         return hasAccounts;
266     }
267 
268     /**
269      * Is this an expense bucket.
270      *
271      * @return true/false
272      */
273     public boolean isExpense() {
274         return isExpense;
275     }
276 
277     /**
278      * Obtain account list.
279      *
280      * @return the account list
281      */
282     private MoneyWiseXAnalysisTaxBasisAccountBucketList getAccounts() {
283         return theAccounts;
284     }
285 
286     /**
287      * Obtain account list iterator.
288      *
289      * @return the iterator
290      */
291     public Iterator<MoneyWiseXAnalysisTaxBasisAccountBucket> accountIterator() {
292         return hasAccounts
293                 ? theAccounts.iterator()
294                 : null;
295     }
296 
297     /**
298      * find an account bucket.
299      *
300      * @param pAccount the account
301      * @return the bucket
302      */
303     public MoneyWiseXAnalysisTaxBasisAccountBucket findAccountBucket(final MoneyWiseTransAsset pAccount) {
304         return hasAccounts
305                 ? theAccounts.findBucket(pAccount)
306                 : null;
307     }
308 
309     /**
310      * Is this bucket idle?
311      *
312      * @return true/false
313      */
314     public boolean isIdle() {
315         return theHistory.isIdle();
316     }
317 
318     /**
319      * Obtain the value map.
320      *
321      * @return the value map
322      */
323     public MoneyWiseXAnalysisTaxBasisValues getValues() {
324         return theValues;
325     }
326 
327     /**
328      * Obtain the value for a particular attribute.
329      *
330      * @param pAttr the attribute
331      * @return the value
332      */
333     public OceanusMoney getMoneyValue(final MoneyWiseXAnalysisTaxBasisAttr pAttr) {
334         return theValues.getMoneyValue(pAttr);
335     }
336 
337     /**
338      * Obtain the base value map.
339      *
340      * @return the base value map
341      */
342     public MoneyWiseXAnalysisTaxBasisValues getBaseValues() {
343         return theBaseValues;
344     }
345 
346     /**
347      * Obtain values for event.
348      *
349      * @param pEvent the event
350      * @return the values (or null)
351      */
352     public MoneyWiseXAnalysisTaxBasisValues getValuesForEvent(final MoneyWiseXAnalysisEvent pEvent) {
353         /* Obtain values for event */
354         return theHistory.getValuesForEvent(pEvent);
355     }
356 
357     /**
358      * Obtain previous values for event.
359      *
360      * @param pEvent the event
361      * @return the values (or null)
362      */
363     public MoneyWiseXAnalysisTaxBasisValues getPreviousValuesForEvent(final MoneyWiseXAnalysisEvent pEvent) {
364         return theHistory.getPreviousValuesForEvent(pEvent);
365     }
366 
367     /**
368      * Obtain delta for event.
369      *
370      * @param pEvent the event
371      * @param pAttr  the attribute
372      * @return the delta (or null)
373      */
374     public OceanusDecimal getDeltaForEvent(final MoneyWiseXAnalysisEvent pEvent,
375                                            final MoneyWiseXAnalysisTaxBasisAttr pAttr) {
376         /* Obtain delta for event */
377         return theHistory.getDeltaValue(pEvent, pAttr);
378     }
379 
380     /**
381      * Obtain the history map.
382      *
383      * @return the history map
384      */
385     private MoneyWiseXAnalysisHistory<MoneyWiseXAnalysisTaxBasisValues, MoneyWiseXAnalysisTaxBasisAttr> getHistoryMap() {
386         return theHistory;
387     }
388 
389     /**
390      * Obtain the analysis.
391      *
392      * @return the analysis
393      */
394     protected MoneyWiseXAnalysis getAnalysis() {
395         return theAnalysis;
396     }
397 
398     /**
399      * Obtain date range.
400      *
401      * @return the range
402      */
403     public OceanusDateRange getDateRange() {
404         return theAnalysis.getDateRange();
405     }
406 
407     /**
408      * Set Attribute.
409      *
410      * @param pAttr  the attribute
411      * @param pValue the value of the attribute
412      */
413     protected void setValue(final MoneyWiseXAnalysisTaxBasisAttr pAttr,
414                             final OceanusMoney pValue) {
415         /* Set the value into the list */
416         theValues.setValue(pAttr, pValue);
417     }
418 
419     /**
420      * Get an attribute value.
421      *
422      * @param pAttr the attribute
423      * @return the value to set
424      */
425     private Object getAttributeValue(final MoneyWiseXAnalysisTaxBasisAttr pAttr) {
426         /* Access value of object */
427         final Object myValue = getValue(pAttr);
428 
429         /* Return the value */
430         return myValue != null
431                 ? myValue
432                 : MetisDataFieldValue.SKIP;
433     }
434 
435     /**
436      * Obtain an attribute value.
437      *
438      * @param pAttr the attribute
439      * @return the value of the attribute or null
440      */
441     private Object getValue(final MoneyWiseXAnalysisTaxBasisAttr pAttr) {
442         /* Obtain the attribute */
443         return theValues.getValue(pAttr);
444     }
445 
446     /**
447      * Adjust Gross and Nett values by amount.
448      *
449      * @param pAmount the amount
450      */
451     public void adjustGrossAndNett(final OceanusMoney pAmount) {
452         adjustGrossAndNett(null, pAmount);
453     }
454 
455     /**
456      * Adjust Gross value by amount.
457      *
458      * @param pAmount the amount
459      */
460     public void adjustGross(final OceanusMoney pAmount) {
461         adjustGross(null, pAmount);
462     }
463 
464     /**
465      * Adjust Gross and Tax values by amount.
466      *
467      * @param pAmount the amount
468      */
469     public void adjustGrossAndTax(final OceanusMoney pAmount) {
470         adjustGrossAndTax(null, pAmount);
471     }
472 
473     /**
474      * Adjust Gross and Nett values by amount.
475      *
476      * @param pAccount the relevant account
477      * @param pAmount  the amount
478      * @return the adjusted taxBasisAccountBucket (or null)
479      */
480     public MoneyWiseXAnalysisTaxBasisAccountBucket adjustGrossAndNett(final MoneyWiseTransAsset pAccount,
481                                                                       final OceanusMoney pAmount) {
482         return adjustValue(pAccount, pAmount, MoneyWiseXTaxBasisAdjust.STANDARD);
483     }
484 
485     /**
486      * Adjust Gross value by amount.
487      *
488      * @param pAccount the relevant account
489      * @param pAmount  the amount
490      * @return the adjusted taxBasisAccountBucket (or null)
491      */
492     public MoneyWiseXAnalysisTaxBasisAccountBucket adjustGross(final MoneyWiseTransAsset pAccount,
493                                                                final OceanusMoney pAmount) {
494         return adjustValue(pAccount, pAmount, MoneyWiseXTaxBasisAdjust.GROSS);
495     }
496 
497     /**
498      * Adjust Gross and Tax values by amount.
499      *
500      * @param pAccount the relevant account
501      * @param pAmount  the amount
502      * @return the adjusted taxBasisAccountBucket (or null)
503      */
504     public MoneyWiseXAnalysisTaxBasisAccountBucket adjustGrossAndTax(final MoneyWiseTransAsset pAccount,
505                                                                      final OceanusMoney pAmount) {
506         return adjustValue(pAccount, pAmount, MoneyWiseXTaxBasisAdjust.TAXCREDIT);
507     }
508 
509     /**
510      * Adjust value.
511      *
512      * @param pAccount the relevant account
513      * @param pValue   the value
514      * @param pAdjust  adjustment control
515      * @return the adjusted taxBasisAccountBucket (or null)
516      */
517     MoneyWiseXAnalysisTaxBasisAccountBucket adjustValue(final MoneyWiseTransAsset pAccount,
518                                                         final OceanusMoney pValue,
519                                                         final MoneyWiseXTaxBasisAdjust pAdjust) {
520         /* Access the existing value */
521         OceanusMoney myGross = theValues.getMoneyValue(MoneyWiseXAnalysisTaxBasisAttr.GROSS);
522         myGross = new OceanusMoney(myGross);
523 
524         /* Subtract or add the value depending as to whether we are an expense bucket */
525         if (isExpense) {
526             myGross.subtractAmount(pValue);
527         } else {
528             myGross.addAmount(pValue);
529         }
530 
531         /* Record the new value */
532         setValue(MoneyWiseXAnalysisTaxBasisAttr.GROSS, myGross);
533 
534         /* If we are adjusting Nett */
535         if (pAdjust.adjustNett()) {
536             /* Access the existing value */
537             OceanusMoney myNett = theValues.getMoneyValue(MoneyWiseXAnalysisTaxBasisAttr.NETT);
538             myNett = new OceanusMoney(myNett);
539 
540             /* Subtract or add the value if we are an expense/income bucket */
541             if (isExpense) {
542                 myNett.subtractAmount(pValue);
543             } else {
544                 myNett.addAmount(pValue);
545             }
546 
547             /* Record the new value */
548             setValue(MoneyWiseXAnalysisTaxBasisAttr.NETT, myNett);
549         }
550 
551         /* If we are adjusting TaxCredit */
552         if (pAdjust.adjustTaxCredit()) {
553             /* Access the existing value */
554             OceanusMoney myTax = theValues.getMoneyValue(MoneyWiseXAnalysisTaxBasisAttr.TAXCREDIT);
555             myTax = new OceanusMoney(myTax);
556 
557             /* Subtract or add the value if we are an expense/income bucket */
558             if (isExpense) {
559                 myTax.subtractAmount(pValue);
560             } else {
561                 myTax.addAmount(pValue);
562             }
563 
564             /* Record the new value */
565             setValue(MoneyWiseXAnalysisTaxBasisAttr.TAXCREDIT, myTax);
566         }
567 
568         /* If we have accounts and are passed an account, adjust value for account and return the bucket */
569         return hasAccounts && pAccount != null
570                 ? theAccounts.adjustValue(pAccount, pValue, pAdjust)
571                 : null;
572     }
573 
574     @Override
575     public void registerEvent(final MoneyWiseXAnalysisEvent pEvent) {
576         /* Register the transaction in the history */
577         theHistory.registerEvent(pEvent, theValues);
578     }
579 
580     /**
581      * Add values.
582      *
583      * @param pBucket tax category bucket
584      */
585     protected void addValues(final MoneyWiseXAnalysisTaxBasisBucket pBucket) {
586         /* Add the values */
587         OceanusMoney myAmount = theValues.getMoneyValue(MoneyWiseXAnalysisTaxBasisAttr.GROSS);
588         myAmount.addAmount(pBucket.getMoneyValue(MoneyWiseXAnalysisTaxBasisAttr.GROSS));
589         myAmount = theValues.getMoneyValue(MoneyWiseXAnalysisTaxBasisAttr.NETT);
590         myAmount.addAmount(pBucket.getMoneyValue(MoneyWiseXAnalysisTaxBasisAttr.NETT));
591     }
592 
593     /**
594      * Adjust to base.
595      */
596     protected void adjustToBase() {
597         /* Adjust to base values */
598         theValues.adjustToBaseValues(theBaseValues);
599         theBaseValues.resetBaseValues();
600     }
601 
602     /**
603      * Value adjust Modes.
604      */
605     public enum MoneyWiseXTaxBasisAdjust {
606         /**
607          * Adjust both Gross and Nett.
608          */
609         STANDARD,
610 
611         /**
612          * Adjust Gross only.
613          */
614         GROSS,
615 
616         /**
617          * Adjust Gross and Tax.
618          */
619         TAXCREDIT;
620 
621         /**
622          * should we adjust Nett?
623          *
624          * @return true/false
625          */
626         private boolean adjustNett() {
627             return this == STANDARD;
628         }
629 
630         /**
631          * should we adjust TaxCredit?
632          *
633          * @return true/false
634          */
635         private boolean adjustTaxCredit() {
636             return this == TAXCREDIT;
637         }
638     }
639 
640     /**
641      * TaxBasisBucketList class.
642      */
643     public static class MoneyWiseXAnalysisTaxBasisBucketList
644             implements MetisFieldItem, MoneyWiseTaxSource, MetisDataList<MoneyWiseXAnalysisTaxBasisBucket> {
645         /**
646          * Local Report fields.
647          */
648         private static final MetisFieldSet<MoneyWiseXAnalysisTaxBasisBucketList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseXAnalysisTaxBasisBucketList.class);
649 
650         /*
651          * Declare Fields.
652          */
653         static {
654             FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_NAME, MoneyWiseXAnalysisTaxBasisBucketList::getAnalysis);
655             FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_CHARGES, MoneyWiseXAnalysisTaxBasisBucketList::getGainSlices);
656             FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_TOTALS, MoneyWiseXAnalysisTaxBasisBucketList::getTotals);
657         }
658 
659         /**
660          * The analysis.
661          */
662         private final MoneyWiseXAnalysis theAnalysis;
663 
664         /**
665          * The list.
666          */
667         private final MetisListIndexed<MoneyWiseXAnalysisTaxBasisBucket> theList;
668 
669         /**
670          * The editSet.
671          */
672         private final PrometheusEditSet theEditSet;
673 
674         /**
675          * The chargeableGains.
676          */
677         private final MoneyWiseChargeableGainSliceList theCharges;
678 
679         /**
680          * The tax basis.
681          */
682         private final MoneyWiseXAnalysisTaxBasisBucket theTotals;
683 
684         /**
685          * Construct a top-level List.
686          *
687          * @param pAnalysis the analysis
688          * @param pGains    the new Gains list
689          */
690         private MoneyWiseXAnalysisTaxBasisBucketList(final MoneyWiseXAnalysis pAnalysis,
691                                                      final MoneyWiseChargeableGainSliceList pGains) {
692             theAnalysis = pAnalysis;
693             theEditSet = theAnalysis.getEditSet();
694             theCharges = pGains;
695             theTotals = allocateTotalsBucket();
696             theList = new MetisListIndexed<>();
697             theList.setComparator(Comparator.comparing(MoneyWiseXAnalysisTaxBasisBucket::getTaxBasis));
698         }
699 
700         /**
701          * Construct a top-level List.
702          *
703          * @param pAnalysis the analysis
704          */
705         protected MoneyWiseXAnalysisTaxBasisBucketList(final MoneyWiseXAnalysis pAnalysis) {
706             this(pAnalysis, new MoneyWiseChargeableGainSliceList());
707         }
708 
709         /**
710          * Construct a dated List.
711          *
712          * @param pAnalysis the analysis
713          * @param pBase     the base list
714          * @param pDate     the Date
715          */
716         protected MoneyWiseXAnalysisTaxBasisBucketList(final MoneyWiseXAnalysis pAnalysis,
717                                                        final MoneyWiseXAnalysisTaxBasisBucketList pBase,
718                                                        final OceanusDate pDate) {
719             /* Initialise class */
720             this(pAnalysis, new MoneyWiseChargeableGainSliceList(pBase.getGainSlices(), pAnalysis.getDateRange()));
721 
722             /* Loop through the buckets */
723             final Iterator<MoneyWiseXAnalysisTaxBasisBucket> myIterator = pBase.iterator();
724             while (myIterator.hasNext()) {
725                 final MoneyWiseXAnalysisTaxBasisBucket myCurr = myIterator.next();
726 
727                 /* Access the bucket for this date */
728                 final MoneyWiseXAnalysisTaxBasisBucket myBucket = new MoneyWiseXAnalysisTaxBasisBucket(pAnalysis, myCurr, pDate);
729 
730                 /* If the bucket is non-idle */
731                 if (!myBucket.isIdle()) {
732                     /* Calculate the delta and add to the list */
733                     theList.add(myBucket);
734                 }
735             }
736         }
737 
738         /**
739          * Construct a ranged List.
740          *
741          * @param pAnalysis the analysis
742          * @param pBase     the base list
743          * @param pRange    the Date Range
744          */
745         protected MoneyWiseXAnalysisTaxBasisBucketList(final MoneyWiseXAnalysis pAnalysis,
746                                                        final MoneyWiseXAnalysisTaxBasisBucketList pBase,
747                                                        final OceanusDateRange pRange) {
748             /* Initialise class */
749             this(pAnalysis, new MoneyWiseChargeableGainSliceList(pBase.getGainSlices(), pAnalysis.getDateRange()));
750 
751             /* Loop through the buckets */
752             final Iterator<MoneyWiseXAnalysisTaxBasisBucket> myIterator = pBase.iterator();
753             while (myIterator.hasNext()) {
754                 final MoneyWiseXAnalysisTaxBasisBucket myCurr = myIterator.next();
755 
756                 /* Access the bucket for this range */
757                 final MoneyWiseXAnalysisTaxBasisBucket myBucket = new MoneyWiseXAnalysisTaxBasisBucket(pAnalysis, myCurr, pRange);
758 
759                 /* If the bucket is non-idle */
760                 if (!myBucket.isIdle()) {
761                     /* Adjust to the base */
762                     myBucket.adjustToBase();
763                     theList.add(myBucket);
764                 }
765             }
766         }
767 
768         @Override
769         public MetisFieldSet<MoneyWiseXAnalysisTaxBasisBucketList> getDataFieldSet() {
770             return FIELD_DEFS;
771         }
772 
773         @Override
774         public List<MoneyWiseXAnalysisTaxBasisBucket> getUnderlyingList() {
775             return theList.getUnderlyingList();
776         }
777 
778         @Override
779         public String formatObject(final OceanusDataFormatter pFormatter) {
780             return getDataFieldSet().getName();
781         }
782 
783         /**
784          * Obtain the analysis.
785          *
786          * @return the analysis
787          */
788         protected MoneyWiseXAnalysis getAnalysis() {
789             return theAnalysis;
790         }
791 
792         /**
793          * Obtain item by id.
794          *
795          * @param pId the id to lookup
796          * @return the item (or null if not present)
797          */
798         public MoneyWiseXAnalysisTaxBasisBucket findItemById(final Integer pId) {
799             /* Return results */
800             return theList.getItemById(pId);
801         }
802 
803         @Override
804         public MoneyWiseChargeableGainSliceList getGainSlices() {
805             return theCharges;
806         }
807 
808         /**
809          * Obtain the Totals.
810          *
811          * @return the totals bucket
812          */
813         public MoneyWiseXAnalysisTaxBasisBucket getTotals() {
814             return theTotals;
815         }
816 
817         /**
818          * Allocate the Totals EventCategoryBucket.
819          *
820          * @return the bucket
821          */
822         private MoneyWiseXAnalysisTaxBasisBucket allocateTotalsBucket() {
823             /* Obtain the totals category */
824             return new MoneyWiseXAnalysisTaxBasisBucket(theAnalysis, null);
825         }
826 
827         /**
828          * Obtain the TaxBasisBucket for a given taxBasis.
829          *
830          * @param pClass the taxBasis
831          * @return the bucket
832          */
833         public MoneyWiseXAnalysisTaxBasisBucket getBucket(final MoneyWiseTaxClass pClass) {
834             /* Locate the bucket in the list */
835             final MoneyWiseTaxBasis myBasis = theEditSet.getDataList(MoneyWiseStaticDataType.TAXBASIS, MoneyWiseTaxBasisList.class).findItemByClass(pClass);
836             MoneyWiseXAnalysisTaxBasisBucket myItem = findItemById(myBasis.getIndexedId());
837 
838             /* If the item does not yet exist */
839             if (myItem == null) {
840                 /* Create the new bucket */
841                 myItem = new MoneyWiseXAnalysisTaxBasisBucket(theAnalysis, myBasis);
842 
843                 /* Add to the list */
844                 theList.add(myItem);
845             }
846 
847             /* Return the bucket */
848             return myItem;
849         }
850 
851         /**
852          * Obtain the matching BasisBucket.
853          *
854          * @param pTaxBasis the taxBasis
855          * @return the matching bucket
856          */
857         public MoneyWiseXAnalysisTaxBasisBucket getMatchingBasis(final MoneyWiseXAnalysisTaxBasisBucket pTaxBasis) {
858             /* Access the matching taxBasis bucket */
859             MoneyWiseXAnalysisTaxBasisBucket myBasis = findItemById(pTaxBasis.getTaxBasis().getIndexedId());
860             if (myBasis == null) {
861                 myBasis = new MoneyWiseXAnalysisTaxBasisBucket(theAnalysis, pTaxBasis.getTaxBasis());
862             }
863 
864             /* If we are matching a TaxBasisAccount Bucket */
865             if (pTaxBasis instanceof MoneyWiseXAnalysisTaxBasisAccountBucket myBucket) {
866                 /* Look up the asset bucket */
867                 final MoneyWiseTransAsset myAsset = myBucket.getAccount();
868                 MoneyWiseXAnalysisTaxBasisAccountBucket myAccountBucket = myBasis.findAccountBucket(myAsset);
869 
870                 /* If there is no such bucket in the analysis */
871                 if (myAccountBucket == null) {
872                     /* Allocate an orphan bucket */
873                     myAccountBucket = new MoneyWiseXAnalysisTaxBasisAccountBucket(theAnalysis, myBasis, myAsset);
874                 }
875 
876                 /* Set bucket as the account bucket */
877                 myBasis = myAccountBucket;
878             }
879 
880             /* Return the basis */
881             return myBasis;
882         }
883 
884         /**
885          * Obtain the default BasisBucket.
886          *
887          * @return the default bucket
888          */
889         public MoneyWiseXAnalysisTaxBasisBucket getDefaultBasis() {
890             /* Return the first basis in the list if it exists */
891             return isEmpty()
892                     ? null
893                     : theList.getUnderlyingList().get(0);
894         }
895 
896         /**
897          * record ChargeableGain.
898          *
899          * @param pTrans the transaction
900          * @param pGain  the gain
901          * @param pSlice the slice
902          * @param pYears the years
903          */
904         public void recordChargeableGain(final MoneyWiseTransaction pTrans,
905                                          final OceanusMoney pGain,
906                                          final OceanusMoney pSlice,
907                                          final Integer pYears) {
908             /* record the chargeable gain */
909             theCharges.addTransaction(pTrans, pGain, pSlice, pYears);
910         }
911 
912         /**
913          * produce Totals.
914          */
915         public void produceTotals() {
916             /* Loop through the buckets */
917             final Iterator<MoneyWiseXAnalysisTaxBasisBucket> myIterator = iterator();
918             while (myIterator.hasNext()) {
919                 final MoneyWiseXAnalysisTaxBasisBucket myBucket = myIterator.next();
920 
921                 /* Remove idle items */
922                 if (myBucket.isIdle()) {
923                     myIterator.remove();
924                     continue;
925                 }
926 
927                 /* Sort the accounts */
928                 if (myBucket.hasAccounts()) {
929                     myBucket.getAccounts().sortBuckets();
930                 }
931 
932                 /* Adjust the Total Profit buckets */
933                 theTotals.addValues(myBucket);
934             }
935 
936             /* Sort the bases */
937             theList.sortList();
938         }
939 
940         @Override
941         public OceanusMoney getAmountForTaxBasis(final MoneyWiseTaxClass pBasis) {
942             /* Access the bucket */
943             final MoneyWiseXAnalysisTaxBasisBucket myItem = findItemById(pBasis.getClassId());
944 
945             /* If the bucket is not found */
946             if (myItem == null) {
947                 final MoneyWiseCurrency myAssetCurrency = theAnalysis.getCurrency();
948                 final Currency myCurrency = myAssetCurrency == null
949                         ? OceanusMoney.getDefaultCurrency()
950                         : myAssetCurrency.getCurrency();
951                 return new OceanusMoney(myCurrency);
952             }
953 
954             return myItem.getMoneyValue(MoneyWiseXAnalysisTaxBasisAttr.GROSS);
955         }
956     }
957 }