Skip to content

Commit

Permalink
Pull request finos#302: CTCTOWALTZ-2837 cost overlay on grids 6728
Browse files Browse the repository at this point in the history
Merge in WALTZ/waltz from WALTZ/waltz-jws:CTCTOWALTZ-2837-cost-overlay-on-grids-6728 to db-feature/waltz-6728-scheduled-job-to-allocate-measurable-costs

* commit 'cd00153b2f095581f1ed0c8d18a000bfc5ba5945':
  Ensure find all returns the complete list of kinds
  Scheduled job for allocating costs to measurable ratings
  • Loading branch information
jessica-woodland-scott-db authored and db-waltz committed Sep 12, 2023
2 parents b0e79a1 + cd00153 commit fee5825
Show file tree
Hide file tree
Showing 13 changed files with 493 additions and 12,049 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.finos.waltz.data.aggregate_overlay_diagram;

import org.finos.waltz.common.StreamUtilities.Siphon;
import org.finos.waltz.model.EntityReference;
import org.finos.waltz.model.aggregate_overlay_diagram.overlay.AllocationDerivation;
import org.finos.waltz.model.aggregate_overlay_diagram.overlay.CostWidgetDatum;
Expand All @@ -16,37 +15,28 @@
import org.finos.waltz.schema.tables.MeasurableRating;
import org.jooq.DSLContext;
import org.jooq.Record1;
import org.jooq.Record5;
import org.jooq.Record7;
import org.jooq.Select;
import org.jooq.SelectConditionStep;
import org.jooq.lambda.tuple.Tuple2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toSet;
import static org.finos.waltz.common.CollectionUtilities.filter;
import static org.finos.waltz.common.CollectionUtilities.map;
import static org.finos.waltz.common.CollectionUtilities.sumInts;
import static org.finos.waltz.common.MapUtilities.groupBy;
import static org.finos.waltz.common.SetUtilities.union;
import static org.finos.waltz.common.StreamUtilities.mkSiphon;
import static org.finos.waltz.data.aggregate_overlay_diagram.AggregateOverlayDiagramUtilities.loadExpandedCellMappingsForDiagram;
import static org.finos.waltz.data.aggregate_overlay_diagram.AggregateOverlayDiagramUtilities.toMeasurableIds;
import static org.finos.waltz.data.cost.CostUtils.calculateAllocatedCosts;

@Repository
public class AppCostWidgetDao {

private static final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100);

