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