Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions code-style/api-code-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ All methods that return tasks must end with `Async`.
public async Task DoSomethingAsync()
```

### Lists
When naming collections (lists, arrays), use plural nouns — not the singular + List suffix.

| Correct | Incorrect |
|-----------|-------------|
| `Users` | `UserList` |
| `Transactions` | `TransactionList` |

But if the noun is uncountable (e.g. time, money, knowledge, information, equipment, traffic, etc.), use the singular + List suffix

| Correct | Incorrect |
|-----------|-------------|
| `MakeUpTimeList` | `MakeUpTimes` |
| `EquipmentList` | `Equipments` |


## 4. Controller Responses Naming

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# 005: Away With Make-Up Time Entry Prototype (Backend)

## Context

Need to design tracking of ***Away With Make-Up Time*** entry feature.

## Entries model
We will have a separate model for Make-Up Time entry which will be validated at the database level.
It will have different rules of validation in comparison with other entries, e.g. it can overlap with a Task entry, whereas Away entry cannot overlap with a Task entry (for the full list see [ADR on overlap validation](003-time-api-overlap-validation.md)).
Make-Up Time entry will not have its own endpoints, because it will be created in a related entry (in this case Away With Make-Up Time entry) when calling this related entry's endpoints for create/update.

### Make-Up Time Entry

```c#
public class MakeUpTimeEntry : TrackedEntryBase
{
// EntityFrameworkCore related empty default constructor
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
public MakeUpTimeEntry()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
{
Type = EntryType.MakeUpTime;
}

public long RelatedEntryId { get; set; }
}
```

### Away With Make-Up Time Entry

```c#
public class AwayWithMakeUpTimeEntry : TrackedEntryBase
{
// EntityFrameworkCore related empty default constructor
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
public AwayWithMakeUpTimeEntry()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
{
Type = EntryType.AwayWithMakeUpTime;
}

public string Description { get; set; }

public List<MakeUpTimeEntry> MakeUpTimeList { get; set; }
}
```

## Endpoints

1. **POST** `/api/tracking/away-with-make-up-time-entries` - add Away With Make-Up Time entry

**Request body:**
```c#
{
// we discarded the idea to call this field "awayReason" in favor of consistency across all entry types
description: string,
startTime: DateTime,
endTime: DateTime,
makeUpTimeList: [
{
startTime: DateTime,
endTime: DateTime,
}
]
}
```

**Response body:**
```c#
{
newAwayWithMakeUpTimeEntryId: long
}
```

2. **POST** `/api/tracking/away-with-make-up-time-entries/{id}` - update Away With Make-Up Time entry

**Request body:**
```c#
{
// we discarded the idea to call this field "awayReason" in favor of consistency across all entry types
description: string,
startTime: DateTime,
endTime: DateTime,
makeUpTimeList: [
{
startTime: DateTime,
endTime: DateTime,
}
]
}
```

## Validation

1. Add new migrations with updated overlap constraint.
2. Make sure Make-Up Time is equal to Away With Make-Up Time. These checks will be made on the level of Handler (or maybe separate validator?).

## Testing Strategy

### E2E Karate Tests

Cases:
1. No Permissions Lead to Unauthorized

Steps:
- Authorization under an account without permissions
- Call **POST** `/api/tracking/away-with-make-up-time-entries` and check response status it should be 403
- Call **POST** `/api/tracking/away-with-make-up-time-entries/{id}` and check response status it should be 403

2. Happy path

Steps:
- Authorization under an account with all permissions
- Call **POST** `/api/tracking/away-with-make-up-time-entries` to add Away With Make-Up Time entry
- Call **POST** `/api/tracking/away-with-make-up-time-entries/{id}` to update Away With Make-Up Time entry
- Call **GET** `/api/tracking/entries` to verify added Away With Make-Up Time entry with related Make-up Time entry/entries
- Call **DELETE** `/api/tracking/entries/{id}/hard-delete` to hard delete the Away With Make-Up Time entry with related Make-up Time entry/entries
- Call **GET** `/api/tracking/entries` to verify that Away With Make-Up Time entry with related Make-up Time entry/entries were deleted

### Integration tests

Cases:
1. Away With Make-Up Time cannot overlap with Unwell entries
2. Away With Make-Up Time cannot overlap with Task entries
3. Make-up Time can overlap with Task entries
4. Make-up Time cannot overlap with Unwell entries
5. Make-up Time cannot overlap with Away With Make-Up Time entries

### Unit tests

Cases:
1. Check exception message in case Make-up Time slot (or sum of slots if there are more than one Make-up Time slot) is not equal to Away With Make-Up Time time.

Exception message: `Away time must be equal to Make-up time.`

## Use Cases for e2e Tests

Cypress Happy path
1.
```
GIVEN time-tracker page,
WHEN add Away With Make-Up Time card with one Make-Up Time slot,
SHOULD create this card and one Make-Up Time card in time-tracker
```
or
```
I was away one hour
I will Make-Up for it today after my working hours
I did as it planned
```

2.
```
GIVEN time-tracker page,
WHEN add Away With Make-Up Time card with two Make-Up Time slots,
SHOULD create this card and two Make-Up Time cards in time-tracker
```

Maybe it is worth uniting use cases 1 & 2.

3.
```
GIVEN an Away entry with two Make-Up Time slots,
WHEN track Task into first Make-Up Time slot,
AND update date of this Make-Up Time slot
SHOULD display the Task on the same slot and move Make-Up Time card to the new date
```

Karate Happy path
```
I was away two hours
I want Make-Up 30 minutes tomorrow and 1:30 day after tomorrow
Then i realized that i can't Make-up tomorrow and want to move this 30 minutes Make-Up to another day
```

```
I plan to be away tommorow from for 4PM to 5PM
I will Make-Up in advance today
I Made-Up as planned
But my away reason was cancelled and thus I will not be away (I delete it)
And my task tracked yesterday remains untouched
```

4.
```
GIVEN an Away entry with two Make-Up Time slots,
WHEN update Away entry start time and end time without changing its duration,
SHOULD move Away card to the new time and leave Make-Up Time slots untouched
```

## Out Of Scope
Update personal report
Why?
74 changes: 61 additions & 13 deletions time-tracker/backend-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

Events:
- Task
- Away (unpaid)
- Away (paid)
- Away (with make-up time)
- Away (with make-up time)
- Late
- Unwell
- Make-up time
Expand Down Expand Up @@ -41,16 +41,18 @@ CREATE INDEX a ON tracked_entries
- [Entries](#entries)
- [Task Entries](#task-entries)
- [Unwell Entries](#unwell-entries)
- [Away With Make-Up Time](#away-with-make-up-time)
- [Reporting Endpoints](#reporting-endpoints)
- [Internal Endpoints](#internal-endpoints)

---

## Tracking Endpoints
We made a decision to split endpoints by entry type to have strict, predictable contract. With separate endpoints for each entry type, every field in the contract can be required and non-nullable because there's no need to accommodate multiple schemas in one payload.

### entries

1. **GET** /api/tracking/entries?startDate={startDate}&endDate={endDate} - return entries by period
1. **GET** `/api/tracking/entries?startDate={startDate}&endDate={endDate}` - return entries by period

**Response body:**
```c#
Expand Down Expand Up @@ -78,7 +80,8 @@ CREATE INDEX a ON tracked_entries
}
```

2. **DELETE** /api/tracking/entries/{entryId}/soft-delete - soft delete
2. **DELETE** `/api/tracking/entries/{entryId}/soft-delete` - soft delete

**Request body:**
```c#
{
Expand All @@ -88,7 +91,7 @@ CREATE INDEX a ON tracked_entries

### task-entries

1. **POST** /api/tracking/task-entries - add task entry
1. **POST** `/api/tracking/task-entries` - add Task entry

**Request body:**
```c#
Expand All @@ -110,7 +113,7 @@ CREATE INDEX a ON tracked_entries
}
```

3. **POST** /api/tracking/task-entries/{id} - update task entry
2. **POST** `/api/tracking/task-entries/{id}` - update Task entry

**Request body:**
```c#
Expand All @@ -127,7 +130,7 @@ CREATE INDEX a ON tracked_entries

**Response body:** 200 OK

4. **GET** /api/tracking/task-entries/projects?date={date} - return employee's projects
3. **GET** `/api/tracking/task-entries/projects?date={date}` - return employee's projects

**Response body:**

Expand All @@ -144,7 +147,7 @@ CREATE INDEX a ON tracked_entries

#### unwell-entries

1. **POST** /api/tracking/unwell-entries - add unwell entries
1. **POST** `/api/tracking/unwell-entries` - add Unwell entries

**Request body:**
```c#
Expand All @@ -162,7 +165,7 @@ CREATE INDEX a ON tracked_entries
}
```

2. **POST** /api/tracking/unwell-entries/{id} - update unwell entry
2. **POST** `/api/tracking/unwell-entries/{id}` - update Unwell entry

**Request body:**
```c#
Expand All @@ -173,9 +176,54 @@ CREATE INDEX a ON tracked_entries
}
```

#### away-with-make-up-time

1. **POST** `/api/tracking/away-with-make-up-time-entries` - add Away With Make-Up Time entry

**Request body:**
```c#
{
// we discarded the idea to call this field "awayReason" in favor of consistency across all entry types
description: string,
startTime: DateTime,
endTime: DateTime,
makeUpTimeList: [
{
startTime: DateTime,
endTime: DateTime,
}
]
}
```

**Response body:**
```c#
{
newAwayWithMakeUpTimeEntryId: long
}
```

2. **POST** `/api/tracking/away-with-make-up-time-entries/{id}` - update Away With Make-Up Time entry

**Request body:**
```c#
{
// we discarded the idea to call this field "awayReason" in favor of consistency across all entry types
description: string,
startTime: DateTime,
endTime: DateTime,
makeUpTimeList: [
{
startTime: DateTime,
endTime: DateTime,
}
]
}
```

## Reporting Endpoints

1. **GET** /api/reporting/personal-report?employeeId={employeeId}&year={year}&month={month} - return personal report
1. **GET** `/api/reporting/personal-report?employeeId={employeeId}&year={year}&month={month}` - return personal report

**Response body:**
```c#
Expand Down Expand Up @@ -204,7 +252,7 @@ CREATE INDEX a ON tracked_entries
}
```

2. **GET** /api/reporting/employees - return employees
2. **GET** `/api/reporting/employees` - return employees

**Response body:**
```c#
Expand All @@ -220,7 +268,7 @@ CREATE INDEX a ON tracked_entries

## Internal endpoints

1. **GET** /api/projects/tracked-task-hours?projectId={projectId}&startDate={startDate}&endDate={endDate} - return employees tracked task hours
1. **GET** `/api/projects/tracked-task-hours?projectId={projectId}&startDate={startDate}&endDate={endDate}` - return employees tracked task hours


**Response body:**
Expand All @@ -235,7 +283,7 @@ CREATE INDEX a ON tracked_entries
}
```

2. **GET** /api/projects - Get all projects
2. **GET** `/api/projects` - Get all projects

**Response body:**
```c#
Expand Down
Loading