1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.moneywise.archive;
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.metis.data.MetisDataItem.MetisDataFieldId;
22 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase;
23 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetDirection;
24 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetType;
25 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
26 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
27 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDataSet;
28 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
29 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio.MoneyWisePortfolioList;
30 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity;
31 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding;
32 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding.MoneyWiseSecurityHoldingMap;
33 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
34 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
35 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
36 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction.MoneyWiseTransactionList;
37 import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
38 import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
39 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
40 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
41
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.ListIterator;
46 import java.util.Map;
47 import java.util.Map.Entry;
48 import java.util.Objects;
49
50
51
52
53 public final class MoneyWiseArchiveCache {
54
55
56
57 private final MoneyWiseDataSet theData;
58
59
60
61
62 private final MoneyWiseTransactionList theList;
63
64
65
66
67 private final Map<String, Object> theNameMap;
68
69
70
71
72 private final Map<String, MoneyWiseTransCategory> theCategoryMap;
73
74
75
76
77 private final List<MoneyWiseArchiveYear> theYears;
78
79
80
81
82 private boolean enableFiltering;
83
84
85
86
87 private MoneyWiseTransaction theLastParent;
88
89
90
91
92 private Object theLastDebit;
93
94
95
96
97 private Object theLastCredit;
98
99
100
101
102 private MoneyWiseTransaction theParent;
103
104
105
106
107 private boolean isSplit;
108
109
110
111
112 private OceanusDate theDate;
113
114
115
116
117 private MoneyWiseAssetDirection theDirection;
118
119
120
121
122 private MoneyWiseTransAsset theAccount;
123
124
125
126
127 private MoneyWiseTransAsset thePartner;
128
129
130
131
132 private MoneyWiseTransCategory theCategory;
133
134
135
136
137 private MoneyWisePortfolio thePortfolio;
138
139
140
141
142 private boolean isDebitReversed;
143
144
145
146
147 private OceanusDate theLastEvent;
148
149
150
151
152 private boolean hitEventLimit;
153
154
155
156
157
158
159 MoneyWiseArchiveCache(final MoneyWiseDataSet pData) {
160
161 theData = pData;
162 theList = theData.getTransactions();
163
164
165 theNameMap = new HashMap<>();
166 theCategoryMap = new HashMap<>();
167 theYears = new ArrayList<>();
168 }
169
170
171
172
173 void enableFiltering() {
174 enableFiltering = true;
175 }
176
177
178
179
180
181
182 void setLastEvent(final OceanusDate pLastEvent) {
183 theLastEvent = pLastEvent;
184 }
185
186
187
188
189
190
191
192 boolean checkDate(final OceanusDate pDate) {
193 return theLastEvent == null || theLastEvent.compareTo(pDate) >= 0;
194 }
195
196
197
198
199
200
201 boolean hitEventLimit() {
202 return hitEventLimit;
203 }
204
205
206
207
208
209
210 void addYear(final String pName) {
211 final MoneyWiseArchiveYear myYear = new MoneyWiseArchiveYear(pName);
212 theYears.add(myYear);
213 }
214
215
216
217
218
219
220 ListIterator<MoneyWiseArchiveYear> getIterator() {
221 return theYears.listIterator();
222 }
223
224
225
226
227
228
229 ListIterator<MoneyWiseArchiveYear> reverseIterator() {
230 return theYears.listIterator(getNumYears());
231 }
232
233
234
235
236
237
238 int getNumYears() {
239 return theYears.size();
240 }
241
242
243
244
245
246
247
248
249
250 MoneyWiseTransaction buildTransaction(final String pAmount,
251 final boolean pReconciled) throws OceanusException {
252
253 final PrometheusDataValues myValues = new PrometheusDataValues(MoneyWiseTransaction.OBJECT_NAME);
254 myValues.addValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE, theDate);
255 myValues.addValue(MoneyWiseBasicDataType.TRANSCATEGORY, theCategory);
256 myValues.addValue(MoneyWiseBasicResource.TRANSACTION_DIRECTION, theDirection);
257 myValues.addValue(MoneyWiseBasicResource.TRANSACTION_ACCOUNT, theAccount);
258 myValues.addValue(MoneyWiseBasicResource.TRANSACTION_PARTNER, thePartner);
259 myValues.addValue(MoneyWiseBasicResource.TRANSACTION_RECONCILED, pReconciled);
260 if (pAmount != null) {
261 myValues.addValue(MoneyWiseBasicResource.TRANSACTION_AMOUNT, pAmount);
262 }
263
264 if (filterTransaction(myValues)) {
265 return null;
266 }
267
268
269 final MoneyWiseTransaction myTrans = theList.addValuesItem(myValues);
270
271
272 if (!isSplit) {
273
274 theLastParent = myTrans;
275 }
276
277
278 return myTrans;
279 }
280
281
282
283
284
285
286 boolean isDebitReversed() {
287 return isDebitReversed;
288 }
289
290
291
292
293
294
295 boolean isRecursive() {
296 return theLastDebit.equals(theLastCredit);
297 }
298
299
300
301
302
303
304
305 boolean filterTransaction(final PrometheusDataValues pTrans) {
306 return enableFiltering
307 && (filterAsset(pTrans, MoneyWiseBasicResource.TRANSACTION_ACCOUNT)
308 || filterAsset(pTrans, MoneyWiseBasicResource.TRANSACTION_PARTNER));
309 }
310
311
312
313
314
315
316
317
318 private boolean filterAsset(final PrometheusDataValues pTrans,
319 final MetisDataFieldId pAsset) {
320 final MoneyWiseTransAsset myAsset = pTrans.getValue(pAsset, MoneyWiseTransAsset.class);
321 switch (myAsset.getAssetType()) {
322 case DEPOSIT:
323 case CASH:
324 case PAYEE:
325 case LOAN:
326 return false;
327 default:
328 return true;
329 }
330 }
331
332
333
334
335
336
337
338
339
340
341
342 boolean resolveValues(final OceanusDate pDate,
343 final String pDebit,
344 final String pCredit,
345 final String pCategory) throws OceanusException {
346
347 if (pDate == null) {
348
349 resolveChildValues(pDebit, pCredit, pCategory);
350 return true;
351 }
352
353
354 if (!checkDate(pDate)) {
355
356 hitEventLimit = true;
357 return false;
358 }
359
360
361 isSplit = Boolean.FALSE;
362 theParent = null;
363
364
365 theDate = pDate;
366
367
368 theLastDebit = theNameMap.get(pDebit);
369 theLastCredit = theNameMap.get(pCredit);
370 theCategory = theCategoryMap.get(pCategory);
371
372
373 checkResolution(pDebit, pCredit, pCategory);
374
375
376 if (theCategory.isCategoryClass(MoneyWiseTransCategoryClass.PORTFOLIOXFER)) {
377
378 resolvePortfolioXfer(theData, theLastDebit, theLastCredit);
379 }
380
381
382 resolveAssets();
383 return true;
384 }
385
386
387
388
389
390
391
392
393
394 private void resolveChildValues(final String pDebit,
395 final String pCredit,
396 final String pCategory) throws OceanusException {
397
398 if (theLastParent == null) {
399 throw new MoneyWiseDataException(theDate, "Missing parent transaction");
400 }
401
402
403 isSplit = Boolean.TRUE;
404 theParent = theLastParent;
405
406
407 final Object myDebit = pDebit == null
408 ? theLastDebit
409 : theNameMap.get(pDebit);
410 final Object myCredit = pCredit == null
411 ? theLastCredit
412 : theNameMap.get(pCredit);
413
414
415 theLastDebit = myDebit;
416 theLastCredit = myCredit;
417
418
419 theCategory = theCategoryMap.get(pCategory);
420
421
422 checkResolution(pDebit, pCredit, pCategory);
423
424
425 resolveAssets();
426 }
427
428
429
430
431
432
433 private void resolveAssets() throws OceanusException {
434 final boolean isDebitHolding = theLastDebit instanceof MoneyWiseSecurityHolding;
435 final boolean isCreditHolding = theLastCredit instanceof MoneyWiseSecurityHolding;
436
437
438 final MoneyWiseTransAsset myDebit = (MoneyWiseTransAsset) theLastDebit;
439 final MoneyWiseTransAsset myCredit = (MoneyWiseTransAsset) theLastCredit;
440
441
442 final MoneyWiseAssetType myDebitType = myDebit.getAssetType();
443 final MoneyWiseAssetType myCreditType = myCredit.getAssetType();
444
445
446 if (!myDebitType.isBaseAccount()) {
447
448 isDebitReversed = true;
449
450
451 } else if (!myCreditType.isBaseAccount()) {
452
453 isDebitReversed = false;
454
455
456 } else if (!isSplit) {
457
458 switch (Objects.requireNonNull(theCategory.getCategoryTypeClass())) {
459 case STOCKRIGHTSISSUE:
460
461 isDebitReversed = !myDebitType.isSecurityHolding();
462 break;
463 case LOANINTERESTEARNED:
464
465 isDebitReversed = !theData.newValidityChecks();
466 break;
467 case LOANINTERESTCHARGED:
468 case WRITEOFF:
469
470 isDebitReversed = theData.newValidityChecks();
471 break;
472 default:
473
474 isDebitReversed = false;
475 break;
476 }
477 } else {
478
479 final MoneyWiseTransAsset myParAccount = theParent.getAccount();
480 final MoneyWiseTransAsset myParPartner = theParent.getPartner();
481
482
483 if (myDebit.equals(myParAccount)) {
484
485 isDebitReversed = false;
486
487
488 } else if (myCredit.equals(myParAccount)) {
489
490 isDebitReversed = true;
491
492
493 } else {
494
495 theParent.flipAssets();
496
497
498 isDebitReversed = !myDebit.equals(myParPartner);
499 }
500 }
501
502
503 if (!isDebitReversed) {
504
505 theAccount = myDebit;
506 thePartner = myCredit;
507 theDirection = MoneyWiseAssetDirection.TO;
508 } else {
509
510 theAccount = myCredit;
511 thePartner = myDebit;
512 theDirection = MoneyWiseAssetDirection.FROM;
513 }
514
515
516 thePortfolio = null;
517 if (isDebitHolding) {
518 thePortfolio = ((MoneyWiseSecurityHolding) theLastDebit).getPortfolio();
519 if (isCreditHolding) {
520 final MoneyWisePortfolio myPortfolio = ((MoneyWiseSecurityHolding) theLastCredit).getPortfolio();
521 if (!thePortfolio.equals(myPortfolio)) {
522 throw new MoneyWiseDataException(theDate, "Inconsistent portfolios");
523 }
524 }
525 } else if (isCreditHolding) {
526 thePortfolio = ((MoneyWiseSecurityHolding) theLastCredit).getPortfolio();
527 }
528 }
529
530
531
532
533
534
535
536
537
538 private void checkResolution(final String pDebit,
539 final String pCredit,
540 final String pCategory) throws OceanusException {
541
542 if (theLastDebit == null) {
543 throw new MoneyWiseDataException(pDebit, "Failed to resolve debit account on " + theDate);
544 }
545
546
547 if (theLastCredit == null) {
548 throw new MoneyWiseDataException(pCredit, "Failed to resolve credit account on " + theDate);
549 }
550
551
552 if (theCategory == null) {
553 throw new MoneyWiseDataException(pCategory, "Failed to resolve category on " + theDate);
554 }
555 }
556
557
558
559
560
561
562
563 void declareAsset(final MoneyWiseAssetBase pAsset) throws OceanusException {
564
565 final String myName = pAsset.getName();
566
567
568 if (theNameMap.get(myName) != null) {
569 throw new MoneyWiseDataException(pAsset, PrometheusDataItem.ERROR_DUPLICATE);
570 }
571
572
573 theNameMap.put(myName, pAsset);
574 }
575
576
577
578
579
580
581
582 void declareCategory(final MoneyWiseTransCategory pCategory) throws OceanusException {
583
584 final String myName = pCategory.getName();
585
586
587 if (theCategoryMap.get(myName) != null) {
588 throw new MoneyWiseDataException(pCategory, PrometheusDataItem.ERROR_DUPLICATE);
589 }
590
591
592 theCategoryMap.put(myName, pCategory);
593 }
594
595
596
597
598
599
600
601
602 void declareSecurityHolding(final MoneyWiseSecurity pSecurity,
603 final String pPortfolio) throws OceanusException {
604
605 final String myName = pSecurity.getName();
606
607
608 if (theNameMap.get(myName) != null) {
609 throw new MoneyWiseDataException(pSecurity, PrometheusDataItem.ERROR_DUPLICATE);
610 }
611
612
613 theNameMap.put(myName, new MoneyWiseSecurityHoldingDef(pSecurity, pPortfolio));
614 }
615
616
617
618
619
620
621
622
623
624 void declareAliasHolding(final String pName,
625 final String pAlias,
626 final String pPortfolio) throws OceanusException {
627
628 final Object myHolding = theNameMap.get(pAlias);
629 if (!(myHolding instanceof MoneyWiseSecurityHoldingDef myAliased)) {
630 throw new MoneyWiseDataException(pAlias, "Aliased security not found");
631 }
632
633
634 theNameMap.put(pName, new MoneyWiseSecurityHoldingDef(myAliased.getSecurity(), pPortfolio));
635 }
636
637
638
639
640
641
642 void resolveSecurityHoldings(final MoneyWiseDataSet pData) {
643
644 final MoneyWiseSecurityHoldingMap myMap = pData.getPortfolios().getSecurityHoldingsMap();
645 final MoneyWisePortfolioList myPortfolios = pData.getPortfolios();
646
647
648 for (Entry<String, Object> myEntry : theNameMap.entrySet()) {
649
650 final Object myValue = myEntry.getValue();
651 if (myValue instanceof MoneyWiseSecurityHoldingDef myDef) {
652
653 final MoneyWisePortfolio myPortfolio = myPortfolios.findItemByName(myDef.getPortfolio());
654 final MoneyWiseSecurityHolding myHolding = myMap.declareHolding(myPortfolio, myDef.getSecurity());
655
656
657 myEntry.setValue(myHolding);
658 }
659 }
660 }
661
662
663
664
665
666
667
668
669
670
671 private void resolvePortfolioXfer(final MoneyWiseDataSet pData,
672 final Object pSource,
673 final Object pTarget) throws OceanusException {
674
675 if (!(pTarget instanceof MoneyWisePortfolio myPortfolio)) {
676 throw new MoneyWiseDataException(pTarget, "Inconsistent portfolios");
677 }
678
679 final MoneyWiseSecurityHoldingMap myMap = pData.getPortfolios().getSecurityHoldingsMap();
680
681
682 for (Entry<String, Object> myEntry : theNameMap.entrySet()) {
683
684 final Object myValue = myEntry.getValue();
685 if (myValue instanceof MoneyWiseSecurityHolding myHolding) {
686
687 if (pSource.equals(myHolding) || pSource.equals(myHolding.getPortfolio())) {
688
689 myHolding = myMap.declareHolding(myPortfolio, myHolding.getSecurity());
690
691
692 myEntry.setValue(myHolding);
693 }
694 }
695 }
696 }
697
698
699
700
701 private static final class MoneyWiseSecurityHoldingDef {
702
703
704
705 private final MoneyWiseSecurity theSecurity;
706
707
708
709
710 private final String thePortfolio;
711
712
713
714
715
716
717
718 private MoneyWiseSecurityHoldingDef(final MoneyWiseSecurity pSecurity,
719 final String pPortfolio) {
720
721 theSecurity = pSecurity;
722 thePortfolio = pPortfolio;
723 }
724
725
726
727
728
729
730 public MoneyWiseSecurity getSecurity() {
731 return theSecurity;
732 }
733
734
735
736
737
738
739 public String getPortfolio() {
740 return thePortfolio;
741 }
742 }
743 }