MoneyWiseUKChargeableGainsScheme.java

/*
 * MoneyWise: Finance Application
 * Copyright 2012-2026. Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.tonywasher.joceanus.moneywise.tax.uk;

import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseChargeableGainSlice;
import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxBandSet;
import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxBandSet.MoneyWiseTaxBand;
import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxDueBucket;
import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxResource;
import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseTaxSource;

import java.util.Iterator;

/**
 * Taxable Gains Scheme.
 */
public class MoneyWiseUKChargeableGainsScheme
        extends MoneyWiseUKIncomeScheme {
    /**
     * Constructor.
     */
    protected MoneyWiseUKChargeableGainsScheme() {
        super(Boolean.FALSE);
    }

    /**
     * Chargeable Gains Sliced Tax Due.
     */
    public static class MoneyWiseUKSlicedTaxDueBucket
            extends MoneyWiseTaxDueBucket {
        /**
         * Local Report fields.
         */
        private static final MetisFieldSet<MoneyWiseUKSlicedTaxDueBucket> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseUKSlicedTaxDueBucket.class);

        /*
         * Declare Fields.
         */
        static {
            FIELD_DEFS.declareLocalField(MoneyWiseTaxResource.CHARGEABLEGAIN_TOTALGAINS, MoneyWiseUKSlicedTaxDueBucket::getTotalGains);
            FIELD_DEFS.declareLocalField(MoneyWiseTaxResource.CHARGEABLEGAIN_TOTALSLICES, MoneyWiseUKSlicedTaxDueBucket::getTotalSlices);
            FIELD_DEFS.declareLocalField(MoneyWiseTaxResource.CHARGEABLEGAIN_RATIO, MoneyWiseUKSlicedTaxDueBucket::getRatio);
            FIELD_DEFS.declareLocalField(MoneyWiseTaxResource.CHARGEABLEGAIN_TAXEDSLICES, MoneyWiseUKSlicedTaxDueBucket::getTaxedSlices);
            FIELD_DEFS.declareLocalField(MoneyWiseTaxResource.CHARGEABLEGAIN_NETTTAXDUE, MoneyWiseUKSlicedTaxDueBucket::getNettTaxDue);
            FIELD_DEFS.declareLocalField(MoneyWiseTaxResource.CHARGEABLEGAIN_TAXRELIEF, MoneyWiseUKSlicedTaxDueBucket::getTaxRelief);
        }

        /**
         * The total gains.
         */
        private final OceanusMoney theTotalGains;

        /**
         * The total slices.
         */
        private final OceanusMoney theTotalSlices;

        /**
         * The ratio.
         */
        private final OceanusRatio theRatio;

        /**
         * The slices taxDueBucket.
         */
        private final MoneyWiseTaxDueBucket theSliceBucket;

        /**
         * The taxRelief.
         */
        private final OceanusMoney theTaxRelief;

        /**
         * The nett taxDue.
         */
        private final OceanusMoney theNettTaxDue;

        /**
         * Constructor.
         *
         * @param pBase   the underlying bucket
         * @param pSource the tax source
         */
        protected MoneyWiseUKSlicedTaxDueBucket(final MoneyWiseTaxDueBucket pBase,
                                                final MoneyWiseTaxSource pSource) {
            /* Initialise underlying bucket */
            super(pBase);

            /* Create the totals */
            final OceanusMoney myZero = new OceanusMoney(pBase.getTaxDue());
            theTotalGains = new OceanusMoney(myZero);
            theTotalSlices = new OceanusMoney(myZero);
            theTaxRelief = new OceanusMoney(myZero);
            theNettTaxDue = new OceanusMoney(myZero);

            /* Process the slices */
            for (MoneyWiseChargeableGainSlice mySlice : pSource.getGainSlices().getUnderlyingList()) {
                processSlice(mySlice);
            }

            /* Calculate the ratio */
            theRatio = new OceanusRatio(theTotalGains, theTotalSlices);

            /* Determine tax due on slices */
            theSliceBucket = buildSliceBucket();
            calculateTax();
        }

        /**
         * Obtain the total gains.
         *
         * @return the gains
         */
        public OceanusMoney getTotalGains() {
            return theTotalGains;
        }

        /**
         * Obtain the total slices.
         *
         * @return the slices
         */
        public OceanusMoney getTotalSlices() {
            return theTotalSlices;
        }

        /**
         * Obtain the ratio.
         *
         * @return the ratio
         */
        public OceanusRatio getRatio() {
            return theRatio;
        }

        /**
         * Obtain the taxed slices.
         *
         * @return the taxed slices
         */
        public MoneyWiseTaxDueBucket getTaxedSlices() {
            return theSliceBucket;
        }

        /**
         * Obtain the tax due.
         *
         * @return the tax due
         */
        public OceanusMoney getNettTaxDue() {
            return theNettTaxDue;
        }

        /**
         * Obtain the tax relief.
         *
         * @return the tax relief
         */
        public OceanusMoney getTaxRelief() {
            return theTaxRelief;
        }

        /**
         * Process slice.
         *
         * @param pSlice the slice
         */
        private void processSlice(final MoneyWiseChargeableGainSlice pSlice) {
            /* Adjust totals */
            theTotalGains.addAmount(pSlice.getGain());
            theTotalSlices.addAmount(pSlice.getSlice());
        }

        /**
         * build slice bucket.
         *
         * @return the slice bucket
         */
        private MoneyWiseTaxDueBucket buildSliceBucket() {
            /* Create a new taxBand set */
            final MoneyWiseTaxBandSet myTaxBands = new MoneyWiseTaxBandSet();
            final OceanusMoney myRemaining = new OceanusMoney(theTotalSlices);

            /* Calculate new tax allocation */
            final Iterator<MoneyWiseTaxBandBucket> myIterator = taxBandIterator();
            while (myRemaining.isNonZero()
                    && myIterator.hasNext()) {
                final MoneyWiseTaxBandBucket myBucket = myIterator.next();

                /* Determine amount in band */
                OceanusMoney myAmount = getAmountInBand(myBucket.getAmount(), myRemaining);
                myAmount = new OceanusMoney(myAmount);

                /* allocate band and adjust */
                myTaxBands.addTaxBand(new MoneyWiseTaxBand(myAmount, myBucket.getRate()));
                myRemaining.subtractAmount(myAmount);
            }

            /* Create the new tax bucket */
            return new MoneyWiseTaxDueBucket(getTaxBasis(), myTaxBands, getTaxConfig());
        }

        /**
         * calculate the tax.
         */
        private void calculateTax() {
            /* Calculate tax due */
            theNettTaxDue.add(theSliceBucket.getTaxDue().valueAtRatio(theRatio));

            /* Calculate tax relief */
            theTaxRelief.addAmount(getTaxDue());
            theTaxRelief.subtractAmount(theNettTaxDue);
        }

        @Override
        public String formatObject(final OceanusDataFormatter pFormatter) {
            return FIELD_DEFS.getName();
        }

        @Override
        public MetisFieldSet<MoneyWiseUKSlicedTaxDueBucket> getDataFieldSet() {
            return FIELD_DEFS;
        }
    }
}