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.tax.uk;
18  
19  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
20  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
21  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
22  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
23  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
24  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTaxClass;
25  import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxBandSet;
26  import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxBandSet.MoneyWiseTaxBand;
27  import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxResource;
28  
29  import java.util.Iterator;
30  
31  /**
32   * Income Tax Scheme.
33   */
34  public class MoneyWiseUKIncomeScheme
35          implements MetisFieldItem {
36      /**
37       * Local Report fields.
38       */
39      private static final MetisFieldSet<MoneyWiseUKIncomeScheme> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseUKIncomeScheme.class);
40  
41      /*
42       * Declare Fields.
43       */
44      static {
45          FIELD_DEFS.declareLocalField(MoneyWiseTaxResource.SCHEME_RELIEF_AVAILABLE, MoneyWiseUKIncomeScheme::taxReliefAvailable);
46      }
47  
48      /**
49       * Tax Relief available.
50       */
51      private final Boolean reliefAvailable;
52  
53      /**
54       * Constructor.
55       */
56      protected MoneyWiseUKIncomeScheme() {
57          this(Boolean.TRUE);
58      }
59  
60      /**
61       * Constructor.
62       *
63       * @param pReliefAvailable Is tax relief available?
64       */
65      protected MoneyWiseUKIncomeScheme(final Boolean pReliefAvailable) {
66          reliefAvailable = pReliefAvailable;
67      }
68  
69      /**
70       * Is tax relief available?
71       *
72       * @return true/false
73       */
74      public Boolean taxReliefAvailable() {
75          return reliefAvailable;
76      }
77  
78      /**
79       * Allocate the amount to the appropriate tax bands.
80       *
81       * @param pConfig the taxConfig
82       * @param pBasis  the taxBasis
83       * @param pAmount the amount to be allocated
84       * @return the tax bands
85       */
86      protected MoneyWiseTaxBandSet allocateToTaxBands(final MoneyWiseUKTaxConfig pConfig,
87                                                       final MoneyWiseTaxClass pBasis,
88                                                       final OceanusMoney pAmount) {
89          /* Handle negative amounts */
90          final OceanusMoney myAmount = new OceanusMoney(pAmount);
91          if (!myAmount.isPositive()) {
92              myAmount.setZero();
93          }
94  
95          /* Determine the taxBand set */
96          final MoneyWiseTaxBandSet myTaxBands = determineTaxBands(pConfig, pBasis, myAmount);
97  
98          /* Adjust allowances and taxBands */
99          final OceanusMoney myRemaining = adjustAllowances(pConfig, myAmount);
100         adjustTaxBands(pConfig, myRemaining);
101 
102         /* return the taxBands */
103         return myTaxBands;
104     }
105 
106     /**
107      * Determine the taxBand set.
108      *
109      * @param pConfig the taxConfig
110      * @param pBasis  the taxBasis
111      * @param pAmount the amount to be allocated
112      * @return the amount remaining
113      */
114     private MoneyWiseTaxBandSet determineTaxBands(final MoneyWiseUKTaxConfig pConfig,
115                                                   final MoneyWiseTaxClass pBasis,
116                                                   final OceanusMoney pAmount) {
117         /* Create a new taxBand set */
118         final MoneyWiseTaxBandSet myTaxBands = new MoneyWiseTaxBandSet();
119         final OceanusMoney myRemaining = new OceanusMoney(pAmount);
120 
121         /* Determine allowance */
122         final OceanusMoney myAllowance = new OceanusMoney(getAmountInAllowance(pConfig, myRemaining));
123         if (myAllowance.isNonZero() && reliefAvailable) {
124             myTaxBands.addTaxBand(new MoneyWiseTaxBand(myAllowance, OceanusRate.getWholePercentage(0)));
125             myRemaining.subtractAmount(myAllowance);
126         }
127 
128         /* Loop through the taxBands */
129         final Iterator<MoneyWiseTaxBand> myIterator = taxBandIterator(pConfig, pBasis);
130         while (myRemaining.isNonZero()
131                 && myIterator.hasNext()) {
132             /* Determine amount in band */
133             final MoneyWiseTaxBand myBand = myIterator.next();
134             OceanusMoney myAmount = getAmountInBand(myBand.getAmount(), myRemaining);
135 
136             /* Add any held-over allowance */
137             if (!reliefAvailable && myAllowance.isNonZero()) {
138                 myAmount = new OceanusMoney(myAmount);
139                 myAmount.addAmount(myAllowance);
140                 myAllowance.setZero();
141             }
142 
143             /* allocate band and adjust */
144             myTaxBands.addTaxBand(new MoneyWiseTaxBand(myAmount, myBand.getRate()));
145             myRemaining.subtractAmount(myAmount);
146         }
147 
148         /* Return the taxBands */
149         return myTaxBands;
150     }
151 
152     /**
153      * Obtain the taxBand iterator.
154      *
155      * @param pConfig the taxConfig
156      * @param pBasis  the taxBasis
157      * @return the iterator
158      */
159     protected Iterator<MoneyWiseTaxBand> taxBandIterator(final MoneyWiseUKTaxConfig pConfig,
160                                                          final MoneyWiseTaxClass pBasis) {
161         return pConfig.getTaxBands().iterator();
162     }
163 
164     /**
165      * Obtain the taxFree amount.
166      *
167      * @param pConfig the taxConfig
168      * @param pAmount the amount that is to be adjusted
169      * @return the amount remaining
170      */
171     protected OceanusMoney getAmountInAllowance(final MoneyWiseUKTaxConfig pConfig,
172                                                 final OceanusMoney pAmount) {
173         /* Obtain the amount covered by the allowance */
174         return getAmountInBand(pConfig.getAllowance(), pAmount);
175     }
176 
177     /**
178      * Adjust Allowances.
179      *
180      * @param pConfig the taxConfig
181      * @param pAmount the amount that is to be adjusted
182      * @return the amount remaining
183      */
184     protected OceanusMoney adjustAllowances(final MoneyWiseUKTaxConfig pConfig,
185                                             final OceanusMoney pAmount) {
186         /* Adjust the basic allowance */
187         return adjustForAllowance(pConfig.getAllowance(), pAmount);
188     }
189 
190     /**
191      * Adjust TaxBands.
192      *
193      * @param pConfig the taxConfig
194      * @param pAmount the amount that is to be adjusted
195      */
196     protected void adjustTaxBands(final MoneyWiseUKTaxConfig pConfig,
197                                   final OceanusMoney pAmount) {
198         /* Loop through the taxBands */
199         OceanusMoney myRemaining = pAmount;
200         for (MoneyWiseTaxBand myBand : pConfig.getTaxBands()) {
201             /* If we have nothing left to adjust, we have finished */
202             if (myRemaining.isZero()
203                     || myBand.getAmount() == null) {
204                 break;
205             }
206 
207             /* Adjust the band */
208             myRemaining = adjustForAllowance(myBand.getAmount(), myRemaining);
209         }
210     }
211 
212     /**
213      * Adjust For an allowance/band.
214      *
215      * @param pAllowance the allowance
216      * @param pAmount    the amount that is to be adjusted
217      * @return the amount remaining
218      */
219     protected OceanusMoney adjustForAllowance(final OceanusMoney pAllowance,
220                                               final OceanusMoney pAmount) {
221         /* Take a copy of the amount */
222         final OceanusMoney myRemaining = new OceanusMoney(pAmount);
223 
224         /* If we have exhausted the allowance */
225         if (myRemaining.compareTo(pAllowance) > 0) {
226             /* Subtract allowance */
227             myRemaining.subtractAmount(pAllowance);
228             pAllowance.setZero();
229 
230             /* else the allowance covers everything */
231         } else {
232             /* adjust the allowance */
233             pAllowance.subtractAmount(myRemaining);
234             myRemaining.setZero();
235         }
236 
237         /* return the remaining amount */
238         return myRemaining;
239     }
240 
241     /**
242      * Obtain the amount of income that falls in a band.
243      *
244      * @param pBand   the band
245      * @param pAmount the amount available
246      * @return the amount within the band
247      */
248     protected static OceanusMoney getAmountInBand(final OceanusMoney pBand,
249                                                   final OceanusMoney pAmount) {
250         /* Return the lesser of the two */
251         return pBand != null && pAmount.compareTo(pBand) > 0
252                 ? pBand
253                 : pAmount;
254     }
255 
256     @Override
257     public MetisFieldSet<? extends MoneyWiseUKIncomeScheme> getDataFieldSet() {
258         return FIELD_DEFS;
259     }
260 
261     @Override
262     public String formatObject(final OceanusDataFormatter pFormatter) {
263         return getDataFieldSet().getName();
264     }
265 }