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.basic;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
22  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
23  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
24  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
25  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
26  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
27  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
28  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryType;
29  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryType.MoneyWiseTransCategoryTypeList;
30  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoClass;
31  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
32  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
33  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
34  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
35  import io.github.tonywasher.joceanus.prometheus.data.PrometheusStaticDataItem;
36  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
37  
38  import java.util.HashMap;
39  import java.util.Iterator;
40  import java.util.Map;
41  import java.util.Objects;
42  
43  /**
44   * Transaction Category class.
45   */
46  public final class MoneyWiseTransCategory
47          extends MoneyWiseCategoryBase {
48      /**
49       * Object name.
50       */
51      public static final String OBJECT_NAME = MoneyWiseBasicDataType.TRANSCATEGORY.getItemName();
52  
53      /**
54       * List name.
55       */
56      public static final String LIST_NAME = MoneyWiseBasicDataType.TRANSCATEGORY.getListName();
57  
58      /**
59       * Local Report fields.
60       */
61      private static final MetisFieldVersionedSet<MoneyWiseTransCategory> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(MoneyWiseTransCategory.class);
62  
63      /*
64       * FieldIds.
65       */
66      static {
67          FIELD_DEFS.declareLinkField(MoneyWiseStaticDataType.TRANSTYPE);
68      }
69  
70      /**
71       * Copy Constructor.
72       *
73       * @param pList     the list
74       * @param pCategory The Category to copy
75       */
76      MoneyWiseTransCategory(final MoneyWiseTransCategoryList pList,
77                             final MoneyWiseTransCategory pCategory) {
78          /* Set standard values */
79          super(pList, pCategory);
80      }
81  
82      /**
83       * Values constructor.
84       *
85       * @param pList   the List to add to
86       * @param pValues the values constructor
87       * @throws OceanusException on error
88       */
89      private MoneyWiseTransCategory(final MoneyWiseTransCategoryList pList,
90                                     final PrometheusDataValues pValues) throws OceanusException {
91          /* Initialise the item */
92          super(pList, pValues);
93  
94          /* Store the Category Type */
95          final Object myValue = pValues.getValue(MoneyWiseStaticDataType.TRANSTYPE);
96          if (myValue instanceof Integer i) {
97              setValueType(i);
98          } else if (myValue instanceof String s) {
99              setValueType(s);
100         }
101     }
102 
103     /**
104      * Edit Constructor.
105      *
106      * @param pList the list
107      */
108     public MoneyWiseTransCategory(final MoneyWiseTransCategoryList pList) {
109         super(pList);
110     }
111 
112     @Override
113     public MetisFieldSetDef getDataFieldSet() {
114         return FIELD_DEFS;
115     }
116 
117     @Override
118     public boolean isActive() {
119         return super.isActive() || isHidden();
120     }
121 
122     @Override
123     public boolean includeXmlField(final MetisDataFieldId pField) {
124         /* Determine whether fields should be included */
125         if (MoneyWiseStaticDataType.TRANSTYPE.equals(pField)) {
126             return true;
127         }
128 
129         /* Pass call on */
130         return super.includeXmlField(pField);
131     }
132 
133     @Override
134     public MoneyWiseTransCategoryType getCategoryType() {
135         return getValues().getValue(MoneyWiseStaticDataType.TRANSTYPE, MoneyWiseTransCategoryType.class);
136     }
137 
138     @Override
139     public MoneyWiseTransCategoryClass getCategoryTypeClass() {
140         final MoneyWiseTransCategoryType myType = getCategoryType();
141         return myType == null
142                 ? null
143                 : myType.getCategoryClass();
144     }
145 
146     @Override
147     public MoneyWiseTransCategory getParentCategory() {
148         return getValues().getValue(PrometheusDataResource.DATAGROUP_PARENT, MoneyWiseTransCategory.class);
149     }
150 
151     /**
152      * Set category type value.
153      *
154      * @param pValue the value
155      */
156     private void setValueType(final MoneyWiseTransCategoryType pValue) {
157         getValues().setUncheckedValue(MoneyWiseStaticDataType.TRANSTYPE, pValue);
158     }
159 
160     /**
161      * Set category type id.
162      *
163      * @param pValue the value
164      */
165     private void setValueType(final Integer pValue) {
166         getValues().setUncheckedValue(MoneyWiseStaticDataType.TRANSTYPE, pValue);
167     }
168 
169     /**
170      * Set category type name.
171      *
172      * @param pValue the value
173      */
174     private void setValueType(final String pValue) {
175         getValues().setUncheckedValue(MoneyWiseStaticDataType.TRANSTYPE, pValue);
176     }
177 
178     @Override
179     public MoneyWiseTransCategoryList getList() {
180         return (MoneyWiseTransCategoryList) super.getList();
181     }
182 
183     /**
184      * Is this event category the required class.
185      *
186      * @param pClass the required category class.
187      * @return true/false
188      */
189     public boolean isCategoryClass(final MoneyWiseTransCategoryClass pClass) {
190         /* Check for match */
191         return getCategoryTypeClass() == pClass;
192     }
193 
194     /**
195      * Is this event category a transfer?
196      *
197      * @return true/false
198      */
199     public boolean isTransfer() {
200         /* Check for match */
201         final MoneyWiseTransCategoryClass myClass = getCategoryTypeClass();
202         return myClass != null
203                 && myClass.isTransfer();
204     }
205 
206     /**
207      * Set defaults.
208      *
209      * @param pParent the parent
210      * @throws OceanusException on error
211      */
212     public void setDefaults(final MoneyWiseTransCategory pParent) throws OceanusException {
213         getList().getValidator().setDefaults(pParent, this);
214     }
215 
216     @Override
217     public void resolveDataSetLinks() throws OceanusException {
218         /* Update the Underlying details */
219         super.resolveDataSetLinks();
220 
221         /* Resolve category type and parent */
222         final MoneyWiseDataSet myData = getDataSet();
223         resolveDataLink(MoneyWiseStaticDataType.TRANSTYPE, myData.getTransCategoryTypes());
224     }
225 
226     @Override
227     protected void resolveEditSetLinks() throws OceanusException {
228         /* Resolve parent within list */
229         resolveDataLink(PrometheusDataResource.DATAGROUP_PARENT, getList());
230 
231         /* Resolve StaticType if required */
232         final PrometheusEditSet myEditSet = getList().getEditSet();
233         if (myEditSet.hasDataType(MoneyWiseStaticDataType.TRANSTYPE)) {
234             resolveDataLink(MoneyWiseStaticDataType.TRANSTYPE, myEditSet.getDataList(MoneyWiseStaticDataType.TRANSTYPE, MoneyWiseTransCategoryTypeList.class));
235         }
236     }
237 
238     @Override
239     public void setCategoryType(final PrometheusStaticDataItem pType) {
240         setValueType((MoneyWiseTransCategoryType) pType);
241     }
242 
243     /**
244      * Update base category from an edited category.
245      *
246      * @param pCategory the edited category
247      * @return whether changes have been made
248      */
249     @Override
250     public boolean applyChanges(final PrometheusDataItem pCategory) {
251         /* Can only update from a transaction category */
252         if (!(pCategory instanceof MoneyWiseTransCategory)) {
253             return false;
254         }
255         final MoneyWiseTransCategory myCategory = (MoneyWiseTransCategory) pCategory;
256 
257         /* Store the current detail into history */
258         pushHistory();
259 
260         /* Apply basic changes */
261         applyBasicChanges(myCategory);
262 
263         /* Update the category type if required */
264         if (!MetisDataDifference.isEqual(getCategoryType(), myCategory.getCategoryType())) {
265             setValueType(myCategory.getCategoryType());
266         }
267 
268         /* Check for changes */
269         return checkForHistory();
270     }
271 
272     /**
273      * Is the category hidden?
274      *
275      * @return true/false
276      */
277     public boolean isHidden() {
278         final MoneyWiseTransCategoryClass myClass = this.getCategoryTypeClass();
279         return myClass != null
280                 && myClass.isHiddenType();
281     }
282 
283     /**
284      * The Transaction Category List class.
285      */
286     public static class MoneyWiseTransCategoryList
287             extends MoneyWiseCategoryBaseList<MoneyWiseTransCategory> {
288         /**
289          * Report fields.
290          */
291         private static final MetisFieldSet<MoneyWiseTransCategoryList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseTransCategoryList.class);
292 
293         /**
294          * The EditSet.
295          */
296         private PrometheusEditSet theEditSet;
297 
298         /**
299          * Construct an empty CORE Category list.
300          *
301          * @param pData the DataSet for the list
302          */
303         public MoneyWiseTransCategoryList(final MoneyWiseDataSet pData) {
304             super(pData, MoneyWiseTransCategory.class, MoneyWiseBasicDataType.TRANSCATEGORY);
305         }
306 
307         /**
308          * Constructor for a cloned List.
309          *
310          * @param pSource the source List
311          */
312         protected MoneyWiseTransCategoryList(final MoneyWiseTransCategoryList pSource) {
313             super(pSource);
314         }
315 
316         @Override
317         public MetisFieldSet<MoneyWiseTransCategoryList> getDataFieldSet() {
318             return FIELD_DEFS;
319         }
320 
321         @Override
322         public String listName() {
323             return LIST_NAME;
324         }
325 
326         @Override
327         public MetisFieldSetDef getItemFields() {
328             return MoneyWiseTransCategory.FIELD_DEFS;
329         }
330 
331         /**
332          * Obtain editSet.
333          *
334          * @return the editSet
335          */
336         public PrometheusEditSet getEditSet() {
337             return theEditSet;
338         }
339 
340         @Override
341         public MoneyWiseTransCategoryDataMap getDataMap() {
342             return (MoneyWiseTransCategoryDataMap) super.getDataMap();
343         }
344 
345         @Override
346         protected MoneyWiseTransCategoryList getEmptyList(final PrometheusListStyle pStyle) {
347             final MoneyWiseTransCategoryList myList = new MoneyWiseTransCategoryList(this);
348             myList.setStyle(pStyle);
349             return myList;
350         }
351 
352         /**
353          * Derive Edit list.
354          *
355          * @param pEditSet the editSet
356          * @return the edit list
357          * @throws OceanusException on error
358          */
359         public MoneyWiseTransCategoryList deriveEditList(final PrometheusEditSet pEditSet) throws OceanusException {
360             /* Build an empty List */
361             final MoneyWiseTransCategoryList myList = getEmptyList(PrometheusListStyle.EDIT);
362             myList.ensureMap();
363             pEditSet.setEditEntryList(MoneyWiseBasicDataType.TRANSCATEGORY, myList);
364             myList.getValidator().setEditSet(pEditSet);
365 
366             /* Store the editSet */
367             myList.theEditSet = pEditSet;
368 
369             /* Loop through the categories */
370             final Iterator<MoneyWiseTransCategory> myIterator = iterator();
371             while (myIterator.hasNext()) {
372                 final MoneyWiseTransCategory myCurr = myIterator.next();
373 
374                 /* Ignore deleted events */
375                 if (myCurr.isDeleted()) {
376                     continue;
377                 }
378 
379                 /* Build the new linked category and add it to the list */
380                 final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(myList, myCurr);
381                 myList.add(myCategory);
382                 myCategory.resolveEditSetLinks();
383 
384                 /* Adjust the map */
385                 myCategory.adjustMapForItem();
386             }
387 
388             /* Return the list */
389             return myList;
390         }
391 
392         /**
393          * Add a new item to the core list.
394          *
395          * @param pCategory item
396          * @return the newly added item
397          */
398         @Override
399         public MoneyWiseTransCategory addCopyItem(final PrometheusDataItem pCategory) {
400             /* Can only clone a TransactionCategory */
401             if (!(pCategory instanceof MoneyWiseTransCategory)) {
402                 throw new UnsupportedOperationException();
403             }
404 
405             final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(this, (MoneyWiseTransCategory) pCategory);
406             add(myCategory);
407             return myCategory;
408         }
409 
410         /**
411          * Add a new item to the edit list.
412          *
413          * @return the new item
414          */
415         @Override
416         public MoneyWiseTransCategory addNewItem() {
417             final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(this);
418             add(myCategory);
419             return myCategory;
420         }
421 
422         /**
423          * Obtain the first category for the specified class.
424          *
425          * @param pClass the category class
426          * @return the category
427          */
428         public MoneyWiseTransCategory getSingularClass(final MoneyWiseTransCategoryClass pClass) {
429             /* Lookup in the map */
430             return getDataMap().findSingularItem(pClass);
431         }
432 
433         /**
434          * Obtain singular category for EventInfoClass.
435          *
436          * @param pInfoClass the Event info class
437          * @return the corresponding category.
438          */
439         public MoneyWiseTransCategory getEventInfoCategory(final MoneyWiseTransInfoClass pInfoClass) {
440             /* Switch on info class */
441             switch (pInfoClass) {
442                 case TAXCREDIT:
443                     return getSingularClass(MoneyWiseTransCategoryClass.INCOMETAX);
444                 case DEEMEDBENEFIT:
445                     return getSingularClass(MoneyWiseTransCategoryClass.VIRTUALINCOME);
446                 case EMPLOYEENATINS:
447                     return getSingularClass(MoneyWiseTransCategoryClass.EMPLOYEENATINS);
448                 case EMPLOYERNATINS:
449                     return getSingularClass(MoneyWiseTransCategoryClass.EMPLOYERNATINS);
450                 case WITHHELD:
451                     return getSingularClass(MoneyWiseTransCategoryClass.WITHHELD);
452                 default:
453                     return null;
454             }
455         }
456 
457         @Override
458         public MoneyWiseTransCategory addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
459             /* Create the category */
460             final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(this, pValues);
461 
462             /* Check that this CategoryId has not been previously added */
463             if (!isIdUnique(myCategory.getIndexedId())) {
464                 myCategory.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
465                 throw new MoneyWiseDataException(myCategory, ERROR_VALIDATION);
466             }
467 
468             /* Add to the list */
469             add(myCategory);
470 
471             /* Return it */
472             return myCategory;
473         }
474 
475         @Override
476         protected MoneyWiseTransCategoryDataMap allocateDataMap() {
477             return new MoneyWiseTransCategoryDataMap();
478         }
479     }
480 
481     /**
482      * The dataMap class.
483      */
484     public static class MoneyWiseTransCategoryDataMap
485             extends MoneyWiseCategoryDataMap<MoneyWiseTransCategory> {
486         /**
487          * Report fields.
488          */
489         private static final MetisFieldSet<MoneyWiseTransCategoryDataMap> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseTransCategoryDataMap.class);
490 
491         /*
492          * Declare Fields.
493          */
494         static {
495             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARMAP, MoneyWiseTransCategoryDataMap::getSingularMap);
496             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARCOUNTS, MoneyWiseTransCategoryDataMap::getSingularCountMap);
497         }
498 
499         /**
500          * Map of category counts.
501          */
502         private final Map<Integer, Integer> theCategoryCountMap;
503 
504         /**
505          * Map of singular categories.
506          */
507         private final Map<Integer, MoneyWiseTransCategory> theCategoryMap;
508 
509         /**
510          * Constructor.
511          */
512         public MoneyWiseTransCategoryDataMap() {
513             /* Create the maps */
514             theCategoryCountMap = new HashMap<>();
515             theCategoryMap = new HashMap<>();
516         }
517 
518         @Override
519         public MetisFieldSet<MoneyWiseTransCategoryDataMap> getDataFieldSet() {
520             return FIELD_DEFS;
521         }
522 
523         @Override
524         public String formatObject(final OceanusDataFormatter pFormatter) {
525             return FIELD_DEFS.getName();
526         }
527 
528         /**
529          * Obtain the categoryMap.
530          *
531          * @return the map
532          */
533         private Map<Integer, MoneyWiseTransCategory> getSingularMap() {
534             return theCategoryMap;
535         }
536 
537         /**
538          * Obtain the categoryCountMap.
539          *
540          * @return the map
541          */
542         private Map<Integer, Integer> getSingularCountMap() {
543             return theCategoryCountMap;
544         }
545 
546         @Override
547         public void resetMap() {
548             super.resetMap();
549             theCategoryCountMap.clear();
550             theCategoryMap.clear();
551         }
552 
553         @Override
554         public void adjustForItem(final PrometheusDataItem pItem) {
555             /* Access item */
556             final MoneyWiseTransCategory myItem = (MoneyWiseTransCategory) pItem;
557 
558             /* If the class is singular */
559             final MoneyWiseTransCategoryClass myClass = Objects.requireNonNull(myItem.getCategoryTypeClass());
560             if (myClass.isSingular()) {
561                 /* Adjust category count */
562                 final Integer myId = myClass.getClassId();
563                 final Integer myCount = theCategoryCountMap.get(myId);
564                 if (myCount == null) {
565                     theCategoryCountMap.put(myId, ONE);
566                 } else {
567                     theCategoryCountMap.put(myId, myCount + 1);
568                 }
569 
570                 /* Adjust category map */
571                 theCategoryMap.put(myId, myItem);
572             }
573 
574             /* Adjust name count */
575             adjustForItem(myItem, myItem.getName());
576         }
577 
578         /**
579          * find singular item.
580          *
581          * @param pClass the class to look up
582          * @return the matching item
583          */
584         public MoneyWiseTransCategory findSingularItem(final MoneyWiseTransCategoryClass pClass) {
585             return theCategoryMap.get(pClass.getClassId());
586         }
587 
588         /**
589          * Check validity of singular count.
590          *
591          * @param pClass the class to look up
592          * @return true/false
593          */
594         public boolean validSingularCount(final MoneyWiseTransCategoryClass pClass) {
595             final Integer myResult = theCategoryCountMap.get(pClass.getClassId());
596             return ONE.equals(myResult);
597         }
598     }
599 }