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
157 changes: 157 additions & 0 deletions hr_expense_trip/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

===============
HR Expense Trip
===============

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:48489de083f98d6ebfbe9d3a0c673f89bcf4718c546781b3b3818a17726247d8
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github
:target: https://github.com/OCA/hr-expense/tree/19.0/hr_expense_trip
:alt: OCA/hr-expense
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/hr-expense-19-0/hr-expense-19-0-hr_expense_trip
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/hr-expense&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module introduces **Trips** in Expenses so employees can group
related expense lines under a single business trip.

Key features:

- Create trips with employee, date range, partner, and reason.
- Link existing expense lines to a trip from the trip form.
- Open linked expenses directly from the trip to review details.
- Optional manager approval flow before collecting receipts.
- Role-based access for employees, approvers, and administrators.
- Dedicated "Trip to Process" workspace for managers/approvers.
- Generate and attach a trip PDF report when the trip is marked done.
- Automatic bill creation from approved expenses with trip PDF
attachment.

**Table of contents**

.. contents::
:local:

Usage
=====

1. Go to *Expenses > My Trips* and create a new trip.
2. Fill in the trip name, dates, employee, and optional partner/reason.
3. Click *Request Approval*: In this step, pre-trip approvals can be
handled, and additional workflows can be triggered after approval
(for example an A1 certificate process).

- If **Auto Approve Trip Requests** is enabled, the trip moves
directly to *Collect Receipts*.
- Otherwise, if the requester is already an allowed approver for
that trip, it is approved immediately without creating an
activity.
- Otherwise, a *Trip Approval Request* activity is created for the
manager.

4. In the *Expenses* tab, use *Add* to select existing draft expenses
for the same employee, then save.
5. Click an expense line to open the *hr.expense* form when you need to
review or edit that expense.
6. When all expenses are complete, click *Mark as Done* to finalize the
trip and attach the generated trip report in chatter.
7. To create bills from approved expenses, click *Create Bill* (visible
only when trip is in state done all expenses are in approved state).
This will post all expenses and attach the trip PDF to each resulting
bill.

Role behavior
-------------

- Employees can create and edit their own trips.
- Managers/approvers can view and approve trips they are responsible
for.
- Expense administrators can edit all trips.
- After a trip is approved, only managers/administrators can edit trip
information.
- The *Expenses* tab remains editable until the trip reaches *Done*.

Manager processing menu
-----------------------

Managers can open *Expenses > Trip to Process* to review requested trips
where they are responsible as manager.

Create Bill feature
-------------------

The *Create Bill* button is automatically displayed when all expenses
linked to the trip are in approved state and the trip is in state done.
Clicking this button will:

1. Post all approved expenses and create bills.
2. Automatically attach the trip PDF report to each created bill in the
chatter.
3. Navigate to the created bill(s) for review.

This streamlines the billing process by ensuring the trip documentation
is automatically associated with the financial records.

Configuration
-------------

To configure auto approval, go to *Expenses > Configuration > Settings*
and in the *Trips* section enable or disable *Auto Approve Trip
Requests*.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/hr-expense/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/hr-expense/issues/new?body=module:%20hr_expense_trip%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Contributors
------------

- `glueckkanja AG <https://www.glueckkanja.com>`__:

- Christopher Rogos

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/hr-expense <https://github.com/OCA/hr-expense/tree/19.0/hr_expense_trip>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
4 changes: 4 additions & 0 deletions hr_expense_trip/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from . import models
from . import wizard
27 changes: 27 additions & 0 deletions hr_expense_trip/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2024 Odoo Community Association (OCA)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

{
"name": "HR Expense Trip",
"version": "19.0.1.0.0",
"category": "Human Resources",
"author": "Odoo Community Association (OCA)",
"license": "LGPL-3",
"website": "https://github.com/OCA/hr-expense",
"depends": ["hr_expense"],
"data": [
"security/hr_trip_security.xml",
"security/ir.model.access.csv",
"views/hr_trip_views.xml",
"views/hr_expense_views.xml",
"views/report_hr_trip.xml",
"views/res_config_settings_views.xml",
],
"installable": True,
"assets": {
"web.assets_backend": [
"hr_expense_trip/static/src/views/*.js",
"hr_expense_trip/static/src/views/trip_list_buttons.xml",
],
},
}
5 changes: 5 additions & 0 deletions hr_expense_trip/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from . import hr_expense
from . import hr_trip
from . import res_config_settings
103 changes: 103 additions & 0 deletions hr_expense_trip/models/hr_expense.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2024 Odoo Community Association (OCA)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from collections import defaultdict

from odoo import fields, models
from odoo.exceptions import UserError


class HrExpense(models.Model):
_inherit = "hr.expense"

trip_id = fields.Many2one(
comodel_name="hr.trip", string="Trip", ondelete="set null", index=True
)
# Related fields used by decoration-success / decoration-warning in the trip
# form's expense list to flag dates outside the trip date range.
trip_start_date = fields.Datetime(related="trip_id.start_date", store=False)
trip_end_date = fields.Datetime(related="trip_id.end_date", store=False)

def action_add_existing_expenses(self):
self.ensure_one()
trip_id = self.env.context.get("default_trip_id")
return {
"type": "ir.actions.act_window",
"name": self.env._("Add: Expenses"),
"res_model": "hr.trip",
"res_id": trip_id,
"view_mode": "form",
"target": "new",
}

def default_get(self, fields_list):
defaults = super().default_get(fields_list)
if "trip_id" in fields_list and not defaults.get("trip_id"):
expense_date = defaults.get("date")
employee_id = defaults.get("employee_id")
if expense_date and employee_id:
trip = self.env["hr.trip"].search(
[
("employee_id", "=", employee_id),
("start_date", "<=", expense_date),
("end_date", ">=", expense_date),
],
limit=1,
)
if trip:
defaults["trip_id"] = trip.id
return defaults

def action_post(self):
# Check if any expense has a linked trip that is not in "done" state
for expense in self:
if expense.trip_id and expense.trip_id.state != "done":
raise UserError(
self.env._(
"Cannot post expense '%(expense)s' because it is linked to "
"trip '%(trip)s' which is not in 'Done' state. "
"Please mark the trip as done first.",
expense=expense.name,
trip=expense.trip_id.name,
)
)
return super().action_post()

def action_create_trip(self):
if not self:
return False

already_linked = self.filtered("trip_id")
if already_linked:
raise UserError(
self.env._(
"Some selected expenses are already linked to a trip. "
"Please unselect them before creating trips."
)
)

expenses_by_employee = defaultdict(lambda: self.env["hr.expense"])
for expense in self:
expenses_by_employee[expense.employee_id] |= expense

created_trips = self.env["hr.trip"]
for employee, expenses in expenses_by_employee.items():
expense_dates = expenses.mapped("date")
trip = self.env["hr.trip"].create(
{
"name": self.env._("Trip - %s", employee.name),
"employee_id": employee.id,
"start_date": min(expense_dates),
"end_date": max(expense_dates),
"state": "receipts",
}
)
expenses.write({"trip_id": trip.id})
created_trips |= trip

action = self.env.ref("hr_expense_trip.action_hr_trip_my").sudo().read()[0]
action["domain"] = [("id", "in", created_trips.ids)]
if len(created_trips) == 1:
action["views"] = [(False, "form")]
action["res_id"] = created_trips.id
return action
Loading
Loading