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.lethe.data.analysis.analyse;
18  
19  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
20  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
21  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceManager;
22  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase;
23  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetType;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
25  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCash;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDataSet;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoan;
29  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
30  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio.MoneyWisePortfolioList;
31  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity;
32  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding;
33  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHoldingMap;
34  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityPrice.MoneyWiseSecurityPriceDataMap;
35  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTax.MoneyWiseTaxCredit;
36  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
37  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
38  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransTag;
39  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
40  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction.MoneyWiseTransactionList;
41  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWisePayeeClass;
42  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWisePortfolioClass;
43  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
44  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
45  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseLogicException;
46  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysis;
47  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisAccountBucket;
48  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisCashBucket.MoneyWiseAnalysisCashBucketList;
49  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisDataResource;
50  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisDepositBucket.MoneyWiseAnalysisDepositBucketList;
51  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisLoanBucket.MoneyWiseAnalysisLoanBucketList;
52  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisPayeeBucket;
53  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisPayeeBucket.MoneyWiseAnalysisPayeeBucketList;
54  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisPortfolioBucket;
55  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisPortfolioBucket.MoneyWiseAnalysisPortfolioBucketList;
56  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisPortfolioCashBucket;
57  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisSecurityBucket;
58  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisTaxBasisBucket.MoneyWiseAnalysisTaxBasisBucketList;
59  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisTransCategoryBucket;
60  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisTransCategoryBucket.MoneyWiseAnalysisTransCategoryBucketList;
61  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisTransTagBucket.MoneyWiseAnalysisTransTagBucketList;
62  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.data.MoneyWiseAnalysisTransactionHelper;
63  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.values.MoneyWiseAnalysisSecurityAttr;
64  import io.github.tonywasher.joceanus.moneywise.lethe.data.analysis.values.MoneyWiseAnalysisSecurityValues;
65  import io.github.tonywasher.joceanus.moneywise.tax.MoneyWiseCashType;
66  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
67  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
68  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
69  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
70  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
71  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
72  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
73  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
74  import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
75  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
76  
77  import java.util.Currency;
78  import java.util.Iterator;
79  import java.util.List;
80  import java.util.Objects;
81  
82  /**
83   * Class to analyse transactions.
84   *
85   * @author Tony Washer
86   */
87  public class MoneyWiseAnalysisTransAnalyser
88          implements MetisFieldItem {
89      /**
90       * Local Report fields.
91       */
92      private static final String ERROR_CATEGORY = "Unexpected Category Type: ";
93  
94      /**
95       * Local Report fields.
96       */
97      private static final MetisFieldSet<MoneyWiseAnalysisTransAnalyser> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseAnalysisTransAnalyser.class);
98  
99      /*
100      * Declare Fields.
101      */
102     static {
103         FIELD_DEFS.declareLocalField(MoneyWiseAnalysisDataResource.ANALYSIS_NAME, MoneyWiseAnalysisTransAnalyser::getAnalysis);
104     }
105 
106     /**
107      * The Amount Tax threshold for "small" transactions (£3000).
108      */
109     private static final OceanusMoney LIMIT_VALUE = OceanusMoney.getWholeUnits(3000);
110 
111     /**
112      * The Rate Tax threshold for "small" transactions (5%).
113      */
114     private static final OceanusRate LIMIT_RATE = OceanusRate.getWholePercentage(5);
115 
116     /**
117      * The security holding map.
118      */
119     private final MoneyWiseSecurityHoldingMap theHoldingMap;
120 
121     /**
122      * The security price map.
123      */
124     private final MoneyWiseSecurityPriceDataMap thePriceMap;
125 
126     /**
127      * The analysis.
128      */
129     private final MoneyWiseAnalysis theAnalysis;
130 
131     /**
132      * The transaction helper.
133      */
134     private final MoneyWiseAnalysisTransactionHelper theHelper;
135 
136     /**
137      * The deposit bucket list.
138      */
139     private final MoneyWiseAnalysisDepositBucketList theDepositBuckets;
140 
141     /**
142      * The cash bucket list.
143      */
144     private final MoneyWiseAnalysisCashBucketList theCashBuckets;
145 
146     /**
147      * The deposit bucket list.
148      */
149     private final MoneyWiseAnalysisLoanBucketList theLoanBuckets;
150 
151     /**
152      * The portfolio bucket list.
153      */
154     private final MoneyWiseAnalysisPortfolioBucketList thePortfolioBuckets;
155 
156     /**
157      * The payee bucket list.
158      */
159     private final MoneyWiseAnalysisPayeeBucketList thePayeeBuckets;
160 
161     /**
162      * The transaction category buckets.
163      */
164     private final MoneyWiseAnalysisTransCategoryBucketList theCategoryBuckets;
165 
166     /**
167      * The transactionTag buckets.
168      */
169     private final MoneyWiseAnalysisTransTagBucketList theTagBuckets;
170 
171     /**
172      * The taxBasis buckets.
173      */
174     private final MoneyWiseAnalysisTaxBasisBucketList theTaxBasisBuckets;
175 
176     /**
177      * The taxMan account.
178      */
179     private final MoneyWiseAnalysisPayeeBucket theTaxMan;
180 
181     /**
182      * The statePension account.
183      */
184     private final MoneyWiseAnalysisSecurityBucket theStatePension;
185 
186     /**
187      * The profile.
188      */
189     private final OceanusProfile theProfile;
190 
191     /**
192      * Constructor for a complete set of accounts.
193      *
194      * @param pTask          the profiled task
195      * @param pEditSet       the EditSet to analyse
196      * @param pPreferenceMgr the preference manager
197      * @throws OceanusException on error
198      */
199     public MoneyWiseAnalysisTransAnalyser(final OceanusProfile pTask,
200                                           final PrometheusEditSet pEditSet,
201                                           final MetisPreferenceManager pPreferenceMgr) throws OceanusException {
202         /* Start a new task */
203         theProfile = pTask;
204         final OceanusProfile myTask = theProfile.startTask("analyseTransactions");
205         final MoneyWiseDataSet myDataSet = (MoneyWiseDataSet) pEditSet.getDataSet();
206 
207         /* Store the parameters */
208         theHoldingMap = (MoneyWiseSecurityHoldingMap) pEditSet.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class)
209                 .getSecurityHoldingsMap();
210         thePriceMap = myDataSet.getSecurityPriceDataMap();
211 
212         /* Access the lists */
213         final MoneyWiseTransactionList myTrans = pEditSet.getDataList(MoneyWiseBasicDataType.TRANSACTION, MoneyWiseTransactionList.class);
214 
215         /* Create a new analysis */
216         myTask.startTask("Initialise");
217         theAnalysis = new MoneyWiseAnalysis(pEditSet, pPreferenceMgr);
218 
219         /* Create new helper and set opening balances */
220         theHelper = new MoneyWiseAnalysisTransactionHelper(myDataSet);
221         theAnalysis.addOpeningBalances(theHelper);
222 
223         /* Access details from the analysis */
224         theDepositBuckets = theAnalysis.getDeposits();
225         theCashBuckets = theAnalysis.getCash();
226         theLoanBuckets = theAnalysis.getLoans();
227         thePortfolioBuckets = theAnalysis.getPortfolios();
228         thePayeeBuckets = theAnalysis.getPayees();
229         theCategoryBuckets = theAnalysis.getTransCategories();
230         theTagBuckets = theAnalysis.getTransactionTags();
231         theTaxBasisBuckets = theAnalysis.getTaxBasis();
232         theTaxMan = thePayeeBuckets.getBucket(MoneyWisePayeeClass.TAXMAN);
233 
234         /* Access the StatePension security holding */
235         theStatePension = getStatePension(myDataSet);
236 
237         /* Loop through the Transactions extracting relevant elements */
238         myTask.startTask("Transactions");
239         final Iterator<MoneyWiseTransaction> myIterator = myTrans.iterator();
240         while (myIterator.hasNext()) {
241             final MoneyWiseTransaction myCurr = myIterator.next();
242 
243             /* Ignore deleted/header transactions */
244             if (myCurr.isDeleted() || myCurr.isHeader()) {
245                 continue;
246             }
247 
248             /* Touch underlying items */
249             myCurr.touchUnderlyingItems();
250 
251             /* Process the transaction in the report set */
252             processTransaction(myCurr);
253         }
254 
255         /* Complete the task */
256         myTask.end();
257     }
258 
259     /**
260      * Obtain statePension bucket.
261      *
262      * @param pData the dataSet
263      * @return the statePension bucket
264      */
265     private MoneyWiseAnalysisSecurityBucket getStatePension(final MoneyWiseDataSet pData) {
266         /* Access the singular portfolio and security */
267         final MoneyWisePortfolio myPensionPort = pData.getPortfolios().getSingularClass(MoneyWisePortfolioClass.PENSION);
268         final MoneyWiseSecurity myStatePension = pData.getSecurities().getSingularClass(MoneyWiseSecurityClass.STATEPENSION);
269 
270         /* If they exist, access the bucket */
271         if (myPensionPort != null
272                 && myStatePension != null) {
273             final MoneyWiseSecurityHoldingMap myMap = (MoneyWiseSecurityHoldingMap) pData.getPortfolios().getSecurityHoldingsMap();
274             final MoneyWiseSecurityHolding myHolding = myMap.declareHolding(myPensionPort, myStatePension);
275             return thePortfolioBuckets.getBucket(myHolding);
276         }
277 
278         /* Default to no bucket */
279         return null;
280     }
281 
282     @Override
283     public MetisFieldSet<MoneyWiseAnalysisTransAnalyser> getDataFieldSet() {
284         return FIELD_DEFS;
285     }
286 
287     @Override
288     public String formatObject(final OceanusDataFormatter pFormatter) {
289         return FIELD_DEFS.getName();
290     }
291 
292     /**
293      * Obtain the analysis.
294      *
295      * @return the analysis
296      */
297     public MoneyWiseAnalysis getAnalysis() {
298         return theAnalysis;
299     }
300 
301     /**
302      * Mark active accounts.
303      *
304      * @throws OceanusException on error
305      */
306     public void postProcessAnalysis() throws OceanusException {
307         /* Start a new task */
308         final OceanusProfile myTask = theProfile.startTask("postProcessAnalysis");
309         myTask.startTask("markActiveAccounts");
310 
311         /* Mark relevant accounts */
312         theDepositBuckets.markActiveAccounts();
313         theCashBuckets.markActiveAccounts();
314         theLoanBuckets.markActiveAccounts();
315 
316         /* Mark relevant securities */
317         thePortfolioBuckets.markActiveSecurities();
318 
319         /* Complete the task */
320         myTask.end();
321     }
322 
323     /**
324      * Process a transaction.
325      *
326      * @param pTrans the transaction to process
327      * @throws OceanusException on error
328      */
329     private void processTransaction(final MoneyWiseTransaction pTrans) throws OceanusException {
330         /* Declare to helper */
331         theHelper.setTransaction(pTrans);
332 
333         /* Access key details */
334         final MoneyWiseTransAsset myDebitAsset = theHelper.getDebitAsset();
335         final MoneyWiseTransAsset myCreditAsset = theHelper.getCreditAsset();
336         final MoneyWiseTaxCredit myYear = pTrans.getTaxYear();
337 
338         /* Look for tags */
339         final List<MoneyWiseTransTag> myTags = pTrans.getTransactionTags();
340         if (myTags != null) {
341             /* Process the transaction tags */
342             theTagBuckets.processTransaction(pTrans, myTags.iterator());
343         }
344 
345         /* If the event relates to a security item, split out the workings */
346         if (myDebitAsset instanceof MoneyWiseSecurityHolding myHolding) {
347             /* Process as a Security transaction */
348             processDebitSecurityTransaction(myHolding, myCreditAsset);
349 
350             /* If the event relates to a security item, split out the workings */
351         } else if (myCreditAsset instanceof MoneyWiseSecurityHolding myHolding) {
352             /* Process as a Security transaction */
353             processCreditSecurityTransaction(myDebitAsset, myHolding);
354 
355             /* Else handle the portfolio transfer */
356         } else if (myDebitAsset instanceof MoneyWisePortfolio myDebitPortfolio
357                 && myCreditAsset instanceof MoneyWisePortfolio myCreditPortfolio
358                 && pTrans.getCategoryClass() == MoneyWiseTransCategoryClass.PORTFOLIOXFER
359                 && !myDebitAsset.equals(myCreditAsset)) {
360             /* Process portfolio transfer */
361             processPortfolioXfer(myDebitPortfolio, myCreditPortfolio);
362 
363             /* Else handle the event normally */
364         } else if (myDebitAsset instanceof MoneyWiseAssetBase myDebit
365                 && myCreditAsset instanceof MoneyWiseAssetBase myCredit) {
366             /* Access correctly */
367             MoneyWiseAssetBase myChild = null;
368             final OceanusMoney myAmount = pTrans.getAmount();
369             MoneyWiseTransCategory myCat = pTrans.getCategory();
370 
371             /* Switch on category class */
372             switch (Objects.requireNonNull(myCat.getCategoryTypeClass())) {
373                 case INTEREST, LOYALTYBONUS:
374                     /* Obtain detailed category */
375                     myCat = myDebit.getDetailedCategory(myCat, myYear);
376 
377                     /* True debit account is the parent */
378                     myChild = myDebit.equals(myCredit)
379                             ? null
380                             : myDebit;
381                     myDebit = myDebit.getParent();
382                     break;
383                 case LOANINTERESTEARNED, CASHBACK:
384                     /* True debit account is the parent of the asset */
385                     myDebit = myDebit.getParent();
386                     break;
387                 case RENTALINCOME, ROOMRENTALINCOME:
388                     /* True debit account is the parent of the security */
389                     myChild = myDebit.equals(myCredit)
390                             ? null
391                             : myDebit;
392                     myDebit = myCredit.getParent();
393                     break;
394                 case WRITEOFF, LOANINTERESTCHARGED:
395                     /* True credit account is the parent of the loan */
396                     myCredit = myCredit.getParent();
397                     break;
398                 default:
399                     break;
400             }
401 
402             /* If the debit account is auto-Expense */
403             if (myDebit instanceof MoneyWiseCash myCash
404                     && myDebit.getAssetType() == MoneyWiseAssetType.AUTOEXPENSE) {
405                 /* Access debit as cash */
406                 final MoneyWiseTransCategory myAuto = myCash.getAutoExpense();
407                 myDebit = myCash.getAutoPayee();
408                 myDebit.touchItem(pTrans);
409 
410                 /* Subtract expense from Payee bucket */
411                 final MoneyWiseAnalysisPayeeBucket myPayee = thePayeeBuckets.getBucket(myDebit);
412                 myPayee.subtractExpense(theHelper, myAmount);
413 
414                 /* Subtract expense from Category bucket */
415                 final MoneyWiseAnalysisTransCategoryBucket myCatBucket = theCategoryBuckets.getBucket(myAuto);
416                 myCatBucket.subtractExpense(theHelper, myAmount);
417                 theTaxBasisBuckets.adjustAutoExpense(theHelper, false);
418 
419                 /* handle Payees */
420             } else if (MoneyWiseAssetType.PAYEE.equals(myDebit.getAssetType())) {
421                 final MoneyWiseAnalysisPayeeBucket myPayee = thePayeeBuckets.getBucket(myDebit);
422                 myPayee.adjustForDebit(theHelper);
423 
424                 /* handle valued assets */
425             } else {
426                 final MoneyWiseAnalysisAccountBucket<?> myBucket = getAccountBucket(myDebit);
427                 myBucket.adjustForDebit(theHelper);
428             }
429 
430             /* If the credit account is auto-Expense */
431             if (myCredit instanceof MoneyWiseCash myCash
432                     && myCredit.getAssetType() == MoneyWiseAssetType.AUTOEXPENSE) {
433                 /* Access credit as cash */
434                 final MoneyWiseTransCategory myAuto = myCash.getAutoExpense();
435                 myCredit = myCash.getAutoPayee();
436                 myCredit.touchItem(pTrans);
437 
438                 /* Add expense to Payee bucket */
439                 final MoneyWiseAnalysisPayeeBucket myPayee = thePayeeBuckets.getBucket(myCredit);
440                 myPayee.addExpense(theHelper, myAmount);
441 
442                 /* Adjust the relevant category bucket */
443                 final MoneyWiseAnalysisTransCategoryBucket myCatBucket = theCategoryBuckets.getBucket(myAuto);
444                 myCatBucket.addExpense(theHelper, myAmount);
445                 theTaxBasisBuckets.adjustAutoExpense(theHelper, true);
446 
447                 /* handle Payees */
448             } else if (MoneyWiseAssetType.PAYEE.equals(myCredit.getAssetType())) {
449                 final MoneyWiseAnalysisPayeeBucket myPayee = thePayeeBuckets.getBucket(myCredit);
450                 myPayee.adjustForCredit(theHelper);
451 
452                 /* handle valued assets */
453             } else {
454                 final MoneyWiseAnalysisAccountBucket<?> myBucket = getAccountBucket(myCredit);
455                 myBucket.adjustForCredit(theHelper);
456             }
457 
458             /* If we should register the event with a child */
459             if (myChild != null) {
460                 /* Access bucket and register it */
461                 final MoneyWiseAnalysisAccountBucket<?> myBucket = getAccountBucket(myChild);
462                 myBucket.registerTransaction(pTrans);
463             }
464 
465             /* Adjust the tax and NI payments */
466             theTaxMan.adjustForTaxPayments(theHelper);
467             theStatePension.adjustForNIPayments(theHelper);
468 
469             /* If the event category is not a transfer */
470             if (!myCat.isTransfer()) {
471                 /* Adjust the relevant category buckets */
472                 theCategoryBuckets.adjustCategories(theHelper, myCat);
473             }
474 
475             /* Unknown combination */
476         } else {
477             throw new MoneyWiseLogicException("Invalid Asset Pair: "
478                     + pTrans.getAccount().getAssetType()
479                     + " "
480                     + pTrans.getDirection()
481                     + " "
482                     + pTrans.getPartner().getAssetType());
483         }
484     }
485 
486     /**
487      * Process a debit security transaction.
488      *
489      * @param pDebit  the debit security
490      * @param pCredit the credit account
491      * @throws OceanusException on error
492      */
493     private void processDebitSecurityTransaction(final MoneyWiseSecurityHolding pDebit,
494                                                  final MoneyWiseTransAsset pCredit) throws OceanusException {
495         /* If credit account is also SecurityHolding */
496         if (pCredit instanceof MoneyWiseSecurityHolding myHolding) {
497             /* Split out working */
498             processDebitCreditSecurityTransaction(pDebit, myHolding);
499             return;
500         }
501 
502         /* Switch on the category */
503         final MoneyWiseTransCategory myCat = theHelper.getCategory();
504         switch (Objects.requireNonNull(myCat.getCategoryTypeClass())) {
505             /* Process a stock right waived */
506             case STOCKRIGHTSISSUE:
507                 processTransferOut(pDebit, (MoneyWiseAssetBase) pCredit);
508                 break;
509             /* Process a dividend */
510             case DIVIDEND:
511                 processDividend(pDebit, pCredit);
512                 break;
513             case PORTFOLIOXFER:
514                 processPortfolioXfer(pDebit, (MoneyWisePortfolio) pCredit);
515                 break;
516             /* Process standard transfer in/out */
517             case TRANSFER, SECURITYCLOSURE, EXPENSE, INHERITED, OTHERINCOME:
518                 if (pDebit.getSecurity().isSecurityClass(MoneyWiseSecurityClass.LIFEBOND)) {
519                     processChargeableGain(pDebit, (MoneyWiseAssetBase) pCredit);
520                 } else {
521                     processTransferOut(pDebit, (MoneyWiseAssetBase) pCredit);
522                 }
523                 break;
524             /* Throw an Exception */
525             default:
526                 throw new MoneyWiseLogicException(ERROR_CATEGORY
527                         + myCat.getCategoryTypeClass());
528         }
529     }
530 
531     /**
532      * Process a debit+credit security transaction.
533      *
534      * @param pDebit  the debit security
535      * @param pCredit the credit security
536      * @throws OceanusException on error
537      */
538     private void processDebitCreditSecurityTransaction(final MoneyWiseSecurityHolding pDebit,
539                                                        final MoneyWiseSecurityHolding pCredit) throws OceanusException {
540         /* Switch on the category */
541         final MoneyWiseTransCategory myCat = theHelper.getCategory();
542         switch (Objects.requireNonNull(myCat.getCategoryTypeClass())) {
543             /* Process a stock split */
544             case STOCKSPLIT, UNITSADJUST:
545                 processUnitsAdjust(pDebit);
546                 break;
547             /* Process a stock DeMerger */
548             case STOCKDEMERGER:
549                 processStockDeMerger(pDebit, pCredit);
550                 break;
551             /* Process a Stock TakeOver */
552             case SECURITYREPLACE, STOCKTAKEOVER:
553                 processStockTakeover(pDebit, pCredit);
554                 break;
555             /* Process a dividend */
556             case DIVIDEND:
557                 processDividend(pDebit, pCredit);
558                 break;
559             /* Process standard transfer in/out */
560             case TRANSFER, EXPENSE, INHERITED, OTHERINCOME:
561                 processStockXchange(pDebit, pCredit);
562                 break;
563             /* Throw an Exception */
564             default:
565                 throw new MoneyWiseLogicException(ERROR_CATEGORY
566                         + myCat.getCategoryTypeClass());
567         }
568     }
569 
570     /**
571      * Process a credit security transaction.
572      *
573      * @param pDebit  the debit account
574      * @param pCredit the credit security holding
575      * @throws OceanusException on error
576      */
577     private void processCreditSecurityTransaction(final MoneyWiseTransAsset pDebit,
578                                                   final MoneyWiseSecurityHolding pCredit) throws OceanusException {
579         /* Input asset must be AssetBase */
580         if (!(pDebit instanceof MoneyWiseAssetBase myDebit)) {
581             throw new MoneyWiseLogicException("Invalid Debit Asset: "
582                     + pDebit.getAssetType());
583         }
584 
585         /* Switch on the category */
586         final MoneyWiseTransCategory myCat = theHelper.getCategory();
587         switch (Objects.requireNonNull(myCat.getCategoryTypeClass())) {
588             /* Process standard transfer in/out */
589             case STOCKRIGHTSISSUE, TRANSFER, EXPENSE, INHERITED, OTHERINCOME, PENSIONCONTRIB:
590                 processTransferIn(myDebit, pCredit);
591                 break;
592             /* Throw an Exception */
593             default:
594                 throw new MoneyWiseLogicException(ERROR_CATEGORY
595                         + myCat.getCategoryTypeClass());
596         }
597     }
598 
599     /**
600      * Process a transaction that is a portfolio transfer.
601      * <p>
602      * This capital event relates only to both Debit and credit accounts.
603      *
604      * @param pSource the source portfolio
605      * @param pTarget the target portfolio
606      */
607     private void processPortfolioXfer(final MoneyWisePortfolio pSource,
608                                       final MoneyWisePortfolio pTarget) {
609         /* Access the portfolio buckets */
610         final MoneyWiseAnalysisPortfolioBucket mySource = thePortfolioBuckets.getBucket(pSource);
611         final MoneyWiseAnalysisPortfolioBucket myTarget = thePortfolioBuckets.getBucket(pTarget);
612 
613         /* Access source cash bucket */
614         final MoneyWiseAnalysisPortfolioCashBucket mySourceCash = mySource.getPortfolioCash();
615         if (mySourceCash.isActive()) {
616             /* Transfer any cash element */
617             final MoneyWiseAnalysisPortfolioCashBucket myTargetCash = myTarget.getPortfolioCash();
618             myTargetCash.adjustForXfer(mySourceCash, theHelper);
619         }
620 
621         /* Loop through the source portfolio */
622         final Iterator<MoneyWiseAnalysisSecurityBucket> myIterator = mySource.securityIterator();
623         while (myIterator.hasNext()) {
624             final MoneyWiseAnalysisSecurityBucket myBucket = myIterator.next();
625 
626             /* If the bucket is active */
627             if (myBucket.isActive()) {
628                 /* Adjust the Target Bucket */
629                 final MoneyWiseSecurityHolding myTargetHolding = theHoldingMap.declareHolding(pTarget, myBucket.getSecurity());
630                 final MoneyWiseAnalysisSecurityBucket myTargetBucket = myTarget.getSecurityBucket(myTargetHolding);
631                 theHelper.setSecurity(myBucket.getSecurity());
632 
633                 /* Process the Transfer */
634                 processPortfolioXfer(myBucket, myTargetBucket);
635             }
636         }
637 
638         /* PortfolioXfer is a transfer, so no need to update the categories */
639     }
640 
641     /**
642      * Process a portfolio transfer.
643      *
644      * @param pSource the source holding
645      * @param pTarget the target holding
646      */
647     private void processPortfolioXfer(final MoneyWiseAnalysisSecurityBucket pSource,
648                                       final MoneyWiseAnalysisSecurityBucket pTarget) {
649         /* Access source details */
650         MoneyWiseAnalysisSecurityValues mySourceValues = pSource.getValues();
651         OceanusUnits myUnits = mySourceValues.getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS);
652         OceanusMoney myCost = mySourceValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST);
653         OceanusMoney myGains = mySourceValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.REALISEDGAINS);
654         OceanusMoney myInvested = mySourceValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.INVESTED);
655         OceanusMoney myForeignInvested = mySourceValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED);
656         final boolean isForeign = pSource.isForeignCurrency();
657 
658         /* Determine value of the stock being transferred */
659         final OceanusPrice myPrice = thePriceMap.getPriceForDate(pSource.getSecurity(), theHelper.getDate());
660         OceanusMoney myStockValue = myPrice.unitsAtPrice(myUnits);
661         OceanusMoney myForeignValue = null;
662         OceanusRatio myRate = null;
663 
664         /* If we are foreign */
665         if (isForeign) {
666             /* Determine foreign and local value */
667             myRate = theHelper.getDebitExchangeRate();
668             myForeignValue = myStockValue;
669             myStockValue = myStockValue.convertCurrency(theAnalysis.getCurrency().getCurrency(), myRate);
670         }
671 
672         /* Allocate current profit between the two stocks */
673         OceanusMoney myProfit = new OceanusMoney(myStockValue);
674         myProfit.subtractAmount(myCost);
675         pSource.adjustCounter(MoneyWiseAnalysisSecurityAttr.GROWTHADJUST, myProfit);
676         myProfit = new OceanusMoney(myProfit);
677         myProfit.negate();
678         pTarget.adjustCounter(MoneyWiseAnalysisSecurityAttr.GROWTHADJUST, myProfit);
679 
680         /* Transfer Units/Cost/Invested to target */
681         pTarget.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myUnits);
682         pTarget.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myCost);
683         pTarget.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myInvested);
684         pTarget.adjustCounter(MoneyWiseAnalysisSecurityAttr.REALISEDGAINS, myGains);
685         final MoneyWiseAnalysisSecurityValues myTargetValues = pTarget.registerTransaction(theHelper);
686         myTargetValues.setValue(MoneyWiseAnalysisSecurityAttr.PRICE, myPrice);
687         myTargetValues.setValue(MoneyWiseAnalysisSecurityAttr.VALUATION, myStockValue);
688         myTargetValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDCOST, myCost);
689         if (isForeign) {
690             myTargetValues.setValue(MoneyWiseAnalysisSecurityAttr.FOREIGNVALUE, myForeignValue);
691             myTargetValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myRate);
692             pTarget.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myForeignInvested);
693         }
694 
695         /* Adjust the Source Units/Cost/Invested to zero */
696         myUnits = new OceanusUnits(myUnits);
697         myUnits.negate();
698         pSource.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myUnits);
699         myCost = new OceanusMoney(myCost);
700         myCost.negate();
701         pSource.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myCost);
702         myCost.negate();
703         myInvested = new OceanusMoney(myInvested);
704         myInvested.negate();
705         pSource.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myInvested);
706         myGains = new OceanusMoney(myGains);
707         myGains.negate();
708         pSource.adjustCounter(MoneyWiseAnalysisSecurityAttr.REALISEDGAINS, myGains);
709         mySourceValues = pSource.registerTransaction(theHelper);
710         mySourceValues.setValue(MoneyWiseAnalysisSecurityAttr.PRICE, myPrice);
711         mySourceValues.setValue(MoneyWiseAnalysisSecurityAttr.VALUATION, myStockValue);
712         mySourceValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDCOST, myCost);
713         if (isForeign) {
714             mySourceValues.setValue(MoneyWiseAnalysisSecurityAttr.FOREIGNVALUE, myForeignValue);
715             mySourceValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myRate);
716             myForeignInvested = new OceanusMoney(myForeignInvested);
717             myForeignInvested.negate();
718             pTarget.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myForeignInvested);
719         }
720     }
721 
722     /**
723      * Process a transaction that is a portfolio transfer.
724      * <p>
725      * This capital event relates only to both Debit and credit accounts.
726      *
727      * @param pSource the source holding
728      * @param pTarget the target portfolio
729      */
730     private void processPortfolioXfer(final MoneyWiseSecurityHolding pSource,
731                                       final MoneyWisePortfolio pTarget) {
732         /* Access the portfolio buckets */
733         final MoneyWiseAnalysisPortfolioBucket mySource = thePortfolioBuckets.getBucket(pSource.getPortfolio());
734         final MoneyWiseAnalysisPortfolioBucket myTarget = thePortfolioBuckets.getBucket(pTarget);
735 
736         /* Access source security bucket */
737         final MoneyWiseAnalysisSecurityBucket myBucket = mySource.getSecurityBucket(pSource);
738 
739         /* If the bucket is active */
740         if (myBucket.isActive()) {
741             /* Adjust the Target Bucket */
742             final MoneyWiseSecurityHolding myTargetHolding = theHoldingMap.declareHolding(pTarget, myBucket.getSecurity());
743             final MoneyWiseAnalysisSecurityBucket myTargetBucket = myTarget.getSecurityBucket(myTargetHolding);
744 
745             /* Process the Transfer */
746             processPortfolioXfer(myBucket, myTargetBucket);
747         }
748 
749         /* PortfolioXfer is a transfer, so no need to update the categories */
750     }
751 
752     /**
753      * Process a transaction that is a stock split.
754      * <p>
755      * This capital event relates only to the Debit Account since the credit account is the same.
756      *
757      * @param pHolding the security holding
758      */
759     private void processUnitsAdjust(final MoneyWiseSecurityHolding pHolding) {
760         /* Access the units */
761         final OceanusUnits myDelta = theHelper.getAccountDeltaUnits();
762 
763         /* Adjust the Security Units */
764         final MoneyWiseAnalysisSecurityBucket myAsset = thePortfolioBuckets.getBucket(pHolding);
765         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myDelta);
766 
767         /* Register the transaction */
768         myAsset.registerTransaction(theHelper);
769 
770         /* StockSplit/Adjust is a transfer, so no need to update the categories */
771     }
772 
773     /**
774      * Process a transaction that is a transfer into capital (also StockRightTaken).
775      * <p>
776      * This capital event relates only to the Credit Account.
777      *
778      * @param pDebit  the debit account
779      * @param pCredit the credit security holding
780      */
781     private void processTransferIn(final MoneyWiseAssetBase pDebit,
782                                    final MoneyWiseSecurityHolding pCredit) {
783         /* Access debit account and category */
784         final MoneyWiseTransCategory myCat = theHelper.getCategory();
785 
786         /* Adjust the credit transfer details */
787         processCreditXferIn(pCredit);
788 
789         /* Adjust the tax payments */
790         theTaxMan.adjustForTaxPayments(theHelper);
791 
792         /* Determine the type of the debit account */
793         if (Objects.requireNonNull(pDebit.getAssetType()) == MoneyWiseAssetType.PAYEE) {
794             final MoneyWiseAnalysisPayeeBucket myPayee = thePayeeBuckets.getBucket(pDebit);
795             myPayee.adjustForDebit(theHelper);
796         } else {
797             final MoneyWiseAnalysisAccountBucket<?> myAccount = getAccountBucket(pDebit);
798             myAccount.adjustForDebit(theHelper);
799         }
800 
801         /* If the event category is not a transfer */
802         if (!myCat.isTransfer()) {
803             /* Adjust the relevant category buckets */
804             theCategoryBuckets.adjustCategories(theHelper, myCat);
805         }
806     }
807 
808     /**
809      * Process the credit side of a transfer in transaction.
810      *
811      * @param pHolding the credit holding
812      */
813     private void processCreditXferIn(final MoneyWiseSecurityHolding pHolding) {
814         /* Transfer is to the credit account and may or may not have a change to the units */
815         OceanusMoney myAmount = theHelper.getCreditAmount();
816         final OceanusRatio myExchangeRate = theHelper.getCreditExchangeRate();
817         final MoneyWiseSecurity mySecurity = pHolding.getSecurity();
818 
819         /* Access the Asset Security Bucket */
820         final MoneyWiseAnalysisSecurityBucket myAsset = thePortfolioBuckets.getBucket(pHolding);
821         final boolean isForeign = myAsset.isForeignCurrency();
822 
823         /* If this is a foreign currency asset */
824         if (isForeign) {
825             /* Adjust foreign invested amount */
826             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myAmount);
827 
828             /* Switch to local amount */
829             myAmount = theHelper.getLocalAmount();
830         }
831 
832         /* Adjust the cost and investment */
833         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myAmount);
834         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myAmount);
835 
836         /* Determine the delta units */
837         final MoneyWiseSecurityClass mySecClass = mySecurity.getCategoryClass();
838         OceanusUnits myDeltaUnits = theHelper.getCreditUnits();
839         OceanusUnits myUnits = myAsset.getValues().getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS);
840         if (mySecClass.isAutoUnits() && myUnits.isZero()) {
841             myDeltaUnits = OceanusUnits.getWholeUnits(mySecClass.getAutoUnits());
842         }
843 
844         /* If we have new units */
845         if (myDeltaUnits != null) {
846             /* Record change in units */
847             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myDeltaUnits);
848         }
849 
850         /* Adjust for National Insurance */
851         myAsset.adjustForNIPayments(theHelper);
852 
853         /* Get the appropriate price for the account */
854         final OceanusPrice myPrice = thePriceMap.getPriceForDate(mySecurity, theHelper.getDate());
855 
856         /* Determine value of this stock after the transaction */
857         myUnits = myAsset.getValues().getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS);
858         OceanusMoney myValue = myPrice.unitsAtPrice(myUnits);
859 
860         /* If we are foreign */
861         if (isForeign) {
862             /* Determine local value */
863             myValue = myValue.convertCurrency(theAnalysis.getCurrency().getCurrency(), myExchangeRate);
864         }
865 
866         /* Register the transaction */
867         final MoneyWiseAnalysisSecurityValues myValues = myAsset.registerTransaction(theHelper);
868         myValues.setValue(MoneyWiseAnalysisSecurityAttr.PRICE, myPrice);
869         myValues.setValue(MoneyWiseAnalysisSecurityAttr.VALUATION, myValue);
870         myValues.setValue(MoneyWiseAnalysisSecurityAttr.CASHINVESTED, myAmount);
871         if (isForeign) {
872             myValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myExchangeRate);
873         }
874     }
875 
876     /**
877      * Process a dividend transaction.
878      * <p>
879      * This capital event relates to the only to Debit account, although the Credit account may be
880      * identical to the credit account in which case the dividend is re-invested
881      *
882      * @param pHolding the debit security holding
883      * @param pCredit  the credit account
884      */
885     private void processDividend(final MoneyWiseSecurityHolding pHolding,
886                                  final MoneyWiseTransAsset pCredit) {
887         /* The main security that we are interested in is the debit account */
888         final MoneyWisePortfolio myPortfolio = pHolding.getPortfolio();
889         final MoneyWiseSecurity mySecurity = pHolding.getSecurity();
890         OceanusMoney myAmount = theHelper.getDebitAmount();
891         final OceanusMoney myTaxCredit = theHelper.getTaxCredit();
892         final OceanusUnits myDeltaUnits = theHelper.getAccountDeltaUnits();
893         final MoneyWiseTaxCredit myYear = theHelper.getTransaction().getTaxYear();
894 
895         /* Obtain detailed category */
896         MoneyWiseTransCategory myCat = myPortfolio.getDetailedCategory(theHelper.getCategory(), myYear);
897         myCat = mySecurity.getDetailedCategory(myCat, myYear);
898 
899         /* True debit account is the parent */
900         final MoneyWiseAssetBase myDebit = mySecurity.getParent();
901 
902         /* Adjust the debit payee bucket */
903         final MoneyWiseAnalysisPayeeBucket myPayee = thePayeeBuckets.getBucket(myDebit);
904         myPayee.adjustForDebit(theHelper);
905 
906         /* Access the Asset Account Bucket */
907         final MoneyWiseAnalysisSecurityBucket myAsset = thePortfolioBuckets.getBucket(pHolding);
908         final boolean isForeign = myAsset.isForeignCurrency();
909         final boolean isReInvest = pCredit instanceof MoneyWiseSecurityHolding;
910 
911         /* If this is a foreign dividend */
912         if (isForeign) {
913             /* If this is a reInvestment */
914             if (isReInvest) {
915                 /* Adjust counters */
916                 myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myAmount);
917                 myAsset.getValues().setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, theHelper.getCreditExchangeRate());
918             }
919 
920             /* Switch to local amount */
921             myAmount = theHelper.getLocalAmount();
922         }
923 
924         /* If this is a re-investment */
925         if (isReInvest) {
926             /* This amount is added to the cost, so record as the delta cost */
927             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myAmount);
928 
929             /* Record the investment */
930             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myAmount);
931 
932             /* If we have new units */
933             if (myDeltaUnits != null) {
934                 /* Record delta units */
935                 myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myDeltaUnits);
936             }
937 
938             /* If we have a tax credit */
939             if (myTaxCredit != null) {
940                 /* The Tax Credit is viewed as a received dividend from the account */
941                 myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.DIVIDEND, myTaxCredit);
942             }
943 
944             /* else we are paying out to another account */
945         } else {
946             /* Adjust the dividend total for this asset */
947             final OceanusMoney myAdjust = new OceanusMoney(myAmount);
948 
949             /* Any tax credit is viewed as a realised dividend from the account */
950             if (myTaxCredit != null) {
951                 myAdjust.addAmount(myTaxCredit);
952             }
953 
954             /* The Dividend is viewed as a dividend from the account */
955             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.DIVIDEND, myAdjust);
956 
957             /* Adjust the credit account bucket */
958             final MoneyWiseAnalysisAccountBucket<?> myBucket = getAccountBucket((MoneyWiseAssetBase) pCredit);
959             myBucket.adjustForCredit(theHelper);
960         }
961 
962         /* Register the transaction */
963         myAsset.registerTransaction(theHelper);
964 
965         /* Adjust the tax payments */
966         theTaxMan.adjustForTaxPayments(theHelper);
967 
968         /* Adjust the relevant category buckets */
969         theCategoryBuckets.adjustCategories(theHelper, myCat);
970     }
971 
972     /**
973      * Process a transaction that is a transfer from capital.
974      * <p>
975      * This capital event relates only to the Debit Account
976      *
977      * @param pHolding the debit holding
978      * @param pCredit  the credit account
979      */
980     private void processTransferOut(final MoneyWiseSecurityHolding pHolding,
981                                     final MoneyWiseAssetBase pCredit) {
982         /* Access credit account and category */
983         final MoneyWiseTransCategory myCat = theHelper.getCategory();
984 
985         /* Adjust the debit transfer details */
986         processDebitXferOut(pHolding);
987 
988         /* Adjust the credit account bucket */
989         final MoneyWiseAnalysisAccountBucket<?> myBucket = getAccountBucket(pCredit);
990         myBucket.adjustForCredit(theHelper);
991 
992         /* If the event category is not a transfer */
993         if (!myCat.isTransfer()) {
994             /* Adjust the relevant category buckets */
995             theCategoryBuckets.adjustCategories(theHelper, myCat);
996         }
997     }
998 
999     /**
1000      * Process the debit side of a transfer out transaction.
1001      * <p>
1002      * This capital event relates only to the Debit Account
1003      *
1004      * @param pHolding the debit holding
1005      */
1006     private void processDebitXferOut(final MoneyWiseSecurityHolding pHolding) {
1007         /* Transfer out is from the debit account and may or may not have units */
1008         final MoneyWiseSecurity myDebit = pHolding.getSecurity();
1009         OceanusMoney myAmount = theHelper.getDebitAmount();
1010         boolean isLargeCash = false;
1011 
1012         /* Access the Asset Security Bucket */
1013         final MoneyWiseAnalysisSecurityBucket myAsset = thePortfolioBuckets.getBucket(pHolding);
1014         MoneyWiseAnalysisSecurityValues myValues = myAsset.getValues();
1015         final OceanusRatio myXchangeRate = theHelper.getDebitExchangeRate();
1016         final boolean isForeign = myAsset.isForeignCurrency();
1017 
1018         /* If this is a foreign currency asset */
1019         if (isForeign) {
1020             /* Adjust foreign invested amount */
1021             final OceanusMoney myDelta = new OceanusMoney(myAmount);
1022             myDelta.negate();
1023             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myDelta);
1024 
1025             /* Switch to local amount */
1026             myAmount = theHelper.getLocalAmount();
1027         }
1028 
1029         /* Record the delta investment */
1030         final OceanusMoney myDelta = new OceanusMoney(myAmount);
1031         myDelta.negate();
1032         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myDelta);
1033 
1034         /* Get the appropriate price for the account */
1035         final OceanusPrice myPrice = thePriceMap.getPriceForDate(myDebit, theHelper.getDate());
1036 
1037         /* Assume that the allowed cost is the full value */
1038         OceanusUnits myUnits = Objects.requireNonNull(myValues.getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS));
1039         OceanusMoney myAllowedCost = new OceanusMoney(myAmount);
1040         final OceanusMoney myCost = myValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST);
1041         OceanusRatio myCostDilution = null;
1042         OceanusMoney myConsideration = null;
1043 
1044         /* Determine the delta units */
1045         OceanusUnits myDeltaUnits = theHelper.getCategoryClass().isSecurityClosure()
1046                 ? myUnits
1047                 : theHelper.getDebitUnits();
1048         final boolean isCapitalDistribution = myDeltaUnits == null;
1049 
1050         /* If this is not a capital distribution */
1051         if (!isCapitalDistribution) {
1052             /* The allowed cost is the relevant fraction of the cost */
1053             myAllowedCost = myCost.valueAtWeight(myDeltaUnits, myUnits);
1054 
1055             /* Access units as negative value */
1056             myDeltaUnits = new OceanusUnits(myDeltaUnits);
1057             myDeltaUnits.negate();
1058 
1059             /* Record delta to units */
1060             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myDeltaUnits);
1061             final OceanusUnits myNewUnits = myValues.getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS);
1062 
1063             /* Determine the cost dilution */
1064             myCostDilution = new OceanusRatio(myNewUnits, myUnits);
1065             myUnits = myNewUnits;
1066         }
1067 
1068         /* Determine value of this stock after the transaction */
1069         OceanusMoney myValue = myPrice.unitsAtPrice(myUnits);
1070 
1071         /* If we are foreign */
1072         if (isForeign) {
1073             /* Determine local value */
1074             myValue = myValue.convertCurrency(theAnalysis.getCurrency().getCurrency(), myXchangeRate);
1075         }
1076 
1077         /* If we are performing a capital distribution */
1078         if (isCapitalDistribution) {
1079             /* Determine condition as to whether this is a large cash transaction */
1080             final OceanusMoney myPortion = myValue.valueAtRate(LIMIT_RATE);
1081             isLargeCash = (myAmount.compareTo(LIMIT_VALUE) > 0)
1082                     && (myAmount.compareTo(myPortion) > 0);
1083 
1084             /* If this is large cash */
1085             if (isLargeCash) {
1086                 /* Determine the total value of rights plus share value */
1087                 myConsideration = new OceanusMoney(myAmount);
1088                 myConsideration.addAmount(myValue);
1089 
1090                 /* Determine the allowedCost as a proportion of the total value */
1091                 myAllowedCost = myCost.valueAtWeight(myAmount, myConsideration);
1092 
1093                 /* Determine the cost dilution */
1094                 myCostDilution = new OceanusRatio(myValue, myConsideration);
1095 
1096                 /* else this is viewed as small and is taken out of the cost */
1097             } else {
1098                 /* Set the allowed cost to be the least of the cost or the returned cash */
1099                 myAllowedCost = myAmount.compareTo(myCost) > 0
1100                         ? new OceanusMoney(myCost)
1101                         : new OceanusMoney(myAmount);
1102             }
1103         }
1104 
1105         /* Determine the delta to the cost */
1106         final OceanusMoney myDeltaCost = new OceanusMoney(myAllowedCost);
1107         myDeltaCost.negate();
1108 
1109         /* If we have a delta to the cost */
1110         if (myDeltaCost.isNonZero()) {
1111             /* Adjust the cost */
1112             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myDeltaCost);
1113         }
1114 
1115         /* Determine the capital gain */
1116         final OceanusMoney myCapitalGain = new OceanusMoney(myAmount);
1117         myCapitalGain.addAmount(myDeltaCost);
1118 
1119         /* If we have a delta to the gains */
1120         if (myCapitalGain.isNonZero()) {
1121             /* Adjust the gains */
1122             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.REALISEDGAINS, myCapitalGain);
1123 
1124             /* Adjust the capitalGains category bucket */
1125             theCategoryBuckets.adjustStandardGain(theHelper, pHolding, myCapitalGain);
1126         }
1127 
1128         /* Register the transaction */
1129         myValues = myAsset.registerTransaction(theHelper);
1130 
1131         /* record details */
1132         myValues.setValue(MoneyWiseAnalysisSecurityAttr.PRICE, myPrice);
1133         myValues.setValue(MoneyWiseAnalysisSecurityAttr.VALUATION, myValue);
1134         myValues.setValue(MoneyWiseAnalysisSecurityAttr.RETURNEDCASH, myAmount);
1135         myValues.setValue(MoneyWiseAnalysisSecurityAttr.ALLOWEDCOST, myAllowedCost);
1136         if (myCostDilution != null) {
1137             myValues.setValue(MoneyWiseAnalysisSecurityAttr.COSTDILUTION, myCostDilution);
1138         }
1139         if (myConsideration != null) {
1140             myValues.setValue(MoneyWiseAnalysisSecurityAttr.CONSIDERATION, myConsideration);
1141         }
1142         if (myCapitalGain.isNonZero()) {
1143             myValues.setValue(MoneyWiseAnalysisSecurityAttr.CAPITALGAIN, myCapitalGain);
1144         }
1145         if (isForeign) {
1146             myValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myXchangeRate);
1147         }
1148         if (isCapitalDistribution) {
1149             myValues.setValue(MoneyWiseAnalysisSecurityAttr.CASHTYPE, isLargeCash
1150                     ? MoneyWiseCashType.LARGECASH
1151                     : MoneyWiseCashType.SMALLCASH);
1152         }
1153     }
1154 
1155     /**
1156      * Process a transaction that is a exchange between two capital accounts.
1157      * <p>
1158      * This represent a transfer out from the debit account and a transfer in to the credit account
1159      *
1160      * @param pDebit  the debit holding
1161      * @param pCredit the credit holding
1162      */
1163     private void processStockXchange(final MoneyWiseSecurityHolding pDebit,
1164                                      final MoneyWiseSecurityHolding pCredit) {
1165         /* Adjust the debit transfer details */
1166         processDebitXferOut(pDebit);
1167 
1168         /* Adjust the credit transfer details */
1169         processCreditXferIn(pCredit);
1170     }
1171 
1172     /**
1173      * Process a transaction that is a chargeable gain.
1174      * <p>
1175      * This capital event relates only to the Debit Asset
1176      *
1177      * @param pHolding the debit security holding
1178      * @param pCredit  the credit account
1179      */
1180     private void processChargeableGain(final MoneyWiseSecurityHolding pHolding,
1181                                        final MoneyWiseAssetBase pCredit) {
1182         /* Chargeable Gain is from the debit account and may or may not have units */
1183         final MoneyWiseSecurity myDebit = pHolding.getSecurity();
1184         OceanusMoney myAmount = theHelper.getDebitAmount();
1185         OceanusUnits myDeltaUnits = theHelper.getDebitUnits();
1186 
1187         /* Access the Asset Security Bucket */
1188         final MoneyWiseAnalysisSecurityBucket myAsset = thePortfolioBuckets.getBucket(pHolding);
1189         final MoneyWiseAnalysisSecurityValues myValues = myAsset.getValues();
1190 
1191         /* If this is a foreign currency asset */
1192         if (Boolean.TRUE.equals(myAsset.isForeignCurrency())) {
1193             /* Adjust foreign invested amount */
1194             final OceanusMoney myDelta = new OceanusMoney(myAmount);
1195             myDelta.negate();
1196             myValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, theHelper.getDebitExchangeRate());
1197             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myDelta);
1198 
1199             /* Switch to local amount */
1200             myAmount = theHelper.getLocalAmount();
1201         }
1202 
1203         /* Record the delta investment */
1204         final OceanusMoney myDelta = new OceanusMoney(myAmount);
1205         myDelta.negate();
1206         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myDelta);
1207 
1208         /* Assume the cost reduction is the full value */
1209         OceanusMoney myReduction = new OceanusMoney(myAmount);
1210         final OceanusMoney myCost = myValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST);
1211 
1212         /* If we are reducing units in the account */
1213         if (myDeltaUnits != null) {
1214             /* The reduction is the relevant fraction of the cost */
1215             final OceanusUnits myUnits = myValues.getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS);
1216             myReduction = myCost.valueAtWeight(myDeltaUnits, myUnits);
1217 
1218             /* Access units as negative value */
1219             myDeltaUnits = new OceanusUnits(myDeltaUnits);
1220             myDeltaUnits.negate();
1221 
1222             /* Record delta to units */
1223             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myDeltaUnits);
1224         }
1225 
1226         /* If the reduction is greater than the total cost */
1227         if (myReduction.compareTo(myCost) > 0) {
1228             /* Reduction is the total cost */
1229             myReduction = new OceanusMoney(myCost);
1230         }
1231 
1232         /* Determine the delta to the cost */
1233         final OceanusMoney myDeltaCost = new OceanusMoney(myReduction);
1234         myDeltaCost.negate();
1235 
1236         /* If we have a delta to the cost */
1237         if (myDeltaCost.isNonZero()) {
1238             /* Adjust the cost */
1239             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myDeltaCost);
1240         }
1241 
1242         /* Determine the delta to the gains */
1243         final OceanusMoney myDeltaGains = new OceanusMoney(myAmount);
1244         myDeltaGains.addAmount(myDeltaCost);
1245 
1246         /* If we have a delta to the gains */
1247         if (myDeltaGains.isNonZero()) {
1248             /* Adjust the gains */
1249             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.REALISEDGAINS, myDeltaGains);
1250         }
1251 
1252         /* Register the event */
1253         myAsset.registerTransaction(theHelper);
1254 
1255         /* True debit account is the parent */
1256         final MoneyWiseAssetBase myParent = myDebit.getParent();
1257 
1258         /* Adjust the debit account bucket */
1259         final MoneyWiseAnalysisPayeeBucket myPayee = thePayeeBuckets.getBucket(myParent);
1260         myPayee.adjustForTaxCredit(theHelper);
1261 
1262         /* Adjust the credit account bucket */
1263         final MoneyWiseAnalysisAccountBucket<?> myBucket = getAccountBucket(pCredit);
1264         myBucket.adjustForCredit(theHelper);
1265 
1266         /* Adjust the chargeableGains category bucket */
1267         theCategoryBuckets.adjustChargeableGain(theHelper, myReduction);
1268 
1269         /* Adjust the TaxMan account for the tax credit */
1270         theTaxMan.adjustForTaxPayments(theHelper);
1271 
1272         /* Add the chargeable event */
1273         theTaxBasisBuckets.recordChargeableGain(theHelper.getTransaction(), myDeltaGains);
1274     }
1275 
1276     /**
1277      * Process a transaction that is Stock DeMerger.
1278      * <p>
1279      * This capital event relates to both the Credit and Debit accounts
1280      *
1281      * @param pDebit  the debit account
1282      * @param pCredit the credit account
1283      */
1284     private void processStockDeMerger(final MoneyWiseSecurityHolding pDebit,
1285                                       final MoneyWiseSecurityHolding pCredit) {
1286         /* Access the Debit Asset Security Bucket */
1287         MoneyWiseAnalysisSecurityBucket myAsset = thePortfolioBuckets.getBucket(pDebit);
1288         MoneyWiseAnalysisSecurityValues myValues = myAsset.getValues();
1289         final OceanusRatio myDebitRate = theHelper.getDebitExchangeRate();
1290 
1291         /* Obtain current cost */
1292         final OceanusMoney myCost = myValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST);
1293         final OceanusRatio myDilution = theHelper.getDilution();
1294         final OceanusUnits myDeltaUnits = theHelper.getAccountDeltaUnits();
1295 
1296         /* If we reduced the units */
1297         if (myDeltaUnits != null) {
1298             /* Record the delta units */
1299             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myDeltaUnits);
1300         }
1301 
1302         /* Calculate the cost dilution */
1303         final OceanusMoney myNewCost = myCost.getDilutedMoney(myDilution);
1304         final OceanusRatio myCostDilution = new OceanusRatio(myNewCost, myCost);
1305 
1306         /* Calculate the delta to the cost */
1307         OceanusMoney myDeltaCost = new OceanusMoney(myNewCost);
1308         myDeltaCost.subtractAmount(myCost);
1309 
1310         /* Record the delta cost/investment */
1311         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myDeltaCost);
1312         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myDeltaCost);
1313         final boolean isForeignDebit = myAsset.isForeignCurrency();
1314         if (isForeignDebit) {
1315             final OceanusMoney myInvested = myDeltaCost.convertCurrency(myAsset.getCurrency().getCurrency(), myDebitRate);
1316             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myInvested);
1317         }
1318 
1319         /* Register the event */
1320         myValues = myAsset.registerTransaction(theHelper);
1321 
1322         /* Access the Credit Asset Account Bucket */
1323         myAsset = thePortfolioBuckets.getBucket(pCredit);
1324 
1325         /* The deltaCost is transferred to the credit account */
1326         myDeltaCost = new OceanusMoney(myDeltaCost);
1327         myDeltaCost.negate();
1328 
1329         /* Record details */
1330         myValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDCOST, myDeltaCost);
1331         myValues.setValue(MoneyWiseAnalysisSecurityAttr.COSTDILUTION, myCostDilution);
1332         if (isForeignDebit) {
1333             myValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myDebitRate);
1334         }
1335 
1336         /* Record the delta cost/investment */
1337         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myDeltaCost);
1338         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myDeltaCost);
1339         final boolean isForeignCredit = myAsset.isForeignCurrency();
1340         final OceanusRatio myCreditRate = theHelper.getCreditExchangeRate();
1341         if (isForeignCredit) {
1342             final OceanusMoney myInvested = myDeltaCost.convertCurrency(myAsset.getCurrency().getCurrency(), myCreditRate);
1343             myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myInvested);
1344         }
1345 
1346         /* Get the appropriate prices/rates for the stock */
1347         final OceanusDate myDate = theHelper.getDate();
1348         final OceanusPrice myCreditPrice = thePriceMap.getPriceForDate(myAsset.getSecurity(), myDate);
1349         final Currency myCurrency = theAnalysis.getCurrency().getCurrency();
1350 
1351         /* Determine value of the stock being deMerged */
1352         final OceanusUnits myCreditUnits = theHelper.getPartnerDeltaUnits();
1353         OceanusMoney myCreditXferValue = myCreditPrice.unitsAtPrice(myCreditUnits);
1354         if (isForeignCredit) {
1355             myCreditXferValue = myCreditXferValue.convertCurrency(myCurrency, myCreditRate);
1356         }
1357 
1358         /* Record the current/delta units */
1359         myAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myCreditUnits);
1360 
1361         /* Register the transaction */
1362         myValues = myAsset.registerTransaction(theHelper);
1363 
1364         /* Record values */
1365         myValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDCOST, myDeltaCost);
1366         myValues.setValue(MoneyWiseAnalysisSecurityAttr.PRICE, myCreditPrice);
1367         myValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDVALUE, myCreditXferValue);
1368         if (isForeignCredit) {
1369             myValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myCreditRate);
1370         }
1371 
1372         /* StockDeMerger is a transfer, so no need to update the categories */
1373     }
1374 
1375     /**
1376      * Process a transaction that is StockTakeover.
1377      * <p>
1378      * This can be accomplished using a cash portion (to a ThirdParty account) and these workings
1379      * are split out.
1380      *
1381      * @param pDebit  the debit holding
1382      * @param pCredit the credit holding
1383      */
1384     private void processStockTakeover(final MoneyWiseSecurityHolding pDebit,
1385                                       final MoneyWiseSecurityHolding pCredit) {
1386         final OceanusMoney myAmount = theHelper.getReturnedCash();
1387         final MoneyWiseTransAsset myReturnedCashAct = theHelper.getReturnedCashAccount();
1388 
1389         /* If we have a returned cash part of the transaction */
1390         if (myReturnedCashAct != null
1391                 && myAmount.isNonZero()) {
1392             /* Process a Stock And Cash TakeOver */
1393             processStockAndCashTakeOver(pDebit, pCredit);
1394         } else {
1395             /* Process a StockOnly TakeOver */
1396             processStockOnlyTakeOver(pDebit, pCredit);
1397         }
1398     }
1399 
1400     /**
1401      * Process a transaction that is a StockOnlyTakeover.
1402      * <p>
1403      * This capital event relates to both the Credit and Debit accounts
1404      *
1405      * @param pDebit  the debit holding
1406      * @param pCredit the credit holding
1407      */
1408     private void processStockOnlyTakeOver(final MoneyWiseSecurityHolding pDebit,
1409                                           final MoneyWiseSecurityHolding pCredit) {
1410         /* Access details */
1411         final MoneyWiseSecurity myCredit = pCredit.getSecurity();
1412         final MoneyWiseSecurity myDebit = pDebit.getSecurity();
1413 
1414         /* Access the Asset Security Buckets */
1415         final MoneyWiseAnalysisSecurityBucket myDebitAsset = thePortfolioBuckets.getBucket(pDebit);
1416         MoneyWiseAnalysisSecurityValues myDebitValues = myDebitAsset.getValues();
1417         final MoneyWiseAnalysisSecurityBucket myCreditAsset = thePortfolioBuckets.getBucket(pCredit);
1418         final OceanusDate myDate = theHelper.getDate();
1419 
1420         /* Get the appropriate prices/rates for the stock */
1421         final OceanusPrice myCreditPrice = thePriceMap.getPriceForDate(myCredit, myDate);
1422         final OceanusPrice myDebitPrice = thePriceMap.getPriceForDate(myDebit, myDate);
1423         final OceanusRatio myDebitRate = theHelper.getDebitExchangeRate();
1424         final OceanusRatio myCreditRate = theHelper.getCreditExchangeRate();
1425         final Currency myCurrency = theAnalysis.getCurrency().getCurrency();
1426 
1427         /* Determine value of the stock in both parts of the takeOver */
1428         OceanusUnits myCreditUnits = theHelper.getPartnerDeltaUnits();
1429         OceanusMoney myCreditXferValue = myCreditPrice.unitsAtPrice(myCreditUnits);
1430         OceanusUnits myDebitUnits = myDebitValues.getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS);
1431         OceanusMoney myDebitValue = myDebitPrice.unitsAtPrice(myDebitUnits);
1432         OceanusMoney myInvested = myDebitValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.INVESTED);
1433 
1434         /* Handle foreign debit */
1435         final boolean isForeignDebit = myDebitAsset.isForeignCurrency();
1436         if (isForeignDebit) {
1437             myDebitValue = myDebitValue.convertCurrency(myCurrency, myDebitRate);
1438         }
1439 
1440         /* Handle foreign credit */
1441         final boolean isForeignCredit = myCreditAsset.isForeignCurrency();
1442         if (isForeignCredit) {
1443             myCreditXferValue = myCreditXferValue.convertCurrency(myCurrency, myCreditRate);
1444         }
1445 
1446         /* Determine the residual cost of the old stock */
1447         final OceanusMoney myDebitCost = myDebitValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST);
1448 
1449         /* Allocate current profit between the two stocks */
1450         OceanusMoney myProfit = new OceanusMoney(myDebitValue);
1451         myProfit.subtractAmount(myDebitCost);
1452         myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.GROWTHADJUST, myProfit);
1453         myProfit = new OceanusMoney(myProfit);
1454         myProfit.negate();
1455         myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.GROWTHADJUST, myProfit);
1456 
1457         /* Adjust cost/units/invested of the credit account */
1458         myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myDebitCost);
1459         myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myCreditUnits);
1460         myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myInvested);
1461         if (isForeignCredit) {
1462             final OceanusMoney myForeign = myInvested.convertCurrency(myCreditAsset.getCurrency().getCurrency(), myCreditRate);
1463             myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myForeign);
1464         }
1465 
1466         /* Determine final value of the credit stock after the takeOver */
1467         myCreditUnits = myCreditAsset.getValues().getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS);
1468         OceanusMoney myCreditValue = myCreditPrice.unitsAtPrice(myCreditUnits);
1469         if (isForeignCredit) {
1470             myCreditValue = myCreditValue.convertCurrency(myCurrency, myCreditRate);
1471         }
1472 
1473         /* Register the transaction */
1474         final MoneyWiseAnalysisSecurityValues myCreditValues = myCreditAsset.registerTransaction(theHelper);
1475         myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.PRICE, myCreditPrice);
1476         myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDVALUE, myCreditXferValue);
1477         myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDCOST, myDebitCost);
1478         myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.VALUATION, myCreditValue);
1479         if (isForeignCredit) {
1480             myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myCreditRate);
1481         }
1482 
1483         /* Drive debit cost down to zero */
1484         final OceanusMoney myDeltaCost = new OceanusMoney(myDebitCost);
1485         myDeltaCost.negate();
1486         myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myDeltaCost);
1487         myDeltaCost.negate();
1488 
1489         /* Drive debit units down to zero */
1490         myDebitUnits = new OceanusUnits(myDebitUnits);
1491         myDebitUnits.negate();
1492         myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myDebitUnits);
1493 
1494         /* Adjust debit Invested amount */
1495         myInvested = new OceanusMoney(myInvested);
1496         myInvested.negate();
1497         myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myInvested);
1498         if (isForeignDebit) {
1499             myInvested = new OceanusMoney(myDebitValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED));
1500             myInvested.negate();
1501             myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myInvested);
1502         }
1503 
1504         /* Register the transaction */
1505         myDebitValues = myDebitAsset.registerTransaction(theHelper);
1506         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.PRICE, myDebitPrice);
1507         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.VALUATION, myDebitValue);
1508         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDVALUE, myCreditXferValue);
1509         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDCOST, myDeltaCost);
1510         if (isForeignDebit) {
1511             myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myDebitRate);
1512         }
1513     }
1514 
1515     /**
1516      * Process a transaction that is StockAndCashTakeover.
1517      * <p>
1518      * This capital event relates to both the Credit and Debit accounts. In particular it makes
1519      * reference to the CashTakeOver aspect of the debit account
1520      *
1521      * @param pDebit  the debit holding
1522      * @param pCredit the credit holding
1523      */
1524     private void processStockAndCashTakeOver(final MoneyWiseSecurityHolding pDebit,
1525                                              final MoneyWiseSecurityHolding pCredit) {
1526         /* Access details */
1527         final MoneyWiseSecurity myDebit = pDebit.getSecurity();
1528         final MoneyWiseSecurity myCredit = pCredit.getSecurity();
1529         final OceanusDate myDate = theHelper.getDate();
1530         final MoneyWiseTransAsset myReturnedCashAccount = theHelper.getReturnedCashAccount();
1531         final OceanusMoney myAmount = theHelper.getLocalReturnedCash();
1532 
1533         /* Access the Asset Security Buckets */
1534         final MoneyWiseAnalysisSecurityBucket myDebitAsset = thePortfolioBuckets.getBucket(pDebit);
1535         MoneyWiseAnalysisSecurityValues myDebitValues = myDebitAsset.getValues();
1536         final MoneyWiseAnalysisSecurityBucket myCreditAsset = thePortfolioBuckets.getBucket(pCredit);
1537 
1538         /* Get the appropriate prices for the assets */
1539         final OceanusPrice myDebitPrice = thePriceMap.getPriceForDate(myDebit, myDate);
1540         final OceanusPrice myCreditPrice = thePriceMap.getPriceForDate(myCredit, myDate);
1541         final OceanusRatio myDebitRate = theHelper.getDebitExchangeRate();
1542         final OceanusRatio myCreditRate = theHelper.getCreditExchangeRate();
1543         final Currency myCurrency = theAnalysis.getCurrency().getCurrency();
1544 
1545         /* Determine value of the base stock */
1546         OceanusUnits myDebitUnits = myDebitValues.getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS);
1547         OceanusMoney myDebitValue = myDebitPrice.unitsAtPrice(myDebitUnits);
1548 
1549         /* Determine value of the stock part of the takeOver */
1550         OceanusUnits myCreditUnits = theHelper.getPartnerDeltaUnits();
1551         OceanusMoney myCreditXferValue = myCreditPrice.unitsAtPrice(myCreditUnits);
1552 
1553         /* Handle foreign debit */
1554         final boolean isForeignDebit = myDebitAsset.isForeignCurrency();
1555         if (isForeignDebit) {
1556             myDebitValue = myDebitValue.convertCurrency(myCurrency, myDebitRate);
1557         }
1558 
1559         /* Handle foreign credit */
1560         final boolean isForeignCredit = myCreditAsset.isForeignCurrency();
1561         if (isForeignCredit) {
1562             myCreditXferValue = myCreditXferValue.convertCurrency(myCurrency, myCreditRate);
1563         }
1564 
1565         /* Calculate the total consideration */
1566         final OceanusMoney myConsideration = new OceanusMoney(myAmount);
1567         myConsideration.addAmount(myCreditXferValue);
1568 
1569         /* Access the current debit cost */
1570         final OceanusMoney myCost = myDebitValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST);
1571         OceanusRatio myCostDilution = null;
1572         final OceanusMoney myCostXfer;
1573         final OceanusMoney myAllowedCost;
1574 
1575         /* Determine condition as to whether this is a large cash transaction */
1576         final OceanusMoney myPortion = myDebitValue.valueAtRate(LIMIT_RATE);
1577         final boolean isLargeCash = (myAmount.compareTo(LIMIT_VALUE) > 0)
1578                 && (myAmount.compareTo(myPortion) > 0);
1579 
1580         /* If this is a large cash takeOver */
1581         if (isLargeCash) {
1582             /* Determine the transferable cost */
1583             myCostXfer = myCost.valueAtWeight(myCreditXferValue, myConsideration);
1584 
1585             /* Determine the cost dilution */
1586             myCostDilution = new OceanusRatio(myAmount, myConsideration);
1587 
1588             /* Determine the allowed cost */
1589             myAllowedCost = new OceanusMoney(myCost);
1590             myAllowedCost.subtractAmount(myCostXfer);
1591 
1592             /* else this is viewed as small and is taken out of the cost */
1593         } else {
1594             /* Set the allowed cost to be the least of the cost or the returned cash */
1595             myAllowedCost = myAmount.compareTo(myCost) > 0
1596                     ? new OceanusMoney(myCost)
1597                     : new OceanusMoney(myAmount);
1598 
1599             /* Transferred cost is cost minus the allowed cost */
1600             myCostXfer = new OceanusMoney(myCost);
1601             myCostXfer.subtractAmount(myAllowedCost);
1602         }
1603 
1604         /* Determine the capital gain */
1605         final OceanusMoney myCapitalGain = new OceanusMoney(myAmount);
1606         myCapitalGain.subtractAmount(myAllowedCost);
1607         if (myCapitalGain.isNonZero()) {
1608             /* Record the delta gains */
1609             myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.REALISEDGAINS, myCapitalGain);
1610 
1611             /* Adjust the capitalGains category bucket */
1612             theCategoryBuckets.adjustStandardGain(theHelper, pDebit, myCapitalGain);
1613         }
1614 
1615         /* Allocate current profit between the two stocks */
1616         OceanusMoney myProfit = new OceanusMoney(myCreditXferValue);
1617         myProfit.subtractAmount(myCostXfer);
1618         myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.GROWTHADJUST, myProfit);
1619         myProfit = new OceanusMoney(myProfit);
1620         myProfit.negate();
1621         myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.GROWTHADJUST, myProfit);
1622 
1623         /* Adjust cost/units/invested of the credit account */
1624         myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myCostXfer);
1625         myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myCreditUnits);
1626         myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myCostXfer);
1627         if (isForeignCredit) {
1628             final OceanusMoney myForeign = myCostXfer.convertCurrency(myCreditAsset.getCurrency().getCurrency(), myCreditRate);
1629             myCreditAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myForeign);
1630         }
1631 
1632         /* Determine final value of the credit stock after the takeOver */
1633         myCreditUnits = myCreditAsset.getValues().getUnitsValue(MoneyWiseAnalysisSecurityAttr.UNITS);
1634         OceanusMoney myCreditValue = myCreditPrice.unitsAtPrice(myCreditUnits);
1635         if (isForeignCredit) {
1636             myCreditValue = myCreditValue.convertCurrency(myCurrency, myCreditRate);
1637         }
1638 
1639         /* Register the transaction */
1640         final MoneyWiseAnalysisSecurityValues myCreditValues = myCreditAsset.registerTransaction(theHelper);
1641         myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.PRICE, myCreditPrice);
1642         myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDVALUE, myCreditXferValue);
1643         myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDCOST, myCostXfer);
1644         myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.VALUATION, myCreditValue);
1645         if (isForeignCredit) {
1646             myCreditValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myCreditRate);
1647         }
1648 
1649         /* Drive debit cost down to zero */
1650         final OceanusMoney myDeltaCost = new OceanusMoney(myCost);
1651         myDeltaCost.negate();
1652         myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.RESIDUALCOST, myDeltaCost);
1653 
1654         /* Drive debit units down to zero */
1655         myDebitUnits = new OceanusUnits(myDebitUnits);
1656         myDebitUnits.negate();
1657         myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.UNITS, myDebitUnits);
1658 
1659         /* Adjust debit Invested amount */
1660         OceanusMoney myInvested = myDebitValues.getMoneyValue(MoneyWiseAnalysisSecurityAttr.INVESTED);
1661         myInvested = new OceanusMoney(myInvested);
1662         myInvested.setZero();
1663         myInvested.subtractAmount(myAmount);
1664         myInvested.subtractAmount(myCostXfer);
1665         myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.INVESTED, myInvested);
1666         if (isForeignDebit) {
1667             myInvested = myInvested.convertCurrency(myDebitAsset.getCurrency().getCurrency(), myDebitRate);
1668             myInvested.negate();
1669             myDebitAsset.adjustCounter(MoneyWiseAnalysisSecurityAttr.FOREIGNINVESTED, myInvested);
1670         }
1671 
1672         /* Register the transaction */
1673         myDebitValues = myDebitAsset.registerTransaction(theHelper);
1674         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.PRICE, myDebitPrice);
1675         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.VALUATION, myDebitValue);
1676         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.CONSIDERATION, myConsideration);
1677         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.RETURNEDCASH, myAmount);
1678         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDVALUE, myCreditXferValue);
1679         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.XFERREDCOST, myCostXfer);
1680         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.ALLOWEDCOST, myAllowedCost);
1681         if (myCostDilution != null) {
1682             myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.COSTDILUTION, myCostDilution);
1683         }
1684         if (myCapitalGain.isNonZero()) {
1685             myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.CAPITALGAIN, myCapitalGain);
1686         }
1687         if (isForeignDebit) {
1688             myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.EXCHANGERATE, myDebitRate);
1689         }
1690         myDebitValues.setValue(MoneyWiseAnalysisSecurityAttr.CASHTYPE, isLargeCash
1691                 ? MoneyWiseCashType.LARGECASH
1692                 : MoneyWiseCashType.SMALLCASH);
1693 
1694         /* Adjust the ThirdParty account bucket */
1695         final MoneyWiseAnalysisAccountBucket<?> myBucket = getAccountBucket((MoneyWiseAssetBase) myReturnedCashAccount);
1696         myBucket.adjustForReturnedCashCredit(theHelper);
1697     }
1698 
1699     /**
1700      * Obtain Account bucket for asset.
1701      *
1702      * @param pAsset the asset
1703      * @return the bucket
1704      */
1705     private MoneyWiseAnalysisAccountBucket<?> getAccountBucket(final MoneyWiseAssetBase pAsset) {
1706         return switch (pAsset.getAssetType()) {
1707             case DEPOSIT -> theDepositBuckets.getBucket((MoneyWiseDeposit) pAsset);
1708             case CASH -> theCashBuckets.getBucket((MoneyWiseCash) pAsset);
1709             case LOAN -> theLoanBuckets.getBucket((MoneyWiseLoan) pAsset);
1710             case PORTFOLIO -> thePortfolioBuckets.getCashBucket((MoneyWisePortfolio) pAsset);
1711             default -> throw new IllegalArgumentException();
1712         };
1713     }
1714 }