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.metis.data.MetisDataItem.MetisDataList;
20  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
21  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldTableItem;
22  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
23  import io.github.tonywasher.joceanus.metis.list.MetisListIndexed;
24  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisBucketRegister;
25  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisTaxBasisAccountBucket.MoneyWiseXAnalysisTaxBasisAccountBucketList;
26  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.values.MoneyWiseXAnalysisTaxBasisAttr;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
29  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
30  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
31  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTaxBasis;
32  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTaxBasis.MoneyWiseTaxBasisList;
33  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTaxClass;
34  import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseChargeableGainSlice.MoneyWiseChargeableGainSliceList;
35  import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxSource;
36  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
37  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
38  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
39  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
40  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
41  
42  import java.util.Comparator;
43  import java.util.Currency;
44  import java.util.Iterator;
45  import java.util.List;
46  
47  /**
48   * The TaxBasis Bucket class.
49   */
50  public class MoneyWiseXAnalysisTaxBasisBucket
51          extends MoneyWiseXAnalysisTaxBasisBaseBucket
52          implements MetisFieldTableItem, MoneyWiseXAnalysisBucketRegister {
53      /**
54       * Local Report fields.
55       */
56      private static final MetisFieldSet<MoneyWiseXAnalysisTaxBasisBucket> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseXAnalysisTaxBasisBucket.class);
57  
58      /*
59       * Declare Fields.
60       */
61      static {
62          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.TAXBASIS_ACCOUNTLIST, MoneyWiseXAnalysisTaxBasisBucket::getAccounts);
63      }
64  
65      /**
66       * Do we have accounts?
67       */
68      private final boolean hasAccounts;
69  
70      /**
71       * AccountBucketList.
72       */
73      private final MoneyWiseXAnalysisTaxBasisAccountBucketList theAccounts;
74  
75      /**
76       * Constructor.
77       *
78       * @param pAnalysis the analysis
79       * @param pTaxBasis the basis
80       */
81      MoneyWiseXAnalysisTaxBasisBucket(final MoneyWiseXAnalysisHolder pAnalysis,
82                                       final MoneyWiseTaxBasis pTaxBasis) {
83          /* Initialise underlying class */
84          super(pAnalysis, pTaxBasis);
85  
86          /* Create the account list */
87          hasAccounts = pTaxBasis != null
88                  && pTaxBasis.getTaxClass().analyseAccounts();
89          theAccounts = hasAccounts
90                  ? new MoneyWiseXAnalysisTaxBasisAccountBucketList(pAnalysis, pTaxBasis)
91                  : null;
92      }
93  
94      /**
95       * Constructor.
96       *
97       * @param pAnalysis the analysis
98       * @param pBase     the underlying bucket
99       * @param pDate     the date for the bucket
100      */
101     MoneyWiseXAnalysisTaxBasisBucket(final MoneyWiseXAnalysisHolder pAnalysis,
102                                      final MoneyWiseXAnalysisTaxBasisBucket pBase,
103                                      final OceanusDate pDate) {
104         /* Initialise underlying class */
105         super(pAnalysis, pBase, pDate);
106 
107         /* Create the account list */
108         hasAccounts = pBase.hasAccounts();
109         theAccounts = hasAccounts
110                 ? new MoneyWiseXAnalysisTaxBasisAccountBucketList(pAnalysis, getTaxBasis(), pBase.getAccounts(), pDate)
111                 : null;
112     }
113 
114     /**
115      * Constructor.
116      *
117      * @param pAnalysis the analysis
118      * @param pBase     the underlying bucket
119      * @param pRange    the range for the bucket
120      */
121     MoneyWiseXAnalysisTaxBasisBucket(final MoneyWiseXAnalysisHolder pAnalysis,
122                                      final MoneyWiseXAnalysisTaxBasisBucket pBase,
123                                      final OceanusDateRange pRange) {
124         /* Initialise underlying class */
125         super(pAnalysis, pBase, pRange);
126 
127         /* Create the account list */
128         hasAccounts = pBase.hasAccounts();
129         theAccounts = hasAccounts
130                 ? new MoneyWiseXAnalysisTaxBasisAccountBucketList(pAnalysis, getTaxBasis(), pBase.getAccounts(), pRange)
131                 : null;
132     }
133 
134     @Override
135     public MetisFieldSet<? extends MoneyWiseXAnalysisTaxBasisBucket> getDataFieldSet() {
136         return FIELD_DEFS;
137     }
138 
139     @Override
140     public String formatObject(final OceanusDataFormatter pFormatter) {
141         return toString();
142     }
143 
144     /**
145      * Do we have accounts.
146      *
147      * @return true/false
148      */
149     public boolean hasAccounts() {
150         return hasAccounts;
151     }
152 
153     /**
154      * Obtain account list.
155      *
156      * @return the account list
157      */
158     private MoneyWiseXAnalysisTaxBasisAccountBucketList getAccounts() {
159         return theAccounts;
160     }
161 
162     /**
163      * Obtain account list iterator.
164      *
165      * @return the iterator
166      */
167     public Iterator<MoneyWiseXAnalysisTaxBasisAccountBucket> accountIterator() {
168         return hasAccounts
169                 ? theAccounts.iterator()
170                 : null;
171     }
172 
173     /**
174      * find an account bucket.
175      *
176      * @param pAccount the account
177      * @return the bucket
178      */
179     public MoneyWiseXAnalysisTaxBasisAccountBucket findAccountBucket(final MoneyWiseTransAsset pAccount) {
180         return hasAccounts
181                 ? theAccounts.findBucket(pAccount)
182                 : null;
183     }
184 
185     @Override
186     MoneyWiseXAnalysisTaxBasisBaseBucket adjustAccountValue(final MoneyWiseTransAsset pAccount,
187                                                             final OceanusMoney pValue,
188                                                             final MoneyWiseXTaxBasisAdjust pAdjust) {
189         /* If we have accounts and are passed an account, adjust value for account and return the bucket */
190         return hasAccounts && pAccount != null
191                 ? theAccounts.adjustValue(pAccount, pValue, pAdjust)
192                 : null;
193     }
194 
195     /**
196      * TaxBasisBucketList class.
197      */
198     public static class MoneyWiseXAnalysisTaxBasisBucketList
199             implements MetisFieldItem, MoneyWiseTaxSource, MetisDataList<MoneyWiseXAnalysisTaxBasisBucket> {
200         /**
201          * Local Report fields.
202          */
203         private static final MetisFieldSet<MoneyWiseXAnalysisTaxBasisBucketList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseXAnalysisTaxBasisBucketList.class);
204 
205         /*
206          * Declare Fields.
207          */
208         static {
209             FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_NAME, MoneyWiseXAnalysisTaxBasisBucketList::getAnalysis);
210             FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_CHARGES, MoneyWiseXAnalysisTaxBasisBucketList::getGainSlices);
211             FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_TOTALS, MoneyWiseXAnalysisTaxBasisBucketList::getTotals);
212         }
213 
214         /**
215          * The analysis.
216          */
217         private final MoneyWiseXAnalysisHolder theAnalysis;
218 
219         /**
220          * The list.
221          */
222         private final MetisListIndexed<MoneyWiseXAnalysisTaxBasisBucket> theList;
223 
224         /**
225          * The editSet.
226          */
227         private final PrometheusEditSet theEditSet;
228 
229         /**
230          * The chargeableGains.
231          */
232         private final MoneyWiseChargeableGainSliceList theCharges;
233 
234         /**
235          * The tax basis.
236          */
237         private final MoneyWiseXAnalysisTaxBasisBucket theTotals;
238 
239         /**
240          * Construct a top-level List.
241          *
242          * @param pAnalysis the analysis
243          * @param pGains    the new Gains list
244          */
245         private MoneyWiseXAnalysisTaxBasisBucketList(final MoneyWiseXAnalysisHolder pAnalysis,
246                                                      final MoneyWiseChargeableGainSliceList pGains) {
247             theAnalysis = pAnalysis;
248             theEditSet = theAnalysis.getEditSet();
249             theCharges = pGains;
250             theTotals = allocateTotalsBucket();
251             theList = new MetisListIndexed<>();
252             theList.setComparator(Comparator.comparing(MoneyWiseXAnalysisTaxBasisBucket::getTaxBasis));
253         }
254 
255         /**
256          * Construct a top-level List.
257          *
258          * @param pAnalysis the analysis
259          */
260         protected MoneyWiseXAnalysisTaxBasisBucketList(final MoneyWiseXAnalysisHolder pAnalysis) {
261             this(pAnalysis, new MoneyWiseChargeableGainSliceList());
262         }
263 
264         /**
265          * Construct a dated List.
266          *
267          * @param pAnalysis the analysis
268          * @param pBase     the base list
269          * @param pDate     the Date
270          */
271         protected MoneyWiseXAnalysisTaxBasisBucketList(final MoneyWiseXAnalysisHolder pAnalysis,
272                                                        final MoneyWiseXAnalysisTaxBasisBucketList pBase,
273                                                        final OceanusDate pDate) {
274             /* Initialise class */
275             this(pAnalysis, new MoneyWiseChargeableGainSliceList(pBase.getGainSlices(), pAnalysis.getDateRange()));
276 
277             /* Loop through the buckets */
278             final Iterator<MoneyWiseXAnalysisTaxBasisBucket> myIterator = pBase.iterator();
279             while (myIterator.hasNext()) {
280                 final MoneyWiseXAnalysisTaxBasisBucket myCurr = myIterator.next();
281 
282                 /* Access the bucket for this date */
283                 final MoneyWiseXAnalysisTaxBasisBucket myBucket = new MoneyWiseXAnalysisTaxBasisBucket(pAnalysis, myCurr, pDate);
284 
285                 /* If the bucket is non-idle */
286                 if (!myBucket.isIdle()) {
287                     /* Calculate the delta and add to the list */
288                     theList.add(myBucket);
289                 }
290             }
291         }
292 
293         /**
294          * Construct a ranged List.
295          *
296          * @param pAnalysis the analysis
297          * @param pBase     the base list
298          * @param pRange    the Date Range
299          */
300         protected MoneyWiseXAnalysisTaxBasisBucketList(final MoneyWiseXAnalysisHolder pAnalysis,
301                                                        final MoneyWiseXAnalysisTaxBasisBucketList pBase,
302                                                        final OceanusDateRange pRange) {
303             /* Initialise class */
304             this(pAnalysis, new MoneyWiseChargeableGainSliceList(pBase.getGainSlices(), pAnalysis.getDateRange()));
305 
306             /* Loop through the buckets */
307             final Iterator<MoneyWiseXAnalysisTaxBasisBucket> myIterator = pBase.iterator();
308             while (myIterator.hasNext()) {
309                 final MoneyWiseXAnalysisTaxBasisBucket myCurr = myIterator.next();
310 
311                 /* Access the bucket for this range */
312                 final MoneyWiseXAnalysisTaxBasisBucket myBucket = new MoneyWiseXAnalysisTaxBasisBucket(pAnalysis, myCurr, pRange);
313 
314                 /* If the bucket is non-idle */
315                 if (!myBucket.isIdle()) {
316                     /* Adjust to the base */
317                     myBucket.adjustToBase();
318                     theList.add(myBucket);
319                 }
320             }
321         }
322 
323         @Override
324         public MetisFieldSet<MoneyWiseXAnalysisTaxBasisBucketList> getDataFieldSet() {
325             return FIELD_DEFS;
326         }
327 
328         @Override
329         public List<MoneyWiseXAnalysisTaxBasisBucket> getUnderlyingList() {
330             return theList.getUnderlyingList();
331         }
332 
333         @Override
334         public String formatObject(final OceanusDataFormatter pFormatter) {
335             return getDataFieldSet().getName();
336         }
337 
338         /**
339          * Obtain the analysis.
340          *
341          * @return the analysis
342          */
343         protected MoneyWiseXAnalysisHolder getAnalysis() {
344             return theAnalysis;
345         }
346 
347         /**
348          * Obtain item by id.
349          *
350          * @param pId the id to lookup
351          * @return the item (or null if not present)
352          */
353         public MoneyWiseXAnalysisTaxBasisBucket findItemById(final Integer pId) {
354             /* Return results */
355             return theList.getItemById(pId);
356         }
357 
358         @Override
359         public MoneyWiseChargeableGainSliceList getGainSlices() {
360             return theCharges;
361         }
362 
363         /**
364          * Obtain the Totals.
365          *
366          * @return the totals bucket
367          */
368         public MoneyWiseXAnalysisTaxBasisBucket getTotals() {
369             return theTotals;
370         }
371 
372         /**
373          * Allocate the Totals EventCategoryBucket.
374          *
375          * @return the bucket
376          */
377         private MoneyWiseXAnalysisTaxBasisBucket allocateTotalsBucket() {
378             /* Obtain the totals category */
379             return new MoneyWiseXAnalysisTaxBasisBucket(theAnalysis, null);
380         }
381 
382         /**
383          * Obtain the TaxBasisBucket for a given taxBasis.
384          *
385          * @param pClass the taxBasis
386          * @return the bucket
387          */
388         public MoneyWiseXAnalysisTaxBasisBucket getBucket(final MoneyWiseTaxClass pClass) {
389             /* Locate the bucket in the list */
390             final MoneyWiseTaxBasis myBasis = theEditSet.getDataList(MoneyWiseStaticDataType.TAXBASIS, MoneyWiseTaxBasisList.class).findItemByClass(pClass);
391             MoneyWiseXAnalysisTaxBasisBucket myItem = findItemById(myBasis.getIndexedId());
392 
393             /* If the item does not yet exist */
394             if (myItem == null) {
395                 /* Create the new bucket */
396                 myItem = new MoneyWiseXAnalysisTaxBasisBucket(theAnalysis, myBasis);
397 
398                 /* Add to the list */
399                 theList.add(myItem);
400             }
401 
402             /* Return the bucket */
403             return myItem;
404         }
405 
406         /**
407          * Obtain the matching BasisBucket.
408          *
409          * @param pTaxBasis the taxBasis
410          * @return the matching bucket
411          */
412         public MoneyWiseXAnalysisTaxBasisBaseBucket getMatchingBasis(final MoneyWiseXAnalysisTaxBasisBaseBucket pTaxBasis) {
413             /* Access the matching taxBasis bucket */
414             MoneyWiseXAnalysisTaxBasisBaseBucket myBasis = findItemById(pTaxBasis.getTaxBasis().getIndexedId());
415             if (myBasis == null) {
416                 myBasis = new MoneyWiseXAnalysisTaxBasisBucket(theAnalysis, pTaxBasis.getTaxBasis());
417             }
418 
419             /* If we are matching a TaxBasisAccount Bucket */
420             if (pTaxBasis instanceof MoneyWiseXAnalysisTaxBasisAccountBucket myBucket) {
421                 /* Look up the asset bucket */
422                 final MoneyWiseTransAsset myAsset = myBucket.getAccount();
423                 MoneyWiseXAnalysisTaxBasisAccountBucket myAccountBucket =
424                         ((MoneyWiseXAnalysisTaxBasisBucket) myBasis).findAccountBucket(myAsset);
425 
426                 /* If there is no such bucket in the analysis */
427                 if (myAccountBucket == null) {
428                     /* Allocate an orphan bucket */
429                     myAccountBucket = new MoneyWiseXAnalysisTaxBasisAccountBucket(theAnalysis, myBasis.getTaxBasis(), myAsset);
430                 }
431 
432                 /* Set bucket as the account bucket */
433                 myBasis = myAccountBucket;
434             }
435 
436             /* Return the basis */
437             return myBasis;
438         }
439 
440         /**
441          * Obtain the default BasisBucket.
442          *
443          * @return the default bucket
444          */
445         public MoneyWiseXAnalysisTaxBasisBucket getDefaultBasis() {
446             /* Return the first basis in the list if it exists */
447             return isEmpty()
448                     ? null
449                     : theList.getUnderlyingList().getFirst();
450         }
451 
452         /**
453          * record ChargeableGain.
454          *
455          * @param pTrans the transaction
456          * @param pGain  the gain
457          * @param pSlice the slice
458          * @param pYears the years
459          */
460         public void recordChargeableGain(final MoneyWiseTransaction pTrans,
461                                          final OceanusMoney pGain,
462                                          final OceanusMoney pSlice,
463                                          final Integer pYears) {
464             /* record the chargeable gain */
465             theCharges.addTransaction(pTrans, pGain, pSlice, pYears);
466         }
467 
468         /**
469          * produce Totals.
470          */
471         public void produceTotals() {
472             /* Loop through the buckets */
473             final Iterator<MoneyWiseXAnalysisTaxBasisBucket> myIterator = iterator();
474             while (myIterator.hasNext()) {
475                 final MoneyWiseXAnalysisTaxBasisBucket myBucket = myIterator.next();
476 
477                 /* Remove idle items */
478                 if (myBucket.isIdle()) {
479                     myIterator.remove();
480                     continue;
481                 }
482 
483                 /* Sort the accounts */
484                 if (myBucket.hasAccounts()) {
485                     myBucket.getAccounts().sortBuckets();
486                 }
487 
488                 /* Adjust the Total Profit buckets */
489                 theTotals.addValues(myBucket);
490             }
491 
492             /* Sort the bases */
493             theList.sortList();
494         }
495 
496         @Override
497         public OceanusMoney getAmountForTaxBasis(final MoneyWiseTaxClass pBasis) {
498             /* Access the bucket */
499             final MoneyWiseXAnalysisTaxBasisBucket myItem = findItemById(pBasis.getClassId());
500 
501             /* If the bucket is not found */
502             if (myItem == null) {
503                 final MoneyWiseCurrency myAssetCurrency = theAnalysis.getCurrency();
504                 final Currency myCurrency = myAssetCurrency == null
505                         ? OceanusMoney.getDefaultCurrency()
506                         : myAssetCurrency.getCurrency();
507                 return new OceanusMoney(myCurrency);
508             }
509 
510             return myItem.getMoneyValue(MoneyWiseXAnalysisTaxBasisAttr.GROSS);
511         }
512     }
513 }