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.metis.data.MetisDataItem.MetisDataFieldId;
20 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase;
21 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetDirection;
22 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetType;
23 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
24 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicResource;
25 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDataSet;
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.MoneyWiseSecurity;
29 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding;
30 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding.MoneyWiseSecurityHoldingMap;
31 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
32 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
33 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
34 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction.MoneyWiseTransactionList;
35 import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
36 import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
37 import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
38 import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
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 return switch (myAsset.getAssetType()) {
322 case DEPOSIT, CASH, PAYEE, LOAN -> false;
323 default -> true;
324 };
325 }
326
327
328
329
330
331
332
333
334
335
336
337 boolean resolveValues(final OceanusDate pDate,
338 final String pDebit,
339 final String pCredit,
340 final String pCategory) throws OceanusException {
341
342 if (pDate == null) {
343
344 resolveChildValues(pDebit, pCredit, pCategory);
345 return true;
346 }
347
348
349 if (!checkDate(pDate)) {
350
351 hitEventLimit = true;
352 return false;
353 }
354
355
356 isSplit = Boolean.FALSE;
357 theParent = null;
358
359
360 theDate = pDate;
361
362
363 theLastDebit = theNameMap.get(pDebit);
364 theLastCredit = theNameMap.get(pCredit);
365 theCategory = theCategoryMap.get(pCategory);
366
367
368 checkResolution(pDebit, pCredit, pCategory);
369
370
371 if (theCategory.isCategoryClass(MoneyWiseTransCategoryClass.PORTFOLIOXFER)) {
372
373 resolvePortfolioXfer(theData, theLastDebit, theLastCredit);
374 }
375
376
377 resolveAssets();
378 return true;
379 }
380
381
382
383
384
385
386
387
388
389 private void resolveChildValues(final String pDebit,
390 final String pCredit,
391 final String pCategory) throws OceanusException {
392
393 if (theLastParent == null) {
394 throw new MoneyWiseDataException(theDate, "Missing parent transaction");
395 }
396
397
398 isSplit = Boolean.TRUE;
399 theParent = theLastParent;
400
401
402 final Object myDebit = pDebit == null
403 ? theLastDebit
404 : theNameMap.get(pDebit);
405 final Object myCredit = pCredit == null
406 ? theLastCredit
407 : theNameMap.get(pCredit);
408
409
410 theLastDebit = myDebit;
411 theLastCredit = myCredit;
412
413
414 theCategory = theCategoryMap.get(pCategory);
415
416
417 checkResolution(pDebit, pCredit, pCategory);
418
419
420 resolveAssets();
421 }
422
423
424
425
426
427
428 private void resolveAssets() throws OceanusException {
429 final boolean isDebitHolding = theLastDebit instanceof MoneyWiseSecurityHolding;
430 final boolean isCreditHolding = theLastCredit instanceof MoneyWiseSecurityHolding;
431
432
433 final MoneyWiseTransAsset myDebit = (MoneyWiseTransAsset) theLastDebit;
434 final MoneyWiseTransAsset myCredit = (MoneyWiseTransAsset) theLastCredit;
435
436
437 final MoneyWiseAssetType myDebitType = myDebit.getAssetType();
438 final MoneyWiseAssetType myCreditType = myCredit.getAssetType();
439
440
441 if (!myDebitType.isBaseAccount()) {
442
443 isDebitReversed = true;
444
445
446 } else if (!myCreditType.isBaseAccount()) {
447
448 isDebitReversed = false;
449
450
451 } else if (!isSplit) {
452
453 switch (Objects.requireNonNull(theCategory.getCategoryTypeClass())) {
454 case STOCKRIGHTSISSUE:
455
456 isDebitReversed = !myDebitType.isSecurityHolding();
457 break;
458 case LOANINTERESTEARNED:
459
460 isDebitReversed = !theData.newValidityChecks();
461 break;
462 case LOANINTERESTCHARGED, WRITEOFF:
463
464 isDebitReversed = theData.newValidityChecks();
465 break;
466 default:
467
468 isDebitReversed = false;
469 break;
470 }
471 } else {
472
473 final MoneyWiseTransAsset myParAccount = theParent.getAccount();
474 final MoneyWiseTransAsset myParPartner = theParent.getPartner();
475
476
477 if (myDebit.equals(myParAccount)) {
478
479 isDebitReversed = false;
480
481
482 } else if (myCredit.equals(myParAccount)) {
483
484 isDebitReversed = true;
485
486
487 } else {
488
489 theParent.flipAssets();
490
491
492 isDebitReversed = !myDebit.equals(myParPartner);
493 }
494 }
495
496
497 if (!isDebitReversed) {
498
499 theAccount = myDebit;
500 thePartner = myCredit;
501 theDirection = MoneyWiseAssetDirection.TO;
502 } else {
503
504 theAccount = myCredit;
505 thePartner = myDebit;
506 theDirection = MoneyWiseAssetDirection.FROM;
507 }
508
509
510 thePortfolio = null;
511 if (isDebitHolding) {
512 thePortfolio = ((MoneyWiseSecurityHolding) theLastDebit).getPortfolio();
513 if (isCreditHolding) {
514 final MoneyWisePortfolio myPortfolio = ((MoneyWiseSecurityHolding) theLastCredit).getPortfolio();
515 if (!thePortfolio.equals(myPortfolio)) {
516 throw new MoneyWiseDataException(theDate, "Inconsistent portfolios");
517 }
518 }
519 } else if (isCreditHolding) {
520 thePortfolio = ((MoneyWiseSecurityHolding) theLastCredit).getPortfolio();
521 }
522 }
523
524
525
526
527
528
529
530
531
532 private void checkResolution(final String pDebit,
533 final String pCredit,
534 final String pCategory) throws OceanusException {
535
536 if (theLastDebit == null) {
537 throw new MoneyWiseDataException(pDebit, "Failed to resolve debit account on " + theDate);
538 }
539
540
541 if (theLastCredit == null) {
542 throw new MoneyWiseDataException(pCredit, "Failed to resolve credit account on " + theDate);
543 }
544
545
546 if (theCategory == null) {
547 throw new MoneyWiseDataException(pCategory, "Failed to resolve category on " + theDate);
548 }
549 }
550
551
552
553
554
555
556
557 void declareAsset(final MoneyWiseAssetBase pAsset) throws OceanusException {
558
559 final String myName = pAsset.getName();
560
561
562 if (theNameMap.get(myName) != null) {
563 throw new MoneyWiseDataException(pAsset, PrometheusDataItem.ERROR_DUPLICATE);
564 }
565
566
567 theNameMap.put(myName, pAsset);
568 }
569
570
571
572
573
574
575
576 void declareCategory(final MoneyWiseTransCategory pCategory) throws OceanusException {
577
578 final String myName = pCategory.getName();
579
580
581 if (theCategoryMap.get(myName) != null) {
582 throw new MoneyWiseDataException(pCategory, PrometheusDataItem.ERROR_DUPLICATE);
583 }
584
585
586 theCategoryMap.put(myName, pCategory);
587 }
588
589
590
591
592
593
594
595
596 void declareSecurityHolding(final MoneyWiseSecurity pSecurity,
597 final String pPortfolio) throws OceanusException {
598
599 final String myName = pSecurity.getName();
600
601
602 if (theNameMap.get(myName) != null) {
603 throw new MoneyWiseDataException(pSecurity, PrometheusDataItem.ERROR_DUPLICATE);
604 }
605
606
607 theNameMap.put(myName, new MoneyWiseSecurityHoldingDef(pSecurity, pPortfolio));
608 }
609
610
611
612
613
614
615
616
617
618 void declareAliasHolding(final String pName,
619 final String pAlias,
620 final String pPortfolio) throws OceanusException {
621
622 final Object myHolding = theNameMap.get(pAlias);
623 if (!(myHolding instanceof MoneyWiseSecurityHoldingDef myAliased)) {
624 throw new MoneyWiseDataException(pAlias, "Aliased security not found");
625 }
626
627
628 theNameMap.put(pName, new MoneyWiseSecurityHoldingDef(myAliased.getSecurity(), pPortfolio));
629 }
630
631
632
633
634
635
636 void resolveSecurityHoldings(final MoneyWiseDataSet pData) {
637
638 final MoneyWiseSecurityHoldingMap myMap = pData.getPortfolios().getSecurityHoldingsMap();
639 final MoneyWisePortfolioList myPortfolios = pData.getPortfolios();
640
641
642 for (Entry<String, Object> myEntry : theNameMap.entrySet()) {
643
644 final Object myValue = myEntry.getValue();
645 if (myValue instanceof MoneyWiseSecurityHoldingDef myDef) {
646
647 final MoneyWisePortfolio myPortfolio = myPortfolios.findItemByName(myDef.getPortfolio());
648 final MoneyWiseSecurityHolding myHolding = myMap.declareHolding(myPortfolio, myDef.getSecurity());
649
650
651 myEntry.setValue(myHolding);
652 }
653 }
654 }
655
656
657
658
659
660
661
662
663
664
665 private void resolvePortfolioXfer(final MoneyWiseDataSet pData,
666 final Object pSource,
667 final Object pTarget) throws OceanusException {
668
669 if (!(pTarget instanceof MoneyWisePortfolio myPortfolio)) {
670 throw new MoneyWiseDataException(pTarget, "Inconsistent portfolios");
671 }
672
673 final MoneyWiseSecurityHoldingMap myMap = pData.getPortfolios().getSecurityHoldingsMap();
674
675
676 for (Entry<String, Object> myEntry : theNameMap.entrySet()) {
677
678 final Object myValue = myEntry.getValue();
679 if (myValue instanceof MoneyWiseSecurityHolding myHolding
680 && (pSource.equals(myHolding) || pSource.equals(myHolding.getPortfolio()))) {
681
682 myHolding = myMap.declareHolding(myPortfolio, myHolding.getSecurity());
683
684
685 myEntry.setValue(myHolding);
686 }
687 }
688 }
689
690
691
692
693 private static final class MoneyWiseSecurityHoldingDef {
694
695
696
697 private final MoneyWiseSecurity theSecurity;
698
699
700
701
702 private final String thePortfolio;
703
704
705
706
707
708
709
710 private MoneyWiseSecurityHoldingDef(final MoneyWiseSecurity pSecurity,
711 final String pPortfolio) {
712
713 theSecurity = pSecurity;
714 thePortfolio = pPortfolio;
715 }
716
717
718
719
720
721
722 public MoneyWiseSecurity getSecurity() {
723 return theSecurity;
724 }
725
726
727
728
729
730
731 public String getPortfolio() {
732 return thePortfolio;
733 }
734 }
735 }