214 lines
13 KiB
Markdown
214 lines
13 KiB
Markdown
# Duck Odoo Calc (Odoo Hours) - Project Summary
|
||
|
||
This document provides a comprehensive overview of the **Duck Odoo Calc** (also referred to as `odoo-hours`) project. It maps the architecture, directory structure, data flows, business logic rules, configuration setup, and tests to act as a token-saving context file for future developer prompts.
|
||
|
||
---
|
||
|
||
## 1. Project Overview & Tech Stack
|
||
The application is a work hours tracker and calculator that integrates with Odoo's attendance registry (`hr.attendance`). It fetches check-in/check-out events for a given month and year, allows users to view their accumulated hours, public holidays, sick leaves, remaining hours, and overtime balance, and enables manual hour adjustments in real time on the frontend interface.
|
||
|
||
### Technology Stack
|
||
* **Backend**:
|
||
* Python 3.12 (slim)
|
||
* FastAPI & Uvicorn (REST API)
|
||
* `requests` (JSON-RPC client for Odoo)
|
||
* `PyJWT` (JSON Web Tokens authentication)
|
||
* `holidays` (Polish public holidays detection)
|
||
* `numpy` (specifically `busday_count` for working days calculations)
|
||
* **Frontend**:
|
||
* Vue 3 (Vite, Options/Composition API)
|
||
* Pinia (State Management)
|
||
* Axios (HTTP client)
|
||
* Chart.js & Vue-Chartjs (Data visualization)
|
||
* Material Design Icons (`@mdi/js`, `@jamescoyle/vue-icon`)
|
||
* **Infrastructure**:
|
||
* Docker Compose
|
||
* Nginx (serving Vue client & proxying requests to the backend)
|
||
|
||
---
|
||
|
||
## 2. Repository Structure
|
||
|
||
```text
|
||
duck-odoo-calc/
|
||
├── docker-compose.yml # Multi-container runtime configuration
|
||
├── project_summary.md # Project summary (this file)
|
||
├── README.md # Main project documentation (currently empty)
|
||
├── backend/
|
||
│ ├── Dockerfile # Python runtime builder
|
||
│ ├── requirements.txt # Python dependencies (FastAPI, JWT, holidays, numpy)
|
||
│ ├── main.py # FastAPI entry point, authentication & formatting logic
|
||
│ └── odoo_api/
|
||
│ ├── __init__.py
|
||
│ └── client.py # Odoo JSON-RPC API client wrapper
|
||
└── frontend/
|
||
├── Dockerfile # Multi-stage build (Node 20 build -> Nginx server)
|
||
├── nginx.conf # Nginx proxy & routing rules
|
||
├── index.html # HTML entry point (loads Inter and Roboto fonts)
|
||
├── package.json # NPM packages & scripts
|
||
├── vite.config.js # Vite bundler configuration
|
||
├── eslint.config.js # Linter rules
|
||
├── .prettierrc.json # Code formatter configuration
|
||
└── src/
|
||
├── App.vue # Base Vue shell (renders RouterView)
|
||
├── main.js # App mounting, imports assets/main.css, Pinia, Router
|
||
├── api/
|
||
│ ├── api.js # Base Axios client with request/response interceptors
|
||
│ ├── authApi.js # Authentication endpoint caller
|
||
│ ├── dataApi.js # Attendance data loader
|
||
│ └── mock_response.js # Local development mock helper (May, June, July data)
|
||
├── assets/
|
||
│ └── main.css # Main styling variables and layouts
|
||
├── components/
|
||
│ ├── BarChart.vue # Chart.js integration displaying worked hours per day
|
||
│ ├── CardTop.vue # KPI indicator card
|
||
│ ├── DayRow.vue # Interactive row for a single day's attendance
|
||
│ ├── HoursSummary.vue # Progress bar and hours breakdown card
|
||
│ ├── MonthMenu.vue # Dropdown menu to switch months
|
||
│ ├── TimeLabel.vue # Formatted duration display (HH:MM)
|
||
│ └── TimeStepper.vue # Hoverable time-adjuster component (increment/decrement)
|
||
├── router/
|
||
│ └── index.js # Vue Router with navigation guards (auth requirements)
|
||
├── stores/
|
||
│ ├── attendanceStore.js # Pinia store for month-level attendance data
|
||
│ └── authStore.js # Pinia store for JWT token management
|
||
└── utils/
|
||
├── utils.js # Time math, break deductions, and date helper utilities
|
||
└── utils.spec.js # Vitest suite verifying hour calculation logic
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Configuration & Environment Variables
|
||
|
||
### Docker Compose Configuration ([docker-compose.yml](file:///home/bartool/local_projects/duck-odoo-calc/docker-compose.yml))
|
||
Coordinates two containers in a single bridge network:
|
||
1. **backend** (`odoo-hours-backend`):
|
||
* Builds from `./backend/Dockerfile` using `python:3.12-slim`.
|
||
* Runs on port `8000` internally.
|
||
2. **frontend** (`odoo-hours-frontend`):
|
||
* Builds from `./frontend/Dockerfile` utilizing a multi-stage approach (builds assets with Node 20, then runs Alpine Nginx).
|
||
* Maps host port `6080` to container port `80`.
|
||
|
||
### Backend Environment Variables (`backend/.env` - Ignored)
|
||
The backend expects a `.env` file containing:
|
||
* `SECRET_KEY`: Private key used for encoding/decoding JWTs.
|
||
* `ALGORITHM`: JWT signing algorithm (e.g., `HS256`).
|
||
* `ACCESS_TOKEN_EXPIRE_MINUTES`: Expiry duration for authentication tokens.
|
||
* `ODDO_URL`: Target Odoo RPC endpoint (Note: spelled `ODDO_URL` in codebase).
|
||
* `DB_NAME`: The company's database name in Odoo.
|
||
* `ORIGINS`: Comma-separated list of CORS-allowed domains.
|
||
|
||
### Nginx Routing Rules ([frontend/nginx.conf](file:///home/bartool/local_projects/duck-odoo-calc/frontend/nginx.conf))
|
||
* Listens on port `80` inside the container.
|
||
* Routes `/odoo/api/` requests to `http://backend:8000/` (removes the prefix).
|
||
* Routes `/odoo/` to serve the Vue application (`dist/index.html` mapping under the subfolder `/odoo/`).
|
||
* Implements caching for static assets under `/odoo/.*` (1-year expiration) and prevents caching for `/odoo/index.html`.
|
||
|
||
---
|
||
|
||
## 4. Backend Logic & API Endpoints
|
||
|
||
### Odoo API Client ([backend/odoo_api/client.py](file:///home/bartool/local_projects/duck-odoo-calc/backend/odoo_api/client.py))
|
||
Handles sessions and authentications using Odoo JSON-RPC endpoints:
|
||
* `login(username, password)`: Calls `/web/session/authenticate` with DB details and logs the user in.
|
||
* `get_hr_attendance_data(month, year)`: Requests check-in, check-out, worked hours, and attendance reasons for the target month from the `hr.attendance` model via `/web/dataset/call_kw/hr.attendance/web_search_read`.
|
||
|
||
### API Endpoints ([backend/main.py](file:///home/bartool/local_projects/duck-odoo-calc/backend/main.py))
|
||
|
||
1. **`POST /token`**
|
||
* Receives `username` and `password` via form submission.
|
||
* Logs the user into Odoo, retrieves the user metadata, and encodes `sub` (email), `uid`, and `full_name` into a signed JWT.
|
||
* Returns `{ "access_token": "...", "token_type": "bearer" }`.
|
||
|
||
2. **`GET /data/{year}/{month}`**
|
||
* Requires a valid JWT Bearer Token.
|
||
* Retrieves all attendance records for the given month/year via `OdooAPIClient`.
|
||
* Retrieves Polish public holidays using the `holidays.Poland` package.
|
||
* Calculates the total days in the month and uses NumPy (`np.busday_count`) to compute standard working days (omitting weekends and public holidays).
|
||
* Returns:
|
||
```json
|
||
{
|
||
"year": 2026,
|
||
"month": 6,
|
||
"days_in_month": 30,
|
||
"employee": "John Doe",
|
||
"working_days": 21,
|
||
"public_holidays": [{"date": "2026-06-04", "name": "Boże Ciało"}],
|
||
"days": [...]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Frontend Stores & State Management
|
||
|
||
### Authentication Store ([frontend/src/stores/authStore.js](file:///home/bartool/local_projects/duck-odoo-calc/frontend/src/stores/authStore.js))
|
||
* **State**: Holds the `token` (initialized from `localStorage.getItem('token')`).
|
||
* **Actions**:
|
||
* `loginUser(email, password)`: Dispatches request to API, updates state, and persists the token in local storage.
|
||
* `logout()`: Deletes token from local storage and state.
|
||
* `isAuthenticated()`: Helper returning boolean check on token presence.
|
||
|
||
### Attendance Store ([frontend/src/stores/attendanceStore.js](file:///home/bartool/local_projects/duck-odoo-calc/frontend/src/stores/attendanceStore.js))
|
||
* **State**: Stores active `employee`, `year`, `month`, `daysInMonth`, `workingDays`, `workingHours` (workingDays × 8), and `days` array.
|
||
* **Calculated Properties (Getters)**:
|
||
* `sumOfHours`: Total hours at the end of the month (combines worked, holiday, and sick hours).
|
||
* `workedHours`: `sumOfHours` minus holiday and sick hours.
|
||
* `overtimeHours`: Overtime balance from the last day of the month.
|
||
* `holidayHours`: `holidayCount` × 8.0.
|
||
* `sickHours`: `sickCount` × 6.4 (80% of 8 hours standard).
|
||
* `toGoHours`: `leaveDayCount` × 8.0.
|
||
* `leaveDayCount`: Calculated days remaining to work in the month (excluding weekends, public holidays, sick/holiday leave, and days with recorded work hours).
|
||
* **Actions**:
|
||
* `loadFromResponse(response)`: Maps the backend response into individual calendar days (1 to days_in_month). It groups daily attendance entries, parses their check-in/out times, links public holidays, runs the initial hour math calculations, and updates the state.
|
||
* `updateDay(day)`: Recalculates hours and cumulative balance from the changed day onwards.
|
||
|
||
---
|
||
|
||
## 6. Core Business Logic & Calculations ([frontend/src/utils/utils.js](file:///home/bartool/local_projects/duck-odoo-calc/frontend/src/utils/utils.js))
|
||
|
||
The core logic handles standard office timings, break policies, and cumulative balances:
|
||
|
||
1. **Lunch Break Deduction**:
|
||
* If a day contains recorded work hours, **0.25 hours (15 minutes)** is automatically subtracted from the day's total worked hours:
|
||
$$\text{workedHours} = \text{totalWorkedTime} - 0.25$$
|
||
2. **Working Days Definition**:
|
||
* A valid working day is Monday through Friday, excluding public holidays:
|
||
$$\text{isValidWorkDay} = \text{dayOfWeek} \notin \{\text{"So"}, \text{"Nd"}\} \land \neg \text{isPublicHoliday}$$
|
||
3. **Overtime & Balances**:
|
||
* For standard working days (not marked as sick or holiday leave):
|
||
$$\text{overtime} = \text{workedHours} - 8.0$$
|
||
* For weekends, holidays, or leaves:
|
||
$$\text{overtime} = \text{workedHours}$$
|
||
4. **Sick and Holiday Leaves**:
|
||
* Holiday leaves grant 8.0 hours towards the month's total.
|
||
* Sick leaves grant 6.4 hours (80% value of an 8-hour day).
|
||
5. **Cumulative Rollforward**:
|
||
* `calculateMonth(days)` and `calculateMonthFromDay(startDay, days)` loop sequentially from a given day to the end of the month. They roll up `accumulatedHours` and compute the running overtime `balanceHours`:
|
||
$$\text{balanceHours}_d = \text{accumulatedHours}_d - (\text{workingDaysElapsed}_d \times 8)$$
|
||
|
||
---
|
||
|
||
## 7. Key Frontend UI Components
|
||
|
||
* **[DashboardView.vue](file:///home/bartool/local_projects/duck-odoo-calc/frontend/src/views/DashboardView.vue)**: Consolidates metrics, the bar chart, and the interactive scrollable table displaying each calendar day.
|
||
* **[DayRow.vue](file:///home/jamescoyle/vue-icon)**: A component representing a single row in the calendar. It supports:
|
||
* Entering/exiting time editing via the `TimeStepper` component.
|
||
* Adding attendance entries (defaults to `07:00`–`15:15`).
|
||
* Marking days as Holiday or Sick Leave (represented by checkboxes).
|
||
* Navigating between multiple entry intervals if the user clocked in/out multiple times in one day.
|
||
* **[TimeStepper.vue](file:///home/bartool/local_projects/duck-odoo-calc/frontend/src/components/TimeStepper.vue)**: Renders HH:MM numbers. Hovering over a block displays vertical adjustment arrows. Pressing (or holding) these buttons triggers incremental changes in hours or minutes (bounded between `06:00` and `20:00`).
|
||
* **[BarChart.vue](file:///home/bartool/local_projects/duck-odoo-calc/frontend/src/components/BarChart.vue)**: Utilizes Chart.js to render a stacked bar chart representing hours (blue), holiday leaves (yellow), and sick leaves (red) for every day of the month. Weekend tick labels are colored dynamically (Saturday = Orange, Sunday = Red, Public Holiday = Green).
|
||
|
||
---
|
||
|
||
## 8. Test Suite ([frontend/src/utils/utils.spec.js](file:///home/bartool/local_projects/duck-odoo-calc/frontend/src/utils/utils.spec.js))
|
||
|
||
The project includes unit tests written in Vitest to validate `calculateDay` logic under various parameters:
|
||
* **Standard day**: Verifies that 8h 30m of clocked time (e.g. `08:00` to `16:30`) correctly results in `workedHours = 8.25` (due to `-0.25` break deduction) and `overtime = 0.25`.
|
||
* **Multiple check-ins**: Confirms that splitting times (e.g. `08:00`-`12:00` and `13:00`-`16:30` - total 7.5 hours) properly calculates `workedHours = 7.25` and `overtime = -0.75`.
|
||
* **Active clock-in**: Tests system time mocking (via `vi.setSystemTime`). When a user is currently clocked in (empty exitTime), the utility dynamically uses the current time to compute the elapsed hours and logs it.
|
||
* **Leaves**: Verifies holiday leave (+8h hours) and sick leave (+6.4h hours) math.
|
||
* **Weekends**: Confirms that working on weekends calculates all worked time as pure overtime (no standard 8-hour requirement subtraction).
|