Skip to content

Commit 8f0e35f

Browse files
authored
Merge pull request #108 from flock-community/fix/small_bugs
Fix/small bugs
2 parents d2620ba + 01b0af6 commit 8f0e35f

File tree

10 files changed

+195
-67
lines changed

10 files changed

+195
-67
lines changed

src/main/kotlin/controllers/AggregationController.kt

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,42 @@
11
package community.flock.eco.workday.controllers
22

3+
import community.flock.eco.workday.model.AggregationClient
4+
import community.flock.eco.workday.model.AggregationMonth
5+
import community.flock.eco.workday.model.AggregationPerson
36
import community.flock.eco.workday.services.AggregationService
4-
import java.math.BigDecimal
5-
import java.time.LocalDate
6-
import java.time.YearMonth
77
import org.springframework.security.access.prepost.PreAuthorize
88
import org.springframework.web.bind.annotation.GetMapping
99
import org.springframework.web.bind.annotation.RequestMapping
1010
import org.springframework.web.bind.annotation.RequestParam
1111
import org.springframework.web.bind.annotation.RestController
12+
import java.time.LocalDate
13+
import java.time.YearMonth
1214

1315
@RestController
1416
@RequestMapping("/api/aggregations")
1517
class AggregationController(
1618
val aggregationService: AggregationService
1719
) {
18-
20+
1921
@GetMapping("/total-per-client", params = ["year"])
2022
@PreAuthorize("hasAuthority('AggregationAuthority.READ')")
21-
fun revenuePerClientByYear(@RequestParam year: Int): List<Map<String, Any>> {
23+
fun revenuePerClientByYear(@RequestParam year: Int): List<AggregationClient> {
2224
val from = LocalDate.of(year, 1, 1)
2325
val to = LocalDate.of(year, 12, 31)
2426
return aggregationService.totalPerClient(from, to)
2527
}
2628

2729
@GetMapping("/total-per-person", params = ["year"])
2830
@PreAuthorize("hasAuthority('AggregationAuthority.READ')")
29-
fun totalsPerPersonByYear(@RequestParam year: Int): List<Map<String, Any>> {
30-
val from = LocalDate.of(year, 1,1)
31-
val to = LocalDate.of(year, 12,31)
31+
fun totalsPerPersonByYear(@RequestParam year: Int): List<AggregationPerson> {
32+
val from = LocalDate.of(year, 1, 1)
33+
val to = LocalDate.of(year, 12, 31)
3234
return aggregationService.totalPerPerson(from, to)
3335
}
3436

3537
@GetMapping("/total-per-person", params = ["year", "month"])
3638
@PreAuthorize("hasAuthority('AggregationAuthority.READ')")
37-
fun totalsPerPersonByYearMonth(@RequestParam year: Int, @RequestParam month: Int): List<Map<String, Any>> {
39+
fun totalsPerPersonByYearMonth(@RequestParam year: Int, @RequestParam month: Int): List<AggregationPerson> {
3840
val yearMonth = YearMonth.of(year, month)
3941
val from = yearMonth.atDay(1)
4042
val to = yearMonth.atEndOfMonth()
@@ -43,7 +45,7 @@ class AggregationController(
4345

4446
@GetMapping("/total-per-month", params = ["year"])
4547
@PreAuthorize("hasAuthority('AggregationAuthority.READ')")
46-
fun totalsPerMonthByYear(@RequestParam year: Int): List<Map<String, Any>> {
48+
fun totalsPerMonthByYear(@RequestParam year: Int): List<AggregationMonth> {
4749
val from = LocalDate.of(year, 1, 1)
4850
val to = LocalDate.of(year, 12, 31)
4951
return aggregationService.totalPerMonth(from, to)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package community.flock.eco.workday.model
2+
3+
import java.math.BigDecimal
4+
5+
data class AggregationClient(
6+
val name: String,
7+
val revenueGross: BigDecimal
8+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package community.flock.eco.workday.model
2+
3+
import java.math.BigDecimal
4+
5+
data class AggregationMonth(
6+
val yearMonth: String,
7+
val countContractInternal: Int,
8+
val forecastRevenueGross: BigDecimal,
9+
val forecastRevenueNet: BigDecimal,
10+
val forecastHoursGross: BigDecimal,
11+
val actualRevenue: BigDecimal,
12+
val actualHours: BigDecimal,
13+
val actualCostContractInternal: BigDecimal,
14+
val actualCostContractExternal: BigDecimal,
15+
val actualCostContractManagement: BigDecimal,
16+
val actualCostContractService: BigDecimal
17+
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package community.flock.eco.workday.model
2+
3+
import java.math.BigDecimal
4+
5+
data class AggregationPerson(
6+
val name: String,
7+
val contractTypes: Set<String>,
8+
val sickDays:BigDecimal,
9+
val workDays:BigDecimal,
10+
val assignment:Int,
11+
val event:Int,
12+
val total:Int,
13+
val holiDayUsed:BigDecimal,
14+
val holiDayBalance:BigDecimal,
15+
val revenue:BigDecimal
16+
)

src/main/kotlin/services/AggregationService.kt

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import community.flock.eco.workday.ApplicationConstants
44
import community.flock.eco.workday.interfaces.Period
55
import community.flock.eco.workday.interfaces.filterInRange
66
import community.flock.eco.workday.interfaces.inRange
7+
import community.flock.eco.workday.model.AggregationClient
8+
import community.flock.eco.workday.model.AggregationMonth
9+
import community.flock.eco.workday.model.AggregationPerson
710
import community.flock.eco.workday.model.Assignment
811
import community.flock.eco.workday.model.Contract
912
import community.flock.eco.workday.model.ContractExternal
@@ -39,14 +42,14 @@ class AggregationService(
3942
private val applicationConstants: ApplicationConstants
4043
) {
4144

42-
fun totalPerClient(from: LocalDate, to: LocalDate): List<Map<String, Any>> {
45+
fun totalPerClient(from: LocalDate, to: LocalDate): List<AggregationClient> {
4346
val allAssignments = assignmentService.findAllActive(from, to)
4447
return clientService.findAll()
45-
.map {
46-
client -> mapOf(
47-
"name" to client.name,
48-
"revenue" to allAssignments
49-
.filter{ it.client == client}
48+
.map { client ->
49+
AggregationClient(
50+
name = client.name,
51+
revenueGross = allAssignments
52+
.filter { it.client == client }
5053
.toMapWorkingDay(from, to).values
5154
.flatten()
5255
.fold(BigDecimal.ZERO) { acc, cur -> acc + cur.revenuePerDay() }
@@ -56,101 +59,108 @@ class AggregationService(
5659

5760
@Transactional
5861
fun
59-
totalPerPerson(from: LocalDate, to: LocalDate): List<Map<String, Any>> {
60-
val all = fetchAll(from, to)
62+
totalPerPerson(from: LocalDate, to: LocalDate): List<AggregationPerson> {
63+
val all = fetchAllData(from, to)
6164
val totalWorkDays = countWorkDaysInPeriod(from, to)
6265
return all.allPersons()
6366
.sortedBy { it.lastname }
6467
.map { person ->
65-
mapOf(
66-
"name" to "${person.firstname} ${person.lastname}",
67-
"type" to all.contract
68+
AggregationPerson(
69+
name = "${person.firstname} ${person.lastname}",
70+
contractTypes = all.contract
6871
.filter { it.person == person }
69-
.map { it::class.simpleName }
70-
.toSet()
71-
.joinToString(","),
72-
"sickDays" to all.sickDay.filter { it.person == person }.totalHoursInPeriod(from, to),
73-
"workDays" to all.workDay.filter { it.assignment.person == person }.totalHoursInPeriod(from, to),
74-
"assignment" to all.assignment
72+
.mapNotNull { it::class.simpleName }
73+
.toSet(),
74+
sickDays = all.sickDay.filter { it.person == person }.totalHoursInPeriod(from, to),
75+
workDays = all.workDay.filter { it.assignment.person == person }.totalHoursInPeriod(from, to),
76+
assignment = all.assignment
7577
.filter { it.person == person }
7678
.toMapWorkingDay(from, to).values
7779
.flatten()
7880
.fold(0) { acc, cur -> acc + cur.hoursPerWeek }
7981
.div(5),
80-
"event" to all.event
82+
event = all.event
8183
.filter { it.persons.isEmpty() || it.persons.contains(person) }
8284
.fold(0) { acc, cur -> acc + cur.hours },
83-
"total" to all.contract
85+
total = all.contract
8486
.filter { it.person == person }
8587
.map { it.totalHoursPerWeek() }
8688
.sum()
8789
.let { countWorkDaysInPeriod(from, to) * 8 * it / 40 },
88-
"holiDayUsed" to all.holiDay.filter { it.person == person }.totalHoursInPeriod(from, to),
89-
"holiDayBalance" to all.contract
90+
holiDayUsed = all.holiDay.filter { it.person == person }.totalHoursInPeriod(from, to),
91+
holiDayBalance = all.contract
9092
.filter { it.person == person }
9193
.filterIsInstance(ContractInternal::class.java)
9294
.mapWorkingDay(from, to)
93-
.map { BigDecimal(it.hoursPerWeek * 24 * 8) }
95+
.map { BigDecimal(it.hoursPerWeek * 24 * 8) }
9496
.sum()
95-
.divide(BigDecimal(totalWorkDays * 40),10, RoundingMode.HALF_UP),
96-
"revenue" to all.workDay
97+
.divide(BigDecimal(totalWorkDays * 40), 10, RoundingMode.HALF_UP),
98+
revenue = all.workDay
9799
.filter { it.assignment.person == person }
98100
.sumAmount()
99101
)
100102
}
101103
}
102104

103105
@Transactional
104-
fun totalPerMonth(from: LocalDate, to: LocalDate): List<Map<String, Any>> {
106+
fun totalPerMonth(from: LocalDate, to: LocalDate): List<AggregationMonth> {
105107

106-
val all = fetchAll(from, to)
108+
val all = fetchAllData(from, to)
107109
val months = (0..ChronoUnit.MONTHS.between(from, to))
108110
.map { from.plusMonths(it) }
109111
.map { YearMonth.from(it) }
110112
return months
111113
.map { yearMonth ->
112-
mapOf(
113-
"yearMonth" to yearMonth.toString(),
114-
"countContractInternal" to all.contract
114+
AggregationMonth(
115+
yearMonth = yearMonth.toString(),
116+
countContractInternal = all.contract
115117
.filterIsInstance(ContractInternal::class.java)
116118
.map { it.toDateRangeInPeriod(yearMonth) }
117119
.filter { it.isNotEmpty() }
118120
.count(),
119-
"forecastRevenueGross" to all.assignment
121+
forecastRevenueGross = all.assignment
120122
.mapWorkingDay(yearMonth)
121123
.sumAmount(yearMonth),
122-
"forecastRevenueNet" to all.assignment
124+
forecastRevenueNet = all.assignment
123125
.mapWorkingDay(yearMonth)
124126
.sumAmount(yearMonth)
125127
.multiply(netRevenueFactor(from, to)),
126-
"actualRevenue" to all.workDay
128+
forecastHoursGross = all.assignment
129+
.mapWorkingDay(yearMonth)
130+
.sumAssignmentHoursPerWeek()
131+
.divide(yearMonth.countWorkDaysInMonth().times(5).toBigDecimal(), 10, RoundingMode.HALF_UP),
132+
actualRevenue = all.workDay
127133
.map { it.totalRevenueInPeriod(yearMonth) }
128134
.sum(),
129-
"actualCostContractInternal" to all.contract
135+
actualHours = all.workDay
136+
.map { it.totalHoursInPeriod(yearMonth) }
137+
.sum()
138+
.divide(yearMonth.countWorkDaysInMonth().toBigDecimal(), 10, RoundingMode.HALF_UP),
139+
actualCostContractInternal = all.contract
130140
.filterIsInstance(ContractInternal::class.java)
131141
.map { it.totalCostInPeriod(yearMonth) }
132142
.sum(),
133-
"actualCostContractExternal" to yearMonth.toDateRange()
143+
actualCostContractExternal = yearMonth.toDateRange()
134144
.flatMap { date ->
135145
cartesianProducts(all.contract.filterIsInstance(ContractExternal::class.java), all.workDay)
136146
.filter { (contract, workDay) -> contract.person == workDay.assignment.person }
137147
.filter { (contract, workDay) -> contract.inRange(date) && workDay.inRange(date) }
138148
.map { (contract, workDay) -> contract.hourlyRate.toBigDecimal() * workDay.hoursPerDay().getValue(date) }
139149
}
140150
.sum(),
141-
"actualCostContractManagement" to all.contract
151+
actualCostContractManagement = all.contract
142152
.filterIsInstance(ContractManagement::class.java)
143153
.map { it.totalCostInPeriod(yearMonth) }
144154
.sum(),
145-
"actualCostContractService" to all.contract
155+
actualCostContractService = all.contract
146156
.filterIsInstance(ContractServiceModel::class.java)
147157
.map { it.totalCostInPeriod(yearMonth) }
148158
.sum()
149159
)
150160
}
151161
}
152162

153-
data class All(
163+
data class AllData(
154164
val sickDay: List<SickDay>,
155165
val holiDay: List<HoliDay>,
156166
val workDay: List<WorkDay>,
@@ -175,7 +185,7 @@ class AggregationService(
175185
.fold(0.0) { acc, cur -> acc + cur.hoursPerWeek / 5 }
176186
.pow(countWorkDaysInPeriod(from, to))
177187

178-
private fun All.allPersons(): Set<Person> {
188+
private fun AllData.allPersons(): Set<Person> {
179189
return (this.assignment.map { it.person } +
180190
this.contract.map { it.person } +
181191
this.sickDay.map { it.person } +
@@ -185,14 +195,14 @@ class AggregationService(
185195
.toSet()
186196
}
187197

188-
private fun fetchAll(from: LocalDate, to: LocalDate): All {
198+
private fun fetchAllData(from: LocalDate, to: LocalDate): AllData {
189199
val activeWorkDay = workDayService.findAllActive(from, to)
190200
val activeHoliDay = holiDayService.findAllActive(from, to)
191201
val activeSickDay = sickDayService.findAllActive(from, to)
192202
val activeAssignment = assignmentService.findAllActive(from, to)
193203
val activeContract = contractService.findAllActive(from, to)
194204
val activeEvent = eventService.findAllActive(from, to)
195-
return All(
205+
return AllData(
196206
activeSickDay,
197207
activeHoliDay,
198208
activeWorkDay,
@@ -303,6 +313,14 @@ private fun <T : Period> List<T>.mapWorkingDay(yearMonth: YearMonth) = yearMonth
303313
private fun <T : Period> Iterable<T>.sumAmount(yearMonth: YearMonth) = this
304314
.fold(BigDecimal.ZERO) { acc, cur -> acc + cur.amountPerWorkingDay(yearMonth) }
305315

316+
private fun Iterable<WorkDay>.sumWorkDayHoursPerWeek() = this
317+
.fold(BigDecimal.ZERO) { acc, cur -> acc + cur.hours.toBigDecimal() }
318+
319+
private fun Iterable<Assignment>.sumAssignmentHoursPerWeek() = this
320+
.fold(BigDecimal.ZERO) { acc, cur -> acc + cur.hoursPerWeek.toBigDecimal() }
321+
322+
323+
306324
private fun Iterable<WorkDay>.sumAmount() = this
307325
.fold(BigDecimal.ZERO) { acc, cur -> acc + (cur.hours * cur.assignment.hourlyRate).toBigDecimal() }
308326

@@ -339,6 +357,9 @@ fun WorkDay.totalRevenueInPeriod(period: Period): BigDecimal = this
339357
fun WorkDay.totalRevenueInPeriod(yearMonth: YearMonth): BigDecimal = this
340358
.totalRevenueInPeriod(yearMonth.toPeriod())
341359

360+
fun WorkDay.totalHoursInPeriod(yearMonth: YearMonth): BigDecimal = this
361+
.totalHoursInPeriod(yearMonth.toPeriod())
362+
342363
fun ContractInternal.totalCostInPeriod(yearMonth: YearMonth): BigDecimal = this
343364
.toDateRangeInPeriod(yearMonth)
344365
.map { this.monthlySalary.toBigDecimal() }
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React, {useEffect, useState} from "react"
2+
import BarChart from "recharts/es6/chart/BarChart"
3+
import CartesianGrid from "recharts/es6/cartesian/CartesianGrid"
4+
import XAxis from "recharts/es6/cartesian/XAxis"
5+
import YAxis from "recharts/es6/cartesian/YAxis"
6+
import Legend from "recharts/es6/component/Legend"
7+
import Tooltip from "recharts/es6/component/Tooltip"
8+
import Bar from "recharts/es6/cartesian/Bar"
9+
import ResponsiveContainer from "recharts/es6/component/ResponsiveContainer"
10+
import PropTypes from "prop-types"
11+
import {AlignedLoader} from "@flock-community/flock-eco-core/src/main/react/components/AlignedLoader"
12+
import {AggregationClient} from "../../clients/AggregationClient"
13+
14+
export function AverageHoursPerDayChart({year}) {
15+
const [state, setState] = useState(null)
16+
17+
useEffect(() => {
18+
const date = new Date()
19+
AggregationClient.totalPerMonthByYear(year || date.getFullYear()).then(res =>
20+
setState(res)
21+
)
22+
}, [])
23+
24+
if (!state) return <AlignedLoader />
25+
26+
return (
27+
<ResponsiveContainer>
28+
<BarChart data={state}>
29+
<CartesianGrid strokeDasharray="3 3" />
30+
<XAxis dataKey="name" />
31+
<YAxis />
32+
<Tooltip formatter={value => new Intl.NumberFormat("en").format(value)} />
33+
<Legend />
34+
<Bar stackId="forcast" dataKey="forecastHoursGross" fill="#1de8b5" />
35+
<Bar stackId="actual" dataKey="actualHours" fill="#3f51b5" />
36+
</BarChart>
37+
</ResponsiveContainer>
38+
)
39+
}
40+
41+
AverageHoursPerDayChart.propTypes = {
42+
year: PropTypes.number,
43+
}

src/main/react/components/charts/RevenuePerClientChart.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export function RevenuePerClientChart({year}) {
1818

1919
return (
2020
<ResponsiveContainer>
21-
<PieChart width={400} height={400}>
22-
<Pie data={state} valueKey="revenue" nameKey="name" fill="#3f51b5" label />
21+
<PieChart height={500}>
22+
<Pie data={state} valueKey="revenueGross" nameKey="name" fill="#3f51b5" label />
2323
<Tooltip formatter={value => new Intl.NumberFormat("en").format(value)} />
2424
</PieChart>
2525
</ResponsiveContainer>

0 commit comments

Comments
 (0)