private static final AggregateOverlayDiagramCellData cd = AggregateOverlayDiagramCellData.AGGREGATE_OVERLAY_DIAGRAM_CELL_DATA;
private static final EntityHierarchy eh = EntityHierarchy.ENTITY_HIERARCHY;
private static final MeasurableRating mr = MeasurableRating.MEASURABLE_RATING;
Expand Down Expand Up @@ -111,7 +101,7 @@ private CostWidgetDatum processMeasurableBackingsForCell(String cellRef,
// backing refs may not be measurables (i.e. app groups, org units, people)
Set<MeasurableCostEntry> costMappings = backingMeasurableIds
.stream()
.flatMap(r -> processCostsForMeasurable(
.flatMap(r -> calculateAllocatedCosts(
costDataByMeasurableId.getOrDefault(r, emptySet()),
costDataByAppId)
.stream())
Expand All @@ -124,88 +114,19 @@ private CostWidgetDatum processMeasurableBackingsForCell(String cellRef,
}


private Set<MeasurableCostEntry> processCostsForMeasurable(Collection<MeasurableCostEntry> costsForMeasurable,
Map<Long, Collection<MeasurableCostEntry>> costDataByAppId) {

Siphon<MeasurableCostEntry> noCostsSiphon = mkSiphon(mce -> mce.overallCost() == null);
Siphon<MeasurableCostEntry> noAllocationSiphon = mkSiphon(mce -> mce.allocationPercentage() == null);

Set<MeasurableCostEntry> explicitCosts = costsForMeasurable
.stream()
.filter(noCostsSiphon)
.filter(noAllocationSiphon)
.map(d -> {
BigDecimal allocPercentage = BigDecimal.valueOf(d.allocationPercentage());

BigDecimal allocatedCost = allocPercentage
.divide(ONE_HUNDRED, 2, RoundingMode.HALF_UP)
.multiply(d.overallCost());

return ImmutableMeasurableCostEntry.copyOf(d).withAllocatedCost(allocatedCost);
})
.collect(toSet());

Set<MeasurableCostEntry> implicitShareOfCost = noAllocationSiphon
.stream()
.map(d -> {
long appId = d.appId();
Collection<MeasurableCostEntry> otherRatings = costDataByAppId.getOrDefault(appId, emptySet());
if (otherRatings.size() == 1) {
// only one rating, therefore entire cost is on this measurable
return ImmutableMeasurableCostEntry
.copyOf(d)
.withAllocationPercentage(100)
.withAllocatedCost(d.overallCost());
} else {
Collection<MeasurableCostEntry> othersWithAllocations = filter(otherRatings, r -> r.allocationPercentage() != null);
if (othersWithAllocations.isEmpty()) {
// no other allocations (but more than 1 rating), therefore share the total out equally
BigDecimal otherRatingsCount = BigDecimal.valueOf(otherRatings.size());

BigDecimal remainingCost = d.overallCost()
.divide(otherRatingsCount, 2, RoundingMode.HALF_UP);

return ImmutableMeasurableCostEntry
.copyOf(d)
.withAllocatedCost(remainingCost)
.withAllocationPercentage(100 / otherRatings.size());
} else {
Long overallAllocatedPercentage = sumInts(map(othersWithAllocations, MeasurableCostEntry::allocationPercentage));
BigDecimal unallocatedPercentage = BigDecimal.valueOf(100 - overallAllocatedPercentage);
BigDecimal numUnallocatedRatings = BigDecimal.valueOf(otherRatings.size() - othersWithAllocations.size());

BigDecimal shareOfUnallocated = unallocatedPercentage
.divide(numUnallocatedRatings, 2,
RoundingMode.HALF_UP);

BigDecimal shareOfRemainingCost = shareOfUnallocated
.divide(ONE_HUNDRED, 2, RoundingMode.HALF_UP)
.multiply(d.overallCost());

return ImmutableMeasurableCostEntry
.copyOf(d)
.withAllocationPercentage(shareOfUnallocated.intValue())
.withAllocatedCost(shareOfRemainingCost);
}
}
})
.collect(toSet());

return union(explicitCosts, implicitShareOfCost);
}


private Set<MeasurableCostEntry> fetchCostData(DSLContext dsl,
Set<Long> costKindIds,
long allocationSchemeId,
Select<Record1<Long>> inScopeApplicationSelector,
Set<Long> backingMeasurableIds) {

SelectConditionStep<Record5<Long, Long, Integer, Long, BigDecimal>> qry = dsl
SelectConditionStep<Record7<Long, Long, Long, Integer, Long, Integer, BigDecimal>> qry = dsl
.select(mr.MEASURABLE_ID,
mr.ENTITY_ID,
mr.ID,
a.ALLOCATION_PERCENTAGE,
ck.ID,
c.YEAR,
c.AMOUNT)
.from(mr)
.innerJoin(ck).on(ck.ID.in(costKindIds))
Expand All @@ -229,6 +150,8 @@ private Set<MeasurableCostEntry> fetchCostData(DSLContext dsl,
: AllocationDerivation.EXPLICIT)
.costKindId(r.get(ck.ID))
.overallCost(r.get(c.AMOUNT))
.measurableRatingId(r.get(mr.ID))
.year(r.get(c.YEAR))
.build());
}

Expand Down
Loading

0 comments on commit fee5825

Please sign in to comment.