Files
fastapi-vue-odoo/project_summary.md
bartool 5e711a9b34
All checks were successful
Deploy Application / deploy (push) Successful in 18s
Test runner / check-runner (push) Successful in 3s
feat: add comprehensive project documentation in README and project summary
2026-06-25 17:20:38 +02:00

13 KiB
Raw Permalink Blame History

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

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)

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)

  • 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)

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)

  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:
      {
        "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)

  • 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)

  • 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)

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: Consolidates metrics, the bar chart, and the interactive scrollable table displaying each calendar day.
  • DayRow.vue: 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:0015: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: 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: 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)

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).