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.data.validate;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
21  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
22  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
23  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
24  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
25  import io.github.tonywasher.joceanus.metis.field.MetisFieldRequired;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetDirection;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetType;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
29  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
30  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDataValidator.MoneyWiseDataValidatorTrans;
31  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit;
32  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoan;
33  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee;
34  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
35  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity;
36  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding;
37  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
38  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransBase;
39  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
40  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransInfoSet;
41  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
42  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseDepositCategoryClass;
43  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWisePayeeClass;
44  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWisePortfolioClass;
45  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
46  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
47  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoClass;
48  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
49  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
50  
51  import java.util.Currency;
52  import java.util.Objects;
53  
54  /**
55   * Validator for transaction.
56   */
57  public class MoneyWiseValidateTransaction
58          implements MoneyWiseDataValidatorTrans<MoneyWiseTransaction> {
59      /**
60       * Are we using new validation?
61       */
62      private final boolean newValidation;
63  
64      /**
65       * The infoSet validator.
66       */
67      private final MoneyWiseValidateTransInfoSet theInfoSet;
68  
69      /**
70       * The defaults engine.
71       */
72      private final MoneyWiseValidateTransDefaults theDefaults;
73  
74      /**
75       * Set the editSet.
76       */
77      private PrometheusEditSet theEditSet;
78  
79      /**
80       * Constructor.
81       *
82       * @param pNewValidation true/false
83       */
84      MoneyWiseValidateTransaction(final boolean pNewValidation) {
85          newValidation = pNewValidation;
86          theInfoSet = new MoneyWiseValidateTransInfoSet(pNewValidation);
87          theDefaults = new MoneyWiseValidateTransDefaults(this);
88      }
89  
90      @Override
91      public void setEditSet(final PrometheusEditSet pEditSet) {
92          theEditSet = pEditSet;
93          theInfoSet.storeEditSet(pEditSet);
94      }
95  
96      /**
97       * Obtain the editSet.
98       *
99       * @return the editSet
100      */
101     PrometheusEditSet getEditSet() {
102         if (theEditSet == null) {
103             throw new IllegalStateException("editSet not set up");
104         }
105         return theEditSet;
106     }
107 
108     /**
109      * Obtain the transInfoSet validator.
110      *
111      * @return the validator
112      */
113     public MoneyWiseValidateTransInfoSet getInfoSetValidator() {
114         return theInfoSet;
115     }
116 
117     /**
118      * Should we perform new validity checks?
119      *
120      * @return true/false
121      */
122     public boolean newValidation() {
123         return newValidation;
124     }
125 
126     @Override
127     public void validate(final PrometheusDataItem pTrans) {
128         final MoneyWiseTransaction myTrans = (MoneyWiseTransaction) pTrans;
129         final OceanusDate myDate = myTrans.getDate();
130         final MoneyWiseTransAsset myAccount = myTrans.getAccount();
131         final MoneyWiseTransAsset myPartner = myTrans.getPartner();
132         final MoneyWiseTransCategory myCategory = myTrans.getCategory();
133         final MoneyWiseAssetDirection myDir = myTrans.getDirection();
134         final OceanusMoney myAmount = myTrans.getAmount();
135         final OceanusUnits myAccountUnits = myTrans.getAccountDeltaUnits();
136         final OceanusUnits myPartnerUnits = myTrans.getPartnerDeltaUnits();
137         boolean doCheckCombo = true;
138 
139         /* Header is always valid */
140         if (pTrans.isHeader()) {
141             pTrans.setValidEdit();
142             return;
143         }
144 
145         /* Determine date range to check for */
146         final OceanusDateRange myRange = myTrans.getDataSet().getDateRange();
147 
148         /* The date must be non-null */
149         if (myDate == null) {
150             pTrans.addError(PrometheusDataItem.ERROR_MISSING, MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE);
151             /* The date must be in-range */
152         } else if (myRange.compareToDate(myDate) != 0) {
153             pTrans.addError(PrometheusDataItem.ERROR_RANGE, MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE);
154         }
155 
156         /* Account must be non-null */
157         if (myAccount == null) {
158             pTrans.addError(PrometheusDataItem.ERROR_MISSING, MoneyWiseBasicResource.TRANSACTION_ACCOUNT);
159             doCheckCombo = false;
160 
161         } else {
162             /* Account must be valid */
163             if (!isValidAccount(myAccount)) {
164                 pTrans.addError(MoneyWiseTransBase.ERROR_COMBO, MoneyWiseBasicResource.TRANSACTION_ACCOUNT);
165                 doCheckCombo = false;
166             }
167         }
168 
169         /* Category must be non-null */
170         if (myCategory == null) {
171             pTrans.addError(PrometheusDataItem.ERROR_MISSING, MoneyWiseBasicDataType.TRANSCATEGORY);
172             doCheckCombo = false;
173 
174             /* Category must be valid for Account */
175         } else if (doCheckCombo
176                 && !isValidCategory(myAccount, myCategory)) {
177             pTrans.addError(MoneyWiseTransBase.ERROR_COMBO, MoneyWiseBasicDataType.TRANSCATEGORY);
178             doCheckCombo = false;
179         }
180 
181         /* Direction must be non-null */
182         if (myDir == null) {
183             pTrans.addError(PrometheusDataItem.ERROR_MISSING, MoneyWiseBasicResource.TRANSACTION_DIRECTION);
184             doCheckCombo = false;
185 
186             /* Direction must be valid for Account */
187         } else if (doCheckCombo
188                 && !isValidDirection(myAccount, myCategory, myDir)) {
189             pTrans.addError(MoneyWiseTransBase.ERROR_COMBO, MoneyWiseBasicResource.TRANSACTION_DIRECTION);
190             doCheckCombo = false;
191         }
192 
193         /* Partner must be non-null */
194         if (myPartner == null) {
195             pTrans.addError(PrometheusDataItem.ERROR_MISSING, MoneyWiseBasicResource.TRANSACTION_PARTNER);
196 
197         } else {
198             /* Partner must be valid for Account */
199             if (doCheckCombo
200                     && !isValidPartner(myAccount, myCategory, myPartner)) {
201                 pTrans.addError(MoneyWiseTransBase.ERROR_COMBO, MoneyWiseBasicResource.TRANSACTION_PARTNER);
202             }
203         }
204 
205         /* If money is null */
206         if (myAmount == null) {
207             /* Check that it must be null */
208             if (!needsNullAmount(myTrans)) {
209                 pTrans.addError(PrometheusDataItem.ERROR_MISSING, MoneyWiseBasicResource.TRANSACTION_AMOUNT);
210             }
211 
212             /* else non-null money */
213         } else {
214             /* Check that it must be null */
215             if (needsNullAmount(myTrans)) {
216                 pTrans.addError(PrometheusDataItem.ERROR_EXIST, MoneyWiseBasicResource.TRANSACTION_AMOUNT);
217             }
218 
219             /* Money must not be negative */
220             if (!myAmount.isPositive()) {
221                 pTrans.addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseBasicResource.TRANSACTION_AMOUNT);
222             }
223 
224             /* Check that amount is correct currency */
225             if (myAccount != null) {
226                 final Currency myCurrency = myAccount.getCurrency();
227                 if (!myAmount.getCurrency().equals(myCurrency)) {
228                     pTrans.addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseBasicResource.TRANSACTION_AMOUNT);
229                 }
230             }
231         }
232 
233         /* Cannot have PartnerUnits if securities are identical */
234         if (myAccountUnits != null
235                 && myPartnerUnits != null
236                 && MetisDataDifference.isEqual(myAccount, myPartner)) {
237             pTrans.addError(MoneyWiseTransaction.ERROR_CIRCULAR, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.PARTNERDELTAUNITS));
238         }
239 
240         /* If we have a category and an infoSet */
241         if (myCategory != null
242                 && myTrans.getInfoSet() != null) {
243             /* Validate the InfoSet */
244             theInfoSet.validate(myTrans.getInfoSet());
245         }
246 
247         /* Set validation flag */
248         if (!pTrans.hasErrors()) {
249             pTrans.setValidEdit();
250         }
251     }
252 
253     /**
254      * Determines whether an event needs a zero amount.
255      *
256      * @param pTrans the transaction
257      * @return true/false
258      */
259     public boolean needsNullAmount(final MoneyWiseTransaction pTrans) {
260         final MoneyWiseTransCategoryClass myClass = pTrans.getCategoryClass();
261         return myClass != null
262                 && myClass.needsNullAmount();
263     }
264 
265     @Override
266     public boolean isValidAccount(final MoneyWiseTransAsset pAccount) {
267         /* Validate securityHolding */
268         if (pAccount instanceof MoneyWiseSecurityHolding myHolding
269                 && !checkSecurityHolding(myHolding)) {
270             return false;
271         }
272 
273         /* Reject pensions portfolio */
274         if (pAccount instanceof MoneyWisePortfolio myPortfolio
275                 && myPortfolio.getCategoryClass().holdsPensions()) {
276             return false;
277         }
278 
279         /* Check type of account */
280         final MoneyWiseAssetType myType = pAccount.getAssetType();
281         return myType.isBaseAccount() && !pAccount.isHidden();
282     }
283 
284     @Override
285     public boolean isValidCategory(final MoneyWiseTransAsset pAccount,
286                                    final MoneyWiseTransCategory pCategory) {
287         /* Access details */
288         final MoneyWiseAssetType myType = pAccount.getAssetType();
289         final MoneyWiseTransCategoryClass myCatClass = Objects.requireNonNull(pCategory.getCategoryTypeClass());
290 
291         /* Immediately reject hidden categories */
292         if (myCatClass.isHiddenType()) {
293             return false;
294         }
295 
296         /* Switch on the CategoryClass */
297         switch (myCatClass) {
298             case TAXEDINCOME:
299             case GROSSINCOME:
300             case RECOVEREDEXPENSES:
301             case OTHERINCOME:
302                 /* Taxed/Other income must be to deposit/cash/loan */
303                 return myType.isValued();
304 
305             case PENSIONCONTRIB:
306                 /* Pension contribution must be to a Pension holding or to a SIPP */
307                 return (pAccount instanceof MoneyWiseSecurityHolding myHolding
308                         && myHolding.getSecurity().getCategoryClass().isPension())
309                         || (pAccount instanceof MoneyWisePortfolio myPortfolio
310                         && myPortfolio.isPortfolioClass(MoneyWisePortfolioClass.SIPP));
311 
312             case GIFTEDINCOME:
313             case INHERITED:
314                 /* Inheritance/Gifted must be to asset */
315                 return myType.isAsset();
316 
317             case INTEREST:
318                 /* Account must be deposit or portfolio */
319                 return myType.isDeposit() || myType.isPortfolio();
320 
321             case DIVIDEND:
322             case SECURITYCLOSURE:
323                 /* Account must be SecurityHolding */
324                 return myType.isSecurityHolding();
325 
326             case BADDEBTCAPITAL:
327             case BADDEBTINTEREST:
328                 /* Account must be peer2Peer */
329                 return pAccount instanceof MoneyWiseDeposit myDeposit
330                         && myDeposit.isDepositClass(MoneyWiseDepositCategoryClass.PEER2PEER);
331 
332             case CASHBACK:
333                 return checkCashBack(pAccount);
334 
335             case LOYALTYBONUS:
336                 return checkLoyaltyBonus(pAccount);
337 
338             case RENTALINCOME:
339             case RENTALEXPENSE:
340             case ROOMRENTALINCOME:
341                 /* Account must be property */
342                 return pAccount instanceof MoneyWiseSecurityHolding myHolding
343                         && myHolding.getSecurity().isSecurityClass(MoneyWiseSecurityClass.PROPERTY);
344 
345             case UNITSADJUST:
346             case SECURITYREPLACE:
347                 /* Account must be capital */
348                 return pAccount.isCapital();
349 
350             case STOCKSPLIT:
351             case STOCKTAKEOVER:
352             case STOCKDEMERGER:
353             case STOCKRIGHTSISSUE:
354                 /* Account must be shares */
355                 return pAccount.isShares();
356 
357             case WRITEOFF:
358             case LOANINTERESTEARNED:
359             case LOANINTERESTCHARGED:
360             case TAXRELIEF:
361                 return myType.isLoan();
362 
363             case LOCALTAXES:
364             case INCOMETAX:
365                 return myType.isValued();
366 
367             case EXPENSE:
368                 return myType.isValued() || myType.isAutoExpense();
369 
370             case PORTFOLIOXFER:
371                 return pAccount instanceof MoneyWiseSecurityHolding
372                         || pAccount instanceof MoneyWisePortfolio;
373 
374             case TRANSFER:
375                 return true;
376 
377             /* Reject other categories */
378             default:
379                 return false;
380         }
381     }
382 
383     @Override
384     public boolean isValidDirection(final MoneyWiseTransAsset pAccount,
385                                     final MoneyWiseTransCategory pCategory,
386                                     final MoneyWiseAssetDirection pDirection) {
387         /* TODO relax some of these rules */
388 
389         /* Access details */
390         final MoneyWiseTransCategoryClass myCatClass = pCategory.getCategoryTypeClass();
391 
392         /* Switch on the CategoryClass */
393         switch (myCatClass) {
394             case TAXEDINCOME:
395             case GROSSINCOME:
396                 /* Cannot refund Taxed Income yet */
397                 return newValidation || pDirection.isFrom();
398 
399             case PENSIONCONTRIB:
400                 /* Cannot refund Pension Contribution */
401                 return pDirection.isFrom();
402 
403             case GIFTEDINCOME:
404             case INHERITED:
405                 /* Cannot refund Gifted/Inherited Income yet */
406                 return newValidation || pDirection.isFrom();
407 
408             case RENTALINCOME:
409             case ROOMRENTALINCOME:
410                 /* Cannot refund Rental Income */
411                 return pDirection.isTo();
412 
413             case RENTALEXPENSE:
414                 /* Cannot refund Rental Expense */
415                 return pDirection.isFrom();
416 
417             case INTEREST:
418                 /* Cannot refund Interest yet */
419                 return newValidation || pDirection.isTo();
420 
421             case DIVIDEND:
422             case SECURITYCLOSURE:
423                 /* Cannot refund Dividend yet */
424                 return pDirection.isTo();
425 
426             case LOYALTYBONUS:
427                 /* Cannot refund loyaltyBonus yet */
428                 return newValidation || pDirection.isTo();
429 
430             case WRITEOFF:
431             case LOANINTERESTCHARGED:
432                 /* All need to be TO */
433                 return newValidation || pDirection.isTo();
434 
435             case LOANINTERESTEARNED:
436                 /* All need to be FROM */
437                 return newValidation || pDirection.isFrom();
438 
439             case UNITSADJUST:
440             case STOCKSPLIT:
441             case STOCKDEMERGER:
442             case STOCKTAKEOVER:
443             case SECURITYREPLACE:
444             case PORTFOLIOXFER:
445                 /* All need to be To */
446                 return pDirection.isTo();
447 
448             default:
449                 return true;
450         }
451     }
452 
453     @Override
454     public boolean isValidPartner(final MoneyWiseTransAsset pAccount,
455                                   final MoneyWiseTransCategory pCategory,
456                                   final MoneyWiseTransAsset pPartner) {
457         /* Access details */
458         final boolean isRecursive = MetisDataDifference.isEqual(pAccount, pPartner);
459         final MoneyWiseAssetType myPartnerType = pPartner.getAssetType();
460         final MoneyWiseTransCategoryClass myCatClass = Objects.requireNonNull(pCategory.getCategoryTypeClass());
461 
462         /* Immediately reject hidden partners */
463         if (pPartner.isHidden()) {
464             return false;
465         }
466 
467         /* Validate securityHolding */
468         if (pPartner instanceof MoneyWiseSecurityHolding myHolding
469                 && !checkSecurityHolding(myHolding)) {
470             return false;
471         }
472 
473         /* Reject pensions portfolio */
474         if (pPartner instanceof MoneyWisePortfolio myPortfolio
475                 && myPortfolio.getCategoryClass().holdsPensions()) {
476             return false;
477         }
478 
479         /* If this involves auto-expense */
480         if (pAccount.isAutoExpense()
481                 || pPartner.isAutoExpense()) {
482             /* Access account type */
483             final MoneyWiseAssetType myAccountType = pAccount.getAssetType();
484 
485             /* Special processing */
486             switch (myCatClass) {
487                 case TRANSFER:
488                     /* Transfer must be to/from deposit/cash/loan */
489                     return myPartnerType.isAutoExpense()
490                             ? myAccountType.isValued()
491                             : myPartnerType.isValued();
492 
493                 case EXPENSE:
494                     /* Transfer must be to/from payee */
495                     return pPartner instanceof MoneyWisePayee;
496 
497                 /* Auto Expense cannot be used for other categories */
498                 default:
499                     return false;
500             }
501         }
502 
503         /* Switch on the CategoryClass */
504         switch (myCatClass) {
505             case TAXEDINCOME:
506             case GROSSINCOME:
507                 /* Taxed Income must have a Payee that can provide income */
508                 return pPartner instanceof MoneyWisePayee myPayee
509                         && myPayee.getCategoryClass().canProvideTaxedIncome();
510 
511             case PENSIONCONTRIB:
512                 /* Pension Contribution must be a payee that can parent */
513                 return pPartner instanceof MoneyWisePayee myPayee
514                         && myPayee.getCategoryClass().canContribPension();
515 
516             case OTHERINCOME:
517             case RECOVEREDEXPENSES:
518                 /* Other Income must have a Payee partner */
519                 return pPartner instanceof MoneyWisePayee;
520 
521             case LOCALTAXES:
522                 /* LocalTaxes must have a Government Payee partner */
523                 return pPartner instanceof MoneyWisePayee myPayee
524                         && myPayee.isPayeeClass(MoneyWisePayeeClass.GOVERNMENT);
525 
526             case GIFTEDINCOME:
527             case INHERITED:
528                 /* Gifted/Inherited Income must have an Individual Payee partner */
529                 return pPartner instanceof MoneyWisePayee myPayee
530                         && myPayee.isPayeeClass(MoneyWisePayeeClass.INDIVIDUAL);
531 
532             case RENTALINCOME:
533             case RENTALEXPENSE:
534             case ROOMRENTALINCOME:
535                 /* RentalIncome/Expense must have a loan partner */
536                 return myPartnerType.isLoan();
537 
538             case WRITEOFF:
539             case LOANINTERESTEARNED:
540             case LOANINTERESTCHARGED:
541                 /* WriteOff/LoanInterestEarned/Charged must be recursive */
542                 return isRecursive;
543 
544             case INTEREST:
545             case CASHBACK:
546                 /* Interest/CashBack is to a valued account */
547                 return myPartnerType.isValued();
548 
549             case DIVIDEND:
550                 return checkDividend(pAccount, pPartner);
551 
552             case LOYALTYBONUS:
553                 return checkLoyaltyBonus(pAccount, pPartner);
554 
555             case BADDEBTCAPITAL:
556             case BADDEBTINTEREST:
557                 return pPartner instanceof MoneyWisePayee
558                         && MetisDataDifference.isEqual(pPartner, pAccount.getParent());
559 
560             case UNITSADJUST:
561             case STOCKSPLIT:
562                 /* Must be recursive */
563                 return isRecursive;
564 
565             case SECURITYREPLACE:
566             case STOCKTAKEOVER:
567             case STOCKDEMERGER:
568                 return checkTakeOver(pAccount, pPartner);
569 
570             case STOCKRIGHTSISSUE:
571                 return checkStockRights(pAccount, pPartner);
572 
573             case TRANSFER:
574                 return checkTransfer(pAccount, pPartner);
575 
576             case SECURITYCLOSURE:
577                 return checkSecurityClosure(pAccount, pPartner);
578 
579             case EXPENSE:
580                 /* Expense must have a Payee partner */
581                 return pPartner instanceof MoneyWisePayee;
582 
583             case INCOMETAX:
584             case TAXRELIEF:
585                 return pPartner instanceof MoneyWisePayee myPayee
586                         && myPayee.isPayeeClass(MoneyWisePayeeClass.TAXMAN);
587 
588             case PORTFOLIOXFER:
589                 return checkPortfolioXfer(pAccount, pPartner);
590 
591             default:
592                 return false;
593         }
594     }
595 
596     /**
597      * Check securityHolding.
598      *
599      * @param pHolding the securityHolding
600      * @return valid true/false
601      */
602     private static boolean checkSecurityHolding(final MoneyWiseSecurityHolding pHolding) {
603         /* Access the components */
604         final MoneyWisePortfolio myPortfolio = pHolding.getPortfolio();
605         final MoneyWiseSecurity mySecurity = pHolding.getSecurity();
606 
607         /* If the portfolio can hold pensions */
608         if (myPortfolio.getCategoryClass().holdsPensions()) {
609             /* Can only hold pensions */
610             return mySecurity.getCategoryClass().isPension();
611         }
612 
613         /* cannot be a pension */
614         return !mySecurity.getCategoryClass().isPension();
615     }
616 
617     /**
618      * Check dividend.
619      *
620      * @param pAccount the holding providing the dividend.
621      * @param pPartner the partner
622      * @return valid true/false
623      */
624     private static boolean checkDividend(final MoneyWiseTransAsset pAccount,
625                                          final MoneyWiseTransAsset pPartner) {
626         /* Recursive is allowed */
627         if (MetisDataDifference.isEqual(pAccount, pPartner)) {
628             return true;
629         }
630 
631         /* partner must be valued */
632         return pPartner.getAssetType().isValued();
633     }
634 
635     /**
636      * Check TakeOver.
637      *
638      * @param pAccount the holding being acted on.
639      * @param pPartner the partner
640      * @return valid true/false
641      */
642     private static boolean checkTakeOver(final MoneyWiseTransAsset pAccount,
643                                          final MoneyWiseTransAsset pPartner) {
644         /* Must be holding <-> holding */
645         if (!(pAccount instanceof MoneyWiseSecurityHolding)
646                 || !(pPartner instanceof MoneyWiseSecurityHolding)) {
647             return false;
648         }
649 
650         /* Recursive is not allowed */
651         if (MetisDataDifference.isEqual(pAccount, pPartner)) {
652             return false;
653         }
654 
655         /* Access holdings */
656         final MoneyWiseSecurityHolding myAccount = (MoneyWiseSecurityHolding) pAccount;
657         final MoneyWiseSecurityHolding myPartner = (MoneyWiseSecurityHolding) pPartner;
658 
659         /* Portfolios must be the same */
660         if (!MetisDataDifference.isEqual(myAccount.getPortfolio(), myPartner.getPortfolio())) {
661             return false;
662         }
663 
664         /* Security types must be the same */
665         return MetisDataDifference.isEqual(myAccount.getSecurity().getCategory(), myPartner.getSecurity().getCategory());
666     }
667 
668     /**
669      * Check stock rights.
670      *
671      * @param pAccount the account being transferred.
672      * @param pPartner the partner
673      * @return valid true/false
674      */
675     private static boolean checkStockRights(final MoneyWiseTransAsset pAccount,
676                                             final MoneyWiseTransAsset pPartner) {
677         /* If this is security -> portfolio */
678         if (pAccount instanceof MoneyWiseSecurityHolding myHolding
679                 && pPartner instanceof MoneyWisePortfolio) {
680             /* Must be same portfolios */
681             return MetisDataDifference.isEqual(myHolding.getPortfolio(), pPartner);
682         }
683 
684         /* partner must be valued */
685         return pPartner.getAssetType().isValued();
686     }
687 
688     /**
689      * Check cashBack.
690      *
691      * @param pAccount the account providing cashBack.
692      * @return valid true/false
693      */
694     private static boolean checkCashBack(final MoneyWiseTransAsset pAccount) {
695         /* If this is deposit then check whether it can support cashBack */
696         if (pAccount instanceof MoneyWiseDeposit myDeposit) {
697             return myDeposit.getCategoryClass().canCashBack();
698         }
699 
700         /* If this is loan then check whether it can support cashBack */
701         if (pAccount instanceof MoneyWiseLoan myLoan) {
702             return myLoan.getCategoryClass().canCashBack();
703         }
704 
705         /* not allowed */
706         return false;
707     }
708 
709     /**
710      * Check loyalty bonus.
711      *
712      * @param pAccount the account providing bonus.
713      * @return valid true/false
714      */
715     private boolean checkLoyaltyBonus(final MoneyWiseTransAsset pAccount) {
716         /* If this is deposit then check whether it can support loyaltyBonus */
717         if (pAccount instanceof MoneyWiseDeposit myDeposit) {
718             return newValidation
719                     || myDeposit.getCategoryClass().canLoyaltyBonus();
720         }
721 
722         /* must be portfolio */
723         return pAccount instanceof MoneyWisePortfolio;
724     }
725 
726     /**
727      * Check loyalty bonus.
728      *
729      * @param pAccount the account providing bonus.
730      * @param pPartner the partner
731      * @return valid true/false
732      */
733     private static boolean checkLoyaltyBonus(final MoneyWiseTransAsset pAccount,
734                                              final MoneyWiseTransAsset pPartner) {
735         /* If this is portfolio -> security holding */
736         if (pAccount instanceof MoneyWisePortfolio
737                 && pPartner instanceof MoneyWiseSecurityHolding myHolding) {
738             /* Must be same portfolios */
739             return MetisDataDifference.isEqual(myHolding.getPortfolio(), pAccount);
740         }
741 
742         /* must be recursive */
743         return MetisDataDifference.isEqual(pAccount, pPartner);
744     }
745 
746     /**
747      * Check transfer.
748      *
749      * @param pAccount the account being transferred.
750      * @param pPartner the partner
751      * @return valid true/false
752      */
753     private static boolean checkTransfer(final MoneyWiseTransAsset pAccount,
754                                          final MoneyWiseTransAsset pPartner) {
755         /* Must not be recursive */
756         if (MetisDataDifference.isEqual(pAccount, pPartner)) {
757             return false;
758         }
759 
760         /* If this is security -> portfolio */
761         if (pAccount instanceof MoneyWiseSecurityHolding myHolding
762                 && pPartner instanceof MoneyWisePortfolio) {
763             /* Must be same portfolios */
764             if (!MetisDataDifference.isEqual(myHolding.getPortfolio(), pPartner)) {
765                 return false;
766             }
767         }
768 
769         /* If this is security <- portfolio */
770         if (pPartner instanceof MoneyWiseSecurityHolding myHolding
771                 && pAccount instanceof MoneyWisePortfolio) {
772             /* Must be same portfolios */
773             if (!MetisDataDifference.isEqual(myHolding.getPortfolio(), pAccount)) {
774                 return false;
775             }
776         }
777 
778         /* partner must be asset */
779         return pPartner.getAssetType().isAsset();
780     }
781 
782     /**
783      * Check securityClosure.
784      *
785      * @param pAccount the account being closed.
786      * @param pPartner the partner
787      * @return valid true/false
788      */
789     private static boolean checkSecurityClosure(final MoneyWiseTransAsset pAccount,
790                                                 final MoneyWiseTransAsset pPartner) {
791         /* Must not be recursive */
792         if (MetisDataDifference.isEqual(pAccount, pPartner)) {
793             return false;
794         }
795 
796         /* partner must be valued */
797         return pPartner.getAssetType().isValued();
798     }
799 
800     /**
801      * Check portfolioXfer.
802      *
803      * @param pAccount the account being transferred.
804      * @param pPartner the partner
805      * @return valid true/false
806      */
807     private static boolean checkPortfolioXfer(final MoneyWiseTransAsset pAccount,
808                                               final MoneyWiseTransAsset pPartner) {
809         /* Partner must be portfolio */
810         if (!(pPartner instanceof MoneyWisePortfolio)) {
811             return false;
812         }
813 
814         /* If account is portfolio */
815         if (pAccount instanceof MoneyWisePortfolio) {
816             /* Cannot be recursive */
817             if (MetisDataDifference.isEqual(pAccount, pPartner)) {
818                 return false;
819             }
820 
821             /* Must be same currency */
822             return MetisDataDifference.isEqual(pAccount.getAssetCurrency(), pPartner.getAssetCurrency());
823         }
824 
825         /* If account is security holding */
826         if (pAccount instanceof MoneyWiseSecurityHolding myHolding) {
827             /* Must be different portfolios */
828             return !MetisDataDifference.isEqual(myHolding.getPortfolio(), pPartner);
829         }
830 
831         /* Not allowed */
832         return false;
833     }
834 
835     /**
836      * Determine if an infoSet class is required.
837      *
838      * @param pTrans the transaction
839      * @param pClass the infoSet class
840      * @return the status
841      */
842     public MetisFieldRequired isClassRequired(final MoneyWiseTransaction pTrans,
843                                               final MoneyWiseTransInfoClass pClass) {
844         theInfoSet.storeInfoSet(pTrans.getInfoSet());
845         return theInfoSet.isClassRequired(pClass);
846     }
847 
848     @Override
849     public void autoCorrect(final MoneyWiseTransaction pItem) throws OceanusException {
850         theDefaults.autoCorrect(pItem);
851     }
852 
853     @Override
854     public MoneyWiseTransaction buildTransaction(final Object pKey) {
855         return theDefaults.buildTransaction(pKey);
856     }
857 
858     @Override
859     public void setRange(final OceanusDateRange pRange) {
860         theDefaults.setRange(pRange);
861     }
862 
863     @Override
864     public OceanusDateRange getRange() {
865         return theDefaults.getRange();
866     }
867 }