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.metis.data.MetisDataDifference;
20  import io.github.tonywasher.joceanus.metis.field.MetisFieldRequired;
21  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetDirection;
22  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
23  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDataSet;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit;
25  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit.MoneyWiseDepositList;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio.MoneyWisePortfolioList;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding;
29  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTax.MoneyWiseTaxCredit;
30  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
31  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransBase;
32  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
33  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransInfo;
34  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransInfoSet;
35  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
36  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
37  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
38  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
39  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoClass;
40  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
41  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
42  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
43  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
44  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
45  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataInfoClass;
46  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
47  import io.github.tonywasher.joceanus.prometheus.validate.PrometheusValidateInfoSet;
48  
49  import java.util.Currency;
50  import java.util.Iterator;
51  import java.util.Objects;
52  
53  /**
54   * Validate TransInfoSet.
55   */
56  public class MoneyWiseValidateTransInfoSet
57          extends PrometheusValidateInfoSet<MoneyWiseTransInfo> {
58      /**
59       * Are we using new validation?
60       */
61      private final boolean newValidation;
62  
63      /**
64       * Constructor.
65       *
66       * @param pNewValidation true/false
67       */
68      MoneyWiseValidateTransInfoSet(final boolean pNewValidation) {
69          newValidation = pNewValidation;
70      }
71  
72      @Override
73      public MoneyWiseTransaction getOwner() {
74          return (MoneyWiseTransaction) super.getOwner();
75      }
76  
77      @Override
78      public MetisFieldRequired isClassRequired(final PrometheusDataInfoClass pClass) {
79          /* Access details about the Transaction */
80          final MoneyWiseTransaction myTransaction = getOwner();
81          final MoneyWiseTransCategory myCategory = myTransaction.getCategory();
82  
83          /* If we have no Category, no class is allowed */
84          if (myCategory == null) {
85              return MetisFieldRequired.NOTALLOWED;
86          }
87          final MoneyWiseTransCategoryClass myClass = myCategory.getCategoryTypeClass();
88          if (myClass == null) {
89              return MetisFieldRequired.NOTALLOWED;
90          }
91  
92          /* Switch on class */
93          return switch ((MoneyWiseTransInfoClass) pClass) {
94              /* Reference and comments are always available */
95              case REFERENCE, COMMENTS, TRANSTAG -> MetisFieldRequired.CANEXIST;
96  
97              /* NatInsurance and benefit can only occur on salary/pensionContribution */
98              case EMPLOYERNATINS, EMPLOYEENATINS -> myClass.isNatInsurance()
99                      ? MetisFieldRequired.CANEXIST
100                     : MetisFieldRequired.NOTALLOWED;
101 
102             /* Benefit can only occur on salary */
103             case DEEMEDBENEFIT -> myClass == MoneyWiseTransCategoryClass.TAXEDINCOME
104                     ? MetisFieldRequired.CANEXIST
105                     : MetisFieldRequired.NOTALLOWED;
106 
107             /* Handle Withheld separately */
108             case WITHHELD -> isWithheldAmountRequired(myClass);
109 
110             /* Handle Tax Credit */
111             case TAXCREDIT -> isTaxCreditClassRequired(myClass);
112 
113             /* Handle AccountUnits */
114             case ACCOUNTDELTAUNITS -> isAccountUnitsDeltaRequired(myClass);
115 
116             /* Handle PartnerUnits */
117             case PARTNERDELTAUNITS -> isPartnerUnitsDeltaRequired(myClass);
118 
119             /* Handle Dilution separately */
120             case DILUTION -> isDilutionClassRequired(myClass);
121 
122             /* Qualify Years is needed only for Taxable Gain */
123             case QUALIFYYEARS -> isQualifyingYearsClassRequired(myClass);
124 
125             /* Handle ThirdParty separately */
126             case RETURNEDCASHACCOUNT -> isReturnedCashAccountRequired(myClass);
127             case RETURNEDCASH -> isReturnedCashRequired(myTransaction);
128             case PARTNERAMOUNT -> isPartnerAmountClassRequired(myClass);
129             case XCHANGERATE -> isXchangeRateClassRequired(myClass);
130             case PRICE -> isPriceClassRequired(myClass);
131             case COMMISSION -> isCommissionClassRequired(myClass);
132             default -> MetisFieldRequired.NOTALLOWED;
133         };
134     }
135 
136     /**
137      * Determine if an infoSet class is metaData.
138      *
139      * @param pClass the infoSet class
140      * @return the status
141      */
142     public boolean isMetaData(final MoneyWiseTransInfoClass pClass) {
143         /* Switch on class */
144         return switch (pClass) {
145             /* Can always change reference/comments/tags */
146             case REFERENCE, COMMENTS, TRANSTAG -> true;
147 
148             /* All others are locked */
149             default -> false;
150         };
151     }
152 
153     /**
154      * Determine if a TaxCredit infoSet class is required.
155      *
156      * @param pClass the category class
157      * @return the status
158      */
159     private MetisFieldRequired isTaxCreditClassRequired(final MoneyWiseTransCategoryClass pClass) {
160         final MoneyWiseTransaction myTrans = getOwner();
161         final MoneyWiseTaxCredit myYear = myTrans.getTaxYear();
162         final MoneyWiseTransAsset myAccount = myTrans.getAccount();
163 
164         /* Switch on class */
165         return switch (pClass) {
166             case TAXEDINCOME -> MetisFieldRequired.MUSTEXIST;
167             case LOANINTERESTCHARGED -> MetisFieldRequired.CANEXIST;
168             case LOYALTYBONUS, INTEREST -> myAccount.isTaxFree()
169                     || myAccount.isGross()
170                     || !myYear.isTaxCreditRequired()
171                     ? MetisFieldRequired.NOTALLOWED
172                     : MetisFieldRequired.MUSTEXIST;
173             case DIVIDEND -> !myAccount.isTaxFree()
174                     && (myYear.isTaxCreditRequired() || myAccount.isForeign())
175                     ? MetisFieldRequired.MUSTEXIST
176                     : MetisFieldRequired.NOTALLOWED;
177             case TRANSFER -> myAccount instanceof MoneyWiseSecurityHolding myHolding
178                     && myHolding.getSecurity().isSecurityClass(MoneyWiseSecurityClass.LIFEBOND)
179                     ? MetisFieldRequired.MUSTEXIST
180                     : MetisFieldRequired.NOTALLOWED;
181             default -> MetisFieldRequired.NOTALLOWED;
182         };
183     }
184 
185     /**
186      * Determine if a Withheld amount is required.
187      *
188      * @param pClass the category class
189      * @return the status
190      */
191     private static MetisFieldRequired isWithheldAmountRequired(final MoneyWiseTransCategoryClass pClass) {
192         /* Withheld is only available for salary and interest */
193         return switch (pClass) {
194             case TAXEDINCOME, INTEREST -> MetisFieldRequired.CANEXIST;
195             default -> MetisFieldRequired.NOTALLOWED;
196         };
197     }
198 
199     /**
200      * Determine if an AccountDeltaUnits infoSet class is required.
201      *
202      * @param pClass the category class
203      * @return the status
204      */
205     private MetisFieldRequired isAccountUnitsDeltaRequired(final MoneyWiseTransCategoryClass pClass) {
206         final MoneyWiseTransaction myTrans = getOwner();
207         final MoneyWiseTransAsset myAccount = myTrans.getAccount();
208         final MoneyWiseTransAsset myPartner = myTrans.getPartner();
209         final MoneyWiseAssetDirection myDir = myTrans.getDirection();
210 
211         /* Account must be security holding */
212         if (!(myAccount instanceof MoneyWiseSecurityHolding)) {
213             return MetisFieldRequired.NOTALLOWED;
214         }
215 
216         /* Account cannot be autoUnits */
217         final MoneyWiseSecurityHolding myHolding = (MoneyWiseSecurityHolding) myAccount;
218         if (myHolding.getSecurity().getCategoryClass().isAutoUnits()) {
219             return MetisFieldRequired.NOTALLOWED;
220         }
221 
222         /* Handle different transaction types */
223         return switch (pClass) {
224             case TRANSFER, STOCKDEMERGER -> MetisFieldRequired.CANEXIST;
225             case UNITSADJUST, STOCKSPLIT, INHERITED -> MetisFieldRequired.MUSTEXIST;
226             case DIVIDEND -> myAccount.equals(myPartner)
227                     ? MetisFieldRequired.CANEXIST
228                     : MetisFieldRequired.NOTALLOWED;
229             case STOCKRIGHTSISSUE -> myDir.isFrom()
230                     ? MetisFieldRequired.MUSTEXIST
231                     : MetisFieldRequired.NOTALLOWED;
232             default -> MetisFieldRequired.NOTALLOWED;
233         };
234     }
235 
236     /**
237      * Determine if an PartnerDeltaUnits infoSet class is required.
238      *
239      * @param pClass the category class
240      * @return the status
241      */
242     private MetisFieldRequired isPartnerUnitsDeltaRequired(final MoneyWiseTransCategoryClass pClass) {
243         final MoneyWiseTransaction myTrans = getOwner();
244         final MoneyWiseTransAsset myPartner = myTrans.getPartner();
245         final MoneyWiseAssetDirection myDir = myTrans.getDirection();
246 
247         /* Partner must be security holding */
248         if (!(myPartner instanceof MoneyWiseSecurityHolding)) {
249             return MetisFieldRequired.NOTALLOWED;
250         }
251 
252         /* Partner cannot be autoUnits */
253         final MoneyWiseSecurityHolding myHolding = (MoneyWiseSecurityHolding) myPartner;
254         if (myHolding.getSecurity().getCategoryClass().isAutoUnits()) {
255             return MetisFieldRequired.NOTALLOWED;
256         }
257 
258         /* Handle different transaction types */
259         return switch (pClass) {
260             case TRANSFER -> MetisFieldRequired.CANEXIST;
261             case STOCKDEMERGER, SECURITYREPLACE, STOCKTAKEOVER -> MetisFieldRequired.MUSTEXIST;
262             case STOCKRIGHTSISSUE -> myDir.isTo()
263                     ? MetisFieldRequired.MUSTEXIST
264                     : MetisFieldRequired.NOTALLOWED;
265             default -> MetisFieldRequired.NOTALLOWED;
266         };
267     }
268 
269     /**
270      * Determine if a Dilution infoSet class is required.
271      *
272      * @param pClass the category class
273      * @return the status
274      */
275     private static MetisFieldRequired isDilutionClassRequired(final MoneyWiseTransCategoryClass pClass) {
276         /* Dilution is only required for stock split/deMerger */
277         return switch (pClass) {
278             case STOCKSPLIT, UNITSADJUST -> MetisFieldRequired.CANEXIST;
279             case STOCKDEMERGER -> MetisFieldRequired.MUSTEXIST;
280             default -> MetisFieldRequired.NOTALLOWED;
281         };
282     }
283 
284     /**
285      * Determine if a ReturnedCash Account class is required.
286      *
287      * @param pClass the category class
288      * @return the status
289      */
290     private static MetisFieldRequired isReturnedCashAccountRequired(final MoneyWiseTransCategoryClass pClass) {
291         /* Returned Cash is possible only for StockTakeOver */
292         return pClass == MoneyWiseTransCategoryClass.STOCKTAKEOVER
293                 ? MetisFieldRequired.CANEXIST
294                 : MetisFieldRequired.NOTALLOWED;
295     }
296 
297     /**
298      * Determine if a ReturnedCash value is required.
299      *
300      * @param pTransaction the transaction
301      * @return the status
302      */
303     private static MetisFieldRequired isReturnedCashRequired(final MoneyWiseTransaction pTransaction) {
304         /* Returned Cash Amount is possible only if ReturnedCashAccount exists */
305         return pTransaction.getReturnedCashAccount() != null
306                 ? MetisFieldRequired.MUSTEXIST
307                 : MetisFieldRequired.NOTALLOWED;
308     }
309 
310     /**
311      * Determine if a PartnerAmount infoSet class is required.
312      *
313      * @param pCategory the category
314      * @return the status
315      */
316     private MetisFieldRequired isPartnerAmountClassRequired(final MoneyWiseTransCategoryClass pCategory) {
317         final MoneyWiseTransaction myTrans = getOwner();
318         final MoneyWiseTransAsset myAccount = myTrans.getAccount();
319         final MoneyWiseTransAsset myPartner = myTrans.getPartner();
320 
321         /* If the transaction requires null amount, then partner amount must also be null */
322         if (pCategory.needsNullAmount()) {
323             return MetisFieldRequired.NOTALLOWED;
324         }
325 
326         /* If Partner currency is null or the same as Account then Partner amount is not allowed */
327         final MoneyWiseCurrency myCurrency = myAccount.getAssetCurrency();
328         final MoneyWiseCurrency myPartnerCurrency = myPartner == null ? null : myPartner.getAssetCurrency();
329         if (myCurrency == null || myPartnerCurrency == null) {
330             return MetisFieldRequired.NOTALLOWED;
331         }
332         return MetisDataDifference.isEqual(myCurrency, myPartnerCurrency)
333                 ? MetisFieldRequired.NOTALLOWED
334                 : MetisFieldRequired.MUSTEXIST;
335     }
336 
337     /**
338      * Determine if an QualifyingYears infoSet class is required.
339      *
340      * @param pCategory the category
341      * @return the status
342      */
343     private MetisFieldRequired isQualifyingYearsClassRequired(final MoneyWiseTransCategoryClass pCategory) {
344         final MoneyWiseTransaction myTrans = getOwner();
345         final MoneyWiseTransAsset myAccount = myTrans.getAccount();
346 
347         return pCategory == MoneyWiseTransCategoryClass.TRANSFER
348                 && myAccount instanceof MoneyWiseSecurityHolding myHolding
349                 && myHolding.getSecurity().isSecurityClass(MoneyWiseSecurityClass.LIFEBOND)
350                 ? MetisFieldRequired.MUSTEXIST
351                 : MetisFieldRequired.NOTALLOWED;
352     }
353 
354     /**
355      * Determine if an XchangeRate infoSet class is required.
356      *
357      * @param pCategory the category
358      * @return the status
359      */
360     private MetisFieldRequired isXchangeRateClassRequired(final MoneyWiseTransCategoryClass pCategory) {
361         final MoneyWiseTransaction myTrans = getOwner();
362         final MoneyWiseDataSet myData = myTrans.getDataSet();
363         final MoneyWiseTransAsset myAccount = myTrans.getAccount();
364 
365         if (newValidation) {
366             return MetisFieldRequired.CANEXIST;
367         }
368         return pCategory.isDividend()
369                 && !myAccount.getAssetCurrency().equals(myData.getReportingCurrency())
370                 ? MetisFieldRequired.MUSTEXIST
371                 : MetisFieldRequired.NOTALLOWED;
372     }
373 
374     /**
375      * Determine if a price infoSet class is required.
376      *
377      * @param pCategory the category
378      * @return the status
379      */
380     private static MetisFieldRequired isPriceClassRequired(final MoneyWiseTransCategoryClass pCategory) {
381         /* Only allowed for stockSplit and UnitsAdjust */
382         return switch (pCategory) {
383             case STOCKSPLIT, UNITSADJUST -> MetisFieldRequired.CANEXIST;
384             default -> MetisFieldRequired.NOTALLOWED;
385         };
386     }
387 
388     /**
389      * Determine if a Commission infoSet class is required.
390      *
391      * @param pCategory the category
392      * @return the status
393      */
394     private static MetisFieldRequired isCommissionClassRequired(final MoneyWiseTransCategoryClass pCategory) {
395         /* Don't allow yet */
396         return MetisFieldRequired.NOTALLOWED;
397         /* Account or Partner must be security holding
398          if (!(pAccount instanceof SecurityHolding)
399          && !(pPartner instanceof SecurityHolding)) {
400          return MetisFieldRequired.NOTALLOWED;
401          }
402          switch (pCategory) {
403          case TRANSFER:
404          return MetisFieldRequired.CANEXIST;
405          case DIVIDEND:
406          return MetisDataDifference.isEqual(pAccount, pPartner)
407          ? MetisFieldRequired.CANEXIST
408          : MetisFieldRequired.NOTALLOWED;
409          default:
410          return MetisFieldRequired.NOTALLOWED;
411          } */
412     }
413 
414     @Override
415     public void validateClass(final MoneyWiseTransInfo pInfo,
416                               final PrometheusDataInfoClass pClass) {
417         /* Switch on class */
418         switch ((MoneyWiseTransInfoClass) pClass) {
419             case QUALIFYYEARS:
420                 validateQualifyYears(pInfo);
421                 break;
422             case TAXCREDIT:
423                 validateTaxCredit(pInfo);
424                 break;
425             case EMPLOYEENATINS, EMPLOYERNATINS, DEEMEDBENEFIT, WITHHELD:
426                 validateOptionalTaxCredit(pInfo);
427                 break;
428             case PARTNERAMOUNT:
429                 validatePartnerAmount(pInfo);
430                 break;
431             case RETURNEDCASHACCOUNT:
432                 validateReturnedCashAccount(pInfo);
433                 break;
434             case RETURNEDCASH:
435                 validateReturnedCash(pInfo);
436                 break;
437             case ACCOUNTDELTAUNITS, PARTNERDELTAUNITS:
438                 validateDeltaUnits(pInfo);
439                 break;
440             case REFERENCE, COMMENTS:
441                 validateInfoLength(pInfo);
442                 break;
443             case PRICE:
444                 validatePrice(pInfo);
445                 break;
446             case TRANSTAG, DILUTION:
447             default:
448                 break;
449         }
450     }
451 
452     /**
453      * Validate the qualifyingYears.
454      *
455      * @param pInfo the info
456      */
457     private void validateQualifyYears(final MoneyWiseTransInfo pInfo) {
458         final Integer myYears = pInfo.getValue(Integer.class);
459         if (myYears == 0) {
460             getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.QUALIFYYEARS));
461         } else if (myYears < 0) {
462             getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.QUALIFYYEARS));
463         }
464     }
465 
466     /**
467      * Validate the taxCredit.
468      *
469      * @param pInfo the info
470      */
471     private void validateTaxCredit(final MoneyWiseTransInfo pInfo) {
472         final OceanusMoney myAmount = pInfo.getValue(OceanusMoney.class);
473         final Currency myCurrency = getOwner().getAccount().getCurrency();
474         if (!myAmount.isPositive()) {
475             getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.TAXCREDIT));
476         } else if (!myAmount.getCurrency().equals(myCurrency)) {
477             getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.TAXCREDIT));
478         }
479     }
480 
481     /**
482      * Validate the optional taxCredits.
483      *
484      * @param pInfo the info
485      */
486     private void validateOptionalTaxCredit(final MoneyWiseTransInfo pInfo) {
487         final OceanusMoney myAmount = pInfo.getValue(OceanusMoney.class);
488         final Currency myCurrency = getOwner().getAccount().getCurrency();
489         if (myAmount.isZero()) {
490             getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
491         } else if (!myAmount.isPositive()) {
492             getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
493         } else if (!myAmount.getCurrency().equals(myCurrency)) {
494             getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
495         }
496     }
497 
498     /**
499      * Validate the partnerAmount.
500      *
501      * @param pInfo the info
502      */
503     private void validatePartnerAmount(final MoneyWiseTransInfo pInfo) {
504         final MoneyWiseTransAsset myPartner = getOwner().getPartner();
505         final OceanusMoney myAmount = pInfo.getValue(OceanusMoney.class);
506         if (!myAmount.isPositive()) {
507             getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
508         } else if (!myAmount.getCurrency().equals(myPartner.getCurrency())) {
509             getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
510         }
511     }
512 
513     /**
514      * Validate the returnedCashAccount.
515      *
516      * @param pInfo the info
517      */
518     private void validateReturnedCashAccount(final MoneyWiseTransInfo pInfo) {
519         final MoneyWiseTransAsset myThirdParty = pInfo.getTransAsset();
520         final Currency myCurrency = getOwner().getAccount().getCurrency();
521         if (!myCurrency.equals(myThirdParty.getCurrency())) {
522             getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASHACCOUNT));
523         }
524     }
525 
526     /**
527      * Validate the returnedCash.
528      *
529      * @param pInfo the info
530      */
531     private void validateReturnedCash(final MoneyWiseTransInfo pInfo) {
532         final MoneyWiseTransAsset myThirdParty = getOwner().getReturnedCashAccount();
533         final OceanusMoney myAmount = pInfo.getValue(OceanusMoney.class);
534         if (myAmount.isZero()) {
535             getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
536         } else if (!myAmount.isPositive()) {
537             getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
538         } else if (!myAmount.getCurrency().equals(myThirdParty.getCurrency())) {
539             getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(MoneyWiseTransInfoClass.RETURNEDCASH));
540         }
541     }
542 
543     /**
544      * Validate the price.
545      *
546      * @param pInfo the info
547      */
548     private void validatePrice(final MoneyWiseTransInfo pInfo) {
549         final OceanusPrice myPrice = pInfo.getValue(OceanusPrice.class);
550         final Currency myCurrency = getOwner().getAccount().getCurrency();
551         if (myPrice.isZero()) {
552             getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
553         } else if (!myPrice.isPositive()) {
554             getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
555         } else if (!myPrice.getCurrency().equals(myCurrency)) {
556             getOwner().addError(MoneyWiseTransBase.ERROR_CURRENCY, MoneyWiseTransInfoSet.getFieldForClass(pInfo.getInfoClass()));
557         }
558     }
559 
560     /**
561      * Validate the deltaUnits.
562      *
563      * @param pInfo the info
564      */
565     private void validateDeltaUnits(final MoneyWiseTransInfo pInfo) {
566         final MoneyWiseTransaction myTrans = getOwner();
567         final MoneyWiseAssetDirection myDir = myTrans.getDirection();
568         final MoneyWiseTransCategoryClass myCatClass = myTrans.getCategoryClass();
569         final MoneyWiseTransInfoClass myInfoClass = pInfo.getInfoClass();
570         final MetisFieldRequired isRequired = myInfoClass == MoneyWiseTransInfoClass.ACCOUNTDELTAUNITS
571                 ? isAccountUnitsPositive(myDir, myCatClass)
572                 : isPartnerUnitsPositive(myDir, myCatClass);
573         final OceanusUnits myUnits = pInfo.getValue(OceanusUnits.class);
574         if (myUnits.isZero()) {
575             getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseTransInfoSet.getFieldForClass(myInfoClass));
576         } else if (myUnits.isPositive() && isRequired.notAllowed()) {
577             getOwner().addError(PrometheusDataItem.ERROR_POSITIVE, MoneyWiseTransInfoSet.getFieldForClass(myInfoClass));
578         } else if (!myUnits.isPositive() && isRequired.mustExist()) {
579             getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseTransInfoSet.getFieldForClass(myInfoClass));
580         }
581     }
582 
583     /**
584      * Validate the info length.
585      *
586      * @param pInfo the info
587      */
588     private void validateInfoLength(final MoneyWiseTransInfo pInfo) {
589         final String myInfo = pInfo.getValue(String.class);
590         final MoneyWiseTransInfoClass myClass = pInfo.getInfoClass();
591         if (myInfo.length() > myClass.getMaximumLength()) {
592             getOwner().addError(PrometheusDataItem.ERROR_LENGTH, MoneyWiseTransInfoSet.getFieldForClass(myClass));
593         }
594     }
595 
596     /**
597      * Determine if AccountDeltaUnits can/mustBe/mustNotBe positive.
598      *
599      * @param pDir   the direction
600      * @param pClass the category class
601      * @return the status
602      */
603     public static MetisFieldRequired isAccountUnitsPositive(final MoneyWiseAssetDirection pDir,
604                                                             final MoneyWiseTransCategoryClass pClass) {
605         switch (pClass) {
606             case TRANSFER:
607                 return pDir.isFrom()
608                         ? MetisFieldRequired.MUSTEXIST
609                         : MetisFieldRequired.NOTALLOWED;
610             case UNITSADJUST:
611             case STOCKSPLIT:
612                 return MetisFieldRequired.CANEXIST;
613             case INHERITED:
614             case DIVIDEND:
615             case STOCKRIGHTSISSUE:
616                 return MetisFieldRequired.MUSTEXIST;
617             case STOCKDEMERGER:
618             default:
619                 return MetisFieldRequired.NOTALLOWED;
620         }
621     }
622 
623     /**
624      * Determine if PartnerDeltaUnits can/mustBe/mustNotBe positive.
625      *
626      * @param pDir   the direction
627      * @param pClass the category class
628      * @return the status
629      */
630     public static MetisFieldRequired isPartnerUnitsPositive(final MoneyWiseAssetDirection pDir,
631                                                             final MoneyWiseTransCategoryClass pClass) {
632         switch (pClass) {
633             case TRANSFER:
634                 return pDir.isTo()
635                         ? MetisFieldRequired.MUSTEXIST
636                         : MetisFieldRequired.NOTALLOWED;
637             case STOCKDEMERGER:
638             case SECURITYREPLACE:
639             case STOCKTAKEOVER:
640             case STOCKRIGHTSISSUE:
641                 return MetisFieldRequired.MUSTEXIST;
642             default:
643                 return MetisFieldRequired.NOTALLOWED;
644         }
645     }
646 
647     @Override
648     protected void setDefault(final PrometheusDataInfoClass pClass) throws OceanusException {
649         /* Switch on the class */
650         switch ((MoneyWiseTransInfoClass) pClass) {
651             case ACCOUNTDELTAUNITS:
652                 getInfoSet().setValue(pClass, getDefaultAccountUnits());
653                 break;
654             case PARTNERDELTAUNITS:
655                 getInfoSet().setValue(pClass, getDefaultPartnerUnits());
656                 break;
657             case DILUTION:
658                 getInfoSet().setValue(pClass, OceanusRatio.ONE);
659                 break;
660             case QUALIFYYEARS:
661                 getInfoSet().setValue(pClass, 1);
662                 break;
663             case TAXCREDIT:
664                 getInfoSet().setValue(pClass, getDefaultTaxCredit());
665                 break;
666             case PARTNERAMOUNT:
667                 getInfoSet().setValue(pClass, getDefaultPartnerAmount());
668                 break;
669             case RETURNEDCASHACCOUNT:
670                 getInfoSet().setValue(pClass, getDefaultReturnedCashAccount());
671                 break;
672             case RETURNEDCASH:
673                 getInfoSet().setValue(pClass, getDefaultReturnedCash());
674                 break;
675             default:
676                 break;
677         }
678     }
679 
680     /**
681      * Obtain default accountUnits.
682      *
683      * @return the default deltaUnits
684      */
685     private OceanusUnits getDefaultAccountUnits() {
686         /* Determine whether the units must be +ve or -ve */
687         final MoneyWiseTransaction myTrans = getOwner();
688         final MoneyWiseAssetDirection myDir = myTrans.getDirection();
689         final MoneyWiseTransCategoryClass myCategoryClass = myTrans.getCategoryClass();
690         final MetisFieldRequired isRequired = isAccountUnitsPositive(myDir, myCategoryClass);
691         return isRequired.notAllowed()
692                 ? OceanusUnits.getWholeUnits(-1)
693                 : OceanusUnits.getWholeUnits(1);
694     }
695 
696     /**
697      * Obtain default partnerUnits.
698      *
699      * @return the default deltaUnits
700      */
701     private OceanusUnits getDefaultPartnerUnits() {
702         /* Determine whether the units must be +ve or -ve */
703         final MoneyWiseTransaction myTrans = getOwner();
704         final MoneyWiseAssetDirection myDir = myTrans.getDirection();
705         final MoneyWiseTransCategoryClass myCategoryClass = myTrans.getCategoryClass();
706         final MetisFieldRequired isRequired = isPartnerUnitsPositive(myDir, myCategoryClass);
707         return isRequired.notAllowed()
708                 ? OceanusUnits.getWholeUnits(-1)
709                 : OceanusUnits.getWholeUnits(1);
710     }
711 
712     /**
713      * Obtain default taxCredit.
714      *
715      * @return the default taxCredit
716      */
717     private OceanusMoney getDefaultTaxCredit() {
718         /* Access the account */
719         final MoneyWiseTransAsset myAsset = getOwner().getAccount();
720         final MoneyWiseCurrency myCurrency = myAsset.getAssetCurrency();
721 
722         /* Return zero cash in the appropriate currency */
723         return new OceanusMoney(myCurrency.getCurrency());
724     }
725 
726     /**
727      * Obtain default partnerAmount.
728      *
729      * @return the default partnerAmount
730      */
731     private OceanusMoney getDefaultPartnerAmount() {
732         /* Access the partner */
733         final MoneyWiseTransAsset myAsset = getOwner().getPartner();
734         final MoneyWiseCurrency myCurrency = myAsset.getAssetCurrency();
735 
736         /* Return zero cash in the appropriate currency */
737         return new OceanusMoney(myCurrency.getCurrency());
738     }
739 
740     /**
741      * Obtain default account for ReturnedCashAccount.
742      *
743      * @return the default returnedCashAccount
744      */
745     private MoneyWiseTransAsset getDefaultReturnedCashAccount() {
746         /* loop through the deposits */
747         final MoneyWiseDepositList myDeposits
748                 = getEditSet().getDataList(MoneyWiseBasicDataType.DEPOSIT, MoneyWiseDepositList.class);
749         final Iterator<MoneyWiseDeposit> myDepIterator = myDeposits.iterator();
750         while (myDepIterator.hasNext()) {
751             final MoneyWiseDeposit myDeposit = myDepIterator.next();
752 
753             /* Use if not deleted or closed */
754             if (!myDeposit.isDeleted() && Boolean.FALSE.equals(myDeposit.isClosed())) {
755                 return myDeposit;
756             }
757         }
758 
759         /* loop through the portfolios */
760         final MoneyWisePortfolioList myPortfolios
761                 = getEditSet().getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class);
762         final Iterator<MoneyWisePortfolio> myPortIterator = myPortfolios.iterator();
763         while (myPortIterator.hasNext()) {
764             final MoneyWisePortfolio myPortfolio = myPortIterator.next();
765 
766             /* Use if not deleted or closed */
767             if (!myPortfolio.isDeleted() && Boolean.FALSE.equals(myPortfolio.isClosed())) {
768                 return myPortfolio;
769             }
770         }
771 
772         /* Return no account */
773         return null;
774     }
775 
776     /**
777      * Obtain default returnedCash.
778      *
779      * @return the default returnedCash
780      */
781     private OceanusMoney getDefaultReturnedCash() {
782         /* Access the returned cash account */
783         final MoneyWiseTransAsset myAsset = getOwner().getReturnedCashAccount();
784         final MoneyWiseCurrency myCurrency = Objects.requireNonNull(myAsset).getAssetCurrency();
785 
786         /* Return zero cash in the appropriate currency */
787         return new OceanusMoney(myCurrency.getCurrency());
788     }
789 
790     @Override
791     protected void autoCorrect(final PrometheusDataInfoClass pClass) throws OceanusException {
792         /* Switch on class */
793         final MoneyWiseTransInfoClass myClass = (MoneyWiseTransInfoClass) pClass;
794         switch (myClass) {
795             case ACCOUNTDELTAUNITS:
796                 autoCorrectAccountDeltaUnits();
797                 break;
798             case PARTNERDELTAUNITS:
799                 autoCorrectPartnerDeltaUnits();
800                 break;
801             case TAXCREDIT:
802             case EMPLOYERNATINS:
803             case EMPLOYEENATINS:
804             case DEEMEDBENEFIT:
805             case WITHHELD:
806                 autoCorrectTaxCredit(myClass);
807                 break;
808             case PARTNERAMOUNT:
809                 autoCorrectPartnerAmount();
810                 break;
811             case RETURNEDCASH:
812                 autoCorrectReturnedCash();
813                 break;
814             case PRICE:
815                 autoCorrectPrice();
816                 break;
817             default:
818                 break;
819         }
820     }
821 
822     /**
823      * AutoCorrect accountDeltaUnits.
824      *
825      * @throws OceanusException on error
826      */
827     private void autoCorrectAccountDeltaUnits() throws OceanusException {
828         /* Determine whether the units must be +ve or -ve */
829         final MoneyWiseTransaction myTrans = getOwner();
830         final MoneyWiseAssetDirection myDir = myTrans.getDirection();
831         final MoneyWiseTransCategoryClass myCategoryClass = myTrans.getCategoryClass();
832         final MetisFieldRequired isRequired = isAccountUnitsPositive(myDir, myCategoryClass);
833         OceanusUnits myUnits = Objects.requireNonNull(myTrans.getAccountDeltaUnits());
834 
835         /* If the units are negative and must be positive or are positive and must be negative */
836         if ((isRequired.mustExist() && !myUnits.isPositive())
837                 || (isRequired.notAllowed() && myUnits.isPositive())) {
838             /* Reverse the sign */
839             myUnits = new OceanusUnits(myUnits);
840             myUnits.negate();
841             getInfoSet().setValue(MoneyWiseTransInfoClass.ACCOUNTDELTAUNITS, myUnits);
842         }
843     }
844 
845 
846     /**
847      * AutoCorrect partnerDeltaUnits.
848      *
849      * @throws OceanusException on error
850      */
851     private void autoCorrectPartnerDeltaUnits() throws OceanusException {
852         /* Determine whether the units must be +ve or -ve */
853         final MoneyWiseTransaction myTrans = getOwner();
854         final MoneyWiseAssetDirection myDir = myTrans.getDirection();
855         final MoneyWiseTransCategoryClass myCategoryClass = myTrans.getCategoryClass();
856         final MetisFieldRequired isRequired = isPartnerUnitsPositive(myDir, myCategoryClass);
857         OceanusUnits myUnits = Objects.requireNonNull(myTrans.getPartnerDeltaUnits());
858 
859         /* If the units are negative and must be positive or are positive and must be negative */
860         if ((isRequired.mustExist() && !myUnits.isPositive())
861                 || (isRequired.notAllowed() && myUnits.isPositive())) {
862             /* Reverse the sign */
863             myUnits = new OceanusUnits(myUnits);
864             myUnits.negate();
865             getInfoSet().setValue(MoneyWiseTransInfoClass.PARTNERDELTAUNITS, myUnits);
866         }
867     }
868 
869     /**
870      * AutoCorrect taxCredit.
871      *
872      * @param pClass the InfoClass
873      * @throws OceanusException on error
874      */
875     private void autoCorrectTaxCredit(final MoneyWiseTransInfoClass pClass) throws OceanusException {
876         /* Obtain the existing value */
877         OceanusMoney myValue = getInfoSet().getValue(pClass, OceanusMoney.class);
878         final MoneyWiseCurrency myAssetCurrency = getOwner().getAccount().getAssetCurrency();
879         final Currency myCurrency = myAssetCurrency.getCurrency();
880 
881         /* If the value is not the correct currency */
882         if (!myValue.getCurrency().equals(myCurrency)) {
883             myValue = myValue.changeCurrency(myCurrency);
884             getInfoSet().setValue(pClass, myValue);
885         }
886     }
887 
888     /**
889      * AutoCorrect partnerAmount.
890      *
891      * @throws OceanusException on error
892      */
893     private void autoCorrectPartnerAmount() throws OceanusException {
894         /* Obtain the existing value */
895         final MoneyWiseTransaction myTrans = getOwner();
896         OceanusMoney myValue = Objects.requireNonNull(myTrans.getPartnerAmount());
897         final MoneyWiseCurrency myAssetCurrency = myTrans.getPartner().getAssetCurrency();
898         final Currency myCurrency = myAssetCurrency.getCurrency();
899 
900         /* If the value is not the correct currency */
901         if (!myCurrency.equals(myValue.getCurrency())) {
902             myValue = myValue.changeCurrency(myCurrency);
903             getInfoSet().setValue(MoneyWiseTransInfoClass.PARTNERAMOUNT, myValue);
904         }
905     }
906 
907     /**
908      * AutoCorrect returnedCash.
909      *
910      * @throws OceanusException on error
911      */
912     private void autoCorrectReturnedCash() throws OceanusException {
913         /* Obtain the existing value */
914         final MoneyWiseTransaction myTrans = getOwner();
915         OceanusMoney myValue = Objects.requireNonNull(myTrans.getReturnedCash());
916         final MoneyWiseTransAsset myAsset = Objects.requireNonNull(myTrans.getReturnedCashAccount());
917         final MoneyWiseCurrency myAssetCurrency = myAsset.getAssetCurrency();
918         final Currency myCurrency = myAssetCurrency.getCurrency();
919 
920         /* If the value is not the correct currency */
921         if (!myCurrency.equals(myValue.getCurrency())) {
922             myValue = myValue.changeCurrency(myCurrency);
923             getInfoSet().setValue(MoneyWiseTransInfoClass.RETURNEDCASH, myValue);
924         }
925     }
926 
927     /**
928      * AutoCorrect price.
929      *
930      * @throws OceanusException on error
931      */
932     private void autoCorrectPrice() throws OceanusException {
933         /* Obtain the existing value */
934         final MoneyWiseTransaction myTrans = getOwner();
935         OceanusPrice myPrice = Objects.requireNonNull(myTrans.getPrice());
936         final MoneyWiseCurrency myAssetCurrency = getOwner().getAccount().getAssetCurrency();
937         final Currency myCurrency = myAssetCurrency.getCurrency();
938 
939         /* If the value is not the correct currency */
940         if (!myPrice.getCurrency().equals(myCurrency)) {
941             myPrice = myPrice.changeCurrency(myCurrency);
942             getInfoSet().setValue(MoneyWiseTransInfoClass.PRICE, myPrice);
943         }
944     }
945 }