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.metis.data.MetisDataDifference;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
22  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
23  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
24  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
25  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
26  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryType;
27  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryType.MoneyWiseTransCategoryTypeList;
28  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransInfoClass;
29  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
30  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
31  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
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 myCategory)) {
253             return false;
254         }
255 
256         /* Store the current detail into history */
257         pushHistory();
258 
259         /* Apply basic changes */
260         applyBasicChanges(myCategory);
261 
262         /* Update the category type if required */
263         if (!MetisDataDifference.isEqual(getCategoryType(), myCategory.getCategoryType())) {
264             setValueType(myCategory.getCategoryType());
265         }
266 
267         /* Check for changes */
268         return checkForHistory();
269     }
270 
271     /**
272      * Is the category hidden?
273      *
274      * @return true/false
275      */
276     public boolean isHidden() {
277         final MoneyWiseTransCategoryClass myClass = this.getCategoryTypeClass();
278         return myClass != null
279                 && myClass.isHiddenType();
280     }
281 
282     /**
283      * The Transaction Category List class.
284      */
285     public static class MoneyWiseTransCategoryList
286             extends MoneyWiseCategoryBaseList<MoneyWiseTransCategory> {
287         /**
288          * Report fields.
289          */
290         private static final MetisFieldSet<MoneyWiseTransCategoryList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseTransCategoryList.class);
291 
292         /**
293          * The EditSet.
294          */
295         private PrometheusEditSet theEditSet;
296 
297         /**
298          * Construct an empty CORE Category list.
299          *
300          * @param pData the DataSet for the list
301          */
302         public MoneyWiseTransCategoryList(final MoneyWiseDataSet pData) {
303             super(pData, MoneyWiseTransCategory.class, MoneyWiseBasicDataType.TRANSCATEGORY);
304         }
305 
306         /**
307          * Constructor for a cloned List.
308          *
309          * @param pSource the source List
310          */
311         protected MoneyWiseTransCategoryList(final MoneyWiseTransCategoryList pSource) {
312             super(pSource);
313         }
314 
315         @Override
316         public MetisFieldSet<MoneyWiseTransCategoryList> getDataFieldSet() {
317             return FIELD_DEFS;
318         }
319 
320         @Override
321         public String listName() {
322             return LIST_NAME;
323         }
324 
325         @Override
326         public MetisFieldSetDef getItemFields() {
327             return MoneyWiseTransCategory.FIELD_DEFS;
328         }
329 
330         /**
331          * Obtain editSet.
332          *
333          * @return the editSet
334          */
335         public PrometheusEditSet getEditSet() {
336             return theEditSet;
337         }
338 
339         @Override
340         public MoneyWiseTransCategoryDataMap getDataMap() {
341             return (MoneyWiseTransCategoryDataMap) super.getDataMap();
342         }
343 
344         @Override
345         protected MoneyWiseTransCategoryList getEmptyList(final PrometheusListStyle pStyle) {
346             final MoneyWiseTransCategoryList myList = new MoneyWiseTransCategoryList(this);
347             myList.setStyle(pStyle);
348             return myList;
349         }
350 
351         /**
352          * Derive Edit list.
353          *
354          * @param pEditSet the editSet
355          * @return the edit list
356          * @throws OceanusException on error
357          */
358         public MoneyWiseTransCategoryList deriveEditList(final PrometheusEditSet pEditSet) throws OceanusException {
359             /* Build an empty List */
360             final MoneyWiseTransCategoryList myList = getEmptyList(PrometheusListStyle.EDIT);
361             myList.ensureMap();
362             pEditSet.setEditEntryList(MoneyWiseBasicDataType.TRANSCATEGORY, myList);
363             myList.getValidator().setEditSet(pEditSet);
364 
365             /* Store the editSet */
366             myList.theEditSet = pEditSet;
367 
368             /* Loop through the categories */
369             final Iterator<MoneyWiseTransCategory> myIterator = iterator();
370             while (myIterator.hasNext()) {
371                 final MoneyWiseTransCategory myCurr = myIterator.next();
372 
373                 /* Ignore deleted events */
374                 if (myCurr.isDeleted()) {
375                     continue;
376                 }
377 
378                 /* Build the new linked category and add it to the list */
379                 final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(myList, myCurr);
380                 myList.add(myCategory);
381                 myCategory.resolveEditSetLinks();
382 
383                 /* Adjust the map */
384                 myCategory.adjustMapForItem();
385             }
386 
387             /* Return the list */
388             return myList;
389         }
390 
391         /**
392          * Add a new item to the core list.
393          *
394          * @param pCategory item
395          * @return the newly added item
396          */
397         @Override
398         public MoneyWiseTransCategory addCopyItem(final PrometheusDataItem pCategory) {
399             /* Can only clone a TransactionCategory */
400             if (!(pCategory instanceof MoneyWiseTransCategory)) {
401                 throw new UnsupportedOperationException();
402             }
403 
404             final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(this, (MoneyWiseTransCategory) pCategory);
405             add(myCategory);
406             return myCategory;
407         }
408 
409         /**
410          * Add a new item to the edit list.
411          *
412          * @return the new item
413          */
414         @Override
415         public MoneyWiseTransCategory addNewItem() {
416             final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(this);
417             add(myCategory);
418             return myCategory;
419         }
420 
421         /**
422          * Obtain the first category for the specified class.
423          *
424          * @param pClass the category class
425          * @return the category
426          */
427         public MoneyWiseTransCategory getSingularClass(final MoneyWiseTransCategoryClass pClass) {
428             /* Lookup in the map */
429             return getDataMap().findSingularItem(pClass);
430         }
431 
432         /**
433          * Obtain singular category for EventInfoClass.
434          *
435          * @param pInfoClass the Event info class
436          * @return the corresponding category.
437          */
438         public MoneyWiseTransCategory getEventInfoCategory(final MoneyWiseTransInfoClass pInfoClass) {
439             /* Switch on info class */
440             switch (pInfoClass) {
441                 case TAXCREDIT:
442                     return getSingularClass(MoneyWiseTransCategoryClass.INCOMETAX);
443                 case DEEMEDBENEFIT:
444                     return getSingularClass(MoneyWiseTransCategoryClass.VIRTUALINCOME);
445                 case EMPLOYEENATINS:
446                     return getSingularClass(MoneyWiseTransCategoryClass.EMPLOYEENATINS);
447                 case EMPLOYERNATINS:
448                     return getSingularClass(MoneyWiseTransCategoryClass.EMPLOYERNATINS);
449                 case WITHHELD:
450                     return getSingularClass(MoneyWiseTransCategoryClass.WITHHELD);
451                 default:
452                     return null;
453             }
454         }
455 
456         @Override
457         public MoneyWiseTransCategory addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
458             /* Create the category */
459             final MoneyWiseTransCategory myCategory = new MoneyWiseTransCategory(this, pValues);
460 
461             /* Check that this CategoryId has not been previously added */
462             if (!isIdUnique(myCategory.getIndexedId())) {
463                 myCategory.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
464                 throw new MoneyWiseDataException(myCategory, ERROR_VALIDATION);
465             }
466 
467             /* Add to the list */
468             add(myCategory);
469 
470             /* Return it */
471             return myCategory;
472         }
473 
474         @Override
475         protected MoneyWiseTransCategoryDataMap allocateDataMap() {
476             return new MoneyWiseTransCategoryDataMap();
477         }
478     }
479 
480     /**
481      * The dataMap class.
482      */
483     public static class MoneyWiseTransCategoryDataMap
484             extends MoneyWiseCategoryDataMap<MoneyWiseTransCategory> {
485         /**
486          * Report fields.
487          */
488         private static final MetisFieldSet<MoneyWiseTransCategoryDataMap> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseTransCategoryDataMap.class);
489 
490         /*
491          * Declare Fields.
492          */
493         static {
494             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARMAP, MoneyWiseTransCategoryDataMap::getSingularMap);
495             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARCOUNTS, MoneyWiseTransCategoryDataMap::getSingularCountMap);
496         }
497 
498         /**
499          * Map of category counts.
500          */
501         private final Map<Integer, Integer> theCategoryCountMap;
502 
503         /**
504          * Map of singular categories.
505          */
506         private final Map<Integer, MoneyWiseTransCategory> theCategoryMap;
507 
508         /**
509          * Constructor.
510          */
511         public MoneyWiseTransCategoryDataMap() {
512             /* Create the maps */
513             theCategoryCountMap = new HashMap<>();
514             theCategoryMap = new HashMap<>();
515         }
516 
517         @Override
518         public MetisFieldSet<MoneyWiseTransCategoryDataMap> getDataFieldSet() {
519             return FIELD_DEFS;
520         }
521 
522         @Override
523         public String formatObject(final OceanusDataFormatter pFormatter) {
524             return FIELD_DEFS.getName();
525         }
526 
527         /**
528          * Obtain the categoryMap.
529          *
530          * @return the map
531          */
532         private Map<Integer, MoneyWiseTransCategory> getSingularMap() {
533             return theCategoryMap;
534         }
535 
536         /**
537          * Obtain the categoryCountMap.
538          *
539          * @return the map
540          */
541         private Map<Integer, Integer> getSingularCountMap() {
542             return theCategoryCountMap;
543         }
544 
545         @Override
546         public void resetMap() {
547             super.resetMap();
548             theCategoryCountMap.clear();
549             theCategoryMap.clear();
550         }
551 
552         @Override
553         public void adjustForItem(final PrometheusDataItem pItem) {
554             /* Access item */
555             final MoneyWiseTransCategory myItem = (MoneyWiseTransCategory) pItem;
556 
557             /* If the class is singular */
558             final MoneyWiseTransCategoryClass myClass = Objects.requireNonNull(myItem.getCategoryTypeClass());
559             if (myClass.isSingular()) {
560                 /* Adjust category count */
561                 final Integer myId = myClass.getClassId();
562                 final Integer myCount = theCategoryCountMap.get(myId);
563                 if (myCount == null) {
564                     theCategoryCountMap.put(myId, ONE);
565                 } else {
566                     theCategoryCountMap.put(myId, myCount + 1);
567                 }
568 
569                 /* Adjust category map */
570                 theCategoryMap.put(myId, myItem);
571             }
572 
573             /* Adjust name count */
574             adjustForItem(myItem, myItem.getName());
575         }
576 
577         /**
578          * find singular item.
579          *
580          * @param pClass the class to look up
581          * @return the matching item
582          */
583         public MoneyWiseTransCategory findSingularItem(final MoneyWiseTransCategoryClass pClass) {
584             return theCategoryMap.get(pClass.getClassId());
585         }
586 
587         /**
588          * Check validity of singular count.
589          *
590          * @param pClass the class to look up
591          * @return true/false
592          */
593         public boolean validSingularCount(final MoneyWiseTransCategoryClass pClass) {
594             final Integer myResult = theCategoryCountMap.get(pClass.getClassId());
595             return ONE.equals(myResult);
596         }
597     }
598 }