Skip to content

Commit

Permalink
#276: Consider interruption in mandatory break (#277)
Browse files Browse the repository at this point in the history
Co-authored-by: kaklakariada <[email protected]>
  • Loading branch information
kaklakariada and kaklakariada authored May 1, 2024
1 parent e8a9e42 commit abbea6f
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 31 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

See [Release](https://github.com/itsallcode/white-rabbit/releases/tag/v1.10.0) / [Milestone](https://github.com/itsallcode/white-rabbit/milestone/12?closed=1)

## [1.9.0] - 2022-??-??
## [1.9.0] - 2024-??-??

See [Release](https://github.com/itsallcode/white-rabbit/releases/tag/v1.9.0) / [Milestone](https://github.com/itsallcode/white-rabbit/milestone/11?closed=1)

Expand All @@ -22,7 +22,8 @@ This release requires Java 21.

### New Features

* [#273](https://github.com/itsallcode/white-rabbit/pull/273): Added buttons to monthly report for jumping to the previous/next month
* [#273](https://github.com/itsallcode/white-rabbit/pull/273): Added buttons to monthly report for jumping to the previous/next month.
* [#276](https://github.com/itsallcode/white-rabbit/pull/276): Added config option `reduce_mandatory_break_by_interruption`.

### Bugfixes

Expand Down
6 changes: 4 additions & 2 deletions docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,12 @@ Restart WhiteRabbit after changing the configuration file.
* We recommend to configure this when starting to use WhiteRabbit.
* `mandatory_break`: mandatory break per day (default: 45 minutes). Format: see [below](#duration-format). Caution: This setting will also affect the past, i.e. the overtime of **all** days will be re-calculated.
* We recommend to configure this when starting to use WhiteRabbit.
* `reduce_mandatory_break_by_interruption`: Reduce the mandatory break by entered interruption (`true` or `false`, default: `false`). If this is `true`, the mandatory break will be set to zero when the interruption is longer than the mandatory break.
* We recommend to configure this when starting to use WhiteRabbit.

#### Duration format

Enter duration values in the format used by [Duration.parse()](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Duration.html#parse(java.lang.CharSequence)). Examples:
Enter duration values in the format used by [Duration.parse()](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/Duration.html#parse(java.lang.CharSequence)). Examples:

* `PT5H`: 5 hours
* `PT5H30M`: 5 hours and 30 minutes
Expand Down Expand Up @@ -221,4 +223,4 @@ The default values are:
csv.destination = $HOME
csv.separator = ","
csv.filter_for_weekdays = False
```
```
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public interface Config

Optional<Duration> getMandatoryBreak();

boolean reduceMandatoryBreakByInterruption();

default Path getProjectFile()
{
return getDataDir().resolve(PROJECTS_JSON);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ public Optional<Duration> getMandatoryBreak()
return getOptionalValue("mandatory_break").map(Duration::parse);
}

@Override
public boolean reduceMandatoryBreakByInterruption()
{
return getOptionalValue("reduce_mandatory_break_by_interruption").map(Boolean::valueOf).orElse(false);
}

@Override
public boolean allowMultipleInstances()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public ContractTermsService(final Config config)
this.config = config;
}


public Duration getMandatoryBreak(final DayRecord day)
{
if (!day.getType().isWorkDay())
Expand All @@ -28,7 +27,15 @@ public Duration getMandatoryBreak(final DayRecord day)
final Duration workingTime = day.getRawWorkingTime().minus(day.getInterruption());
if (workingTime.compareTo(MIN_WORKING_TIME_WITHOUT_BREAK) > 0)
{
return getMandatoryBreak();
Duration mandatoryBreak = getMandatoryBreak();
if (config.reduceMandatoryBreakByInterruption())
{
mandatoryBreak = mandatoryBreak.minus(day.getInterruption());
}
if (mandatoryBreak.isPositive())
{
return mandatoryBreak;
}
}
return Duration.ZERO;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ void getMandatoryBreak_returnsCustomValue()
assertThat(configFile.getMandatoryBreak()).isPresent().hasValue(Duration.ofMinutes(0));
}

@Test
void reduceMandatoryBreakByInterruption_returnsDefault()
{
when(propertiesMock.getProperty("reduce_mandatory_break_by_interruption")).thenReturn(null);
assertThat(configFile.reduceMandatoryBreakByInterruption()).isFalse();
}

@Test
void reduceMandatoryBreakByInterruption_returnsCustomValue()
{
when(propertiesMock.getProperty("reduce_mandatory_break_by_interruption")).thenReturn("true");
assertThat(configFile.reduceMandatoryBreakByInterruption()).isTrue();
}

@Test
void reduceMandatoryBreakByInterruption_invalidValue()
{
when(propertiesMock.getProperty("reduce_mandatory_break_by_interruption")).thenReturn("invalid");
assertThat(configFile.reduceMandatoryBreakByInterruption()).isFalse();
}

@Test
void getLocale_returnsDefault()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ public Optional<Duration> getMandatoryBreak()
return Optional.empty();
}

@Override
public boolean reduceMandatoryBreakByInterruption()
{
return false;
}

@Override
public boolean allowMultipleInstances()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import org.itsallcode.whiterabbit.logic.service.project.ProjectService;
import org.itsallcode.whiterabbit.logic.storage.data.JsonModelFactory;
import org.itsallcode.whiterabbit.logic.test.TestingConfig;
import org.itsallcode.whiterabbit.logic.test.TestingConfig.Builder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
Expand All @@ -27,6 +29,13 @@ class DayRecordTest
@Mock
private ProjectService projectServiceMock;
private final ModelFactory modelFactory = new JsonModelFactory();
private Builder configBuilder;

@BeforeEach
void createDefaultConfig()
{
configBuilder = TestingConfig.builder();
}

@Test
void mandatoryWorkingTimeIsZeroOnWeekend()
Expand Down Expand Up @@ -119,6 +128,30 @@ void testMandatoryBreakConsidersInterruptionLessThan6hours()
Duration.ZERO);
}

@Test
void testMandatoryBreakReducedByInterruption()
{
configBuilder.withReduceMandatoryBreakByInterruption(true);
assertMandatoryBreak(LocalDate.of(2018, 10, 1), LocalTime.of(8, 0), LocalTime.of(18, 0), Duration.ofMinutes(10),
Duration.ofMinutes(35));
}

@Test
void testMandatoryBreakNotReducedByInterruptionWhenWorkingLessThan6h()
{
configBuilder.withReduceMandatoryBreakByInterruption(true);
assertMandatoryBreak(LocalDate.of(2018, 10, 1), LocalTime.of(8, 0), LocalTime.of(13, 0), Duration.ofMinutes(10),
Duration.ZERO);
}

@Test
void testMandatoryBreakZeroForLongerInterruption()
{
configBuilder.withReduceMandatoryBreakByInterruption(true);
assertMandatoryBreak(LocalDate.of(2018, 10, 1), LocalTime.of(8, 0), LocalTime.of(18, 0), Duration.ofMinutes(50),
Duration.ZERO);
}

@Test
void testWorkingTime1h()
{
Expand Down Expand Up @@ -507,17 +540,17 @@ void dayWithActivitiesListIsNonDummy()
assertNonDummyDay(day);
}

private void assertDummyDay(DayRecord day)
private void assertDummyDay(final DayRecord day)
{
assertThat(day.isDummyDay()).as("dummy").isTrue();
}

private void assertNonDummyDay(DayRecord day)
private void assertNonDummyDay(final DayRecord day)
{
assertThat(day.isDummyDay()).as("dummy").isFalse();
}

private MonthIndex month(LocalDate date, Duration overtimePreviousMonth, DayData... days)
private MonthIndex month(final LocalDate date, final Duration overtimePreviousMonth, final DayData... days)
{
final MonthData jsonMonth = modelFactory.createMonthData();
jsonMonth.setDays(asList(days));
Expand All @@ -527,59 +560,63 @@ private MonthIndex month(LocalDate date, Duration overtimePreviousMonth, DayData
return MonthIndex.create(contractTerms(), projectServiceMock, modelFactory, jsonMonth);
}

private void assertOvertime(LocalDate date, LocalTime begin, LocalTime end, Duration expectedOvertime)
private void assertOvertime(final LocalDate date, final LocalTime begin, final LocalTime end,
final Duration expectedOvertime)
{
final DayRecord day = createDay(date, begin, end, null, null);
assertThat(day.getOvertime()).as("overtime").isEqualTo(expectedOvertime);
}

private void assertMandatoryBreak(LocalDate date, LocalTime begin, LocalTime end, Duration expectedDuration)
private void assertMandatoryBreak(final LocalDate date, final LocalTime begin, final LocalTime end,
final Duration expectedDuration)
{
assertMandatoryBreak(date, begin, end, Duration.ZERO, expectedDuration);
}

private void assertMandatoryBreak(LocalDate date, LocalTime begin, LocalTime end, Duration interruption,
Duration expectedDuration)
private void assertMandatoryBreak(final LocalDate date, final LocalTime begin, final LocalTime end,
final Duration interruption,
final Duration expectedDuration)
{
assertThat(getMandatoryBreak(date, begin, end, interruption)).as("mandatory break").isEqualTo(expectedDuration);
}

private void assertMandatoryWorkingTime(LocalDate date, LocalTime begin, LocalTime end,
Duration expectedMandatoryWorkingTime)
private void assertMandatoryWorkingTime(final LocalDate date, final LocalTime begin, final LocalTime end,
final Duration expectedMandatoryWorkingTime)
{
assertThat(getMandatoryWorkingTime(date, begin, end)).as("mandatory working time")
.isEqualTo(expectedMandatoryWorkingTime);
}

private Duration getMandatoryWorkingTime(LocalDate date, LocalTime begin, LocalTime end)
private Duration getMandatoryWorkingTime(final LocalDate date, final LocalTime begin, final LocalTime end)
{
final DayRecord day = createDay(date, begin, end, null, null);
return day.getMandatoryWorkingTime();
}

private Duration getMandatoryBreak(LocalDate date, LocalTime begin, LocalTime end, Duration interruption)
private Duration getMandatoryBreak(final LocalDate date, final LocalTime begin, final LocalTime end,
final Duration interruption)
{
final DayRecord day = createDay(date, begin, end, null, interruption);
return day.getMandatoryBreak();
}

private void assertWorkingDay(LocalDate date, boolean expected)
private void assertWorkingDay(final LocalDate date, final boolean expected)
{
assertThat(createDay(date).getType().isWorkDay()).isEqualTo(expected);
}

private void assertType(LocalDate date, DayType expected)
private void assertType(final LocalDate date, final DayType expected)
{
final DayRecord day = createDay(date);
assertDayType(day, expected);
}

private void assertDayType(DayRecord day, DayType expected)
private void assertDayType(final DayRecord day, final DayType expected)
{
assertThat(day.getType()).isEqualTo(expected);
}

private DayRecord createDay(LocalDate date)
private DayRecord createDay(final LocalDate date)
{
return createDay(date, null, null);
}
Expand All @@ -589,29 +626,32 @@ private DayRecord createDummyDay()
return createDummyDay(LocalDate.of(2021, 7, 20));
}

private DayRecord createDummyDay(LocalDate date)
private DayRecord createDummyDay(final LocalDate date)
{
return createDay(date);
}

private DayRecord createDay(LocalDate date, LocalTime begin, LocalTime end)
private DayRecord createDay(final LocalDate date, final LocalTime begin, final LocalTime end)
{
return createDay(date, begin, end, null, null);
}

private DayRecord createDay(LocalDate date, LocalTime begin, LocalTime end, DayType type, Duration interruption)
private DayRecord createDay(final LocalDate date, final LocalTime begin, final LocalTime end, final DayType type,
final Duration interruption)
{
return createDay(date, begin, end, type, interruption, null);
}

private DayRecord createDay(LocalDate date, LocalTime begin, LocalTime end, DayType type, Duration interruption,
DayRecord previousDay)
private DayRecord createDay(final LocalDate date, final LocalTime begin, final LocalTime end, final DayType type,
final Duration interruption,
final DayRecord previousDay)
{
return createDay(date, begin, end, type, interruption, previousDay, null);
}

private DayRecord createDay(LocalDate date, LocalTime begin, LocalTime end, DayType type, Duration interruption,
DayRecord previousDay, MonthIndex month)
private DayRecord createDay(final LocalDate date, final LocalTime begin, final LocalTime end, final DayType type,
final Duration interruption,
final DayRecord previousDay, final MonthIndex month)
{
final DayData day = modelFactory.createDayData();
day.setBegin(begin);
Expand All @@ -628,14 +668,14 @@ private DayRecord createDay(final DayData DayData)
return new DayRecord(null, DayData, null, null, projectServiceMock, modelFactory);
}

private DayRecord dayRecord(DayData day, DayRecord previousDay, MonthIndex month)
private DayRecord dayRecord(final DayData day, final DayRecord previousDay, final MonthIndex month)
{
final ContractTermsService contractTerms = contractTerms();
return new DayRecord(contractTerms, day, previousDay, month, projectServiceMock, modelFactory);
}

private ContractTermsService contractTerms()
{
return new ContractTermsService(TestingConfig.builder().build());
return new ContractTermsService(configBuilder.build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ public class TestingConfig implements Config
private final Locale locale;
private final Duration currentHoursPerDay;
private final Duration mandatoryBreak;
private final boolean reduceMandatoryBreakByInterruption;

private TestingConfig(final Builder builder)
{
this.dataDir = builder.dataDir;
this.locale = builder.locale;
this.currentHoursPerDay = builder.currentHoursPerDay;
this.mandatoryBreak = builder.mandatoryBreak;
this.reduceMandatoryBreakByInterruption = builder.reduceMandatoryBreakByInterruption;
}

@Override
Expand All @@ -46,6 +48,12 @@ public Optional<Duration> getMandatoryBreak()
return Optional.ofNullable(mandatoryBreak);
}

@Override
public boolean reduceMandatoryBreakByInterruption()
{
return reduceMandatoryBreakByInterruption;
}

@Override
public boolean allowMultipleInstances()
{
Expand Down Expand Up @@ -77,7 +85,8 @@ public static Builder builder()

public static final class Builder
{
public Duration mandatoryBreak;
private boolean reduceMandatoryBreakByInterruption = false;
private Duration mandatoryBreak;
private Path dataDir;
private Locale locale;
private Duration currentHoursPerDay;
Expand Down Expand Up @@ -110,6 +119,12 @@ public Builder withMandatoryBreak(final Duration mandatoryBreak)
return this;
}

public Builder withReduceMandatoryBreakByInterruption(final boolean reduceMandatoryBreakByInterruption)
{
this.reduceMandatoryBreakByInterruption = reduceMandatoryBreakByInterruption;
return this;
}

public TestingConfig build()
{
return new TestingConfig(this);
Expand Down

0 comments on commit abbea6f

Please sign in to comment.